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"); | |
| } | |
| } |