Skip to content

WIP: POC: refactor node tree using query dtos #3944

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: 9.0
Choose a base branch
from
Draft
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

/*
* This file is part of the Neos.Neos.Ui package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* 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 Neos\Neos\Ui\Application\GetChildrenForTreeNode\Controller;

use Neos\Flow\Annotations as Flow;
use Neos\Neos\Ui\Application\GetChildrenForTreeNode\GetChildrenForTreeNodeQuery;
use Neos\Neos\Ui\Application\GetChildrenForTreeNode\GetChildrenForTreeNodeQueryHandler;
use Neos\Neos\Ui\Application\Shared\NodeWasNotFound;
use Neos\Neos\Ui\Framework\MVC\QueryController;
use Neos\Neos\Ui\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,75 @@
<?php

/*
* This file is part of the Neos.Neos.Ui package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* 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 Neos\Neos\Ui\Application\GetChildrenForTreeNode;

use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint;
use Neos\ContentRepository\Core\NodeType\NodeTypeNames;
use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId;
use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId;
use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName;
use Neos\Flow\Annotations as Flow;

/**
* @internal
*/
#[Flow\Proxy(false)]
final class GetChildrenForTreeNodeQuery
{
public function __construct(
public readonly ContentRepositoryId $contentRepositoryId,
public readonly WorkspaceName $workspaceName,
public readonly DimensionSpacePoint $dimensionSpacePoint,
public readonly NodeAggregateId $treeNodeId,
public readonly string $nodeTypeFilter,
public readonly NodeTypeNames $linkableNodeTypes,
) {
}

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

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['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(
contentRepositoryId: ContentRepositoryId::fromString($array['contentRepositoryId']),
workspaceName: WorkspaceName::fromString($array['workspaceName']),
dimensionSpacePoint: DimensionSpacePoint::fromLegacyDimensionArray($array['dimensionValues'] ?? []),
treeNodeId: NodeAggregateId::fromString($array['treeNodeId']),
nodeTypeFilter: $array['nodeTypeFilter'] ?? '',
linkableNodeTypes: NodeTypeNames::fromStringArray($array['linkableNodeTypes'] ?? []),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

/*
* This file is part of the Neos.Neos.Ui package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* 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 Neos\Neos\Ui\Application\GetChildrenForTreeNode;

use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\NodeType\NodeTypeCriteria;
use Neos\ContentRepository\Core\Projection\ContentGraph\Node;
use Neos\Flow\Annotations as Flow;
use Neos\Neos\Ui\Application\Shared\TreeNodes;
use Neos\Neos\Ui\Infrastructure\ESCR\NodeService;
use Neos\Neos\Ui\Infrastructure\ESCR\NodeServiceFactory;
use Neos\Neos\Ui\Infrastructure\ESCR\NodeTypeService;
use Neos\Neos\Ui\Infrastructure\ESCR\NodeTypeServiceFactory;

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

#[Flow\Inject]
protected NodeTypeServiceFactory $nodeTypeServiceFactory;

public function handle(GetChildrenForTreeNodeQuery $query): GetChildrenForTreeNodeQueryResult
{
$nodeService = $this->nodeServiceFactory->create(
contentRepositoryId: $query->contentRepositoryId,
workspaceName: $query->workspaceName,
dimensionSpacePoint: $query->dimensionSpacePoint,
);
$nodeTypeService = $this->nodeTypeServiceFactory->create(
contentRepositoryId: $query->contentRepositoryId,
);

$node = $nodeService->requireNodeById($query->treeNodeId);

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

private function createTreeNodesFromChildrenOfNode(NodeService $nodeService, NodeTypeService $nodeTypeService, Node $node, GetChildrenForTreeNodeQuery $query): TreeNodes
{
$linkableNodeTypesFilter = $nodeTypeService->createNodeTypeFilterFromNodeTypeNames(
nodeTypeNames: $query->linkableNodeTypes
);

$items = [];
$nodeTypeCriteria = NodeTypeCriteria::fromFilterString($query->nodeTypeFilter);

foreach ($nodeService->findChildNodes($node, $nodeTypeCriteria) as $childNode) {
/** @var Node $childNode */
$items[] = $nodeService->createTreeNodeBuilderForNode($childNode)
->setIsMatchedByFilter(true)
->setIsLinkable($linkableNodeTypesFilter->isSatisfiedByNode($childNode))
->setHasUnloadedChildren($nodeService->getNumberOfChildNodes($childNode, $nodeTypeCriteria) > 0)
->build();
}

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

/*
* This file is part of the Neos.Neos.Ui package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* 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 Neos\Neos\Ui\Application\GetChildrenForTreeNode;

use Neos\Flow\Annotations as Flow;
use Neos\Neos\Ui\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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

/*
* This file is part of the Neos.Neos.Ui package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* 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 Neos\Neos\Ui\Application\GetNodeTypeFilterOptions\Controller;

use Neos\Flow\Annotations as Flow;
use Neos\Neos\Ui\Application\GetNodeTypeFilterOptions\GetNodeTypeFilterOptionsQuery;
use Neos\Neos\Ui\Application\GetNodeTypeFilterOptions\GetNodeTypeFilterOptionsQueryHandler;
use Neos\Neos\Ui\Framework\MVC\QueryController;
use Neos\Neos\Ui\Framework\MVC\QueryResponse;

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

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

return QueryResponse::success($queryResult);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

/*
* This file is part of the Neos.Neos.Ui package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* 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 Neos\Neos\Ui\Application\GetNodeTypeFilterOptions;

use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\NodeType\NodeTypeCriteria;
use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId;
use Neos\Flow\Annotations as Flow;

/**
* @internal
*/
#[Flow\Proxy(false)]
final class GetNodeTypeFilterOptionsQuery
{
public function __construct(
public readonly ContentRepositoryId $contentRepositoryId,
public readonly NodeTypeCriteria $baseNodeTypeFilter,
) {
}

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

isset($array['baseNodeTypeFilter'])
or throw new \InvalidArgumentException('Base node type filter must be set');
is_string($array['baseNodeTypeFilter'])
or throw new \InvalidArgumentException('Base node type filter must be a string');

return new self(
contentRepositoryId: ContentRepositoryId::fromString($array['contentRepositoryId']),
baseNodeTypeFilter: NodeTypeCriteria::fromFilterString($array['baseNodeTypeFilter']),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

/*
* This file is part of the Neos.Neos.Ui package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* 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 Neos\Neos\Ui\Application\GetNodeTypeFilterOptions;

use Neos\Flow\Annotations as Flow;
use Neos\Neos\Ui\Infrastructure\ESCR\NodeTypeServiceFactory;
use Neos\Neos\Ui\Presentation\Option\Options;
use Neos\Neos\Ui\Presentation\Option\OptionsFactory;

/**
* @internal
*/
#[Flow\Scope("singleton")]
final class GetNodeTypeFilterOptionsQueryHandler
{
/** @var array<string,array<mixed>> */
#[Flow\InjectConfiguration(package: 'Neos.Neos', path: 'userInterface.navigateComponent.nodeTree.presets')]
protected array $nodeTreePresets;

#[Flow\Inject]
protected NodeTypeServiceFactory $nodeTypeServiceFactory;

#[Flow\Inject]
protected OptionsFactory $optionsFactory;

public function handle(GetNodeTypeFilterOptionsQuery $query): GetNodeTypeFilterOptionsQueryResult
{
return new GetNodeTypeFilterOptionsQueryResult(
options: $this->thereAreNodeTreePresetsOtherThanDefault()
? $this->renderOptionsForNodeTreePresets()
: $this->renderOptionsForNodeTypes($query),
);
}

private function thereAreNodeTreePresetsOtherThanDefault(): bool
{
$defaultExists = isset($this->nodeTreePresets['default']);
$numberOfPresets = count($this->nodeTreePresets);

return ($defaultExists && $numberOfPresets > 1)
|| (!$defaultExists && $numberOfPresets > 0);
}

private function renderOptionsForNodeTreePresets(): Options
{
return $this->optionsFactory->forNodeTreePresets($this->nodeTreePresets);
}

private function renderOptionsForNodeTypes(GetNodeTypeFilterOptionsQuery $query): Options
{
$nodeTypeService = $this->nodeTypeServiceFactory->create(
contentRepositoryId: $query->contentRepositoryId,
);

return $this->optionsFactory->forNodeTypes(
...$nodeTypeService->getAllNodeTypesMatching($query->baseNodeTypeFilter)
);
}
}
Loading
Loading