...
 
Commits (5)
......@@ -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,
......
<?php
declare(strict_types=1);
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));
}
}
......@@ -18,7 +18,26 @@ declare(strict_types=1);
namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception;
use Hgraca\AppMapper\Core\SharedKernel\Exception\AppMapperRuntimeException;
use PhpParser\Node;
final class CircularReferenceDetectedException extends AppMapperRuntimeException
{
public function __construct(Node $node, string $fqcn)
{
$relevantInfo = [];
$loopNode = $node;
while ($loopNode->hasAttribute('parentNode')) {
$relevantInfo[] = get_class($loopNode) . ' => '
. (property_exists($loopNode, 'name')
? $loopNode->name
: 'no_name'
);
$loopNode = $loopNode->getAttribute('parentNode');
}
$message = "Circular reference detected when adding type '$fqcn' to collection in node:\n"
. json_encode($relevantInfo, JSON_PRETTY_PRINT);
parent::__construct($message);
}
}
......@@ -18,16 +18,15 @@ 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\Infrastructure\Parser\NikicPhpParser\AbstractCollection;
final class NonUniqueTypeCollectionException extends AppMapperLogicException
{
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\PhpExtension\Exception\RuntimeException;
final class UnknownPropertyException extends RuntimeException
{
public function __construct(string $propertyName)
{
parent::__construct("Unknown property $propertyName");
}
}
......@@ -20,7 +20,7 @@ namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception;
use Hgraca\AppMapper\Core\SharedKernel\Exception\AppMapperRuntimeException;
use PhpParser\Node;
final class TypeNotFoundInNodeException extends AppMapperRuntimeException
final class UnresolvableNodeTypeException extends AppMapperRuntimeException
{
public function __construct(Node $node)
{
......
......@@ -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();
}
}
......
......@@ -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,16 @@ declare(strict_types=1);
namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception\TypeNotFoundInNodeException;
use Hgraca\AppMapper\Core\SharedKernel\Exception\NotImplementedException;
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 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);
......@@ -37,10 +39,10 @@ trait NodeTypeManagerTrait
$typeCollection->addTypeCollection($newTypeCollection);
}
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());
......@@ -54,10 +56,13 @@ trait NodeTypeManagerTrait
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 +76,37 @@ trait NodeTypeManagerTrait
return $node->hasAttribute(TypeCollection::getName());
}
/**
* @param Node|Callable $typeResolver
*
* FIXME All type resolvers need to return a TypeCollection
*/
public static function addTypeResolver(Node $node, $typeResolver): void
{
if (!$typeResolver instanceof Node && !is_callable($typeResolver)) {
throw new NotImplementedException(
'Resolvers of type ' . get_class($typeResolver) . ' are not implemented.'
);
}
if (!$node->hasAttribute(ResolverCollection::getName())) {
$resolverCollection = new ResolverCollection();
$node->setAttribute(ResolverCollection::getName(), $resolverCollection);
} else {
$resolverCollection = $node->getAttribute(ResolverCollection::getName());
}
$resolverCollection->addResolver($typeResolver);
}
public static function hasTypeResolver(Node $node): bool
{
return $node->hasAttribute(ResolverCollection::getName());
}
public static function resolveType(Node $node): TypeCollection
{
return $node->getAttribute(ResolverCollection::getName())->resolve();
}
}
......@@ -18,7 +18,7 @@ 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\Exception\UnresolvableNodeTypeException;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor\AbstractTypeInjectorVisitor;
use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
......@@ -140,9 +140,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 PhpParser\Node\Expr\Assign;
/**
* @mixin AbstractTypeInjectorVisitor
*/
trait AssignVisitorTrait
{
private function leaveAssignNode(Assign $assignNode): void
{
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);
}
}
<?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__]
);
}
}
}
<?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 PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Name;
use function array_key_exists;
trait NativeFunctionsTrait
{
private $functionList = [
'sprintf' => [
'return' => 'string',
],
'array_unique' => [
'return' => 'array',
],
];
private function isNative(FuncCall $funcCall): bool
{
if ($funcCall->name instanceof Name) {
$name = (string) $funcCall->name;
} elseif (property_exists($funcCall->name, 'name')) {
$name = $funcCall->name->name;
}
return array_key_exists($name, $this->functionList);
}
private function getReturnType(FuncCall $funcCall): string
{
return $this->functionList[(string) $funcCall->name]['return'];
}
}
<?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\SharedKernel\Exception\NotImplementedException;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception\UnknownPropertyException;
use Hgraca\PhpExtension\Type\TypeHelper;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Stmt\Property;
trait PropertyCollectorTrait
{
private $propertyTypeBuffer = [];
private function collectPropertyType(string $propertyName, TypeCollection $propertyType): void
{
$this->propertyTypeBuffer[$propertyName] = $propertyType;
}
private function hasCollectedPropertyType(string $propertyName): bool
{
return array_key_exists($propertyName, $this->propertyTypeBuffer);
}
protected function getCollectedPropertyType(string $propertyName): TypeCollection
{
if (!$this->hasCollectedPropertyType($propertyName)) {
throw new UnknownPropertyException($propertyName);
}
return $this->propertyTypeBuffer[$propertyName];
}
private function resetCollectedPropertyType(): void
{
$this->propertyTypeBuffer = [];
}
private function getPropertyName($property): string
{
switch (true) {
case $property instanceof Property:
return (string) $property->props[0]->name;
break;
case $property instanceof PropertyFetch:
return (string) $property->name;
break;
default:
throw new NotImplementedException(
'Can\'t get name from property of type ' . TypeHelper::getType($property)
);
}
}
}
<?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\TypeNotFoundInNodeException;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception\UnknownPropertyException;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\NodeTypeManagerTrait;
use PhpParser\Node;
use PhpParser\Node\Expr\PropertyFetch;
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
{
use NodeTypeManagerTrait;
use PropertyCollectorTrait;
public function enterNode(Node $node): void
{
switch (true) {
case $node instanceof Property:
// Properties declared at the top of the class are added to buffer
try {
$this->collectPropertyType(
(string) $node->props[0]->name,
self::getTypeCollectionFromNode($node)
);
} 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) {
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"
. $e->getMessage(),
[__METHOD__]
);
}
break;
}
}
public function leaveNode(Node $node): void
{
if ($node instanceof Class_) {
$this->resetCollectedPropertyType();
}
}
}
......@@ -17,39 +17,47 @@ declare(strict_types=1);
namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception\UnknownVariableException;
use PhpParser\Node\Expr\Variable;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\AbstractCollection;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\NodeTypeManagerTrait;
use Hgraca\PhpExtension\String\ClassHelper;
trait VariableTypeCollectorTrait
/**
* @property callable[] $itemList
*/
final class ResolverCollection extends AbstractCollection
{
private $variableTypeBuffer = [];
use NodeTypeManagerTrait;
private function collectVariableType(string $variableName, TypeCollection $variableType): void
public function __construct(callable ...$itemList)
{
$this->variableTypeBuffer[$variableName] = $variableType;
parent::__construct($itemList);
}
private function hasCollectedVariableType(string $variableName): bool
public static function getName(): string
{
return array_key_exists($variableName, $this->variableTypeBuffer);
return ClassHelper::extractCanonicalClassName(__CLASS__);
}
private function getCollectedVariableType(string $variableName): TypeCollection
public function addResolver(callable $resolver): void
{
if (!$this->hasCollectedVariableType($variableName)) {
throw new UnknownVariableException($variableName);
}
return $this->variableTypeBuffer[$variableName];
$resolverId = spl_object_hash((object) $resolver);
$this->itemList[$resolverId] = $resolver;
}
private function resetCollectedVariableTypes(): void
public function addResolverCollection(self $newResolverCollection): void
{
$this->variableTypeBuffer = [];
foreach ($newResolverCollection as $resolver) {
$this->addResolver($resolver);
}
}
private function getVariableName(Variable $variable): string
public function resolve(): TypeCollection
{
return $variable->name;
$typeCollection = new TypeCollection();
foreach ($this->itemList as $resolver) {
$typeCollection->addTypeCollection($resolver());
}
return $typeCollection;
}
}
......@@ -52,11 +52,6 @@ final class Type
return new self('void');
}
public static function constructNull(): self
{
return new self('null');
}
public static function getName(): string
{
return ClassHelper::extractCanonicalClassName(__CLASS__);
......
......@@ -17,42 +17,19 @@ declare(strict_types=1);
namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor;
use Hgraca\AppMapper\Core\Port\Logger\StaticLoggerFacade;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception\CircularReferenceDetectedException;
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\Collection\Collection;
use Hgraca\PhpExtension\String\ClassHelper;
use PhpParser\Node;
use function uniqid;
/**
* @property Type[] $itemList
*/
final class TypeCollection extends Collection
final class TypeCollection extends AbstractCollection
{
private const REPEATED_TYPE_ADD_LIMIT = 1000;
/**
* @var Node
*/
private $node;
/**
* @var int[]
*/
private $repeatedTypeAddition = [];
/**
* @var string
*/
private $id;
public function __construct(?Node $node = null, Type ...$itemList)
public function __construct(Type ...$itemList)
{
parent::__construct($itemList);
$this->node = $node;
$this->id = uniqid('', false);
}
public static function getName(): string
......@@ -62,35 +39,7 @@ final class TypeCollection extends Collection
public function addType(Type $item): void
{
if (
isset($this->repeatedTypeAddition[$item->getFcqn()])
&& $this->repeatedTypeAddition[$item->getFcqn()] >= 100
) {
$count = $this->repeatedTypeAddition[$item->getFcqn()] + 1;
StaticLoggerFacade::notice(
"Adding type '{$item->getFcqn()}' to collection {$this->id} with size {$this->count()} for the {$count}th time\n"
);
}
$this->itemList[$item->getFcqn()] = $item;
$this->repeatedTypeAddition[$item->getFcqn()] = isset($this->repeatedTypeAddition[$item->getFcqn()])
? $this->repeatedTypeAddition[$item->getFcqn()] + 1
: 1;
if ($this->repeatedTypeAddition[$item->getFcqn()] >= self::REPEATED_TYPE_ADD_LIMIT) {
$relevantInfo = [];
$loopNode = $this->node;
while ($loopNode->hasAttribute('parentNode')) {
$relevantInfo[] = get_class($loopNode) . ' => '
. (property_exists($loopNode, 'name')
? $loopNode->name
: 'no_name'
);
$loopNode = $loopNode->getAttribute('parentNode');
}
throw new CircularReferenceDetectedException(
"Circular reference detected when adding type '{$item->getFcqn()}' to collection in node:\n"
. json_encode($relevantInfo, JSON_PRETTY_PRINT)
);
}
}
public function addTypeCollection(self $newTypeCollection): void
......@@ -100,14 +49,6 @@ final class TypeCollection extends Collection
}
}
/**
* @return Type[]
*/
public function toArray(): array
{
return $this->itemList;
}
public function getUniqueType(): Type
{
if ($this->count() > 1) {
......
<?php
declare(strict_types=1);
namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Exception\UnknownVariableException;
final class TypeResolverCollector
{
private $collector = [];
public function collectResolver(string $variableName, callable $resolver): void
{
$this->collector[$variableName] = $resolver;
}
public function hasCollectedResolver(string $variableName): bool
{
return array_key_exists($variableName, $this->collector);
}
public function getCollectedResolver(string $variableName): callable
{
if (!$this->hasCollectedResolver($variableName)) {
throw new UnknownVariableException($variableName);
}
return $this->collector[$variableName];
}
public function resetCollectedResolvers(): void
{
$this->collector = [];
}
}
<?php
declare(strict_types=1);
namespace Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\Visitor;
use Hgraca\AppMapper\Core\Port\Logger\StaticLoggerFacade;
use Hgraca\AppMapper\Infrastructure\Parser\NikicPhpParser\NodeTypeManagerTrait;
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;
final class TypeResolverVisitor extends NodeVisitorAbstract
{
use NodeTypeManagerTrait;
public function enterNode(Node $node): void
{
if (!$node->hasAttribute(ResolverCollection::getName())) {
StaticLoggerFacade::notice(
"Can't find type resolver in node:\n"
. json_encode($this->getNodeTrace($node), JSON_PRETTY_PRINT),
[__METHOD__]
);
return;
}
self::resolveType($node);
}
private function getNodeTrace(Node $node): array
{
$relevantInfo = [];
$loopNode = $node;
while ($loopNode->hasAttribute('parentNode')) {
$relevantInfo[] = 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 $relevantInfo;
}
}
......@@ -24,8 +24,8 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Stopwatch\Stopwatch;
use const PHP_EOL;
use function class_exists;
use const PHP_EOL;
abstract class AbstractCommandStopwatchDecorator extends Command
{
......