...
 
Commits (32)
......@@ -43,7 +43,10 @@ return PhpCsFixer\Config::create()
'no_unreachable_default_argument_value' => true,
'no_useless_else' => true,
'no_useless_return' => true,
'ordered_imports' => true,
'ordered_imports' => [
'sort_algorithm' => 'alpha',
'imports_order' => ['class', 'function', 'const'],
],
'phpdoc_align' => false,
'phpdoc_order' => true,
'phpdoc_summary' => false,
......
This diff is collapsed.
......@@ -4,7 +4,7 @@
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/7.1/phpunit.xsd"
colors="true"
bootstrap="vendor/autoload.php"
bootstrap="tests/Framework/bootstrap.php"
>
<php>
<ini name="error_reporting" value="-1" />
......
......@@ -28,9 +28,9 @@ final class ConsoleLogger implements LoggerInterface
{
private const DEFAULT_LOGGING_LEVEL_MAP = [
OutputInterface::VERBOSITY_QUIET => [],
OutputInterface::VERBOSITY_NORMAL => ['info', 'error', 'critical', 'alert', 'emergency'],
OutputInterface::VERBOSITY_VERBOSE => ['warning', 'info', 'error', 'critical', 'alert', 'emergency'],
OutputInterface::VERBOSITY_VERY_VERBOSE => ['notice', 'info', 'error', 'critical', 'alert', 'emergency'],
OutputInterface::VERBOSITY_NORMAL => ['debug', 'info', 'error', 'critical', 'alert', 'emergency'],
OutputInterface::VERBOSITY_VERBOSE => ['warning', 'debug', 'info', 'error', 'critical', 'alert', 'emergency'],
OutputInterface::VERBOSITY_VERY_VERBOSE => ['notice', 'debug', 'info', 'error', 'critical', 'alert', 'emergency'],
OutputInterface::VERBOSITY_DEBUG => ['debug', 'notice', 'warning', 'info', 'error', 'critical', 'alert', 'emergency'],
];
......
<?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 Hgraca\PhpExtension\Collection\Collection;
abstract class AbstractCollection extends Collection
{
public function implodeKeys(string $glue): string
{
return implode($glue, array_keys($this->itemList));
}
}
......@@ -169,6 +169,7 @@ final class AstMap implements AstMapInterface
return;
}
$this->completeNodeCollection->enhance();
$this->completeNodeCollection->resolveAllTypes();
$this->hasTypeInformation = true;
}
}
......@@ -17,8 +17,17 @@ declare(strict_types=1);
namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception;
use Hgraca\AppMapper\Core\SharedKernel\Exception\AppMapperRuntimeException;
use Hgraca\AppMapper\Core\Port\Parser\Exception\ParserException;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\NodeTypeManagerTrait;
use PhpParser\Node;
final class CircularReferenceDetectedException extends AppMapperRuntimeException
final class CircularReferenceDetectedException extends ParserException
{
public function __construct(Node $node, string $fqcn)
{
parent::__construct(
"Circular reference detected when adding type '$fqcn' to collection in node:\n"
. NodeTypeManagerTrait::resolveNodeTreeAsJson($node)
);
}
}
......@@ -17,8 +17,8 @@ declare(strict_types=1);
namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception;
use Hgraca\AppMapper\Core\SharedKernel\Exception\AppMapperLogicException;
use Hgraca\AppMapper\Core\Port\Parser\Exception\ParserException;
final class EmptyCollectionException extends AppMapperLogicException
final class EmptyCollectionException extends ParserException
{
}
......@@ -17,12 +17,8 @@ declare(strict_types=1);
namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception;
use Hgraca\PhpExtension\Exception\RuntimeException;
use Hgraca\AppMapper\Core\Port\Parser\Exception\ParserException;
final class UnknownPropertyException extends RuntimeException
final class InvalidArgumentException extends ParserException
{
public function __construct(string $propertyName)
{
parent::__construct("Unknown property $propertyName");
}
}
......@@ -18,11 +18,20 @@ declare(strict_types=1);
namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception;
use Hgraca\AppMapper\Core\Port\Parser\Exception\ParserException;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\TypeCollection;
final class MethodNotFoundInClassException extends ParserException
{
public function __construct(string $methodName, string $classFqcn)
public static function constructFromFqcn(string $methodName, string $classFqcn): self
{
parent::__construct("Method '$methodName' not found in class '$classFqcn'.");
return new self("Method '$methodName' not found in class '$classFqcn'.");
}
public static function constructFromCollection(string $methodName, TypeCollection $typeCollection): self
{
return new self(
"Method '$methodName' not found in any of the classes '{$typeCollection->implodeKeys(', ')}'. "
. 'It should have been found in at least one.'
);
}
}
......@@ -17,17 +17,16 @@ declare(strict_types=1);
namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception;
use Hgraca\AppMapper\Core\SharedKernel\Exception\AppMapperLogicException;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\TypeCollection;
use function array_keys;
use Hgraca\AppMapper\Core\Port\Parser\Exception\ParserException;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\AbstractCollection;
final class NonUniqueTypeCollectionException extends AppMapperLogicException
final class NonUniqueTypeCollectionException extends ParserException
{
public function __construct(TypeCollection $typeCollection)
public function __construct(AbstractCollection $collection)
{
parent::__construct(
"The type collection contains more than one type: \n"
. implode("\n", array_keys($typeCollection->toArray()))
. $collection->implodeKeys("\n")
);
}
}
<?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\NodeTypeManagerTrait;
use Hgraca\PhpExtension\Type\TypeHelper;
use PhpParser\Node;
final class NotImplementedException extends ParserException
{
public static function constructFromNode(Node $node)
{
return new self(
'Can\'t build Type from ' . TypeHelper::getType($node) . "\n"
. NodeTypeManagerTrait::resolveNodeTreeAsJson($node)
);
}
}
......@@ -17,8 +17,8 @@ declare(strict_types=1);
namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception;
use Hgraca\PhpExtension\Exception\RuntimeException;
use Hgraca\AppMapper\Core\Port\Parser\Exception\ParserException;
final class UnknownFqcnException extends RuntimeException
final class UnknownFqcnException extends ParserException
{
}
......@@ -17,9 +17,9 @@ declare(strict_types=1);
namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception;
use Hgraca\PhpExtension\Exception\RuntimeException;
use Hgraca\AppMapper\Core\Port\Parser\Exception\ParserException;
final class UnknownVariableException extends RuntimeException
final class UnknownVariableException extends ParserException
{
public function __construct(string $variableName)
{
......
......@@ -15,22 +15,16 @@ declare(strict_types=1);
* file that was distributed with this source code.
*/
namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor;
namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception;
use PhpParser\Node\Expr\Assign;
use Hgraca\AppMapper\Core\Port\Parser\Exception\ParserException;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\NodeTypeManagerTrait;
use PhpParser\Node;
/**
* @mixin AbstractTypeInjectorVisitor
*/
trait AssignVisitorTrait
final class UnresolvableNodeTypeException extends ParserException
{
private function leaveAssignNode(Assign $assignNode): void
public function __construct(Node $node)
{
if (!self::hasTypeCollection($assignNode->expr)) {
// TODO stop ignoring unresolved and resolve all detected
return;
}
$this->addTypeCollectionToNode($assignNode->var, self::getTypeCollectionFromNode($assignNode->expr));
$this->collectVariableTypes($assignNode->var);
parent::__construct(NodeTypeManagerTrait::resolveNodeTreeAsJson($node));
}
}
......@@ -94,7 +94,7 @@ final class ClassAdapter implements ClassInterface
}
}
throw new MethodNotFoundInClassException($methodName, $this->getFullyQualifiedType());
throw MethodNotFoundInClassException::constructFromFqcn($methodName, $this->getFullyQualifiedType());
}
/**
......
......@@ -20,7 +20,7 @@ namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Node;
use Hgraca\AppMapper\Core\Port\Logger\StaticLoggerFacade;
use Hgraca\AppMapper\Core\Port\Parser\Node\AdapterNodeInterface;
use Hgraca\AppMapper\Core\Port\Parser\Node\MethodArgumentInterface;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception\TypeNotFoundInNodeException;
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;
......@@ -41,16 +41,16 @@ final class MethodArgumentAdapter extends Collection implements MethodArgumentIn
self::getTypeCollectionFromNode($argumentValue)
)->toArray()
);
} catch (TypeNotFoundInNodeException $e) {
} catch (UnresolvableNodeTypeException $e) {
StaticLoggerFacade::warning(
"Silently ignoring a TypeNotFoundInNodeException in this adapter.\n"
"Silently ignoring a UnresolvableNodeTypeException in this adapter.\n"
. "This should be fixed in the type addition visitors.\n"
. $e->getMessage(),
[__METHOD__]
);
$this->itemList =
NodeAdapterFactory::constructFromTypeCollection(
new TypeCollection($argument, Type::constructUnknownFromNode($argumentValue))
new TypeCollection(Type::constructUnknownFromNode($argumentValue))
)->toArray();
}
}
......
......@@ -86,11 +86,8 @@ final class MethodCallAdapter implements MethodCallInterface
private function getEnclosingClass(): ClassInterface
{
if ($this->enclosingClass === null) {
$node = $this->methodCall;
do {
$node = $node->getAttribute(ParentConnectorVisitor::PARENT_NODE);
} while (!$node instanceof Class_);
/** @var Class_ $node */
$node = ParentConnectorVisitor::getFirstParentNodeOfType($this->methodCall, Class_::class);
$this->enclosingClass = ClassAdapter::constructFromClassNode($node);
}
......@@ -100,11 +97,8 @@ final class MethodCallAdapter implements MethodCallInterface
private function getEnclosingMethod(): MethodInterface
{
if ($this->enclosingMethod === null) {
$node = $this->methodCall;
do {
$node = $node->getAttribute(ParentConnectorVisitor::PARENT_NODE);
} while (!$node instanceof ClassMethod);
/** @var ClassMethod $node */
$node = ParentConnectorVisitor::getFirstParentNodeOfType($this->methodCall, ClassMethod::class);
$this->enclosingMethod = new MethodAdapter($node);
}
......
......@@ -19,7 +19,7 @@ namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Node;
use Hgraca\AppMapper\Core\Port\Parser\Node\MethodParameterInterface;
use Hgraca\AppMapper\Core\Port\Parser\Node\TypeNodeInterface;
use Hgraca\AppMapper\Core\SharedKernel\Exception\NotImplementedException;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception\NotImplementedException;
use PhpParser\Node\Param;
final class MethodParameterAdapter implements TypeNodeInterface, MethodParameterInterface
......
......@@ -29,7 +29,7 @@ use PhpParser\Node\Stmt\ClassMethod;
final class NodeAdapterFactory
{
/**
* @param null|string|Node $parserNode
* @param string|Node|null $parserNode
*/
public static function constructFromNode($parserNode): AdapterNodeInterface
{
......
......@@ -18,7 +18,7 @@ declare(strict_types=1);
namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Node;
use Hgraca\AppMapper\Core\Port\Parser\Node\TypeNodeInterface;
use Hgraca\AppMapper\Core\SharedKernel\Exception\NotImplementedException;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception\NotImplementedException;
use Hgraca\PhpExtension\String\ClassHelper;
use PhpParser\Node;
use PhpParser\Node\Name;
......
......@@ -35,7 +35,7 @@ final class UnknownTypeNode implements TypeNodeInterface
private $phpParserNodeType = '';
/**
* @param null|string|Node $node
* @param string|Node|null $node
*/
public function __construct($node = null)
{
......
......@@ -17,13 +17,11 @@ declare(strict_types=1);
namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser;
use Hgraca\AppMapper\Core\Port\Logger\StaticLoggerFacade;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception\AstNodeNotFoundException;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception\UnitNotFoundInNamespaceException;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\AssignmentFromMethodCallTypeInjectorVisitor;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\ParentConnectorVisitor;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\PropertyFetchTypeInjectorVisitor;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\TypeInjectorVisitor;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\TypeResolverInjectorVisitor;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\TypeResolverVisitor;
use Hgraca\PhpExtension\String\JsonEncoder;
use PhpParser\JsonDecoder;
use PhpParser\Node;
......@@ -122,43 +120,18 @@ final class NodeCollection
public function enhance(): void
{
StaticLoggerFacade::notice(
"TODO This whole method can and should be refactored to a better design. \n"
. "We can: \n"
. " 1. Use resolver `callable`s instead of injecting the type \n"
. " 2. Make better use of the Visitor::leaveNode(), when we need to first visit inner \n"
. ' nodes to resolve an outer node, as in the Assign nodes',
[__METHOD__]
);
$nodeList = array_values($this->nodeList);
// Add all nodes into the collection
// Add visitors here if they don't need the final collection
$traverser = new NodeTraverser();
$traverser->addVisitor(new NameResolver(null, ['preserveOriginalNames' => true, 'replaceNodes' => false]));
$traverser->addVisitor(new ParentConnectorVisitor());
$traverser->traverse($nodeList);
$traverser = new NodeTraverser();
$traverser->addVisitor(new TypeInjectorVisitor($this));
$traverser->traverse($nodeList);
// After setting the type in the properties declaration, we can copy it to every property call
// We need a separate traverse because a property might be set only in the end of the file,
// after the property is used
$traverser = new NodeTraverser();
$traverser->addVisitor(new PropertyFetchTypeInjectorVisitor());
$traverser->traverse($nodeList);
$traverser = new NodeTraverser();
// This one needs the properties to already be set
$traverser->addVisitor(new AssignmentFromMethodCallTypeInjectorVisitor($this));
$traverser->traverse($nodeList);
$traverser->addVisitor(new TypeResolverInjectorVisitor($this));
$traverser->traverse(array_values($this->nodeList));
}
// Make a second pass to make sure we got all properties, including the ones captured in the last visitor
public function resolveAllTypes(): void
{
$traverser = new NodeTraverser();
$traverser->addVisitor(new PropertyFetchTypeInjectorVisitor());
$traverser->traverse($nodeList);
$traverser->addVisitor(new TypeResolverVisitor());
$traverser->traverse(array_values($this->nodeList));
}
private function addCollections(self ...$nodeCollectionList): void
......
......@@ -17,14 +17,17 @@ declare(strict_types=1);
namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception\TypeNotFoundInNodeException;
use Hgraca\AppMapper\Core\Port\Logger\StaticLoggerFacade;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception\UnresolvableNodeTypeException;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\ResolverCollection;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\Type;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\TypeCollection;
use Hgraca\PhpExtension\Type\TypeHelper;
use PhpParser\Node;
trait NodeTypeManagerTrait
{
public function addTypeCollectionToNode(Node $node, TypeCollection $newTypeCollection): void
public static function addTypeCollectionToNode(Node $node, TypeCollection $newTypeCollection): void
{
if (!$node->hasAttribute(TypeCollection::getName())) {
$node->setAttribute(TypeCollection::getName(), $newTypeCollection);
......@@ -33,31 +36,35 @@ trait NodeTypeManagerTrait
}
/** @var TypeCollection $typeCollection */
$typeCollection = $node->getAttribute(TypeCollection::getName());
$typeCollection->addTypeCollection($newTypeCollection);
$typeCollection = $node->getAttribute(TypeCollection::getName())->addTypeCollection($newTypeCollection);
$node->setAttribute(TypeCollection::getName(), $typeCollection);
}
public function addTypeToNode(Node $node, Type ...$typeList): void
public static function addTypeToNode(Node $node, Type ...$typeList): void
{
if (!$node->hasAttribute(TypeCollection::getName())) {
$typeCollection = new TypeCollection($node);
$typeCollection = new TypeCollection();
$node->setAttribute(TypeCollection::getName(), $typeCollection);
} else {
$typeCollection = $node->getAttribute(TypeCollection::getName());
}
foreach ($typeList as $type) {
$typeCollection->addType($type);
$typeCollection = $typeCollection->addType($type);
}
}
public static function getTypeCollectionFromNode(?Node $node): TypeCollection
{
if (!$node) {
return new TypeCollection();
return new TypeCollection(Type::constructVoid());
}
if (!$node->hasAttribute(TypeCollection::getName())) {
throw new TypeNotFoundInNodeException($node);
if (!self::hasTypeCollection($node)) {
if (!self::hasTypeResolver($node)) {
throw new UnresolvableNodeTypeException($node);
}
self::addTypeCollectionToNode($node, self::resolveType($node));
}
return $node->getAttribute(TypeCollection::getName());
......@@ -71,4 +78,69 @@ trait NodeTypeManagerTrait
return $node->hasAttribute(TypeCollection::getName());
}
public static function addTypeResolver(Node $node, callable $typeResolver): void
{
$node->setAttribute(
ResolverCollection::getName(),
self::getNodeResolverCollection($node)->addResolver($typeResolver)
);
}
public static function addTypeResolverCollection(Node $node, ResolverCollection $typeResolverCollection): void
{
$node->setAttribute(
ResolverCollection::getName(),
self::getNodeResolverCollection($node)->addResolverCollection($typeResolverCollection)
);
}
public static function hasTypeResolver(Node $node): bool
{
return $node->hasAttribute(ResolverCollection::getName());
}
public static function resolveType(Node $node): TypeCollection
{
StaticLoggerFacade::debug(
'Resolving type ' . TypeHelper::getType($node) . "\n" . self::resolveNodeTreeAsJson($node)
);
$resolverCollection = $node->getAttribute(ResolverCollection::getName());
return $resolverCollection->resolve();
}
private static function getNodeResolverCollection(Node $node): ResolverCollection
{
if (!$node->hasAttribute(ResolverCollection::getName())) {
$resolverCollection = new ResolverCollection();
$node->setAttribute(ResolverCollection::getName(), $resolverCollection);
}
return $node->getAttribute(ResolverCollection::getName());
}
public static function resolveNodeTreeAsJson(Node $node): string
{
return json_encode(self::resolveNodeTree($node), JSON_PRETTY_PRINT);
}
private static function resolveNodeTree(Node $node): array
{
$nodeTree = [];
$loopNode = $node;
while ($loopNode->hasAttribute('parentNode')) {
$nodeTree[] = get_class($loopNode) . ' => '
. (property_exists($loopNode, 'name')
? $loopNode->name
: (property_exists($loopNode, 'var') && property_exists($loopNode->var, 'name')
? $loopNode->var->name
: 'no_name')
);
$loopNode = $loopNode->getAttribute('parentNode');
}
return $nodeTree;
}
}
......@@ -18,14 +18,15 @@ declare(strict_types=1);
namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser;
use Hgraca\AppMapper\Core\Port\Logger\StaticLoggerFacade;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception\TypeNotFoundInNodeException;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\AbstractTypeInjectorVisitor;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception\UnresolvableNodeTypeException;
use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Stmt\Class_;
final class QueryBuilder
{
use NodeTypeManagerTrait;
/** @var Query */
private $currentQuery;
......@@ -125,9 +126,7 @@ final class QueryBuilder
}
$methodCall = $node;
try {
$dispatcherTypeCollection = AbstractTypeInjectorVisitor::getTypeCollectionFromNode(
$methodCall->var
);
$dispatcherTypeCollection = $this->getTypeCollectionFromNode($methodCall->var);
foreach ($dispatcherTypeCollection as $type) {
$dispatcherFqcn = (string) $type;
......@@ -140,9 +139,9 @@ final class QueryBuilder
return true;
}
}
} catch (TypeNotFoundInNodeException $e) {
} catch (UnresolvableNodeTypeException $e) {
StaticLoggerFacade::warning(
"Silently ignoring a TypeNotFoundInNodeException in this filter.\n"
"Silently ignoring a UnresolvableNodeTypeException in this filter.\n"
. "The type is not in the node so it can't pass the filter.\n"
. "This should be fixed in the type addition visitors.\n"
. $e->getMessage(),
......
<?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;
use Hgraca\AppMapper\Core\Port\Logger\StaticLoggerFacade;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception\MethodNotFoundInClassException;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception\TypeNotFoundInNodeException;
use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Class_;
final class AssignmentFromMethodCallTypeInjectorVisitor extends AbstractTypeInjectorVisitor
{
use AssignVisitorTrait;
public function leaveNode(Node $node): void
{
switch (true) {
case $node instanceof MethodCall:
$this->leaveMethodCall($node);
break;
case $node instanceof Assign:
try {
$this->leaveAssignNode($node);
} catch (TypeNotFoundInNodeException $e) {
StaticLoggerFacade::warning(
"Silently ignoring a TypeNotFoundInNodeException 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__]
);
}
break;
case $node instanceof Variable:
// After collecting the variable types, inject it in the following variable nodes
$this->addCollectedVariableTypes($node);
break;
case $node instanceof Class_:
$this->addCollectedPropertiesTypeToTheirDeclaration($node);
break;
}
parent::leaveNode($node);
}
private function leaveMethodCall(MethodCall $methodCall): void
{
try {
$varCollectionType = self::getTypeCollectionFromNode($methodCall->var);
/** @var Type $methodCallVarType */
foreach ($varCollectionType as $methodCallVarType) {
if (!$methodCallVarType->hasAst()) {
$this->addTypeToNode($methodCall, Type::constructUnknownFromNode($methodCall));
continue;
}
$returnTypeNode = $methodCallVarType->getAstMethod((string) $methodCall->name)->returnType;
if ($returnTypeNode === null) {
$this->addTypeToNode($methodCall, Type::constructVoid());
} else {
$classMethodReturnTypeCollection = self::getTypeCollectionFromNode($returnTypeNode);
$this->addTypeCollectionToNode($methodCall, $classMethodReturnTypeCollection);
}
}
} catch (TypeNotFoundInNodeException $e) {
StaticLoggerFacade::warning(
"Silently ignoring a TypeNotFoundInNodeException.\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, 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__]
);
}
}
}
......@@ -23,7 +23,7 @@ use function count;
class ParentConnectorVisitor extends NodeVisitorAbstract
{
public const PARENT_NODE = 'parentNode';
private const PARENT_NODE = 'parentNode';
private $stack;
......@@ -44,4 +44,18 @@ class ParentConnectorVisitor extends NodeVisitorAbstract
{
array_pop($this->stack);
}
public static function getParentNode(Node $node): Node
{
return $node->getAttribute(self::PARENT_NODE);
}
public static function getFirstParentNodeOfType(Node $node, string $type): Node
{
do {
$node = self::getParentNode($node);
} while (!$node instanceof $type);
return $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;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\AbstractCollection;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\NodeTypeManagerTrait;
use Hgraca\PhpExtension\String\ClassHelper;
/**
* FIXME make this collection immutable, and with a fluent interface
*
* @property callable[] $itemList
*/
final class ResolverCollection extends AbstractCollection
{
use NodeTypeManagerTrait;
public function __construct(callable ...$itemList)
{
foreach ($itemList as $resolver) {
$resolverId = spl_object_hash((object) $resolver);
$this->itemList[$resolverId] = $resolver;
}
}
public static function getName(): string
{
return ClassHelper::extractCanonicalClassName(__CLASS__);
}
public function addResolver(callable $resolver): self
{
$itemList = $this->itemList;
$resolverId = spl_object_hash((object) $resolver);
$itemList[$resolverId] = $resolver;
return new self(...array_values($itemList));
}
public function addResolverCollection(self $newResolverCollection): self
{
$itemList = $this->itemList;
/** @var callable $resolver */
foreach ($newResolverCollection as $resolver) {
$resolverId = spl_object_hash((object) $resolver);
$itemList[$resolverId] = $resolver;
}
return new self(...array_values($itemList));
}
public function resolve(): TypeCollection
{
$typeCollection = new TypeCollection();
foreach ($this->itemList as $resolver) {
$typeCollection = $typeCollection->addTypeCollection($resolver());
}
return $typeCollection;
}
}
......@@ -15,36 +15,31 @@ declare(strict_types=1);
* file that was distributed with this source code.
*/
namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor;
namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\Strategy;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Name;
use function array_key_exists;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception\InvalidArgumentException;
use PhpParser\Node;
use function get_class;
trait NativeFunctionsTrait
abstract class AbstractStrategy implements NodeVisitorStrategyInterface
{
private $functionList = [
'sprintf' => [
'return' => 'string',
],
'array_unique' => [
'return' => 'array',
],
];
private function isNative(FuncCall $funcCall): bool
public function enterNode(Node $node): void
{
if ($funcCall->name instanceof Name) {
$name = (string) $funcCall->name;
} elseif (property_exists($funcCall->name, 'name')) {
$name = $funcCall->name->name;
}
$this->validateNode($node);
}
return array_key_exists($name, $this->functionList);
public function leaveNode(Node $node): void
{
$this->validateNode($node);
}
private function getReturnType(FuncCall $funcCall): string
protected function validateNode(Node $node): void
{
return $this->functionList[(string) $funcCall->name]['return'];
$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;
}
}
......@@ -15,67 +15,75 @@ declare(strict_types=1);
* file that was distributed with this source code.
*/
namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor;
namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\Strategy;
use Hgraca\AppMapper\Core\Port\Logger\StaticLoggerFacade;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception\TypeNotFoundInNodeException;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception\UnknownPropertyException;
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\Expr\PropertyFetch;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Property;
use PhpParser\NodeVisitorAbstract;
/**
* This visitor makes a swipe injecting the types from the property declarations into the property usages
* (PropertyFetch)
*/
final class PropertyFetchTypeInjectorVisitor extends NodeVisitorAbstract
final class ClassNodeStrategy extends AbstractStrategy
{
use NodeTypeManagerTrait;
use PropertyCollectorTrait;
use VariableNameExtractorTrait;
private $propertyCollector;
public function __construct(TypeResolverCollector $propertyCollector)
{
$this->propertyCollector = $propertyCollector;
}
public function enterNode(Node $node): void
/**
* @param Node|Class_ $class
*/
public function leaveNode(Node $class): void
{
switch (true) {
case $node instanceof Property:
// Properties declared at the top of the class are added to buffer
$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 {
$this->collectPropertyType(
(string) $node->props[0]->name,
self::getTypeCollectionFromNode($node)
self::addTypeResolverCollection(
$property,
$this->propertyCollector->getCollectedResolverCollection($this->getPropertyName($property))
);
} catch (TypeNotFoundInNodeException $e) {
// If the property does not have the type, we ignore it
}
break;
case $node instanceof PropertyFetch:
// Properties used within the class are injected with type from buffer
StaticLoggerFacade::notice(
"We are only adding types to properties in the class itself.\n"
. "We should fix this by adding them also to the super classes and traits.\n",
[__METHOD__]
);
try {
$this->addTypeCollectionToNode($node, $this->getCollectedPropertyType((string) $node->name));
} catch (UnknownPropertyException $e) {
} catch (UnknownVariableException $e) {
StaticLoggerFacade::warning(
"Silently ignoring a UnknownPropertyException in this visitor.\n"
. "The property is not in the buffer, so we can't add it to the PropertyFetch.\n"
. "This should be fixed in the type addition visitors.\n"
"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__]
);
}
break;
}
}
}
public function leaveNode(Node $node): void
private function isCollectedProperty(Stmt $stmt): bool
{
if ($node instanceof Class_) {
$this->resetCollectedPropertyType();
}
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);
$methodName = (string) $methodCall->name;
$typeCollection = new TypeCollection();
$countTypesWithoutMethod = 0;
/** @var Type $methodCallVarType */
foreach ($varCollectionType as $methodCallVarType) {
if (!$methodCallVarType->hasAst()) {
continue;
}
try {
$returnTypeNode = $methodCallVarType->getAstMethod($methodName)->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) {
++$countTypesWithoutMethod;