diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index b006fa1019d..76d17cdf6c1 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -26,6 +26,7 @@ use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\NodeType\NodeTypeName; +use Neos\ContentRepository\Core\NodeType\NodeTypeNames; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphWithRuntimeCaches\ContentSubgraphWithRuntimeCaches; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface; @@ -132,6 +133,9 @@ public function findRootNodeAggregateByType( foreach ($rootNodeAggregates as $rootNodeAggregate) { $ids[] = $rootNodeAggregate->nodeAggregateId->value; } + + // We throw if multiple root node aggregates of the given $nodeTypeName were found, + // as this would lead to nondeterministic results. Must not happen. throw new \RuntimeException(sprintf( 'More than one root node aggregate of type "%s" found (IDs: %s).', $nodeTypeName->value, @@ -151,12 +155,12 @@ public function findRootNodeAggregates( FindRootNodeAggregatesFilter $filter, ): NodeAggregates { $rootNodeAggregateQueryBuilder = $this->nodeQueryBuilder->buildFindRootNodeAggregatesQuery($this->contentStreamId, $filter); - return NodeAggregates::fromArray(iterator_to_array($this->mapQueryBuilderToNodeAggregates($rootNodeAggregateQueryBuilder))); + return $this->mapQueryBuilderToNodeAggregates($rootNodeAggregateQueryBuilder); } public function findNodeAggregatesByType( NodeTypeName $nodeTypeName - ): iterable { + ): NodeAggregates { $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeAggregateQuery(); $queryBuilder ->andWhere('n.nodetypename = :nodeTypeName') @@ -190,12 +194,10 @@ public function findNodeAggregateById( * Parent node aggregates can have a greater dimension space coverage than the given child. * Thus, it is not enough to just resolve them from the nodes and edges connected to the given child node aggregate. * Instead, we resolve all parent node aggregate ids instead and fetch the complete aggregates from there. - * - * @return iterable */ public function findParentNodeAggregates( NodeAggregateId $childNodeAggregateId - ): iterable { + ): NodeAggregates { $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeAggregateQuery() ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ch', 'ch.parentnodeanchor = n.relationanchorpoint') ->innerJoin('ch', $this->nodeQueryBuilder->tableNames->node(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') @@ -209,12 +211,9 @@ public function findParentNodeAggregates( return $this->mapQueryBuilderToNodeAggregates($queryBuilder); } - /** - * @return iterable - */ public function findChildNodeAggregates( NodeAggregateId $parentNodeAggregateId - ): iterable { + ): NodeAggregates { $queryBuilder = $this->nodeQueryBuilder->buildChildNodeAggregateQuery($parentNodeAggregateId, $this->contentStreamId); return $this->mapQueryBuilderToNodeAggregates($queryBuilder); } @@ -252,7 +251,7 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint(NodeAggr ); } - public function findTetheredChildNodeAggregates(NodeAggregateId $parentNodeAggregateId): iterable + public function findTetheredChildNodeAggregates(NodeAggregateId $parentNodeAggregateId): NodeAggregates { $queryBuilder = $this->nodeQueryBuilder->buildChildNodeAggregateQuery($parentNodeAggregateId, $this->contentStreamId) ->andWhere('cn.classification = :tetheredClassification') @@ -319,12 +318,12 @@ public function countNodes(): int } } - public function findUsedNodeTypeNames(): iterable + public function findUsedNodeTypeNames(): NodeTypeNames { - return array_map( + return NodeTypeNames::fromArray(array_map( static fn (array $row) => NodeTypeName::fromString($row['nodetypename']), $this->fetchRows($this->nodeQueryBuilder->buildFindUsedNodeTypeNamesQuery()) - ); + )); } private function createQueryBuilder(): QueryBuilder @@ -344,9 +343,8 @@ private function mapQueryBuilderToNodeAggregate(QueryBuilder $queryBuilder): ?No /** * @param QueryBuilder $queryBuilder - * @return iterable */ - private function mapQueryBuilderToNodeAggregates(QueryBuilder $queryBuilder): iterable + private function mapQueryBuilderToNodeAggregates(QueryBuilder $queryBuilder): NodeAggregates { return $this->nodeFactory->mapNodeRowsToNodeAggregates( $this->fetchRows($queryBuilder), diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/NodeFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/NodeFactory.php index 02bf350f48a..4a83e209be0 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/NodeFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/NodeFactory.php @@ -25,6 +25,7 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\DimensionSpacePointsBySubtreeTags; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; +use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregates; use Neos\ContentRepository\Core\Projection\ContentGraph\Nodes; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeTags; use Neos\ContentRepository\Core\Projection\ContentGraph\OriginByCoverage; @@ -233,16 +234,21 @@ public function mapNodeRowsToNodeAggregate( } /** - * @param iterable> $nodeRows - * @return iterable + * @param array> $nodeRows * @throws NodeTypeNotFound */ public function mapNodeRowsToNodeAggregates( - iterable $nodeRows, + array $nodeRows, WorkspaceName $workspaceName, ContentStreamId $contentStreamId, VisibilityConstraints $visibilityConstraints - ): iterable { + ): NodeAggregates { + if (empty($nodeRows)) { + return NodeAggregates::createEmpty(); + } + + $nodeAggregates = []; + $nodeTypeNames = []; $nodeNames = []; $occupiedDimensionSpacePointsByNodeAggregate = []; @@ -308,7 +314,7 @@ public function mapNodeRowsToNodeAggregates( foreach ($nodesByOccupiedDimensionSpacePointsByNodeAggregate as $rawNodeAggregateId => $nodes) { /** @var string $rawNodeAggregateId */ - yield NodeAggregate::create( + $nodeAggregates[] = NodeAggregate::create( $this->contentRepositoryId, $workspaceName, NodeAggregateId::fromString($rawNodeAggregateId), @@ -334,6 +340,8 @@ public function mapNodeRowsToNodeAggregates( $contentStreamId, ); } + + return NodeAggregates::fromArray($nodeAggregates); } public static function extractNodeTagsFromJson(string $subtreeTagsJson): NodeTags diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php index 25a70995cd5..3127c86e061 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php @@ -23,6 +23,7 @@ use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\NodeType\NodeTypeName; +use Neos\ContentRepository\Core\NodeType\NodeTypeNames; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindRootNodeAggregatesFilter; @@ -127,13 +128,10 @@ public function findRootNodeAggregates( throw new \BadMethodCallException('method findRootNodeAggregates is not implemented yet.', 1645782874); } - /** - * @return \Iterator - */ public function findNodeAggregatesByType( NodeTypeName $nodeTypeName - ): \Iterator { - return new \Generator(); + ): NodeAggregates { + return NodeAggregates::createEmpty(); } public function findNodeAggregateById( @@ -188,12 +186,9 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint( ); } - /** - * @return iterable - */ public function findParentNodeAggregates( NodeAggregateId $childNodeAggregateId - ): iterable { + ): NodeAggregates { $query = HypergraphParentQuery::create($this->contentStreamId, $this->tableNamePrefix); $query = $query->withChildNodeAggregateId($childNodeAggregateId); @@ -205,12 +200,9 @@ public function findParentNodeAggregates( ); } - /** - * @return iterable - */ public function findChildNodeAggregates( NodeAggregateId $parentNodeAggregateId - ): iterable { + ): NodeAggregates { $query = HypergraphChildQuery::create( $this->contentStreamId, $parentNodeAggregateId, @@ -244,12 +236,9 @@ public function findChildNodeAggregateByName( ); } - /** - * @return iterable - */ public function findTetheredChildNodeAggregates( NodeAggregateId $parentNodeAggregateId - ): iterable { + ): NodeAggregates { $query = HypergraphChildQuery::create( $this->contentStreamId, $parentNodeAggregateId, @@ -298,12 +287,9 @@ public function countNodes(): int return $this->dbal->executeQuery($query)->fetchOne(); } - /** - * @return iterable - */ - public function findUsedNodeTypeNames(): iterable + public function findUsedNodeTypeNames(): NodeTypeNames { - return []; + return NodeTypeNames::createEmpty(); } public function getContentStreamId(): ContentStreamId diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/NodeFactory.php b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/NodeFactory.php index df19bae4b34..4fa61afde28 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/NodeFactory.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/NodeFactory.php @@ -25,6 +25,7 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\DimensionSpacePointsBySubtreeTags; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; +use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregates; use Neos\ContentRepository\Core\Projection\ContentGraph\Nodes; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeTags; use Neos\ContentRepository\Core\Projection\ContentGraph\OriginByCoverage; @@ -252,15 +253,15 @@ public function mapNodeRowsToNodeAggregate( /** * @param array> $nodeRows - * @return iterable */ - public function mapNodeRowsToNodeAggregates(array $nodeRows, VisibilityConstraints $visibilityConstraints): iterable + public function mapNodeRowsToNodeAggregates(array $nodeRows, VisibilityConstraints $visibilityConstraints): NodeAggregates { - $nodeAggregates = []; if (empty($nodeRows)) { - return $nodeAggregates; + return NodeAggregates::createEmpty(); } + $nodeAggregates = []; + $contentStreamId = null; /** @var NodeAggregateId[] $nodeAggregateIds */ $nodeAggregateIds = []; @@ -330,7 +331,7 @@ public function mapNodeRowsToNodeAggregates(array $nodeRows, VisibilityConstrain } foreach ($nodeAggregateIds as $key => $nodeAggregateId) { - yield NodeAggregate::create( + $nodeAggregates[] = NodeAggregate::create( $this->contentRepositoryId, WorkspaceName::fromString('missing'), // todo $nodeAggregateId, @@ -348,6 +349,8 @@ public function mapNodeRowsToNodeAggregates(array $nodeRows, VisibilityConstrain $contentStreamId, ); } + + return NodeAggregates::fromArray($nodeAggregates); } private static function parseDateTimeString(string $string): \DateTimeImmutable diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php index 6a5ef9a7948..edc925f622d 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php @@ -18,6 +18,7 @@ use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\NodeType\NodeTypeName; +use Neos\ContentRepository\Core\NodeType\NodeTypeNames; use Neos\ContentRepository\Core\Projection\ProjectionStateInterface; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregatesTypeIsAmbiguous; @@ -58,11 +59,7 @@ public function getSubgraph( ): ContentSubgraphInterface; /** - * Throws exception if no root aggregate found, because a Content Repository needs at least - * one root node to function. - * - * Also throws exceptions if multiple root node aggregates of the given $nodeTypeName were found, - * as this would lead to nondeterministic results in your code. + * Throws exception if no root aggregate of the given type found. * * @throws RootNodeAggregateDoesNotExist * @api @@ -79,12 +76,11 @@ public function findRootNodeAggregates( ): NodeAggregates; /** - * @return iterable * @api */ public function findNodeAggregatesByType( NodeTypeName $nodeTypeName - ): iterable; + ): NodeAggregates; /** * @throws NodeAggregatesTypeIsAmbiguous @@ -97,10 +93,9 @@ public function findNodeAggregateById( /** * Returns all node types in use, from the graph projection * - * @return iterable * @api */ - public function findUsedNodeTypeNames(): iterable; + public function findUsedNodeTypeNames(): NodeTypeNames; /** * @internal only for consumption inside the Command Handler @@ -111,20 +106,18 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint( ): ?NodeAggregate; /** - * @return iterable * @internal only for consumption inside the Command Handler */ public function findParentNodeAggregates( NodeAggregateId $childNodeAggregateId - ): iterable; + ): NodeAggregates; /** - * @return iterable * @internal only for consumption inside the Command Handler */ public function findChildNodeAggregates( NodeAggregateId $parentNodeAggregateId - ): iterable; + ): NodeAggregates; /** * A node aggregate can have no or exactly one child node aggregate with a given name as enforced by constraint checks @@ -137,12 +130,11 @@ public function findChildNodeAggregateByName( ): ?NodeAggregate; /** - * @return iterable * @internal only for consumption inside the Command Handler */ public function findTetheredChildNodeAggregates( NodeAggregateId $parentNodeAggregateId - ): iterable; + ): NodeAggregates; /** * @internal only for consumption inside the Command Handler