Segregate the AstMap and encapsulate querying

parent 03afed23
<?php
declare(strict_types=1);
/*
* This file is part of the Context 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\ContextMapper\Core\Component\Main\Application\Query;
use Hgraca\ContextMapper\Core\Component\Main\Domain\DomainNodeCollection;
use Hgraca\ContextMapper\Core\Component\Main\Domain\EventDispatchingNode;
use Hgraca\ContextMapper\Core\Port\Configuration\Collector\ClassFqcnRegexCriteria;
use Hgraca\ContextMapper\Core\Port\Configuration\Collector\CodeUnitCollector;
use Hgraca\ContextMapper\Core\Port\Configuration\Exception\ConfigurationException;
use Hgraca\ContextMapper\Core\Port\Parser\AstMapInterface;
use Hgraca\ContextMapper\Core\Port\Parser\QueryBuilderInterface;
final class EventDispatchingQuery
{
/**
* @var QueryBuilderInterface
*/
private $queryBuilder;
public function __construct(QueryBuilderInterface $queryBuilder)
{
$this->queryBuilder = $queryBuilder;
}
public function queryAst(
AstMapInterface $ast,
CodeUnitCollector $collector
): DomainNodeCollection {
$this->validateCollector($collector);
$query = $this->queryBuilder->create()
->selectClassesCallingMethod(...$collector->getCriteriaListAsString())
->build();
return $ast->query($query)->decorateByDomainNode(EventDispatchingNode::class);
}
private function validateCollector(CodeUnitCollector $collector): void
{
if (!$collector->hasCriteria(ClassFqcnRegexCriteria::class)) {
throw new ConfigurationException(
'Collector provided to ' . self::class
. ' does have the required criteria: ' . ClassFqcnRegexCriteria::class
);
}
}
}
<?php
declare(strict_types=1);
/*
* This file is part of the Context 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\ContextMapper\Core\Component\Main\Application\Query;
use Hgraca\ContextMapper\Core\Component\Main\Domain\DomainNodeCollection;
use Hgraca\ContextMapper\Core\Component\Main\Domain\ListenerNode;
use Hgraca\ContextMapper\Core\Port\Configuration\Collector\ClassFqcnRegexCriteria;
use Hgraca\ContextMapper\Core\Port\Configuration\Collector\CodeUnitCollector;
use Hgraca\ContextMapper\Core\Port\Configuration\Exception\ConfigurationException;
use Hgraca\ContextMapper\Core\Port\Parser\AstMapInterface;
use Hgraca\ContextMapper\Core\Port\Parser\Node\ClassInterface;
use Hgraca\ContextMapper\Core\Port\Parser\QueryBuilderInterface;
final class ListenerQuery
{
/**
* @var QueryBuilderInterface
*/
private $queryBuilder;
public function __construct(QueryBuilderInterface $queryBuilder)
{
$this->queryBuilder = $queryBuilder;
}
public function queryAst(AstMapInterface $ast, CodeUnitCollector $collector): DomainNodeCollection
{
$this->validateCollector($collector);
$query = $this->queryBuilder->create()
->selectClassesWithFqcnMatchingRegex(...$collector->getCriteriaListAsString())
->build();
$nodeCollection = $ast->query($query);
$listenerList = [];
/* @var ClassInterface $classAdapter */
foreach ($nodeCollection as $classAdapter) {
foreach ($classAdapter->getMethodList() as $methodAdapter) {
if ($methodAdapter->isConstructor() || !$methodAdapter->isPublic()) {
continue;
}
$listenerList[] = new ListenerNode($classAdapter, $methodAdapter);
}
}
return new DomainNodeCollection(...$listenerList);
}
private function validateCollector(CodeUnitCollector $collector): void
{
if (!$collector->hasCriteria(ClassFqcnRegexCriteria::class)) {
throw new ConfigurationException(
'Collector provided to ' . self::class
. ' does have the required criteria: ' . ClassFqcnRegexCriteria::class
);
}
}
}
<?php
declare(strict_types=1);
/*
* This file is part of the Context 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\ContextMapper\Core\Component\Main\Application\Query;
use Hgraca\ContextMapper\Core\Component\Main\Domain\DomainNodeCollection;
use Hgraca\ContextMapper\Core\Component\Main\Domain\UseCaseNode;
use Hgraca\ContextMapper\Core\Port\Configuration\Collector\ClassFqcnRegexCriteria;
use Hgraca\ContextMapper\Core\Port\Configuration\Collector\CodeUnitCollector;
use Hgraca\ContextMapper\Core\Port\Configuration\Exception\ConfigurationException;
use Hgraca\ContextMapper\Core\Port\Parser\AstMapInterface;
use Hgraca\ContextMapper\Core\Port\Parser\QueryBuilderInterface;
final class UseCaseQuery
{
/**
* @var QueryBuilderInterface
*/
private $queryBuilder;
public function __construct(QueryBuilderInterface $queryBuilder)
{
$this->queryBuilder = $queryBuilder;
}
public function queryAst(AstMapInterface $ast, CodeUnitCollector $collector): DomainNodeCollection
{
$this->validateCollector($collector);
$query = $this->queryBuilder->create()
->selectClassesWithFqcnMatchingRegex(...$collector->getCriteriaListAsString())
->build();
return $ast->query($query)->decorateByDomainNode(UseCaseNode::class);
}
private function validateCollector(CodeUnitCollector $collector): void
{
if (!$collector->hasCriteria(ClassFqcnRegexCriteria::class)) {
throw new ConfigurationException(
'Collector provided to ' . self::class
. ' does have the required criteria: ' . ClassFqcnRegexCriteria::class
);
}
}
}
......@@ -17,14 +17,9 @@ declare(strict_types=1);
namespace Hgraca\ContextMapper\Core\Component\Main\Application\Service;
use Hgraca\ContextMapper\Core\Component\Main\Application\Query\EventDispatchingQuery;
use Hgraca\ContextMapper\Core\Component\Main\Application\Query\ListenerQuery;
use Hgraca\ContextMapper\Core\Component\Main\Application\Query\MethodCallerQuery;
use Hgraca\ContextMapper\Core\Component\Main\Application\Query\SubscriberQuery;
use Hgraca\ContextMapper\Core\Component\Main\Application\Query\UseCaseQuery;
use Hgraca\ContextMapper\Core\Component\Main\Domain\Component;
use Hgraca\ContextMapper\Core\Component\Main\Domain\ContextMap;
use Hgraca\ContextMapper\Core\Component\Main\Domain\MethodCallerNode;
use Hgraca\ContextMapper\Core\Component\Main\Domain\DomainAstMap;
use Hgraca\ContextMapper\Core\Port\Configuration\Configuration;
use Hgraca\ContextMapper\Core\Port\Parser\AstMapFactoryInterface;
use Hgraca\ContextMapper\Core\Port\Printer\PrinterInterface;
......@@ -41,40 +36,12 @@ final class ContextMapService
*/
private $astMapFactory;
/**
* @var UseCaseQuery
*/
private $useCaseQuery;
/**
* @var ListenerQuery
*/
private $listenerQuery;
/**
* @var SubscriberQuery
*/
private $subscriberQuery;
/**
* @var EventDispatchingQuery
*/
private $eventDispatchingQuery;
public function __construct(
PrinterInterface $printer,
AstMapFactoryInterface $astMapFactory,
UseCaseQuery $useCaseQuery,
ListenerQuery $listenerQuery,
SubscriberQuery $subscriberQuery,
EventDispatchingQuery $eventDispatchingQuery
AstMapFactoryInterface $astMapFactory
) {
$this->printer = $printer;
$this->astMapFactory = $astMapFactory;
$this->useCaseQuery = $useCaseQuery;
$this->listenerQuery = $listenerQuery;
$this->subscriberQuery = $subscriberQuery;
$this->eventDispatchingQuery = $eventDispatchingQuery;
}
public function printContextMap(ContextMap $contextMap, Configuration $config): void
......@@ -87,19 +54,22 @@ final class ContextMapService
public function createFromConfig(Configuration $config): ContextMap
{
$useCaseCollector = $config->getUseCaseCollector();
$listenerCollector = $config->getListenerCollector();
$subscriberCollector = $config->getSubscriberCollector();
$eventDispatchingCollector = $config->getEventDispatchingCollector();
$componentList = [];
foreach ($config->getComponents() as $componentDto) {
$componentAstMap = $componentDto->isDir()
? $this->astMapFactory->constructFromFolder($componentDto->getPath())
: $this->astMapFactory->unserializeFromFile($componentDto->getPath());
$componentList[] = new Component(
$componentDto->getName(),
$componentAstMap, // FIXME all this below should be encapsulated
$this->useCaseQuery->queryAst($componentAstMap, $config->getUseCaseCollector()),
$this->listenerQuery->queryAst($componentAstMap, $config->getListenerCollector()),
$this->subscriberQuery->queryAst($componentAstMap, $config->getSubscriberCollector()),
$this->eventDispatchingQuery->queryAst($componentAstMap, $config->getEventDispatchingCollector())
new DomainAstMap(
$this->astMapFactory->constructFromPath($componentDto->getPath()),
$useCaseCollector,
$listenerCollector,
$subscriberCollector,
$eventDispatchingCollector
)
);
}
......
......@@ -17,9 +17,6 @@ declare(strict_types=1);
namespace Hgraca\ContextMapper\Core\Component\Main\Domain;
use Hgraca\ContextMapper\Core\Port\Parser\AstMapInterface;
use Hgraca\ContextMapper\Infrastructure\Parser\NikicPhpParser\AstMap;
final class Component
{
/**
......@@ -28,49 +25,46 @@ final class Component
private $name;
/** @var UseCaseNode[]|DomainNodeCollection */
private $useCaseList;
private $useCaseCollection;
/** @var ListenerNode[]|DomainNodeCollection */
private $listenerList;
private $listenerCollection;
/** @var ListenerNode[]|DomainNodeCollection */
private $subscriberList;
private $subscriberCollection;
/**
* @var EventDispatchingNode[]|DomainNodeCollection
*/
private $eventDispatchingList;
private $eventDispatchingCollection;
/**
* @var AstMap
* @var DomainAstMap
*/
private $astMap;
public function __construct(
string $name,
AstMapInterface $astMap,
DomainNodeCollection $useCaseList = null,
DomainNodeCollection $listenerList = null,
DomainNodeCollection $subscriberList = null,
DomainNodeCollection $eventDispatchingList = null
) {
public function __construct(string $name, DomainAstMap $astMap)
{
$this->name = $name;
$this->astMap = $astMap;
$this->useCaseList = $useCaseList ?? new DomainNodeCollection();
$this->listenerList = $listenerList ?? new DomainNodeCollection();
$this->subscriberList = $subscriberList ?? new DomainNodeCollection();
$this->eventDispatchingList = $eventDispatchingList ?? new DomainNodeCollection();
foreach ($this->useCaseList as $useCase) {
$this->useCaseCollection = $astMap->findUseCases();
foreach ($this->useCaseCollection as $useCase) {
$useCase->setComponent($this);
}
foreach ($this->listenerList as $listener) {
$this->listenerCollection = $astMap->findListeners();
foreach ($this->listenerCollection as $listener) {
$listener->setComponent($this);
}
foreach ($this->subscriberList as $subscriber) {
$this->subscriberCollection = $astMap->findSubscribers();
foreach ($this->subscriberCollection as $subscriber) {
$subscriber->setComponent($this);
}
foreach ($this->eventDispatchingList as $eventDispatching) {
$this->eventDispatchingCollection = $astMap->findEventDispatching();
foreach ($this->eventDispatchingCollection as $eventDispatching) {
$eventDispatching->setComponent($this);
}
}
......@@ -80,7 +74,7 @@ final class Component
return $this->name;
}
public function getAstMap(): AstMap
public function getAstMap(): DomainAstMap
{
return $this->astMap;
}
......@@ -88,32 +82,32 @@ final class Component
/**
* @return UseCaseNode[]|DomainNodeCollection
*/
public function getUseCaseList(): DomainNodeCollection
public function getUseCaseCollection(): DomainNodeCollection
{
return $this->useCaseList;
return $this->useCaseCollection;
}
/**
* @return ListenerNode[]|DomainNodeCollection
*/
public function getListenerList(): DomainNodeCollection
public function getListenerCollection(): DomainNodeCollection
{
return $this->listenerList;
return $this->listenerCollection;
}
/**
* @return ListenerNode[]|DomainNodeCollection
*/
public function getSubscriberList(): DomainNodeCollection
public function getSubscriberCollection(): DomainNodeCollection
{
return $this->subscriberList;
return $this->subscriberCollection;
}
/**
* @return EventDispatchingNode[]|DomainNodeCollection
*/
public function getEventDispatchingList(): DomainNodeCollection
public function getEventDispatchingCollection(): DomainNodeCollection
{
return $this->eventDispatchingList;
return $this->eventDispatchingCollection;
}
}
......@@ -66,12 +66,12 @@ final class ContextMap
{
$listenersList = [];
foreach ($this->getComponentList() as $component) {
foreach ($component->getListenerList() as $listener) {
foreach ($component->getListenerCollection() as $listener) {
if ($listener->listensTo($eventDispatching)) {
$listenersList[] = $listener;
}
}
foreach ($component->getSubscriberList() as $subscriber) {
foreach ($component->getSubscriberCollection() as $subscriber) {
if ($subscriber->listensTo($eventDispatching)) {
$listenersList[] = $subscriber;
}
......
......@@ -15,38 +15,87 @@ declare(strict_types=1);
* file that was distributed with this source code.
*/
namespace Hgraca\ContextMapper\Core\Component\Main\Application\Query;
namespace Hgraca\ContextMapper\Core\Component\Main\Domain;
use Hgraca\ContextMapper\Core\Component\Main\Domain\DomainNodeCollection;
use Hgraca\ContextMapper\Core\Component\Main\Domain\ListenerNode;
use Hgraca\ContextMapper\Core\Port\Configuration\Collector\ClassFqcnRegexCriteria;
use Hgraca\ContextMapper\Core\Port\Configuration\Collector\CodeUnitCollector;
use Hgraca\ContextMapper\Core\Port\Configuration\Exception\ConfigurationException;
use Hgraca\ContextMapper\Core\Port\Parser\AstMapInterface;
use Hgraca\ContextMapper\Core\Port\Parser\Node\ClassInterface;
use Hgraca\ContextMapper\Core\Port\Parser\QueryBuilderInterface;
final class SubscriberQuery
final class DomainAstMap
{
/**
* @var QueryBuilderInterface
* @var AstMapInterface
*/
private $queryBuilder;
private $astMap;
public function __construct(QueryBuilderInterface $queryBuilder)
/**
* @var CodeUnitCollector
*/
private $useCaseCollector;
/**
* @var CodeUnitCollector
*/
private $listenerCollector;
/**
* @var CodeUnitCollector
*/
private $subscriberCollector;
/**
* @var CodeUnitCollector
*/
private $eventDispatchingCollector;
public function __construct(
AstMapInterface $astMap,
CodeUnitCollector $useCaseCollector,
CodeUnitCollector $listenerCollector,
CodeUnitCollector $subscriberCollector,
CodeUnitCollector $eventDispatchingCollector
) {
$this->astMap = $astMap;
$this->useCaseCollector = $useCaseCollector;
$this->listenerCollector = $listenerCollector;
$this->subscriberCollector = $subscriberCollector;
$this->eventDispatchingCollector = $eventDispatchingCollector;
}
public function findUseCases(): DomainNodeCollection
{
$this->queryBuilder = $queryBuilder;
$nodeCollection = $this->astMap->findClassesWithFqcnMatchingRegex(
...$this->useCaseCollector->getCriteriaListAsString()
);
return $nodeCollection->decorateByDomainNode(UseCaseNode::class);
}
public function queryAst(AstMapInterface $ast, CodeUnitCollector $collector): DomainNodeCollection
public function findListeners(): DomainNodeCollection
{
$this->validateCollector($collector);
$nodeCollection = $this->astMap->findClassesWithFqcnMatchingRegex(
...$this->listenerCollector->getCriteriaListAsString()
);
$query = $this->queryBuilder->create()
->selectClassesWithFqcnMatchingRegex(...$collector->getCriteriaListAsString())
->build();
$listenerList = [];
/* @var ClassInterface $classAdapter */
foreach ($nodeCollection as $classAdapter) {
foreach ($classAdapter->getMethodList() as $methodAdapter) {
if ($methodAdapter->isConstructor() || !$methodAdapter->isPublic()) {
continue;
}
$listenerList[] = new ListenerNode($classAdapter, $methodAdapter);
}
}
return new DomainNodeCollection(...$listenerList);
}
$nodeCollection = $ast->query($query);
public function findSubscribers(): DomainNodeCollection
{
$nodeCollection = $this->astMap->findClassesWithFqcnMatchingRegex(
...$this->subscriberCollector->getCriteriaListAsString()
);
$subscriberList = [];
/* @var ClassInterface $classAdapter */
......@@ -62,13 +111,9 @@ final class SubscriberQuery
return new DomainNodeCollection(...$subscriberList);
}
private function validateCollector(CodeUnitCollector $collector): void
public function findEventDispatching(): DomainNodeCollection
{
if (!$collector->hasCriteria(ClassFqcnRegexCriteria::class)) {
throw new ConfigurationException(
'Collector provided to ' . self::class
. ' does have the required criteria: ' . ClassFqcnRegexCriteria::class
);
}
return $this->astMap->findClassesCallingMethod(...$this->eventDispatchingCollector->getCriteriaListAsString())
->decorateByDomainNode(EventDispatchingNode::class);
}
}
......@@ -17,11 +17,16 @@ declare(strict_types=1);
namespace Hgraca\ContextMapper\Core\Port\Parser;
use Hgraca\ContextMapper\Core\Port\Parser\Node\AdapterNodeCollection;
interface AstMapInterface
{
public static function constructFromFolder(string $folder): self;
public function serializeToFile(string $filePath, bool $prettyPrint = false): void;
public static function constructFromAstMapList(self ...$astMapList): self;
public function findClassesWithFqcnMatchingRegex(string $fqcnRegex): AdapterNodeCollection;
public function findClassesCallingMethod(
string $methodClassFqcnRegex,
string $methodNameRegex
): AdapterNodeCollection;
}
<?php
declare(strict_types=1);
/*
* This file is part of the Context 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\ContextMapper\Core\Port\Parser;
interface QueryBuilderInterface
{
public function create(): self;
public function selectClasses(): self;
public function selectClassesExtending(string $fqcn): self;
public function selectClassesImplementing(string $fqcn): self;
public function selectClassesWithFqcnMatchingRegex(string $fqcnRegex): self;
public function selectClassWithFqcn(string $fqcn): self;
public function selectClassesCallingMethod(
string $eventDispatchingTypeRegex,
string $eventDispatchingMethodRegex
): self;
public function build(): QueryInterface;
}
<?php
declare(strict_types=1);
/*
* This file is part of the Context 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\ContextMapper\Core\Port\Parser;
interface QueryInterface
{
public function addFilter(callable $filter): void;
/**
* @return callable[]
*/
public function getFilterList(): array;
public function returnSingleResult(): void;
public function shouldReturnSingleResult(): bool;
}
......@@ -24,9 +24,11 @@ final class AstMapFactory implements AstMapFactoryInterface
{
public function constructFromPath(string $path): AstMapInterface
{
return is_dir($path)
? AstMap::constructFromFolder($path)
: AstMap::unserializeFromFile($path);
return AstMap::constructFromNodeCollection(
is_dir($path)
? NodeCollection::constructFromFolder($path)
: NodeCollection::unserializeFromFile($path)
);
}
public function constructFromAstMapList(AstMapInterface ...$astMapList): AstMapInterface
......
......@@ -23,6 +23,6 @@ final class AstNodeNotFoundException extends ParserException
{
public function __construct(string $fqcn)
{
parent::__construct('AstMap node not found in AstMap map: ' . $fqcn);
parent::__construct('NodeCollection node not found in NodeCollection map: ' . $fqcn);
}
}
<?php
declare(strict_types=1);
/*
* This file is part of the Context 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\ContextMapper\Infrastructure\Parser\NikicPhpParser;
use Hgraca\ContextMapper\Infrastructure\Parser\NikicPhpParser\Exception\AstNodeNotFoundException;
use Hgraca\ContextMapper\Infrastructure\Parser\NikicPhpParser\Exception\UnitNotFoundInNamespaceException;
use Hgraca\ContextMapper\Infrastructure\Parser\NikicPhpParser\Visitor\InstantiationTypeInjectorVisitor;
use Hgraca\ContextMapper\Infrastructure\Parser\NikicPhpParser\Visitor\MethodReturnTypeInjectorVisitor;
use Hgraca\ContextMapper\Infrastructure\Parser\NikicPhpParser\Visitor\ParentConnectorVisitor;
use Hgraca\ContextMapper\Infrastructure\Parser\NikicPhpParser\Visitor\PropertyDeclarationTypeInjectorVisitor;
use Hgraca\ContextMapper\Infrastructure\Parser\NikicPhpParser\Visitor\PropertyTypeInjectorVisitor;
use Hgraca\ContextMapper\Infrastructure\Parser\NikicPhpParser\Visitor\StaticCallClassTypeInjectorVisitor;
use Hgraca\ContextMapper\Infrastructure\Parser\NikicPhpParser\Visitor\VariableTypeInjectorVisitor;
use