| <?php |
| |
| |
| |
| |
| |
| namespace Trismegiste\Mondrian\Visitor; |
| |
| use Trismegiste\Mondrian\Transform\Vertex\MethodVertex; |
| |
| |
| |
| |
| |
| |
| class EdgeCollector extends PassCollector |
| { |
| |
| protected $currentClassVertex = null; |
| protected $currentMethodNode = null; |
| |
| |
| |
| |
| public function enterNode(\PHPParser_Node $node) |
| { |
| parent::enterNode($node); |
| |
| |
| if ($this->currentMethod) { |
| |
| switch ($node->getType()) { |
| |
| case 'Expr_MethodCall' : |
| $this->enterMethodCall($node); |
| break; |
| |
| case 'Expr_New': |
| $this->enterNewInstance($node); |
| break; |
| |
| case 'Expr_StaticCall': |
| $this->enterStaticCall($node); |
| break; |
| } |
| } |
| |
| |
| if ($node->getType() === 'Stmt_TraitUse') { |
| $this->enterTraitUse($node); |
| } |
| } |
| |
| |
| |
| |
| public function leaveNode(\PHPParser_Node $node) |
| { |
| parent::leaveNode($node); |
| |
| switch ($node->getType()) { |
| |
| case 'Stmt_Class': |
| case 'Stmt_Interface': |
| case 'Stmt_Trait': |
| $this->currentClassVertex = null; |
| break; |
| |
| case 'Stmt_ClassMethod' : |
| $this->currentMethodNode = null; |
| break; |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| protected function findParamVertexIdx($className, $methodName, $idx) |
| { |
| return $this->findVertex('param', $className . '::' . $methodName . '/' . $idx); |
| } |
| |
| |
| |
| |
| |
| |
| |
| protected function findTypeVertex($type) |
| { |
| foreach (array('class', 'interface') as $pool) { |
| $typeVertex = $this->findVertex($pool, $type); |
| if (!is_null($typeVertex)) { |
| return $typeVertex; |
| } |
| } |
| |
| return null; |
| } |
| |
| |
| |
| |
| |
| |
| |
| protected function enterDeclaredMethodNode(\PHPParser_Node_Stmt_ClassMethod $node, MethodVertex $signature) |
| { |
| $this->graph->addEdge($this->currentClassVertex, $signature); |
| |
| foreach ($node->params as $idx => $param) { |
| |
| $paramVertex = $this->findParamVertexIdx($this->currentClass, $this->currentMethod, $idx); |
| $this->graph->addEdge($signature, $paramVertex); |
| |
| if ($param->type instanceof \PHPParser_Node_Name) { |
| $paramType = (string) $this->resolveClassName($param->type); |
| |
| $typeVertex = $this->findTypeVertex($paramType); |
| if (!is_null($typeVertex)) { |
| |
| $this->graph->addEdge($paramVertex, $typeVertex); |
| } |
| } |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| protected function enterImplementationNode(\PHPParser_Node_Stmt_ClassMethod $node, $signature, $declaringClass) |
| { |
| $impl = $this->findVertex('impl', $this->currentClass . '::' . $node->name); |
| $this->graph->addEdge($impl, $this->currentClassVertex); |
| |
| if ($declaringClass == $this->currentClass) { |
| $this->graph->addEdge($signature, $impl); |
| } else { |
| $this->graph->addEdge($this->currentClassVertex, $impl); |
| } |
| |
| foreach ($node->params as $idx => $param) { |
| |
| $paramVertex = $this->findParamVertexIdx($declaringClass, $this->currentMethod, $idx); |
| |
| |
| if (!is_null($paramVertex)) { |
| $this->graph->addEdge($impl, $paramVertex); |
| } |
| } |
| } |
| |
| |
| |
| |
| protected function enterPublicMethodNode(\PHPParser_Node_Stmt_ClassMethod $node) |
| { |
| $this->currentMethodNode = $node; |
| |
| if ($this->isTrait($this->currentClass)) { |
| $this->enterTraitMethod($node); |
| } elseif ($this->isInterface($this->currentClass)) { |
| $this->enterInterfaceMethod($node); |
| } else { |
| $this->enterClassMethod($node); |
| } |
| } |
| |
| private function enterInterfaceMethod(\PHPParser_Node_Stmt_ClassMethod $node) |
| { |
| |
| $declaringClass = $this->getDeclaringClass($this->currentClass, $this->currentMethod); |
| $signature = $this->findVertex('method', $declaringClass . '::' . $node->name); |
| |
| if ($declaringClass == $this->currentClass) { |
| $this->enterDeclaredMethodNode($node, $signature); |
| } |
| } |
| |
| private function enterClassMethod(\PHPParser_Node_Stmt_ClassMethod $node) |
| { |
| |
| $declaringClass = $this->getDeclaringClass($this->currentClass, $this->currentMethod); |
| $signature = $this->findVertex('method', $declaringClass . '::' . $node->name); |
| |
| if ($declaringClass == $this->currentClass) { |
| $this->enterDeclaredMethodNode($node, $signature); |
| } |
| |
| |
| |
| if (!$node->isAbstract()) { |
| $this->enterImplementationNode($node, $signature, $declaringClass); |
| } |
| } |
| |
| private function enterTraitMethod(\PHPParser_Node_Stmt_ClassMethod $node) |
| { |
| |
| $implVetex = $this->findVertex('impl', $this->getCurrentMethodIndex()); |
| $traitVertex = $this->currentClassVertex; |
| $this->graph->addEdge($implVetex, $traitVertex); |
| $this->graph->addEdge($traitVertex, $implVetex); |
| |
| |
| foreach ($node->params as $idx => $param) { |
| |
| $paramVertex = $this->findParamVertexIdx($this->currentClass, $this->currentMethod, $idx); |
| $this->graph->addEdge($implVetex, $paramVertex); |
| |
| if ($param->type instanceof \PHPParser_Node_Name) { |
| $paramType = (string) $this->resolveClassName($param->type); |
| |
| $typeVertex = $this->findTypeVertex($paramType); |
| if (!is_null($typeVertex)) { |
| |
| $this->graph->addEdge($paramVertex, $typeVertex); |
| } |
| } |
| } |
| |
| |
| $traitUser = $this->getClassesUsingTraitForDeclaringMethod($this->currentClass, $this->currentMethod); |
| foreach ($traitUser as $classname) { |
| |
| $source = $this->findVertex('class', $classname); |
| $target = $this->findVertex('method', $classname . '::' . $this->currentMethod); |
| $this->graph->addEdge($source, $target); |
| |
| foreach ($node->params as $idx => $param) { |
| $paramVertex = $this->findParamVertexIdx($this->currentClass, $this->currentMethod, $idx); |
| $this->graph->addEdge($target, $paramVertex); |
| } |
| } |
| } |
| |
| |
| |
| |
| protected function enterInterfaceNode(\PHPParser_Node_Stmt_Interface $node) |
| { |
| $src = $this->findVertex('interface', $this->currentClass); |
| $this->currentClassVertex = $src; |
| |
| |
| foreach ($node->extends as $interf) { |
| if (null !== $dst = $this->findVertex('interface', (string) $this->resolveClassName($interf))) { |
| $this->graph->addEdge($src, $dst); |
| } |
| } |
| } |
| |
| |
| |
| |
| protected function enterClassNode(\PHPParser_Node_Stmt_Class $node) |
| { |
| $src = $this->findVertex('class', $this->currentClass); |
| $this->currentClassVertex = $src; |
| |
| |
| if (!is_null($node->extends)) { |
| if (null !== $dst = $this->findVertex('class', (string) $this->resolveClassName($node->extends))) { |
| $this->graph->addEdge($src, $dst); |
| } |
| } |
| |
| foreach ($node->implements as $interf) { |
| if (null !== $dst = $this->findVertex('interface', (string) $this->resolveClassName($interf))) { |
| $this->graph->addEdge($src, $dst); |
| } |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| protected function enterMethodCall(\PHPParser_Node_Expr_MethodCall $node) |
| { |
| if (is_string($node->name)) { |
| $this->enterNonDynamicMethodCall($node); |
| } |
| } |
| |
| protected function enterStaticCall(\PHPParser_Node_Expr_StaticCall $node) |
| { |
| if (($node->class instanceof \PHPParser_Node_Name) && is_string($node->name)) { |
| $impl = $this->findVertex('impl', $this->getCurrentMethodIndex()); |
| $target = $this->findVertex('method', (string) $this->resolveClassName($node->class) . '::' . $node->name); |
| if (!is_null($target)) { |
| $this->graph->addEdge($impl, $target); |
| } |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| protected function getCalledMethodVertexOn($called, $method) |
| { |
| |
| if ($called == 'this') { |
| return array(); |
| } |
| |
| |
| $idx = false; |
| foreach ($this->currentMethodNode->params as $k => $paramSign) { |
| if ($paramSign->name == $called) { |
| $idx = $k; |
| break; |
| } |
| } |
| if (false !== $idx) { |
| $param = $this->currentMethodNode->params[$idx]; |
| |
| if ($param->type instanceof \PHPParser_Node_Name) { |
| $paramType = (string) $this->resolveClassName($param->type); |
| |
| if (!is_null($cls = $this->findMethodInInheritanceTree($paramType, $method))) { |
| if (!is_null($signature = $this->findVertex('method', "$cls::$method"))) { |
| return array($signature); |
| } |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| protected function enterNonDynamicMethodCall(\PHPParser_Node_Expr_MethodCall $node) |
| { |
| $method = $node->name; |
| $candidate = null; |
| |
| if (($node->var->getType() == 'Expr_Variable') && (is_string($node->var->name))) { |
| |
| |
| $candidate = $this->getCalledMethodVertexOn($node->var->name, $method); |
| } |
| |
| if (is_null($candidate)) { |
| $candidate = $this->findAllMethodSameName($method); |
| if (count($candidate)) { |
| |
| foreach ($candidate as $called) { |
| $this->logFallbackCall($this->currentClass, $this->currentMethod, $called->getName()); |
| } |
| } |
| } |
| $impl = $this->findVertex('impl', $this->currentClass . '::' . $this->currentMethod); |
| |
| $exclude = $this->getExcludedCall($this->currentClass, $this->currentMethod); |
| foreach ($candidate as $methodVertex) { |
| if (!in_array($methodVertex->getName(), $exclude)) { |
| $this->graph->addEdge($impl, $methodVertex); |
| } |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| protected function enterNewInstance(\PHPParser_Node_Expr_New $node) |
| { |
| if ($node->class instanceof \PHPParser_Node_Name) { |
| $classVertex = $this->findVertex('class', (string) $this->resolveClassName($node->class)); |
| if (!is_null($classVertex)) { |
| $impl = $this->findVertex('impl', $this->getCurrentMethodIndex()); |
| $this->graph->addEdge($impl, $classVertex); |
| } |
| } |
| } |
| |
| protected function enterTraitNode(\PHPParser_Node_Stmt_Trait $node) |
| { |
| $src = $this->findVertex('trait', $this->currentClass); |
| $this->currentClassVertex = $src; |
| } |
| |
| protected function enterTraitUse(\PHPParser_Node_Stmt_TraitUse $node) |
| { |
| if (!$this->currentClassVertex) { |
| throw new \LogicException('using a trait when not in a class'); |
| } |
| |
| foreach ($node->traits as $import) { |
| $name = (string) $this->resolveClassName($import); |
| $target = $this->findVertex('trait', $name); |
| |
| |
| if (!is_null($target)) { |
| $this->graph->addEdge($this->currentClassVertex, $target); |
| } |
| } |
| } |
| |
| } |