Skip to content

Commit

Permalink
Merge pull request #63 from grebaldi/feature/tree-cqrs
Browse files Browse the repository at this point in the history
BUGFIX: Refactor node tree using Query DTOs
  • Loading branch information
nezaniel authored Jul 15, 2024
2 parents e4bd63d + 9771a80 commit a1f2e50
Show file tree
Hide file tree
Showing 80 changed files with 3,349 additions and 1,307 deletions.
27 changes: 27 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
root = true

[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 4

[*.{yaml,yml}]
indent_size = 2

[{package.json,.babelrc,.eslintrc,.stylelintrc}]
indent_size = 2

[Makefile]
indent_style = tab

[*.makefile]
indent_style = tab

[*.md]
trim_trailing_whitespace = false

[*.yaml.example]
indent_size = 2
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

/*
* This script belongs to the package "Sitegeist.Archaeopteryx".
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

declare(strict_types=1);

namespace Sitegeist\Archaeopteryx\Application\GetChildrenForTreeNode\Controller;

use Neos\Flow\Annotations as Flow;
use Sitegeist\Archaeopteryx\Application\GetChildrenForTreeNode\GetChildrenForTreeNodeQuery;
use Sitegeist\Archaeopteryx\Application\GetChildrenForTreeNode\GetChildrenForTreeNodeQueryHandler;
use Sitegeist\Archaeopteryx\Application\Shared\NodeWasNotFound;
use Sitegeist\Archaeopteryx\Framework\MVC\QueryController;
use Sitegeist\Archaeopteryx\Framework\MVC\QueryResponse;

#[Flow\Scope("singleton")]
final class GetChildrenForTreeNodeController extends QueryController
{
#[Flow\Inject]
protected GetChildrenForTreeNodeQueryHandler $queryHandler;

public function processQuery(array $arguments): QueryResponse
{
try {
$query = GetChildrenForTreeNodeQuery::fromArray($arguments);
$queryResult = $this->queryHandler->handle($query);

return QueryResponse::success($queryResult);
} catch (NodeWasNotFound $e) {
return QueryResponse::clientError($e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

/*
* This script belongs to the package "Sitegeist.Archaeopteryx".
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

declare(strict_types=1);

namespace Sitegeist\Archaeopteryx\Application\GetChildrenForTreeNode;

use Neos\ContentRepository\Domain\NodeAggregate\NodeAggregateIdentifier;
use Neos\ContentRepository\Domain\NodeType\NodeTypeName;
use Neos\Flow\Annotations as Flow;
use Sitegeist\Archaeopteryx\Application\Shared\NodeTypeNames;

/**
* @internal
*/
#[Flow\Proxy(false)]
final class GetChildrenForTreeNodeQuery
{
/**
* @param array<string,array<int,string>> $dimensionValues
*/
public function __construct(
public readonly string $workspaceName,
public readonly array $dimensionValues,
public readonly NodeAggregateIdentifier $treeNodeId,
public readonly string $nodeTypeFilter,
public readonly NodeTypeNames $linkableNodeTypes,
) {
}

/**
* @param array<string,mixed> $array
*/
public static function fromArray(array $array): self
{
isset($array['workspaceName'])
or throw new \InvalidArgumentException('Workspace name must be set');
is_string($array['workspaceName'])
or throw new \InvalidArgumentException('Workspace name must be a string');

isset($array['dimensionValues'])
or throw new \InvalidArgumentException('Dimension values must be set');
is_array($array['dimensionValues'])
or throw new \InvalidArgumentException('Dimension values must be an array');

isset($array['treeNodeId'])
or throw new \InvalidArgumentException('Tree node id must be set');
is_string($array['treeNodeId'])
or throw new \InvalidArgumentException('Tree node id must be a string');

!isset($array['nodeTypeFilter']) or is_string($array['nodeTypeFilter'])
or throw new \InvalidArgumentException('Node type filter must be a string');

!isset($array['linkableNodeTypes']) or is_array($array['linkableNodeTypes'])
or throw new \InvalidArgumentException('Linkable node types must be an array');

return new self(
workspaceName: $array['workspaceName'],
dimensionValues: $array['dimensionValues'],
treeNodeId: NodeAggregateIdentifier::fromString($array['treeNodeId']),
nodeTypeFilter: $array['nodeTypeFilter'] ?? '',
linkableNodeTypes: NodeTypeNames::fromArray($array['linkableNodeTypes'] ?? []),
);
}

/**
* @return array<string,string>
*/
public function getTargetDimensionValues(): array
{
$result = [];

foreach ($this->dimensionValues as $dimensionName => $fallbackChain) {
$result[$dimensionName] = $fallbackChain[0] ?? '';
}

return $result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

/*
* This script belongs to the package "Sitegeist.Archaeopteryx".
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

declare(strict_types=1);

namespace Sitegeist\Archaeopteryx\Application\GetChildrenForTreeNode;

use Neos\ContentRepository\Domain\Model\Node;
use Neos\ContentRepository\Domain\Service\NodeTypeManager;
use Neos\Flow\Annotations as Flow;
use Neos\Neos\Domain\Service\ContentContextFactory;
use Sitegeist\Archaeopteryx\Application\Shared\NodeWasNotFound;
use Sitegeist\Archaeopteryx\Application\Shared\TreeNodeBuilder;
use Sitegeist\Archaeopteryx\Application\Shared\TreeNodes;
use Sitegeist\Archaeopteryx\Infrastructure\ContentRepository\NodeTypeFilter;

/**
* @internal
*/
#[Flow\Scope("singleton")]
final class GetChildrenForTreeNodeQueryHandler
{
#[Flow\Inject]
protected ContentContextFactory $contentContextFactory;

#[Flow\Inject]
protected NodeTypeManager $nodeTypeManager;

public function handle(GetChildrenForTreeNodeQuery $query): GetChildrenForTreeNodeQueryResult
{
$contentContext = $this->contentContextFactory->create([
'workspaceName' => $query->workspaceName,
'dimensions' => $query->dimensionValues,
'targetDimensions' => $query->getTargetDimensionValues(),
'invisibleContentShown' => true,
'removedContentShown' => false,
'inaccessibleContentShown' => true
]);

$node = $contentContext->getNodeByIdentifier((string) $query->treeNodeId);
if (!$node instanceof Node) {
throw NodeWasNotFound::becauseNodeWithGivenIdentifierDoesNotExistInContext(
nodeAggregateIdentifier: $query->treeNodeId,
contentContext: $contentContext,
);
}

return new GetChildrenForTreeNodeQueryResult(
children: $this->createTreeNodesFromChildrenOfNode($node, $query),
);
}

private function createTreeNodesFromChildrenOfNode(Node $node, GetChildrenForTreeNodeQuery $query): TreeNodes
{
$linkableNodeTypesFilter = NodeTypeFilter::fromNodeTypeNames(
nodeTypeNames: $query->linkableNodeTypes,
nodeTypeManager: $this->nodeTypeManager
);

$items = [];

foreach ($node->getChildNodes($query->nodeTypeFilter) as $childNode) {
/** @var Node $childNode */
$items[] = TreeNodeBuilder::forNode($childNode)
->setIsMatchedByFilter(true)
->setIsLinkable($linkableNodeTypesFilter->isSatisfiedByNode($childNode))
->setHasUnloadedChildren($childNode->getNumberOfChildNodes($query->nodeTypeFilter) > 0)
->build();
}

return new TreeNodes(...$items);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

/*
* This script belongs to the package "Sitegeist.Archaeopteryx".
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

declare(strict_types=1);

namespace Sitegeist\Archaeopteryx\Application\GetChildrenForTreeNode;

use Neos\Flow\Annotations as Flow;
use Sitegeist\Archaeopteryx\Application\Shared\TreeNodes;

/**
* @internal
*/
#[Flow\Proxy(false)]
final class GetChildrenForTreeNodeQueryResult implements \JsonSerializable
{
public function __construct(
public readonly TreeNodes $children,
) {
}

public function jsonSerialize(): mixed
{
return get_object_vars($this);
}
}
33 changes: 33 additions & 0 deletions Classes/Application/GetNodeSummary/Breadcrumb.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

/*
* This script belongs to the package "Sitegeist.Archaeopteryx".
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

declare(strict_types=1);

namespace Sitegeist\Archaeopteryx\Application\GetNodeSummary;

use Neos\Flow\Annotations as Flow;

/**
* @internal
*/
#[Flow\Proxy(false)]
final class Breadcrumb implements \JsonSerializable
{
public function __construct(
public readonly string $icon,
public readonly string $label,
) {
}

public function jsonSerialize(): mixed
{
return get_object_vars($this);
}
}
35 changes: 35 additions & 0 deletions Classes/Application/GetNodeSummary/Breadcrumbs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

/*
* This script belongs to the package "Sitegeist.Archaeopteryx".
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

declare(strict_types=1);

namespace Sitegeist\Archaeopteryx\Application\GetNodeSummary;

use Neos\Flow\Annotations as Flow;

/**
* @internal
*/
#[Flow\Proxy(false)]
final class Breadcrumbs implements \JsonSerializable
{
/** @var Breadcrumb[] */
private readonly array $items;

public function __construct(Breadcrumb ...$items)
{
$this->items = array_values($items);
}

public function jsonSerialize(): mixed
{
return $this->items;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

/*
* This script belongs to the package "Sitegeist.Archaeopteryx".
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

declare(strict_types=1);

namespace Sitegeist\Archaeopteryx\Application\GetNodeSummary\Controller;

use Neos\Flow\Annotations as Flow;
use Sitegeist\Archaeopteryx\Application\GetNodeSummary\GetNodeSummaryQuery;
use Sitegeist\Archaeopteryx\Application\GetNodeSummary\GetNodeSummaryQueryHandler;
use Sitegeist\Archaeopteryx\Application\Shared\NodeWasNotFound;
use Sitegeist\Archaeopteryx\Framework\MVC\QueryController;
use Sitegeist\Archaeopteryx\Framework\MVC\QueryResponse;

#[Flow\Scope("singleton")]
final class GetNodeSummaryController extends QueryController
{
#[Flow\Inject]
protected GetNodeSummaryQueryHandler $queryHandler;

public function processQuery(array $arguments): QueryResponse
{
try {
$query = GetNodeSummaryQuery::fromArray($arguments);
$queryResult = $this->queryHandler->handle($query);

return QueryResponse::success($queryResult);
} catch (NodeWasNotFound $e) {
return QueryResponse::clientError($e);
}
}
}
Loading

0 comments on commit a1f2e50

Please sign in to comment.