...
 
Commits (13)
......@@ -2,7 +2,7 @@
"name": "hgraca/app-mapper",
"type": "project",
"license": "MIT",
"description": "A static analysis tool that can draw an application map, which is similar to a application map but more detailed and accurate.",
"description": "A static analysis tool that can draw an application map, which is similar to a DDD contex map but more detailed and accurate.",
"require": {
"php": "^7.1.3",
"ext-ctype": "*",
......
......@@ -22,9 +22,6 @@ use Hgraca\AppMapper\Core\Port\Parser\AstMapInterface;
use Hgraca\AppMapper\Core\Port\Parser\Node\AdapterNodeCollection;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Node\NodeAdapterFactory;
use PhpParser\Node;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitor\FindingVisitor;
use PhpParser\NodeVisitor\FirstFindingVisitor;
use function array_values;
final class AstMap implements AstMapInterface
......@@ -54,9 +51,15 @@ final class AstMap implements AstMapInterface
*/
private $nodeAdapterFactory;
/**
* @var NodeFinder
*/
private $nodeFinder;
private function __construct()
{
$this->nodeAdapterFactory = new NodeAdapterFactory();
$this->nodeFinder = new NodeFinder();
}
public static function constructFromNodeCollectionList(NodeCollection ...$nodeCollectionList): self
......@@ -115,8 +118,8 @@ final class AstMap implements AstMapInterface
);
$parserNodeList = $query->shouldReturnSingleResult()
? [$this->findFirst($this->createFilter($query), ...$nodeList)]
: $this->find($this->createFilter($query), ...$nodeList);
? [$this->nodeFinder->findFirst($this->createFilter($query), ...$nodeList)]
: $this->nodeFinder->find($this->createFilter($query), ...$nodeList);
return $this->mapNodeList($parserNodeList);
}
......@@ -144,26 +147,6 @@ final class AstMap implements AstMapInterface
};
}
private function find(callable $filter, Node ...$nodes): array
{
$traverser = new NodeTraverser();
$visitor = new FindingVisitor($filter);
$traverser->addVisitor($visitor);
$traverser->traverse($nodes);
return $visitor->getFoundNodes();
}
private function findFirst(callable $filter, Node ...$nodes): ?Node
{
$traverser = new NodeTraverser();
$visitor = new FirstFindingVisitor($filter);
$traverser->addVisitor($visitor);
$traverser->traverse($nodes);
return $visitor->getFoundNode();
}
private function getComponentAstCollection(string $componentName): NodeCollection
{
return $this->componentNodeCollectionList[$componentName];
......
<?php
declare(strict_types=1);
/*
* This file is part of the Application mapper application,
* following the Explicit Architecture principles.
*
* @link https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together
* @link https://herbertograca.com/2018/07/07/more-than-concentric-layers/
*
* (c) Herberto Graça
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception;
use Hgraca\AppMapper\Core\Port\Parser\Exception\ParserException;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\NodeDecorator\ParamNodeDecorator;
final class UnknownParameterException extends ParserException
{
public function __construct(ParamNodeDecorator $searchParamDecorator)
{
$parameterName = $searchParamDecorator->getName();
$method = $searchParamDecorator->getEnclosingMethodNode();
$methodName = $method->getName();
$className = $method->getEnclosingClassLikeNode()->getName();
parent::__construct(
"Unknown parameter '$parameterName' in method '$methodName' in class '$className'.\n"
. $searchParamDecorator->resolveNodeTreeAsJson()
);
}
}
......@@ -37,7 +37,7 @@ final class MethodParameterAdapter implements TypeNodeInterface, MethodParameter
public function getFullyQualifiedType(): string
{
return $this->parameterNodeDecorator->getType()->getTypeCollection()->getUniqueType()->getFqn();
return $this->parameterNodeDecorator->getDeclaredType()->getTypeCollection()->getUniqueType()->getFqn();
}
public function getCanonicalType(): string
......
......@@ -129,6 +129,7 @@ final class NodeCollection
public function resolveAllTypes(): void
{
$GLOBALS['nodeList'] = $this->nodeList;
$traverser = new NodeTraverser();
$traverser->addVisitor(new TypeResolverVisitor());
$traverser->traverse(array_values($this->nodeList));
......
<?php
declare(strict_types=1);
/*
* This file is part of the Application mapper application,
* following the Explicit Architecture principles.
*
* @link https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together
* @link https://herbertograca.com/2018/07/07/more-than-concentric-layers/
*
* (c) Herberto Graça
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser;
use PhpParser\Node;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitor\FindingVisitor;
use PhpParser\NodeVisitor\FirstFindingVisitor;
final class NodeFinder
{
public function find(callable $filter, Node ...$nodes): array
{
$traverser = new NodeTraverser();
$visitor = new FindingVisitor($filter);
$traverser->addVisitor($visitor);
$traverser->traverse($nodes);
return $visitor->getFoundNodes();
}
public function findFirst(callable $filter, Node ...$nodes): ?Node
{
$traverser = new NodeTraverser();
$visitor = new FirstFindingVisitor($filter);
$traverser->addVisitor($visitor);
$traverser->traverse($nodes);
return $visitor->getFoundNode();
}
}
......@@ -19,14 +19,17 @@ namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\NodeDeco
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\TypeCollection;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\TypeNodeCollector;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\Trait_;
use PhpParser\Node\Stmt\TraitUse;
use function array_key_exists;
/**
* @property Class_|Trait_ $node
*/
abstract class AbstractClassLikeNodeDecorator extends AbstractInterfaceLikeNodeDecorator
abstract class AbstractClassLikeNodeDecorator extends AbstractInterfaceLikeNodeDecorator implements NamedNodeDecoratorInterface
{
/**
* @var TypeNodeCollector
......@@ -38,6 +41,17 @@ abstract class AbstractClassLikeNodeDecorator extends AbstractInterfaceLikeNodeD
*/
private $propertyNodesTypeCollections = [];
/**
* @var PropertyNodeDecorator[]
*/
private $propertyList = [];
public function __construct(Node $node, AbstractNodeDecorator $parentNode = null)
{
parent::__construct($node, $parentNode);
$this->propertyNodesSiblingCollection = new TypeNodeCollector();
}
abstract protected function getPropertyTypeCollectionFromHierarchy(
NamedNodeDecoratorInterface $nodeDecorator
): TypeCollection;
......@@ -61,7 +75,7 @@ abstract class AbstractClassLikeNodeDecorator extends AbstractInterfaceLikeNodeD
public function getPropertyTypeCollection(NamedNodeDecoratorInterface $nodeDecorator): TypeCollection
{
if (!$this->hasProperty($nodeDecorator)) {
if (!$this->hasDeclaredProperty($nodeDecorator)) {
return $this->getPropertyTypeCollectionFromHierarchy($nodeDecorator);
}
......@@ -73,6 +87,24 @@ abstract class AbstractClassLikeNodeDecorator extends AbstractInterfaceLikeNodeD
return $this->propertyNodesTypeCollections[$propertyName];
}
/**
* @return PropertyNodeDecorator[]
*/
public function getDeclaredProperties(): array
{
if (empty($this->propertyList)) {
foreach ($this->node->stmts as $stmt) {
if ($stmt instanceof Property) {
/** @var PropertyNodeDecorator $propertyDecorator */
$propertyDecorator = $this->getNodeDecorator($stmt);
$this->propertyList[$propertyDecorator->getName()] = $propertyDecorator;
}
}
}
return $this->propertyList;
}
protected function getPropertyTypeCollectionFromTraits(NamedNodeDecoratorInterface $nodeDecorator): TypeCollection
{
/** @var TraitNodeDecorator[] $traitList */
......@@ -111,9 +143,16 @@ abstract class AbstractClassLikeNodeDecorator extends AbstractInterfaceLikeNodeD
return array_key_exists($nodeDecorator->getName(), $this->propertyNodesTypeCollections);
}
private function hasProperty(NamedNodeDecoratorInterface $nodeDecorator): bool
private function hasDeclaredProperty(NamedNodeDecoratorInterface $nodeDecorator): bool
{
return $this->propertyNodesSiblingCollection->hasNodesFor($nodeDecorator);
return array_key_exists($nodeDecorator->getName(), $this->getDeclaredProperties());
}
private function getDeclaredProperty(NamedNodeDecoratorInterface $nodeDecorator): PropertyNodeDecorator
{
$declaredProperties = $this->getDeclaredProperties();
return $declaredProperties[$nodeDecorator->getName()];
}
public function storePropertiesSiblings(TypeNodeCollector $nodeCollector): void
......@@ -123,7 +162,7 @@ abstract class AbstractClassLikeNodeDecorator extends AbstractInterfaceLikeNodeD
private function resolvePropertyTypeCollection(NamedNodeDecoratorInterface $nodeDecorator): void
{
$typeCollection = new TypeCollection();
$typeCollection = $this->getDeclaredProperty($nodeDecorator)->getTypeCollection();
foreach ($this->propertyNodesSiblingCollection->getNodesFor($nodeDecorator) as $siblingNode) {
$typeCollection = $typeCollection->addTypeCollection($siblingNode->getTypeCollection());
}
......
......@@ -180,16 +180,20 @@ abstract class AbstractNodeDecorator
protected function getFirstParentNodeOfType(string $type): self
{
$node = $this;
$nodeDecorator = $this;
do {
$node = $node->getParentNode();
} while ($node !== null && !$node->isInternalNodeInstanceOf($type));
if (!$node) {
$nodeDecorator = $nodeDecorator->getParentNode();
} while (
$nodeDecorator !== null
&& !$nodeDecorator->isInternalNodeInstanceOf($type)
&& !$nodeDecorator instanceof $type
);
if (!$nodeDecorator) {
throw new ParentNodeNotFoundException($type, $this);
}
return $node;
return $nodeDecorator;
}
protected function getTypeCollectionFromUses(string $type): TypeCollection
......
......@@ -17,6 +17,8 @@ declare(strict_types=1);
namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\NodeDecorator;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception\ParentNodeNotFoundException;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception\UnknownParameterException;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\TypeCollection;
use PhpParser\Node\Stmt\ClassMethod;
......@@ -52,8 +54,45 @@ final class ClassMethodNodeDecorator extends AbstractNodeDecorator implements Na
return $this->getNodeDecorator($this->node->params[$index]);
}
public function getParameterIndex(ParamNodeDecorator $searchParamDecorator): int
{
foreach ($this->node->params as $index => $param) {
$currentParamDecorator = $this->getNodeDecorator($param);
if ($currentParamDecorator === $searchParamDecorator) {
return $index;
}
}
throw new UnknownParameterException($searchParamDecorator);
}
public function isPublic(): bool
{
return $this->node->isPublic();
}
public function isWithinClass(): bool
{
try {
if ($this->getEnclosingClassLikeNode() instanceof StmtClassNodeDecorator) {
return true;
}
} catch (ParentNodeNotFoundException $e) {
}
return false;
}
public function isWithinTrait(): bool
{
try {
if ($this->getEnclosingClassLikeNode() instanceof TraitNodeDecorator) {
return true;
}
} catch (ParentNodeNotFoundException $e) {
}
return false;
}
}
<?php
declare(strict_types=1);
/*
* This file is part of the Application mapper application,
* following the Explicit Architecture principles.
*
* @link https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together
* @link https://herbertograca.com/2018/07/07/more-than-concentric-layers/
*
* (c) Herberto Graça
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\NodeDecorator;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\TypeCollection;
final class ClosureNodeDecorator extends AbstractNodeDecorator
{
public function resolveTypeCollection(): TypeCollection
{
return new TypeCollection();
}
}
......@@ -112,6 +112,11 @@ final class MethodCallNodeDecorator extends AbstractNodeDecorator implements Nam
return $argumentList;
}
public function getArgumentInIndex(int $index): ?ArgNodeDecorator
{
return $this->getArguments()[$index] ?? null;
}
public function getLine(): int
{
return (int) $this->node->getAttribute('startLine');
......
......@@ -17,39 +17,151 @@ declare(strict_types=1);
namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\NodeDecorator;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\NodeCollection;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\NodeFinder;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\TypeCollection;
use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Param;
use function array_values;
/**
* @property Param $node
*/
final class ParamNodeDecorator extends AbstractNodeDecorator
final class ParamNodeDecorator extends AbstractNodeDecorator implements NamedNodeDecoratorInterface
{
public function __construct(Param $node, AbstractNodeDecorator $parentNode)
/**
* @var NodeFinder
*/
private $nodeFinder;
public function __construct(Param $node, AbstractNodeDecorator $parentNode, NodeCollection $nodeCollection)
{
parent::__construct($node, $parentNode);
$this->nodeCollection = $nodeCollection;
$this->nodeFinder = new NodeFinder();
}
public function resolveTypeCollection(): TypeCollection
{
return $this->getType()
$typeCollection = $this->getDeclaredType()
->getTypeCollection()
->addTypeCollection(
$this->getDefaultValueTypeCollection()
);
if ($this->isWithinClass() && !$typeCollection->isConcrete() && !$this->isParameterOfClosure()) {
// FIXME this makes it VERY slow! We need to investigate why and maybe make it optional using a CLI param.
$this->addTypesFromMethodCallsArgument();
}
return $typeCollection->addTypeCollection($this->getSiblingTypeCollection());
}
public function getType(): AbstractNodeDecorator
public function getDeclaredType(): AbstractNodeDecorator
{
return $this->node->type === null
? new NullNodeDecorator($this)
: $this->getNodeDecorator($this->node->type);
}
public function getName(): string
{
return (string) $this->node->var->name;
}
private function getDefaultValueTypeCollection(): TypeCollection
{
return $this->node->default
? $this->getNodeDecorator($this->node->default)->getTypeCollection()
: new TypeCollection();
}
/**
* @return MethodCallNodeDecorator[]
*/
private function findMethodCalls(string $dispatcherTypeRegex, string $methodRegex): array
{
$nodeList = $this->nodeFinder->find(
$this->getFindMethodCallsFilter($dispatcherTypeRegex, $methodRegex),
...array_values($this->nodeCollection->toArray())
);
$decoratorList = [];
foreach ($nodeList as $node) {
$decoratorList[] = $this->getNodeDecorator($node);
}
return $decoratorList;
}
private function getFindMethodCallsFilter(string $dispatcherTypeRegex, string $methodRegex): callable
{
return function (Node $node) use ($dispatcherTypeRegex, $methodRegex) {
if (!$node instanceof MethodCall) {
return false;
}
/** @var MethodCallNodeDecorator $methodCallDecorator */
$methodCallDecorator = $this->getNodeDecorator($node);
$calleeTypeCollection = $methodCallDecorator->getCallee()->getTypeCollection();
foreach ($calleeTypeCollection as $type) {
if (
$methodRegex === $methodCallDecorator->getMethodName()
&& $dispatcherTypeRegex === $type->getFqn()
) {
return true;
}
}
return false;
};
}
private function getParamIndex(): int
{
return $this->getClassMethod()->getParameterIndex($this);
}
private function getClassMethod(): ClassMethodNodeDecorator
{
return $this->getFirstParentNodeOfType(ClassMethodNodeDecorator::class);
}
private function isWithinClass(): bool
{
return $this->getEnclosingInterfaceLikeNode() instanceof StmtClassNodeDecorator;
}
private function isParameterOfClosure(): bool
{
return $this->getParentNode() instanceof ClosureNodeDecorator;
}
/**
* @param TypeCollection $typeCollection
*
* @return TypeCollection
*/
private function addTypesFromMethodCallsArgument(): void
{
$methodCallList = $this->findMethodCalls(
$this->getEnclosingClassLikeNode()->getTypeCollection()->getUniqueType()->getFqn(),
$this->getEnclosingMethodNode()->getName()
);
$paramIndex = $this->getParamIndex();
$argumentList = [];
foreach ($methodCallList as $methodCall) {
$argument = $methodCall->getArgumentInIndex($paramIndex);
if ($argument) {
$argumentList[] = $argument;
}
}
if (!empty($argumentList)) {
$this->addSiblingNodes(...$argumentList);
}
}
}
......@@ -41,9 +41,7 @@ final class PropertyFetchNodeDecorator extends AbstractNodeDecorator implements
return $parentNode->getExpression()->getTypeCollection();
}
return $this->getSiblingTypeCollection()->addTypeCollection(
$this->getEnclosingClassLikeNode()->getPropertyTypeCollection($this)
);
return $this->getSiblingTypeCollection();
}
public function getName(): string
......
......@@ -23,7 +23,7 @@ use PhpParser\Node\Stmt\Class_;
/**
* @property Class_ $node
*/
final class StmtClassNodeDecorator extends AbstractClassLikeNodeDecorator implements NamedNodeDecoratorInterface
final class StmtClassNodeDecorator extends AbstractClassLikeNodeDecorator
{
public function __construct(Class_ $node, AbstractNodeDecorator $parentNode)
{
......@@ -61,6 +61,11 @@ final class StmtClassNodeDecorator extends AbstractClassLikeNodeDecorator implem
: null;
}
public function isAbstract(): bool
{
return $this->node->isAbstract();
}
protected function getPropertyTypeCollectionFromHierarchy(
NamedNodeDecoratorInterface $nodeDecorator
): TypeCollection {
......
......@@ -23,7 +23,7 @@ use PhpParser\Node\Stmt\Trait_;
/**
* @property Trait_ $node
*/
final class TraitNodeDecorator extends AbstractClassLikeNodeDecorator implements NamedNodeDecoratorInterface
final class TraitNodeDecorator extends AbstractClassLikeNodeDecorator
{
public function __construct(Trait_ $node, AbstractNodeDecorator $parentNode)
{
......
......@@ -18,9 +18,7 @@ declare(strict_types=1);
namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\Strategy;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\NodeDecoratorAccessorTrait;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\NodeDecorator\AbstractInterfaceLikeNodeDecorator;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\NodeDecorator\AbstractNodeDecorator;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\NodeDecorator\PropertyNodeDecorator;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\TypeNodeCollector;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
......@@ -47,8 +45,8 @@ abstract class AbstractPropertyContainerNodeStrategy extends AbstractStrategy
{
$this->validateNode($node);
$this->addCollectedPropertySiblingsToTheirDeclaration($node);
$this->storePropertiesSiblingsInNode($node);
// $this->addCollectedPropertySiblingsToTheirDeclaration($node);
// $this->storePropertiesSiblingsInNode($node);
$this->propertyFetchCollector->reset();
}
......@@ -57,20 +55,20 @@ abstract class AbstractPropertyContainerNodeStrategy extends AbstractStrategy
*
* @param Class_|Trait_ $node
*/
private function addCollectedPropertySiblingsToTheirDeclaration(Node $node): void
{
foreach ($node->stmts as $stmt) {
$stmtNodeDecorator = $this->getNodeDecorator($stmt);
if ($stmtNodeDecorator instanceof PropertyNodeDecorator) {
$stmtNodeDecorator->addSiblingNodes(
...$this->excludeNode(
$this->propertyFetchCollector->getNodesFor($stmtNodeDecorator),
$stmtNodeDecorator
)
);
}
}
}
// private function addCollectedPropertySiblingsToTheirDeclaration(Node $node): void
// {
// foreach ($node->stmts as $stmt) {
// $stmtNodeDecorator = $this->getNodeDecorator($stmt);
// if ($stmtNodeDecorator instanceof PropertyNodeDecorator) {
// $stmtNodeDecorator->addSiblingNodes(
// ...$this->excludeNode(
// $this->propertyFetchCollector->getNodesFor($stmtNodeDecorator),
// $stmtNodeDecorator
// )
// );
// }
// }
// }
private function excludeNode(array $nodeList, AbstractNodeDecorator $nodeToRemove): array
{
......@@ -82,10 +80,11 @@ abstract class AbstractPropertyContainerNodeStrategy extends AbstractStrategy
);
}
private function storePropertiesSiblingsInNode(Node $node): void
{
/** @var AbstractInterfaceLikeNodeDecorator $classLikeNodeDecorator */
$classLikeNodeDecorator = $this->getNodeDecorator($node);
$classLikeNodeDecorator->storePropertiesSiblings($this->propertyFetchCollector->clone());
}
//
// private function storePropertiesSiblingsInNode(Node $node): void
// {
// /** @var AbstractInterfaceLikeNodeDecorator $classLikeNodeDecorator */
// $classLikeNodeDecorator = $this->getNodeDecorator($node);
// $classLikeNodeDecorator->storePropertiesSiblings($this->propertyFetchCollector->clone());
// }
}
......@@ -17,19 +17,48 @@ declare(strict_types=1);
namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\Strategy;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\NodeDecoratorAccessorTrait;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\NodeDecorator\ClassMethodNodeDecorator;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\TypeNodeCollector;
use PhpParser\Node;
use PhpParser\Node\Stmt\ClassMethod;
final class ClassMethodNodeStrategy extends AbstractStrategy
{
use NodeDecoratorAccessorTrait;
/**
* @var TypeNodeCollector
*/
private $propertyFetchCollector;
/**
* @var TypeNodeCollector
*/
private $variableCollector;
public function __construct(TypeNodeCollector $variableCollector)
public function __construct(TypeNodeCollector $propertyFetchCollector, TypeNodeCollector $variableCollector)
{
$this->propertyFetchCollector = $propertyFetchCollector;
$this->variableCollector = $variableCollector;
}
/**
* @param Node|ClassMethod $classMethod
*/
public function enterNode(Node $classMethod): void
{
$this->validateNode($classMethod);
/** @var ClassMethodNodeDecorator $classMethodDecorator */
$classMethodDecorator = $this->getNodeDecorator($classMethod);
if ($classMethodDecorator->isWithinClass() || $classMethodDecorator->isWithinTrait()) {
$this->propertyFetchCollector->initializeWith(
$classMethodDecorator->getEnclosingClassLikeNode()->getDeclaredProperties()
);
}
}
/**
* @param Node|ClassMethod $classMethod
*/
......
......@@ -81,7 +81,7 @@ final class ForeachNodeStrategy extends AbstractStrategy
$this->variableCollector->reassign($exprDecorator);
} elseif ($exprDecorator instanceof PropertyFetchNodeDecorator) {
// Assignment to property
$this->propertyCollector->collectNodeFor($exprDecorator);
$this->propertyCollector->collectNode($exprDecorator);
}
}
}
......@@ -27,11 +27,11 @@ final class PropertyFetchNodeStrategy extends AbstractStrategy
{
use NodeDecoratorAccessorTrait;
private $propertyCollector;
private $propertyFetchCollector;
public function __construct(TypeNodeCollector $propertyCollector)
public function __construct(TypeNodeCollector $propertyFetchCollector)
{
$this->propertyCollector = $propertyCollector;
$this->propertyFetchCollector = $propertyFetchCollector;
}
/**
......@@ -45,13 +45,13 @@ final class PropertyFetchNodeStrategy extends AbstractStrategy
$propertyFetchNodeDecorator = $this->getNodeDecorator($propertyFetchNode);
if ($propertyFetchNodeDecorator->isAssignee()) {
$this->propertyCollector->collectNodeFor($propertyFetchNodeDecorator);
$this->propertyFetchCollector->collectNode($propertyFetchNodeDecorator);
return;
}
$propertyFetchNodeDecorator->addSiblingNodes(
...$this->propertyCollector->getNodesFor($propertyFetchNodeDecorator)
...$this->propertyFetchCollector->getNodesFor($propertyFetchNodeDecorator)
);
}
......
......@@ -27,11 +27,11 @@ final class PropertyNodeStrategy extends AbstractStrategy
{
use NodeDecoratorAccessorTrait;
private $propertyCollector;
private $propertyFetchCollector;
public function __construct(TypeNodeCollector $propertyCollector)
public function __construct(TypeNodeCollector $propertyFetchCollector)
{
$this->propertyCollector = $propertyCollector;
$this->propertyFetchCollector = $propertyFetchCollector;
}
/**
......@@ -44,7 +44,7 @@ final class PropertyNodeStrategy extends AbstractStrategy
/** @var PropertyNodeDecorator $propertyDecorator */
$propertyDecorator = $this->getNodeDecorator($property);
$this->propertyCollector->collectNodeFor($propertyDecorator);
$this->propertyFetchCollector->collectNode($propertyDecorator);
}
public static function getNodeTypeHandled(): string
......
......@@ -48,13 +48,13 @@ final class VariableNodeStrategy extends AbstractStrategy
$variableNodeDecorator = $this->getNodeDecorator($variableNode);
if ($variableNodeDecorator->isParameterDeclaration()) {
$this->variableCollector->collectNodeFor($variableNodeDecorator);
$this->variableCollector->collectNode($variableNodeDecorator);
return;
}
if ($variableNodeDecorator->isAssignee()) {
$this->variableCollector->collectNodeFor($variableNodeDecorator);
$this->variableCollector->collectNode($variableNodeDecorator);
return;
}
......
......@@ -141,4 +141,20 @@ final class Type
return $this->nodeTree;
}
public function isInterface(): bool
{
return $this instanceof InterfaceNodeDecorator;
}
public function isAbstract(): bool
{
return $this->nodeDecorator instanceof StmtClassNodeDecorator
&& $this->nodeDecorator->isAbstract();
}
public function isUnknown(): bool
{
return $this->typeAsString === self::UNKNOWN;
}
}
......@@ -21,6 +21,7 @@ use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\AbstractCollection;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception\EmptyCollectionException;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception\NonUniqueTypeCollectionException;
use Hgraca\PhpExtension\String\ClassHelper;
use Hgraca\PhpExtension\String\StringHelper;
use function array_key_exists;
use function array_values;
......@@ -92,4 +93,55 @@ final class TypeCollection extends AbstractCollection
{
return array_key_exists($typeFqn, $this->itemList);
}
public function containsInterface(): bool
{
foreach ($this->itemList as $fqcn => $type) {
if (
StringHelper::hasEnding('Interface', $fqcn)
|| $type->isInterface()
) {
return true;
}
}
return false;
}
public function containsAbstract(): bool
{
foreach ($this->itemList as $fqcn => $type) {
if (
StringHelper::hasBeginning('Abstract', $fqcn)
|| $type->isAbstract()
) {
return true;
}
}
return false;
}
public function containsMixed(): bool
{
foreach ($this->itemList as $fqcn => $type) {
if (
StringHelper::hasBeginning('Abstract', $fqcn)
|| $type->isAbstract()
) {
return true;
}
}
return false;
}
public function isConcrete(): bool
{
return !(
$this->containsInterface()
|| $this->containsAbstract()
|| $this->containsMixed()
);
}
}
......@@ -32,7 +32,7 @@ final class TypeNodeCollector
*/
private $collector = [];
public function collectNodeFor(NamedNodeDecoratorInterface $nodeDecorator): void
public function collectNode(NamedNodeDecoratorInterface $nodeDecorator): void
{
$this->collector[$nodeDecorator->getName()][spl_object_hash($nodeDecorator)] = $nodeDecorator;
}
......@@ -40,7 +40,7 @@ final class TypeNodeCollector
public function reassign(NamedNodeDecoratorInterface $nodeDecorator): void
{
$this->collector[$nodeDecorator->getName()] = [];
$this->collectNodeFor($nodeDecorator);
$this->collectNode($nodeDecorator);
}
/**
......@@ -68,4 +68,13 @@ final class TypeNodeCollector
return $clone;
}
public function initializeWith(array $nodeDecoratorList): void
{
$this->reset();
foreach ($nodeDecoratorList as $nodeDecorator) {
$this->collectNode($nodeDecorator);
}
}
}
......@@ -39,7 +39,7 @@ final class TypeResolverInjectorVisitor extends NodeVisitorAbstract
$this->strategyCollection = new NodeVisitorStrategyCollection(
new AssignNodeStrategy($propertyFetchCollector, $variableCollector),
new ClassMethodNodeStrategy($variableCollector),
new ClassMethodNodeStrategy($propertyFetchCollector, $variableCollector),
new ClassNodeStrategy($propertyFetchCollector),
new ForeachNodeStrategy($propertyFetchCollector, $variableCollector),
new PropertyFetchNodeStrategy($propertyFetchCollector),
......
......@@ -23,6 +23,7 @@ use Hgraca\AppMapper\Core\Port\Configuration\ConfigurationFactoryInterface;
use Hgraca\AppMapper\Test\Framework\AbstractIntegrationTest;
use Hgraca\AppMapper\Test\StubProjectSrc\Core\Component\X\Application\Service\XxxAaaService;
use Hgraca\AppMapper\Test\StubProjectSrc\Core\SharedKernel\Event\AaaEvent;
use Hgraca\AppMapper\Test\StubProjectSrc\Core\SharedKernel\Event\BbbEvent;
use Hgraca\AppMapper\Test\StubProjectSrc\Core\SharedKernel\Event\CccEvent;
use Hgraca\AppMapper\Test\StubProjectSrc\Core\SharedKernel\Event\DddEvent;
use Hgraca\AppMapper\Test\StubProjectSrc\Core\SharedKernel\Event\EeeEvent;
......@@ -52,21 +53,21 @@ final class AppMapServiceIntegrationTest extends AbstractIntegrationTest
}
}
// /**
// * @test
// */
// public function event_type_is_inferred_correctly_when_injected_but_type_hinted_interface(): void
// {
// $this->assertMethodDispatchesEvent(XxxAaaService::class, 'methodC', BbbEvent::class);
// }
//
// /**
// * @test
// */
// public function event_type_is_inferred_correctly_when_ternary_operator_is_used(): void
// {
// $this->assertMethodDispatchesEvent(XxxAaaService::class, 'methodC', CccEvent::class);
// }
/**
* @test
*/
public function event_type_is_inferred_correctly_when_injected_but_type_hinted_interface(): void
{
$this->assertMethodDispatchesEvent(XxxAaaService::class, 'methodC', BbbEvent::class);
}
/**
* @test
*/
public function event_type_is_inferred_correctly_when_ternary_operator_is_used(): void
{
$this->assertMethodDispatchesEvent(XxxAaaService::class, 'methodC', CccEvent::class);
}
/**
* @test
......
......@@ -41,6 +41,7 @@ use Hgraca\AppMapper\Test\StubProjectSrc\Core\Port\EventDispatcher\EventDispatch
use Hgraca\AppMapper\Test\StubProjectSrc\Core\Port\EventDispatcher\EventInterface;
use Hgraca\AppMapper\Test\StubProjectSrc\Core\SharedKernel\DddTrait;
use Hgraca\AppMapper\Test\StubProjectSrc\Core\SharedKernel\Event\AaaEvent;
use Hgraca\AppMapper\Test\StubProjectSrc\Core\SharedKernel\Event\BbbEvent;
use Hgraca\AppMapper\Test\StubProjectSrc\Core\SharedKernel\Event\CccEvent;
use Hgraca\AppMapper\Test\StubProjectSrc\Core\SharedKernel\Event\DddEvent;
use Hgraca\PhpExtension\Reflection\ReflectionHelper;
......@@ -908,6 +909,103 @@ final class NodeCollectionIntegrationTest extends AbstractIntegrationTest
);
}
/**
* @test
*
* @throws \ReflectionException
*/
public function type_is_inferred_correctly_when_injected_but_type_hinted_interface(): void
{
$methodNode = $this->getMethod('methodC', XxxAaaService::class);
$assignmentExpressionVariableTypeList = ReflectionHelper::getNestedProperty(
'stmts.0.expr.var.attributes.decorator.typeCollection.itemList',
$methodNode
);
// Assignment variable should have all these types
self::assertCount(
4,
$assignmentExpressionVariableTypeList,
json_encode(array_keys($assignmentExpressionVariableTypeList), JSON_PRETTY_PRINT)
);
self::assertArrayHasKey(
EventInterface::class,
$assignmentExpressionVariableTypeList,
json_encode(array_keys($assignmentExpressionVariableTypeList), JSON_PRETTY_PRINT)
);
self::assertArrayHasKey(
BbbEvent::class,
$assignmentExpressionVariableTypeList,
json_encode(array_keys($assignmentExpressionVariableTypeList), JSON_PRETTY_PRINT)
);
self::assertArrayHasKey(
CccEvent::class,
$assignmentExpressionVariableTypeList,
json_encode(array_keys($assignmentExpressionVariableTypeList), JSON_PRETTY_PRINT)
);
self::assertArrayHasKey(
'null',
$assignmentExpressionVariableTypeList,
json_encode(array_keys($assignmentExpressionVariableTypeList), JSON_PRETTY_PRINT)
);
// Dispatched type should have all these types
$dispatchedVariableTypeList = ReflectionHelper::getNestedProperty(
'stmts.1.expr.args.0.attributes.decorator.typeCollection.itemList',
$methodNode
);
self::assertCount(
4,
$dispatchedVariableTypeList,
json_encode(array_keys($dispatchedVariableTypeList), JSON_PRETTY_PRINT)
);
self::assertArrayHasKey(
EventInterface::class,
$dispatchedVariableTypeList,
json_encode(array_keys($dispatchedVariableTypeList), JSON_PRETTY_PRINT)
);
self::assertArrayHasKey(
BbbEvent::class,
$dispatchedVariableTypeList,
json_encode(array_keys($dispatchedVariableTypeList), JSON_PRETTY_PRINT)
);
self::assertArrayHasKey(
CccEvent::class,
$dispatchedVariableTypeList,
json_encode(array_keys($dispatchedVariableTypeList), JSON_PRETTY_PRINT)
);
self::assertArrayHasKey(
'null',
$dispatchedVariableTypeList,
json_encode(array_keys($dispatchedVariableTypeList), JSON_PRETTY_PRINT)
);
}
/**
* @test
*
* @throws \ReflectionException
*/
public function xxx_aaa_service_method_g_dispatches_ccc_event(): void
{
$methodNode = $this->getMethod('methodG', XxxAaaService::class);
$dispatchedEventTypeList = ReflectionHelper::getNestedProperty(
'stmts.0.expr.args.0.attributes.decorator.typeCollection.itemList',
$methodNode
);
self::assertCount(
1,
$dispatchedEventTypeList,
json_encode(array_keys($dispatchedEventTypeList), JSON_PRETTY_PRINT)
);
self::assertArrayHasKey(
CccEvent::class,
$dispatchedEventTypeList,
json_encode(array_keys($dispatchedEventTypeList), JSON_PRETTY_PRINT)
);
}
private function getProperty(string $propertyName, string $classFqcn): Property
{
/** @var Class_ $classNode */
......@@ -954,4 +1052,47 @@ final class NodeCollectionIntegrationTest extends AbstractIntegrationTest
self::fail("Could not find parameter with name $ in method $methodName of class node $classFqcn");
}
/**
* @test
* @dataProvider methodNameProvider
*
* @throws \ReflectionException
*/
public function xxx_aaa_service_method_event_dispatcher_property_type_is_recognized(
int $stmtNbr,
string $methodName
): void {
$methodNode = $this->getMethod($methodName, XxxAaaService::class);
$dispatcherTypeList = ReflectionHelper::getNestedProperty(
"stmts.$stmtNbr.expr.var.attributes.decorator.typeCollection.itemList",
$methodNode
);
self::assertCount(
2,
$dispatcherTypeList,
json_encode(array_keys($dispatcherTypeList), JSON_PRETTY_PRINT)
);
self::assertArrayHasKey(
EventDispatcherInterface::class,
$dispatcherTypeList,
json_encode(array_keys($dispatcherTypeList), JSON_PRETTY_PRINT)
);
}
public function methodNameProvider(): array
{
return [
[1, 'methodC'],
[1, 'methodD'],
[0, 'methodE'],
[0, 'methodF'],
[0, 'methodG'],
[0, 'methodH'],
[1, 'methodJ'],
[1, 'methodK'],
[1, 'methodM'],
];
}
}