Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
100.00% |
1 / 1 |
|
100.00% |
3 / 3 |
CRAP | |
100.00% |
36 / 36 |
HiddenCoupling | |
100.00% |
1 / 1 |
|
100.00% |
3 / 3 |
15 | |
100.00% |
36 / 36 |
createReducedGraph() | |
100.00% |
1 / 1 |
5 | |
100.00% |
18 / 18 |
|||
findOwningClassVertex(ImplVertex $impl) | |
100.00% |
1 / 1 |
4 | |
100.00% |
7 / 7 |
|||
findDeclaringVertex(MethodVertex $meth) | |
100.00% |
1 / 1 |
6 | |
100.00% |
11 / 11 |
<?php | |
/* | |
* Mondrian | |
*/ | |
namespace Trismegiste\Mondrian\Analysis; | |
use Trismegiste\Mondrian\Graph\BreadthFirstSearch; | |
use Trismegiste\Mondrian\Transform\Vertex\ImplVertex; | |
use Trismegiste\Mondrian\Transform\Vertex\MethodVertex; | |
use Trismegiste\Mondrian\Transform\Vertex\ClassVertex; | |
use Trismegiste\Mondrian\Transform\Vertex\InterfaceVertex; | |
use Trismegiste\Mondrian\Graph\Edge; | |
/** | |
* HiddenCoupling is an analyser which checks and finds hidden coupling | |
* between types | |
* | |
* How ? | |
* This analyser searches for method calls. Everytime there is a call of | |
* a method against an object ( $obj->getThing() ), it means an edge from | |
* an implementation vertex where the call is to a method signature vertex. | |
* | |
* Since "$obj" does not come from nowhere, its type (class or interface) | |
* must be known by the class owning the implementation vertex. | |
* In other words : | |
* If there is an edge from an implementation to a method, there must be | |
* at least one another directed path between these two vertices | |
* (through the class vertex, through a parameter vertex, superclass etc...) | |
* If you can't figure why, I recommand you to read the digraph language | |
* I've defined in this intent. | |
* | |
* If there is none, *maybe* it means a hidden coupling. I add the "maybe" | |
* because, it's hard to find the type of "$obj" in soft-typed language like | |
* PHP. That's why there can be false positive. But it's easier to check | |
* false positives than to search through all over the php files to find | |
* that kind of weakness in the code. | |
* | |
* One another thing, since I cannot detect calls from "call_user_func" and | |
* other magic features of PHP like "$obj->$methodName()" or "new $className" | |
* there is a big limit of this static analyser. | |
* | |
* Neverthesless I pretend this tool can find at least 50% of hidden coupling | |
* in poorly-coded classes and about 10% of false positive, from what I've | |
* seen. | |
* | |
*/ | |
class HiddenCoupling extends BreadthFirstSearch implements Generator | |
{ | |
/** | |
* Generate a digraph reduced to the hidden coupled vertices | |
* | |
* Since all vertices need to be scanned, go for the Floyd–Warshall algorithm | |
* http://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm | |
* in O(n³) | |
* | |
*/ | |
public function createReducedGraph() | |
{ | |
$reducedGraph = new \Trismegiste\Mondrian\Graph\Digraph(); | |
$dependency = $this->getEdgeSet(); | |
foreach ($dependency as $edge) { | |
if (($edge->getSource() instanceof ImplVertex) | |
&& ($edge->getTarget() instanceof MethodVertex)) { | |
$this->resetVisited(); | |
$edge->visited = true; | |
$otherPath = $this->searchPath($edge->getSource(), $edge->getTarget()); | |
if (count($otherPath) == 0) { | |
// not found => hidden coupling : | |
// source is impl and target is method | |
$reducedGraph->addEdge($edge->getSource(), $edge->getTarget()); | |
$reducedGraph->addEdge( | |
$this->findOwningClassVertex($edge->getSource()), $edge->getSource()); | |
$reducedGraph->addEdge( | |
$this->findDeclaringVertex($edge->getTarget()), $edge->getTarget()); | |
} | |
} | |
} | |
return $reducedGraph; | |
} | |
protected function findOwningClassVertex(ImplVertex $impl) | |
{ | |
list($className, $methodName) = explode('::', $impl->getName()); | |
foreach ($this->graph->getSuccessor($impl) as $succ) { | |
if (($succ instanceof ClassVertex) | |
&& ($succ->getName() == $className)) { | |
return $succ; | |
} | |
} | |
throw new \RuntimeException("$methodName has no owning class"); | |
} | |
protected function findDeclaringVertex(MethodVertex $meth) | |
{ | |
list($className, $methodName) = explode('::', $meth->getName()); | |
foreach ($this->graph->getEdgeSet() as $edge) { | |
if ($edge->getTarget() == $meth) { | |
$src = $edge->getSource(); | |
if (($src instanceof ClassVertex) || ($src instanceof InterfaceVertex)) { | |
if ($src->getName() == $className) { | |
return $src; | |
} | |
} | |
} | |
} | |
throw new \RuntimeException("$methodName has no declaring class"); | |
} | |
} |