Refactor the AstMap

So that it contains node collections per component and one collection
with all nodes. This way we can link nodes in different components.
parent d8e6610c
......@@ -60,12 +60,16 @@ final class ContextMapService
$subscriberCollector = $config->getSubscriberCollector();
$eventDispatcherCollector = $config->getEventDispatcherCollector();
$completeAstMap = $this->astMapFactory->constructFromComponentDtoList(
...array_values($config->getComponents())
);
$componentList = [];
foreach ($config->getComponents() as $componentDto) {
$componentList[] = new Component(
$componentDto->getName(),
new DomainAstMap(
$this->astMapFactory->constructFromPath($componentDto->getPath(), $componentDto->getName()),
$completeAstMap,
$useCaseCollector,
$listenerCollector,
$subscriberCollector,
......
......@@ -58,23 +58,23 @@ final class Component
$this->name = $name;
$this->astMap = $astMap;
$this->useCaseCollection = $astMap->findUseCases();
$this->useCaseCollection = $astMap->findUseCases($this->name);
foreach ($this->useCaseCollection as $useCase) {
$useCase->setComponent($this);
}
$this->listenerCollection = $astMap->findListeners();
$this->listenerCollection = $astMap->findListeners($this->name);
foreach ($this->listenerCollection as $listener) {
$listener->setComponent($this);
}
$this->subscriberCollection = $astMap->findSubscribers();
$this->subscriberCollection = $astMap->findSubscribers($this->name);
foreach ($this->subscriberCollection as $subscriber) {
$subscriber->setComponent($this);
}
$this->partialUseCaseNodeCollection = new DomainNodeCollection();
$this->eventDispatcherCollection = $astMap->findEventDispatchers();
$this->eventDispatcherCollection = $astMap->findEventDispatchers($this->name);
foreach ($this->eventDispatcherCollection as $eventDispatcher) {
$eventDispatcher->setComponent($this);
......
......@@ -65,19 +65,21 @@ final class DomainAstMap
$this->eventDispatcherCollector = $eventDispatcherCollector;
}
public function findUseCases(): DomainNodeCollection
public function findUseCases(string $component): DomainNodeCollection
{
$nodeCollection = $this->astMap->findClassesWithFqcnMatchingRegex(
...$this->useCaseCollector->getCriteriaListAsString()
(string) $this->useCaseCollector->getCriteriaByType(CodeUnitCollector::CRITERIA_CLASS_FQCN),
$component
);
return $nodeCollection->decorateByDomainNode(UseCaseNode::class);
}
public function findListeners(): DomainNodeCollection
public function findListeners(string $component): DomainNodeCollection
{
$nodeCollection = $this->astMap->findClassesWithFqcnMatchingRegex(
...$this->listenerCollector->getCriteriaListAsString()
(string) $this->listenerCollector->getCriteriaByType(CodeUnitCollector::CRITERIA_CLASS_FQCN),
$component
);
$listenerList = [];
......@@ -94,10 +96,11 @@ final class DomainAstMap
return new DomainNodeCollection(...$listenerList);
}
public function findSubscribers(): DomainNodeCollection
public function findSubscribers(string $component): DomainNodeCollection
{
$nodeCollection = $this->astMap->findClassesWithFqcnMatchingRegex(
...$this->subscriberCollector->getCriteriaListAsString()
(string) $this->subscriberCollector->getCriteriaByType(CodeUnitCollector::CRITERIA_CLASS_FQCN),
$component
);
$subscriberList = [];
......@@ -114,9 +117,13 @@ final class DomainAstMap
return new DomainNodeCollection(...$subscriberList);
}
public function findEventDispatchers(): DomainNodeCollection
public function findEventDispatchers(string $component): DomainNodeCollection
{
return $this->astMap->findClassesCallingMethod(...$this->eventDispatcherCollector->getCriteriaListAsString())
return $this->astMap->findClassesCallingMethod(
(string) $this->eventDispatcherCollector->getCriteriaByType(CodeUnitCollector::CRITERIA_CLASS_FQCN),
(string) $this->eventDispatcherCollector->getCriteriaByType(CodeUnitCollector::CRITERIA_METHOD_NAME),
$component
)
->decorateByDomainNode(EventDispatcherNode::class);
}
}
......@@ -18,10 +18,15 @@ declare(strict_types=1);
namespace Hgraca\ContextMapper\Core\Port\Configuration\Collector;
use Hgraca\ContextMapper\Core\Port\Configuration\Exception\ConfigurationException;
use InvalidArgumentException;
use function array_key_exists;
use function array_keys;
class CodeUnitCollector
{
public const CRITERIA_CLASS_FQCN = 'classFqcn';
public const CRITERIA_METHOD_NAME = 'methodName';
/**
* @var array
*/
......@@ -35,23 +40,26 @@ class CodeUnitCollector
{
$self = new self();
foreach ($collector as $criteria) {
foreach ($collector as $index => $criteria) {
if (!isset($criteria['type'])) {
throw new \InvalidArgumentException('Collector needs a type.');
throw new InvalidArgumentException('Collector needs a type.');
}
switch (true) {
case $criteria['type'] === 'classFqcn'
case $criteria['type'] === self::CRITERIA_CLASS_FQCN
&& array_key_exists('regex', $criteria):
$self->criteriaList[] = new ClassFqcnRegexCriteria($criteria['regex']);
$criteriaInstance = new ClassFqcnRegexCriteria($criteria['regex']);
break;
case $criteria['type'] === 'methodName'
case $criteria['type'] === self::CRITERIA_METHOD_NAME
&& array_key_exists('regex', $criteria):
$self->criteriaList[] = new MethodNameRegexCriteria($criteria['regex']);
$criteriaInstance = new MethodNameRegexCriteria($criteria['regex']);
break;
default:
$criteriaType = $criteria['type'];
throw new ConfigurationException("Unknown criteria type '$criteriaType'");
throw new ConfigurationException(
"Unknown or incomplete criteria with index '$index' and type '$criteriaType'"
);
}
$self->criteriaList[$criteria['type']] = $criteriaInstance;
}
return $self;
......@@ -77,13 +85,15 @@ class CodeUnitCollector
return false;
}
public function getCriteriaListAsString(): array
public function getCriteriaByType(string $type): CriteriaInterface
{
$list = [];
foreach ($this->criteriaList as $criteria) {
$list[] = (string) $criteria;
if (!isset($this->criteriaList[$type])) {
throw new InvalidArgumentException(
"This collector does not have the criteria type '$type'. Existing types are: "
. implode(', ', array_keys($this->criteriaList))
);
}
return $list;
return $this->criteriaList[$type];
}
}
......@@ -17,11 +17,15 @@ declare(strict_types=1);
namespace Hgraca\ContextMapper\Core\Port\Parser;
use Hgraca\ContextMapper\Core\Port\Configuration\ComponentDto;
/**
* This interface makes it possible to instantiate and AstMapInterface in the Core, without specifying the actual
* class instantiated.
*/
interface AstMapFactoryInterface
{
public function constructFromPath(string $path, string $name = ''): AstMapInterface;
public function constructFromPath(string $path): AstMapInterface;
public function constructFromComponentDtoList(ComponentDto ...$componentDtoList): AstMapInterface;
}
......@@ -23,10 +23,14 @@ interface AstMapInterface
{
public function serializeToFile(string $filePath, bool $prettyPrint = false): void;
public function findClassesWithFqcnMatchingRegex(string $fqcnRegex): AdapterNodeCollection;
public function findClassesWithFqcnMatchingRegex(
string $fqcnRegex,
string $componentName = ''
): AdapterNodeCollection;
public function findClassesCallingMethod(
string $methodClassFqcnRegex,
string $methodNameRegex
string $methodNameRegex,
string $componentName = ''
): AdapterNodeCollection;
}
......@@ -25,33 +25,56 @@ use PhpParser\Node;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitor\FindingVisitor;
use PhpParser\NodeVisitor\FirstFindingVisitor;
use function array_values;
final class AstMap implements AstMapInterface
{
/**
* @var NodeCollection[]
*/
private $componentNodeCollectionList;
/**
* @var NodeCollection
*/
private $nodeCollection;
private $completeNodeCollection;
/**
* @var QueryBuilder
*/
private $queryBuilder;
private function __construct(NodeCollection $nodeCollection)
private function __construct()
{
$this->nodeCollection = $nodeCollection;
$this->queryBuilder = new QueryBuilder();
}
public function serializeToFile(string $filePath, bool $prettyPrint = false): void
public static function constructFromNodeCollectionList(NodeCollection ...$nodeCollectionList): self
{
$this->nodeCollection->serializeToFile($filePath, $prettyPrint);
$self = new self();
foreach ($nodeCollectionList as $nodeCollection) {
$self->componentNodeCollectionList[$nodeCollection->getName()] = $nodeCollection;
}
$self->completeNodeCollection = NodeCollection::constructFromNodeCollectionList(
...array_values($self->componentNodeCollectionList)
);
$self->completeNodeCollection->enhance();
$self->queryBuilder = new QueryBuilder();
return $self;
}
public function findClassesWithFqcnMatchingRegex(string $fqcnRegex): AdapterNodeCollection
public function serializeToFile(string $filePath, bool $prettyPrint = false): void
{
$this->completeNodeCollection->serializeToFile($filePath, $prettyPrint);
}
public function findClassesWithFqcnMatchingRegex(
string $fqcnRegex,
string $componentName = ''
): AdapterNodeCollection {
$query = $this->queryBuilder->create()
->selectComponent($componentName)
->selectClassesWithFqcnMatchingRegex($fqcnRegex)
->build();
......@@ -60,27 +83,28 @@ final class AstMap implements AstMapInterface
public function findClassesCallingMethod(
string $methodClassFqcnRegex,
string $methodNameRegex
string $methodNameRegex,
string $componentName = ''
): AdapterNodeCollection {
$query = $this->queryBuilder->create()
->selectComponent($componentName)
->selectClassesCallingMethod($methodClassFqcnRegex, $methodNameRegex)
->build();
return $this->query($query);
}
public static function constructFromNodeCollection(NodeCollection $nodeCollection): self
{
return new self($nodeCollection);
}
private function query(Query $query): AdapterNodeCollection
{
$itemList = array_values($this->nodeCollection->toArray());
$nodeList = array_values(
$query->getComponentFilter()
? $this->getComponentAstCollection($query->getComponentFilter())->toArray()
: $this->completeNodeCollection->toArray()
);
$parserNodeList = $query->shouldReturnSingleResult()
? [$this->findFirst($this->createFilter($query), ...$itemList)]
: $this->find($this->createFilter($query), ...$itemList);
? [$this->findFirst($this->createFilter($query), ...$nodeList)]
: $this->find($this->createFilter($query), ...$nodeList);
return $this->mapNodeList($parserNodeList);
}
......@@ -127,4 +151,9 @@ final class AstMap implements AstMapInterface
return $visitor->getFoundNode();
}
private function getComponentAstCollection(string $componentName): NodeCollection
{
return $this->componentNodeCollectionList[$componentName];
}
}
......@@ -17,17 +17,30 @@ declare(strict_types=1);
namespace Hgraca\ContextMapper\Infrastructure\Parser\NikicPhpParser;
use Hgraca\ContextMapper\Core\Port\Configuration\ComponentDto;
use Hgraca\ContextMapper\Core\Port\Parser\AstMapFactoryInterface;
use Hgraca\ContextMapper\Core\Port\Parser\AstMapInterface;
final class AstMapFactory implements AstMapFactoryInterface
{
public function constructFromPath(string $path, string $name = ''): AstMapInterface
public function constructFromPath(string $path): AstMapInterface
{
return AstMap::constructFromNodeCollection(
return AstMap::constructFromNodeCollectionList(
is_dir($path)
? NodeCollection::constructFromFolder($path, $name)
: NodeCollection::unserializeFromFile($path, $name)
? NodeCollection::constructFromFolder($path)
: NodeCollection::unserializeFromFile($path)
);
}
public function constructFromComponentDtoList(ComponentDto ...$componentDtoList): AstMapInterface
{
$componentNodeCollectionList = [];
foreach ($componentDtoList as $componentDto) {
$componentNodeCollectionList[] = is_dir($componentDto->getPath())
? NodeCollection::constructFromFolder($componentDto->getPath(), $componentDto->getName())
: NodeCollection::unserializeFromFile($componentDto->getPath(), $componentDto->getName());
}
return AstMap::constructFromNodeCollectionList(...$componentNodeCollectionList);
}
}
......@@ -60,6 +60,14 @@ final class NodeCollection
file_put_contents($filePath, $this->toSerializedAst($prettyPrint));
}
public static function constructFromNodeCollectionList(self ...$nodeCollectionList): self
{
$self = new self();
$self->addCollections(...$nodeCollectionList);
return $self;
}
public static function constructFromFolder(string $folder, string $name = ''): self
{
$files = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($folder));
......@@ -72,7 +80,6 @@ final class NodeCollection
$self = new self();
$self->name = $name ?: uniqid('', true);
$self->nodeList = array_merge(...$nodeList);
$self->enhanceAst();
return $self;
}
......@@ -80,7 +87,6 @@ final class NodeCollection
public static function unserializeFromFile(string $filePath, string $name = ''): self
{
$self = self::fromSerializedAst(file_get_contents($filePath));
$self->enhanceAst();
$self->name = $name ?: uniqid('', true);
return $self;
......@@ -116,6 +122,48 @@ final class NodeCollection
return $this->name;
}
public function enhance(): void
{
$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 ParentConnectorVisitor());
$traverser->addVisitor(new NameResolver(null, ['preserveOriginalNames' => true, 'replaceNodes' => false]));
$traverser->traverse($nodeList);
// First we need to set all nodes in the collection, then we can run the visitors that need the collection
$traverser = new NodeTraverser();
$traverser->addVisitor(new StaticCallClassTypeInjectorVisitor($this));
$traverser->addVisitor(new MethodReturnTypeInjectorVisitor($this));
$traverser->addVisitor(new InstantiationTypeInjectorVisitor($this));
$traverser->addVisitor(new VariableTypeInjectorVisitor($this));
$traverser->traverse($nodeList);
$traverser = new NodeTraverser();
$traverser->addVisitor(new PropertyDeclarationTypeInjectorVisitor($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 PropertyTypeInjectorVisitor($this));
$traverser->traverse($nodeList);
$GLOBALS['nodes'] = $nodeList;
}
private function addCollections(self ...$nodeCollectionList): void
{
$newNodeList = [];
foreach ($nodeCollectionList as $nodeCollection) {
$newNodeList[] = $nodeCollection->nodeList;
}
$this->nodeList = array_merge($this->nodeList, ...$newNodeList);
}
private static function fromSerializedAst(string $serializedAst): self
{
$self = new self();
......@@ -177,30 +225,4 @@ final class NodeCollection
'Could not find a class in the namespace ' . $namespaceNode->name->toCodeString()
);
}
private function enhanceAst(): void
{
$nodeList = array_values($this->nodeList);
$traverser = new NodeTraverser();
$traverser->addVisitor(new ParentConnectorVisitor());
$traverser->addVisitor(new NameResolver(null, ['preserveOriginalNames' => true, 'replaceNodes' => false]));
$traverser->addVisitor(new StaticCallClassTypeInjectorVisitor($this));
$traverser->addVisitor(new MethodReturnTypeInjectorVisitor($this));
$traverser->addVisitor(new InstantiationTypeInjectorVisitor($this));
$traverser->addVisitor(new VariableTypeInjectorVisitor($this));
$traverser->traverse($nodeList);
// First we finish the previous swipe to set all variables
$traverser = new NodeTraverser();
$traverser->addVisitor(new PropertyDeclarationTypeInjectorVisitor($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 PropertyTypeInjectorVisitor($this));
$traverser->traverse($nodeList);
}
}
......@@ -24,11 +24,23 @@ final class Query
private $singleResult = false;
private $componentName = '';
public function addComponentFilter(string $componentName): void
{
$this->componentName = $componentName;
}
public function addFilter(callable $filter): void
{
$this->filterList[] = $filter;
}
public function getComponentFilter(): string
{
return $this->componentName;
}
/**
* @return callable[]
*/
......
......@@ -35,6 +35,13 @@ final class QueryBuilder
return $this;
}
public function selectComponent(string $componentName): self
{
$this->currentQuery->addComponentFilter($componentName);
return $this;
}
public function selectClasses(): self
{
$this->selectNodeType(Class_::class);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment