Refactor the Visitor

So that each node visitor logic is in its own strategy class.
parent 97c4a886
<?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;
final class InvalidArgumentException extends ParserException
{
}
<?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\Strategy;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception\InvalidArgumentException;
use PhpParser\Node;
use function get_class;
abstract class AbstractStrategy implements NodeVisitorStrategyInterface
{
public function enterNode(Node $node): void
{
$this->validateNode($node);
}
public function leaveNode(Node $node): void
{
$this->validateNode($node);
}
protected function validateNode(Node $node): void
{
$handledNodeClass = static::getNodeTypeHandled();
if (!$node instanceof $handledNodeClass) {
throw new InvalidArgumentException(
'Visitor strategy \'' . static::class . '\' can not visit node of type \'' . get_class($node) . '\''
);
}
}
}
<?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\Strategy;
use Hgraca\AppMapper\Core\Port\Logger\StaticLoggerFacade;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception\UnresolvableNodeTypeException;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\NodeTypeManagerTrait;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\TypeCollection;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\TypeResolverCollector;
use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
final class AssignNodeStrategy extends AbstractStrategy
{
use NodeTypeManagerTrait;
use VariableNameExtractorTrait;
private $propertyCollector;
private $variableCollector;
public function __construct(TypeResolverCollector $propertyCollector, TypeResolverCollector $variableCollector)
{
$this->propertyCollector = $propertyCollector;
$this->variableCollector = $variableCollector;
}
/**
* @param Node|Assign $assignNode
*/
public function leaveNode(Node $assignNode): void
{
$this->validateNode($assignNode);
$variable = $assignNode->var;
$expression = $assignNode->expr;
$resolver = function () use ($expression): TypeCollection {
try {
return self::resolveType($expression);
} catch (UnresolvableNodeTypeException $e) {
StaticLoggerFacade::warning(
"Silently ignoring a UnresolvableNodeTypeException in this filter.\n"
. 'This is failing, at least, for nested method calls like'
. '`$invoice->transactions->first()->getServicePro();`.' . "\n"
. "This should be fixed in the type addition visitors.\n"
. $e->getMessage(),
[__METHOD__]
);
return new TypeCollection();
}
};
self::addTypeResolver($variable, $resolver);
if ($variable instanceof Variable) {
// Assignment to variable
$this->variableCollector->resetResolverCollection($this->getVariableName($variable), $resolver);
} elseif ($variable instanceof PropertyFetch) {
// Assignment to property
$this->propertyCollector->collectResolver($this->getPropertyName($variable), $resolver);
}
}
public static function getNodeTypeHandled(): string
{
return Assign::class;
}
}
<?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\Strategy;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\TypeResolverCollector;
use PhpParser\Node;
use PhpParser\Node\Stmt\ClassMethod;
final class ClassMethodNodeStrategy extends AbstractStrategy
{
private $variableCollector;
public function __construct(TypeResolverCollector $variableCollector)
{
$this->variableCollector = $variableCollector;
}
/**
* @param Node|ClassMethod $classMethod
*/
public function leaveNode(Node $classMethod): void
{
$this->validateNode($classMethod);
$this->variableCollector->resetCollectedResolvers();
}
public static function getNodeTypeHandled(): string
{
return ClassMethod::class;
}
}
<?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\Strategy;
use Hgraca\AppMapper\Core\Port\Logger\StaticLoggerFacade;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception\UnknownVariableException;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\NodeTypeManagerTrait;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\TypeResolverCollector;
use PhpParser\Node;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Property;
final class ClassNodeStrategy extends AbstractStrategy
{
use NodeTypeManagerTrait;
use VariableNameExtractorTrait;
private $propertyCollector;
public function __construct(TypeResolverCollector $propertyCollector)
{
$this->propertyCollector = $propertyCollector;
}
/**
* @param Node|Class_ $class
*/
public function leaveNode(Node $class): void
{
$this->validateNode($class);
$this->addCollectedPropertyResolversToTheirDeclaration($class);
$this->propertyCollector->resetCollectedResolvers();
}
public static function getNodeTypeHandled(): string
{
return Class_::class;
}
/**
* After collecting app possible class properties, we inject them in their declaration
*
* TODO We are only adding properties types in the class itself.
* We should fix this by adding them also to the super classes.
*/
private function addCollectedPropertyResolversToTheirDeclaration(Class_ $node): void
{
foreach ($node->stmts as $property) {
if ($this->isCollectedProperty($property)) {
try {
self::addTypeResolverCollection(
$property,
$this->propertyCollector->getCollectedResolverCollection($this->getPropertyName($property))
);
} catch (UnknownVariableException $e) {
StaticLoggerFacade::warning(
"Silently ignoring a UnknownVariableException.\n"
. "The property is not in the collector, so we can't add it to the Property declaration.\n"
. $e->getMessage(),
[__METHOD__]
);
}
}
}
}
private function isCollectedProperty(Stmt $stmt): bool
{
return $stmt instanceof Property
&& $this->propertyCollector->hasCollectedResolverCollection($this->getPropertyName($stmt));
}
}
<?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\Strategy;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\NodeTypeManagerTrait;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\TypeCollection;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\TypeFactory;
use PhpParser\Node;
use PhpParser\Node\Expr\BinaryOp\Coalesce;
final class CoalesceNodeStrategy extends AbstractStrategy
{
use NodeTypeManagerTrait;
/**
* @var TypeFactory
*/
private $typeFactory;
public function __construct(TypeFactory $typeFactory)
{
$this->typeFactory = $typeFactory;
}
/**
* @param Node|Coalesce $coalesceNode
*/
public function enterNode(Node $coalesceNode): void
{
$this->validateNode($coalesceNode);
self::addTypeResolver(
$coalesceNode,
function () use ($coalesceNode): TypeCollection {
return self::getTypeCollectionFromNode($coalesceNode->left)
->removeTypeEqualTo($this->typeFactory->buildNull())
->addTypeCollection(self::getTypeCollectionFromNode($coalesceNode->right));
}
);
}
public static function getNodeTypeHandled(): string
{
return Coalesce::class;
}
}
<?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\Strategy;
use Hgraca\AppMapper\Core\Port\Logger\StaticLoggerFacade;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception\UnresolvableNodeTypeException;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\NodeTypeManagerTrait;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\Type;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\TypeCollection;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\TypeResolverCollector;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Foreach_;
final class ForeachNodeStrategy extends AbstractStrategy
{
use NodeTypeManagerTrait;
use VariableNameExtractorTrait;
private $propertyCollector;
private $variableCollector;
public function __construct(TypeResolverCollector $propertyCollector, TypeResolverCollector $variableCollector)
{
$this->propertyCollector = $propertyCollector;
$this->variableCollector = $variableCollector;
}
/**
* @param Node|Foreach_ $foreachNode
*/
public function enterNode(Node $foreachNode): void
{
$this->validateNode($foreachNode);
$this->assignTypeToForeachKey($foreachNode->keyVar);
$this->assignTypeToForeachVar($foreachNode->expr, $foreachNode->valueVar);
}
public static function getNodeTypeHandled(): string
{
return Foreach_::class;
}
private function assignTypeToForeachKey(?Expr $keyVar): void
{
if ($keyVar === null) {
return;
}
$resolver = function (): TypeCollection {
return new TypeCollection(new Type('string'), new Type('int'));
};
self::addTypeResolver($keyVar, $resolver);
if ($keyVar instanceof Variable) {
// Assignment to variable
$this->variableCollector->resetResolverCollection($this->getVariableName($keyVar), $resolver);
} elseif ($keyVar instanceof PropertyFetch) {
// Assignment to property
$this->propertyCollector->collectResolver($this->getPropertyName($keyVar), $resolver);
}
}
private function assignTypeToForeachVar(Expr $expression, Expr $valueVar): void
{
$resolver = function () use ($expression): TypeCollection {
try {
/** @var Type[] $typeCollection */
$typeCollection = self::resolveType($expression);
$nestedTypeCollection = new TypeCollection();
foreach ($typeCollection as $type) {
if ($type->hasNestedType()) {
$nestedTypeCollection = $nestedTypeCollection->addType($type->getNestedType());
}
}
return $nestedTypeCollection;
} catch (UnresolvableNodeTypeException $e) {
StaticLoggerFacade::warning(
"Silently ignoring a UnresolvableNodeTypeException in this filter.\n"
. 'This is failing, at least, for nested method calls like'
. '`$invoice->transactions->first()->getServicePro();`.' . "\n"
. "This should be fixed in the type addition visitors.\n"
. $e->getMessage(),
[__METHOD__]
);
return new TypeCollection();
}
};
self::addTypeResolver($valueVar, $resolver);
if ($valueVar instanceof Variable) {
// Assignment to variable
$this->variableCollector->resetResolverCollection($this->getVariableName($valueVar), $resolver);
} elseif ($valueVar instanceof PropertyFetch) {
// Assignment to property
$this->propertyCollector->collectResolver($this->getPropertyName($valueVar), $resolver);
}
}
}
<?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\Strategy;
use Hgraca\AppMapper\Core\Port\Logger\StaticLoggerFacade;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception\MethodNotFoundInClassException;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception\UnresolvableNodeTypeException;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\NodeTypeManagerTrait;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\Type;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\TypeCollection;
use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
final class MethodCallNodeStrategy extends AbstractStrategy
{
use NodeTypeManagerTrait;
/**
* @param Node|MethodCall $methodCall
*/
public function enterNode(Node $methodCall): void
{
$this->validateNode($methodCall);
self::addTypeResolver(
$methodCall,
function () use ($methodCall): TypeCollection {
$varCollectionType = self::getTypeCollectionFromNode($methodCall->var);
$typeCollection = new TypeCollection();
/** @var Type $methodCallVarType */
foreach ($varCollectionType as $methodCallVarType) {
if (!$methodCallVarType->hasAst()) {
continue;
}
try {
$returnTypeNode = $methodCallVarType->getAstMethod((string) $methodCall->name)->returnType;
$typeCollection = $typeCollection->addTypeCollection(
self::getTypeCollectionFromNode($returnTypeNode)
);
} catch (UnresolvableNodeTypeException $e) {
StaticLoggerFacade::warning(
"Silently ignoring a UnresolvableNodeTypeException.\n"
. 'This is failing, at least, for nested method calls like'
. '`$invoice->transactions->first()->getServicePro();`.' . "\n"
. "This should be fixed, otherwise we might be missing events.\n"
. $e->getMessage(),
[__METHOD__]
);
} catch (MethodNotFoundInClassException $e) {
StaticLoggerFacade::warning(
"Silently ignoring a MethodNotFoundInClassException.\n"
. "This should be fixed, otherwise we might be missing events.\n"
. $e->getMessage(),
[__METHOD__]
);
}
}
return $typeCollection;
}
);
}
public static function getNodeTypeHandled(): string
{
return MethodCall::class;
}
}
<?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\Strategy;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\NodeTypeManagerTrait;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\TypeCollection;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\TypeFactory;
use PhpParser\Node;
final class NodeStrategy extends AbstractStrategy
{
use NodeTypeManagerTrait;
/**
* @var TypeFactory
*/
private $typeFactory;
public function __construct(TypeFactory $typeFactory)
{
$this->typeFactory = $typeFactory;
}
public function enterNode(Node $node): void
{
$this->validateNode($node);
if (!$this->typeFactory->canBuildTypeFor($node)) {
return;
}
self::addTypeResolver(
$node,
function () use ($node): TypeCollection {
return $this->typeFactory->buildTypeCollection($node);
}
);
}
public static function getNodeTypeHandled(): string
{
return Node::class;
}
}
<?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\Strategy;
use Hgraca\PhpExtension\Collection\Collection;
use PhpParser\Node;
use function get_class;
final class NodeVisitorStrategyCollection extends Collection
{
/**
* @var NodeVisitorStrategyInterface
*/
private $defaultNodeVisitorStrategy;
public function __construct(
NodeVisitorStrategyInterface $defaultNodeVisitorStrategy,
NodeVisitorStrategyInterface ...$nodeVisitorStrategyList
) {
$this->defaultNodeVisitorStrategy = $defaultNodeVisitorStrategy;
foreach ($nodeVisitorStrategyList as $nodeVisitorStrategy) {
$this->itemList[$nodeVisitorStrategy::getNodeTypeHandled()] = $nodeVisitorStrategy;
}
}
public function getStrategyForNode(Node $node): NodeVisitorStrategyInterface
{
return $this->itemList[get_class($node)] ?? $this->defaultNodeVisitorStrategy;
}
}
<?php
declare(strict_types=1);
/*
* This file is part of the Application mapper application,