diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php index cb43d749e47..28aad3f9561 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php @@ -17,7 +17,7 @@ use Behat\Gherkin\Node\TableNode; use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Exception\InvalidArgumentException; -use Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphProjectionFactory; +use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphTableNames; use Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalProjectionIntegrityViolationDetectionRunnerFactory; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\NodeFactory; use Neos\ContentGraph\DoctrineDbalAdapter\Tests\Behavior\Features\Bootstrap\Helpers\TestingNodeAggregateId; @@ -52,9 +52,9 @@ trait ProjectionIntegrityViolationDetectionTrait */ abstract private function getObject(string $className): object; - protected function getTableNamePrefix(): string + private function tableNames(): ContentGraphTableNames { - return DoctrineDbalContentGraphProjectionFactory::graphProjectionTableNamePrefix( + return ContentGraphTableNames::create( $this->currentContentRepository->id ); } @@ -80,7 +80,7 @@ public function iRemoveTheFollowingSubtreeTag(TableNode $payloadTable): void throw new \RuntimeException(sprintf('Failed to remove subtree tag "%s" because that tag is not set', $subtreeTagToRemove->value), 1708618267); } $this->dbalClient->getConnection()->update( - $this->getTableNamePrefix() . '_hierarchyrelation', + $this->tableNames()->hierarchyRelation(), [ 'subtreetags' => json_encode($subtreeTags->without($subtreeTagToRemove), JSON_THROW_ON_ERROR | JSON_FORCE_OBJECT), ], @@ -97,7 +97,7 @@ public function iAddTheFollowingHierarchyRelation(TableNode $payloadTable): void $dataset = $this->transformPayloadTableToDataset($payloadTable); $record = $this->transformDatasetToHierarchyRelationRecord($dataset); $this->dbalClient->getConnection()->insert( - $this->getTableNamePrefix() . '_hierarchyrelation', + $this->tableNames()->hierarchyRelation(), $record ); } @@ -114,7 +114,7 @@ public function iChangeTheFollowingHierarchyRelationsDimensionSpacePointHash(Tab unset($record['position']); $this->dbalClient->getConnection()->update( - $this->getTableNamePrefix() . '_hierarchyrelation', + $this->tableNames()->hierarchyRelation(), [ 'dimensionspacepointhash' => $dataset['newDimensionSpacePointHash'] ], @@ -134,7 +134,7 @@ public function iChangeTheFollowingHierarchyRelationsEdgeName(TableNode $payload unset($record['position']); $this->dbalClient->getConnection()->update( - $this->getTableNamePrefix() . '_hierarchyrelation', + $this->tableNames()->hierarchyRelation(), [ 'name' => $dataset['newName'] ], @@ -158,7 +158,7 @@ public function iSetTheFollowingPosition(TableNode $payloadTable): void ]; $this->dbalClient->getConnection()->update( - $this->getTableNamePrefix() . '_hierarchyrelation', + $this->tableNames()->hierarchyRelation(), [ 'position' => $dataset['newPosition'] ], @@ -176,7 +176,7 @@ public function iDetachTheFollowingReferenceRelationFromItsSource(TableNode $pay $dataset = $this->transformPayloadTableToDataset($payloadTable); $this->dbalClient->getConnection()->update( - $this->getTableNamePrefix() . '_referencerelation', + $this->tableNames()->referenceRelation(), [ 'nodeanchorpoint' => 7777777 ], @@ -194,7 +194,7 @@ public function iSetTheFollowingReferencePosition(TableNode $payloadTable): void $dataset = $this->transformPayloadTableToDataset($payloadTable); $this->dbalClient->getConnection()->update( - $this->getTableNamePrefix() . '_referencerelation', + $this->tableNames()->referenceRelation(), [ 'position' => $dataset['newPosition'] ], @@ -265,8 +265,8 @@ private function findHierarchyRelationByIds( ): array { $nodeRecord = $this->dbalClient->getConnection()->executeQuery( 'SELECT h.* - FROM ' . $this->getTableNamePrefix() . '_node n - INNER JOIN ' . $this->getTableNamePrefix() . '_hierarchyrelation h + FROM ' . $this->tableNames()->node() . ' n + INNER JOIN ' . $this->tableNames()->hierarchyRelation() . ' h ON n.relationanchorpoint = h.childnodeanchor WHERE n.nodeaggregateid = :nodeAggregateId AND h.contentstreamid = :contentStreamId diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php new file mode 100644 index 00000000000..66ae7720899 --- /dev/null +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php @@ -0,0 +1,71 @@ +contentRepositoryId->value, + 'workspace' + )); + + $row = $this->client->getConnection()->executeQuery( + ' + SELECT * FROM ' . $tableName . ' + WHERE workspaceName = :workspaceName + LIMIT 1 + ', + [ + 'workspaceName' => $workspaceName->value, + ] + )->fetchAssociative(); + + if ($row === false) { + throw WorkspaceDoesNotExist::butWasSupposedTo($workspaceName); + } + + return $this->buildForWorkspaceAndContentStream($workspaceName, ContentStreamId::fromString($row['currentcontentstreamid'])); + } + + public function buildForWorkspaceAndContentStream(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraph + { + return new ContentGraph($this->client, $this->nodeFactory, $this->contentRepositoryId, $this->nodeTypeManager, $this->tableNames, $workspaceName, $contentStreamId); + } +} diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php new file mode 100644 index 00000000000..0804d77e7e0 --- /dev/null +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php @@ -0,0 +1,47 @@ +value)); + } + + public function node(): string + { + return $this->tableNamePrefix . '_node'; + } + + public function hierarchyRelation(): string + { + return $this->tableNamePrefix . '_hierarchyrelation'; + } + + public function dimensionSpacePoints(): string + { + return $this->tableNamePrefix . '_dimensionspacepoints'; + } + + public function referenceRelation(): string + { + return $this->tableNamePrefix . '_referencerelation'; + } + + public function checkpoint(): string + { + return $this->tableNamePrefix . '_checkpoint'; + } +} diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index a3a92150aa8..d97e80194c7 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -14,10 +14,9 @@ use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\HierarchyRelation; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRecord; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRelationAnchorPoint; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ContentGraph; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\DimensionSpacePointsRepository; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\NodeFactory; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ProjectionContentGraph; +use Neos\ContentRepository\Core\ContentGraphFinder; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; @@ -47,15 +46,12 @@ use Neos\ContentRepository\Core\Infrastructure\DbalCheckpointStorage; use Neos\ContentRepository\Core\Infrastructure\DbalClientInterface; use Neos\ContentRepository\Core\Infrastructure\DbalSchemaDiff; -use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\NodeType\NodeTypeName; use Neos\ContentRepository\Core\Projection\CheckpointStorageStatusType; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeTags; use Neos\ContentRepository\Core\Projection\ContentGraph\Timestamps; use Neos\ContentRepository\Core\Projection\ProjectionInterface; use Neos\ContentRepository\Core\Projection\ProjectionStatus; -use Neos\ContentRepository\Core\Projection\WithMarkStaleInterface; -use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateClassification; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; @@ -64,10 +60,10 @@ use Neos\EventStore\Model\EventEnvelope; /** - * @implements ProjectionInterface + * @implements ProjectionInterface * @internal but the graph projection is api */ -final class DoctrineDbalContentGraphProjection implements ProjectionInterface, WithMarkStaleInterface +final class DoctrineDbalContentGraphProjection implements ProjectionInterface { use NodeVariation; use SubtreeTagging; @@ -77,26 +73,18 @@ final class DoctrineDbalContentGraphProjection implements ProjectionInterface, W public const RELATION_DEFAULT_OFFSET = 128; - /** - * @var ContentGraph|null Cache for the content graph returned by {@see getState()}, - * so that always the same instance is returned - */ - private ?ContentGraph $contentGraph = null; - private DbalCheckpointStorage $checkpointStorage; public function __construct( private readonly DbalClientInterface $dbalClient, - private readonly NodeFactory $nodeFactory, - private readonly ContentRepositoryId $contentRepositoryId, - private readonly NodeTypeManager $nodeTypeManager, private readonly ProjectionContentGraph $projectionContentGraph, - private readonly string $tableNamePrefix, - private readonly DimensionSpacePointsRepository $dimensionSpacePointsRepository + private readonly ContentGraphTableNames $tableNames, + private readonly DimensionSpacePointsRepository $dimensionSpacePointsRepository, + private readonly ContentGraphFinder $contentGraphFinder ) { $this->checkpointStorage = new DbalCheckpointStorage( $this->dbalClient->getConnection(), - $this->tableNamePrefix . '_checkpoint', + $this->tableNames->checkpoint(), self::class ); } @@ -106,11 +94,6 @@ protected function getProjectionContentGraph(): ProjectionContentGraph return $this->projectionContentGraph; } - protected function getTableNamePrefix(): string - { - return $this->tableNamePrefix; - } - public function setUp(): void { foreach ($this->determineRequiredSqlStatements() as $statement) { @@ -129,7 +112,7 @@ private function determineRequiredSqlStatements(): array if (!$schemaManager instanceof AbstractSchemaManager) { throw new \RuntimeException('Failed to retrieve Schema Manager', 1625653914); } - $schema = (new DoctrineDbalContentGraphSchemaBuilder($this->tableNamePrefix))->buildSchema($schemaManager); + $schema = (new DoctrineDbalContentGraphSchemaBuilder($this->tableNames))->buildSchema($schemaManager); return DbalSchemaDiff::determineRequiredSqlStatements($connection, $schema); } @@ -164,20 +147,16 @@ public function reset(): void $this->checkpointStorage->acquireLock(); $this->checkpointStorage->updateAndReleaseLock(SequenceNumber::none()); - - $contentGraph = $this->getState(); - foreach ($contentGraph->getSubgraphs() as $subgraph) { - $subgraph->inMemoryCache->enable(); - } + $this->getState()->forgetInstances(); } private function truncateDatabaseTables(): void { $connection = $this->dbalClient->getConnection(); - $connection->executeQuery('TRUNCATE table ' . $this->tableNamePrefix . '_node'); - $connection->executeQuery('TRUNCATE table ' . $this->tableNamePrefix . '_hierarchyrelation'); - $connection->executeQuery('TRUNCATE table ' . $this->tableNamePrefix . '_referencerelation'); - $connection->executeQuery('TRUNCATE table ' . $this->tableNamePrefix . '_dimensionspacepoints'); + $connection->executeQuery('TRUNCATE table ' . $this->tableNames->node()); + $connection->executeQuery('TRUNCATE table ' . $this->tableNames->hierarchyRelation()); + $connection->executeQuery('TRUNCATE table ' . $this->tableNames->referenceRelation()); + $connection->executeQuery('TRUNCATE table ' . $this->tableNames->dimensionSpacePoints()); } public function canHandle(EventInterface $event): bool @@ -234,26 +213,9 @@ public function getCheckpointStorage(): DbalCheckpointStorage return $this->checkpointStorage; } - public function getState(): ContentGraph + public function getState(): ContentGraphFinder { - if (!$this->contentGraph) { - $this->contentGraph = new ContentGraph( - $this->dbalClient, - $this->nodeFactory, - $this->contentRepositoryId, - $this->nodeTypeManager, - $this->tableNamePrefix - ); - } - return $this->contentGraph; - } - - public function markStale(): void - { - $contentGraph = $this->getState(); - foreach ($contentGraph->getSubgraphs() as $subgraph) { - $subgraph->inMemoryCache->disable(); - } + return $this->contentGraphFinder; } /** @@ -265,7 +227,7 @@ private function whenRootNodeAggregateWithNodeWasCreated(RootNodeAggregateWithNo $originDimensionSpacePoint = OriginDimensionSpacePoint::createWithoutDimensions(); $node = NodeRecord::createNewInDatabase( $this->getDatabaseConnection(), - $this->tableNamePrefix, + $this->tableNames, $event->nodeAggregateId, $originDimensionSpacePoint->coordinates, $originDimensionSpacePoint->hash, @@ -311,7 +273,7 @@ private function whenRootNodeAggregateDimensionsWereUpdated(RootNodeAggregateDim $this->transactional(function () use ($rootNodeAnchorPoint, $event) { // delete all hierarchy edges of the root node $this->getDatabaseConnection()->executeUpdate(' - DELETE FROM ' . $this->tableNamePrefix . '_hierarchyrelation + DELETE FROM ' . $this->tableNames->hierarchyRelation() . ' WHERE parentnodeanchor = :parentNodeAnchor AND childnodeanchor = :childNodeAnchor @@ -360,8 +322,8 @@ private function whenNodeAggregateNameWasChanged(NodeAggregateNameWasChanged $ev { $this->transactional(function () use ($event, $eventEnvelope) { $this->getDatabaseConnection()->executeStatement(' - UPDATE ' . $this->tableNamePrefix . '_hierarchyrelation h - INNER JOIN ' . $this->tableNamePrefix . '_node n on + UPDATE ' . $this->tableNames->hierarchyRelation() . ' h + INNER JOIN ' . $this->tableNames->node() . ' n on h.childnodeanchor = n.relationanchorpoint SET h.name = :newName, @@ -401,7 +363,7 @@ private function createNodeWithHierarchy( ): void { $node = NodeRecord::createNewInDatabase( $this->getDatabaseConnection(), - $this->tableNamePrefix, + $this->tableNames, $nodeAggregateId, $originDimensionSpacePoint->jsonSerialize(), $originDimensionSpacePoint->hash, @@ -493,7 +455,7 @@ private function connectHierarchy( $inheritedSubtreeTags, ); - $hierarchyRelation->addToDatabase($this->getDatabaseConnection(), $this->tableNamePrefix); + $hierarchyRelation->addToDatabase($this->getDatabaseConnection(), $this->tableNames); } } @@ -580,7 +542,7 @@ private function getRelationPositionAfterRecalculation( $position = $offset; $offset += self::RELATION_DEFAULT_OFFSET; } - $relation->assignNewPosition($offset, $this->getDatabaseConnection(), $this->tableNamePrefix); + $relation->assignNewPosition($offset, $this->getDatabaseConnection(), $this->tableNames); } return $position; @@ -597,7 +559,7 @@ private function whenContentStreamWasForked(ContentStreamWasForked $event): void // 1) Copy HIERARCHY RELATIONS (this is the MAIN OPERATION here) // $this->getDatabaseConnection()->executeUpdate(' - INSERT INTO ' . $this->tableNamePrefix . '_hierarchyrelation ( + INSERT INTO ' . $this->tableNames->hierarchyRelation() . ' ( parentnodeanchor, childnodeanchor, `name`, @@ -615,7 +577,7 @@ private function whenContentStreamWasForked(ContentStreamWasForked $event): void h.subtreetags, "' . $event->newContentStreamId->value . '" AS contentstreamid FROM - ' . $this->tableNamePrefix . '_hierarchyrelation h + ' . $this->tableNames->hierarchyRelation() . ' h WHERE h.contentstreamid = :sourceContentStreamId ', [ 'sourceContentStreamId' => $event->sourceContentStreamId->value @@ -632,7 +594,7 @@ private function whenContentStreamWasRemoved(ContentStreamWasRemoved $event): vo // Drop hierarchy relations $this->getDatabaseConnection()->executeUpdate(' - DELETE FROM ' . $this->tableNamePrefix . '_hierarchyrelation + DELETE FROM ' . $this->tableNames->hierarchyRelation() . ' WHERE contentstreamid = :contentStreamId ', [ @@ -641,23 +603,23 @@ private function whenContentStreamWasRemoved(ContentStreamWasRemoved $event): vo // Drop non-referenced nodes (which do not have a hierarchy relation anymore) $this->getDatabaseConnection()->executeUpdate(' - DELETE FROM ' . $this->tableNamePrefix . '_node + DELETE FROM ' . $this->tableNames->node() . ' WHERE NOT EXISTS ( - SELECT 1 FROM ' . $this->tableNamePrefix . '_hierarchyrelation - WHERE ' . $this->tableNamePrefix . '_hierarchyrelation.childnodeanchor - = ' . $this->tableNamePrefix . '_node.relationanchorpoint + SELECT 1 FROM ' . $this->tableNames->hierarchyRelation() . ' + WHERE ' . $this->tableNames->hierarchyRelation() . '.childnodeanchor + = ' . $this->tableNames->node() . '.relationanchorpoint ) '); // Drop non-referenced reference relations (i.e. because the referenced nodes are gone by now) $this->getDatabaseConnection()->executeUpdate(' - DELETE FROM ' . $this->tableNamePrefix . '_referencerelation + DELETE FROM ' . $this->tableNames->referenceRelation() . ' WHERE NOT EXISTS ( - SELECT 1 FROM ' . $this->tableNamePrefix . '_node - WHERE ' . $this->tableNamePrefix . '_node.relationanchorpoint - = ' . $this->tableNamePrefix . '_referencerelation.nodeanchorpoint + SELECT 1 FROM ' . $this->tableNames->node() . ' + WHERE ' . $this->tableNames->node() . '.relationanchorpoint + = ' . $this->tableNames->referenceRelation() . '.nodeanchorpoint ) '); }); @@ -742,7 +704,7 @@ function (NodeRecord $node) use ($eventEnvelope) { ); // remove old - $this->getDatabaseConnection()->delete($this->tableNamePrefix . '_referencerelation', [ + $this->getDatabaseConnection()->delete($this->tableNames->referenceRelation(), [ 'nodeanchorpoint' => $nodeAnchorPoint?->value, 'name' => $event->referenceName->value ]); @@ -751,7 +713,7 @@ function (NodeRecord $node) use ($eventEnvelope) { $position = 0; /** @var SerializedNodeReference $reference */ foreach ($event->references as $reference) { - $this->getDatabaseConnection()->insert($this->tableNamePrefix . '_referencerelation', [ + $this->getDatabaseConnection()->insert($this->tableNames->referenceRelation(), [ 'name' => $event->referenceName->value, 'position' => $position, 'nodeanchorpoint' => $nodeAnchorPoint?->value, @@ -795,7 +757,7 @@ protected function copyHierarchyRelationToDimensionSpacePoint( ), $inheritedSubtreeTags, ); - $copy->addToDatabase($this->getDatabaseConnection(), $this->tableNamePrefix); + $copy->addToDatabase($this->getDatabaseConnection(), $this->tableNames); return $copy; } @@ -810,7 +772,7 @@ protected function copyNodeToDimensionSpacePoint( ): NodeRecord { return NodeRecord::createNewInDatabase( $this->getDatabaseConnection(), - $this->tableNamePrefix, + $this->tableNames, $sourceNode->nodeAggregateId, $originDimensionSpacePoint->coordinates, $originDimensionSpacePoint->hash, @@ -862,15 +824,15 @@ private function updateNodeRecordWithCopyOnWrite( // 1) fetch node, adjust properties, assign new Relation Anchor Point /** @var NodeRecord $originalNode The anchor point appears in a content stream, so there must be a node */ $originalNode = $this->projectionContentGraph->getNodeByAnchorPoint($anchorPoint); - $copiedNode = NodeRecord::createCopyFromNodeRecord($this->getDatabaseConnection(), $this->tableNamePrefix, $originalNode); + $copiedNode = NodeRecord::createCopyFromNodeRecord($this->getDatabaseConnection(), $this->tableNames, $originalNode); $result = $operations($copiedNode); - $copiedNode->updateToDatabase($this->getDatabaseConnection(), $this->tableNamePrefix); + $copiedNode->updateToDatabase($this->getDatabaseConnection(), $this->tableNames); // 2) reconnect all edges belonging to this content stream to the new "copied node". // IMPORTANT: We need to reconnect BOTH the incoming and outgoing edges. $this->getDatabaseConnection()->executeStatement( ' - UPDATE ' . $this->tableNamePrefix . '_hierarchyrelation h + UPDATE ' . $this->tableNames->hierarchyRelation() . ' h SET -- if our (copied) node is the child, we update h.childNodeAnchor h.childnodeanchor @@ -903,7 +865,7 @@ private function updateNodeRecordWithCopyOnWrite( } $result = $operations($node); - $node->updateToDatabase($this->getDatabaseConnection(), $this->tableNamePrefix); + $node->updateToDatabase($this->getDatabaseConnection(), $this->tableNames); } return $result; } @@ -914,7 +876,7 @@ private function copyReferenceRelations( NodeRelationAnchorPoint $destinationRelationAnchorPoint ): void { $this->getDatabaseConnection()->executeStatement(' - INSERT INTO ' . $this->tableNamePrefix . '_referencerelation ( + INSERT INTO ' . $this->tableNames->referenceRelation() . ' ( nodeanchorpoint, name, position, @@ -926,7 +888,7 @@ private function copyReferenceRelations( ref.position, ref.destinationnodeaggregateid FROM - ' . $this->tableNamePrefix . '_referencerelation ref + ' . $this->tableNames->referenceRelation() . ' ref WHERE ref.nodeanchorpoint = :sourceNodeAnchorPoint ', [ 'sourceNodeAnchorPoint' => $sourceRelationAnchorPoint->value, @@ -945,8 +907,8 @@ private function whenDimensionSpacePointWasMoved(DimensionSpacePointWasMoved $ev // 1) originDimensionSpacePoint on Node $rel = $this->getDatabaseConnection()->executeQuery( 'SELECT n.relationanchorpoint, n.origindimensionspacepointhash - FROM ' . $this->tableNamePrefix . '_node n - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h + FROM ' . $this->tableNames->node() . ' n + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint AND h.contentstreamid = :contentStreamId @@ -975,7 +937,7 @@ function (NodeRecord $nodeRecord) use ($event) { // 2) hierarchy relations $this->getDatabaseConnection()->executeStatement( ' - UPDATE ' . $this->tableNamePrefix . '_hierarchyrelation h + UPDATE ' . $this->tableNames->hierarchyRelation() . ' h SET h.dimensionspacepointhash = :newDimensionSpacePointHash WHERE @@ -999,7 +961,7 @@ private function whenDimensionShineThroughWasAdded(DimensionShineThroughWasAdded // 1) hierarchy relations $this->getDatabaseConnection()->executeStatement( ' - INSERT INTO ' . $this->tableNamePrefix . '_hierarchyrelation ( + INSERT INTO ' . $this->tableNames->hierarchyRelation() . ' ( parentnodeanchor, childnodeanchor, `name`, @@ -1017,7 +979,7 @@ private function whenDimensionShineThroughWasAdded(DimensionShineThroughWasAdded :newDimensionSpacePointHash AS dimensionspacepointhash, h.contentstreamid FROM - ' . $this->tableNamePrefix . '_hierarchyrelation h + ' . $this->tableNames->hierarchyRelation() . ' h WHERE h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :sourceDimensionSpacePointHash', [ diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php index 64891e2cc33..402d767abcf 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php @@ -7,6 +7,7 @@ use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\DimensionSpacePointsRepository; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\NodeFactory; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ProjectionContentGraph; +use Neos\ContentRepository\Core\ContentGraphFinder; use Neos\ContentRepository\Core\Factory\ProjectionFactoryDependencies; use Neos\ContentRepository\Core\Infrastructure\DbalClientInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphProjection; @@ -27,39 +28,41 @@ public function __construct( ) { } - public static function graphProjectionTableNamePrefix( - ContentRepositoryId $contentRepositoryId - ): string { - return sprintf('cr_%s_p_graph', $contentRepositoryId->value); - } - public function build( ProjectionFactoryDependencies $projectionFactoryDependencies, array $options, ): ContentGraphProjection { - $tableNamePrefix = self::graphProjectionTableNamePrefix( + $tableNames = ContentGraphTableNames::create( $projectionFactoryDependencies->contentRepositoryId ); - $dimensionSpacePointsRepository = new DimensionSpacePointsRepository($this->dbalClient->getConnection(), $tableNamePrefix); + $dimensionSpacePointsRepository = new DimensionSpacePointsRepository($this->dbalClient->getConnection(), $tableNames); + + $nodeFactory = new NodeFactory( + $projectionFactoryDependencies->contentRepositoryId, + $projectionFactoryDependencies->nodeTypeManager, + $projectionFactoryDependencies->propertyConverter, + $dimensionSpacePointsRepository + ); + + $contentGraphFactory = new ContentGraphFactory( + $this->dbalClient, + $nodeFactory, + $projectionFactoryDependencies->contentRepositoryId, + $projectionFactoryDependencies->nodeTypeManager, + $tableNames + ); return new ContentGraphProjection( new DoctrineDbalContentGraphProjection( $this->dbalClient, - new NodeFactory( - $projectionFactoryDependencies->contentRepositoryId, - $projectionFactoryDependencies->nodeTypeManager, - $projectionFactoryDependencies->propertyConverter, - $dimensionSpacePointsRepository - ), - $projectionFactoryDependencies->contentRepositoryId, - $projectionFactoryDependencies->nodeTypeManager, new ProjectionContentGraph( $this->dbalClient, - $tableNamePrefix + $tableNames ), - $tableNamePrefix, - $dimensionSpacePointsRepository + $tableNames, + $dimensionSpacePointsRepository, + new ContentGraphFinder($contentGraphFactory) ) ); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php index 7722ff41f6f..c8520724f01 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php @@ -18,7 +18,7 @@ class DoctrineDbalContentGraphSchemaBuilder private const DEFAULT_TEXT_COLLATION = 'utf8mb4_unicode_520_ci'; public function __construct( - private readonly string $tableNamePrefix + private readonly ContentGraphTableNames $contentGraphTableNames ) { } @@ -34,7 +34,7 @@ public function buildSchema(AbstractSchemaManager $schemaManager): Schema private function createNodeTable(): Table { - $table = new Table($this->tableNamePrefix . '_node', [ + $table = new Table($this->contentGraphTableNames->node(), [ DbalSchemaFactory::columnForNodeAnchorPoint('relationanchorpoint')->setAutoincrement(true), DbalSchemaFactory::columnForNodeAggregateId('nodeaggregateid')->setNotnull(false), DbalSchemaFactory::columnForDimensionSpacePointHash('origindimensionspacepointhash')->setNotnull(false), @@ -55,7 +55,7 @@ private function createNodeTable(): Table private function createHierarchyRelationTable(): Table { - $table = new Table($this->tableNamePrefix . '_hierarchyrelation', [ + $table = new Table($this->contentGraphTableNames->hierarchyRelation(), [ (new Column('name', Type::getType(Types::STRING)))->setLength(255)->setNotnull(false)->setCustomSchemaOption('charset', 'ascii')->setCustomSchemaOption('collation', 'ascii_general_ci'), (new Column('position', Type::getType(Types::INTEGER)))->setNotnull(true), DbalSchemaFactory::columnForContentStreamId('contentstreamid')->setNotnull(true), @@ -75,7 +75,7 @@ private function createHierarchyRelationTable(): Table private function createDimensionSpacePointsTable(): Table { - $table = new Table($this->tableNamePrefix . '_dimensionspacepoints', [ + $table = new Table($this->contentGraphTableNames->dimensionSpacePoints(), [ DbalSchemaFactory::columnForDimensionSpacePointHash('hash')->setNotnull(true), DbalSchemaFactory::columnForDimensionSpacePoint('dimensionspacepoint')->setNotnull(true) ]); @@ -86,7 +86,7 @@ private function createDimensionSpacePointsTable(): Table private function createReferenceRelationTable(): Table { - $table = new Table($this->tableNamePrefix . '_referencerelation', [ + $table = new Table($this->contentGraphTableNames->referenceRelation(), [ (new Column('name', Type::getType(Types::STRING)))->setLength(255)->setNotnull(true)->setCustomSchemaOption('charset', 'ascii')->setCustomSchemaOption('collation', 'ascii_general_ci'), (new Column('position', Type::getType(Types::INTEGER)))->setNotnull(true), DbalSchemaFactory::columnForNodeAnchorPoint('nodeanchorpoint'), diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalProjectionIntegrityViolationDetectionRunnerFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalProjectionIntegrityViolationDetectionRunnerFactory.php index 2808fdf6301..2daf3c6a5b9 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalProjectionIntegrityViolationDetectionRunnerFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalProjectionIntegrityViolationDetectionRunnerFactory.php @@ -25,7 +25,7 @@ public function build( return new ProjectionIntegrityViolationDetectionRunner( new ProjectionIntegrityViolationDetector( $this->dbalClient, - DoctrineDbalContentGraphProjectionFactory::graphProjectionTableNamePrefix( + ContentGraphTableNames::create( $serviceFactoryDependencies->contentRepositoryId ) ) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php index fdf61f7eceb..efa4f0badd9 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php @@ -27,8 +27,6 @@ trait NodeMove { abstract protected function getProjectionContentGraph(): ProjectionContentGraph; - abstract protected function getTableNamePrefix(): string; - /** * @param NodeAggregateWasMoved $event * @throws \Throwable @@ -121,7 +119,7 @@ private function moveNodeBeforeSucceedingSibling( $ingoingHierarchyRelation->assignNewPosition( $newPosition, $this->getDatabaseConnection(), - $this->getTableNamePrefix() + $this->tableNames ); } @@ -190,7 +188,7 @@ private function moveNodeBeneathParent( $newParent->relationAnchorPoint, $newPosition, $this->getDatabaseConnection(), - $this->getTableNamePrefix() + $this->tableNames ); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php index 86fbb00b696..41f00af02fa 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php @@ -20,8 +20,6 @@ trait NodeRemoval { abstract protected function getProjectionContentGraph(): ProjectionContentGraph; - abstract protected function getTableNamePrefix(): string; - protected LoggerInterface $systemLogger; /** @@ -51,7 +49,7 @@ private function whenNodeAggregateWasRemoved(NodeAggregateWasRemoved $event): vo protected function removeRelationRecursivelyFromDatabaseIncludingNonReferencedNodes( HierarchyRelation $ingoingRelation ): void { - $ingoingRelation->removeFromDatabase($this->getDatabaseConnection(), $this->tableNamePrefix); + $ingoingRelation->removeFromDatabase($this->getDatabaseConnection(), $this->tableNames); foreach ( $this->getProjectionContentGraph()->findOutgoingHierarchyRelationsForNode( @@ -67,11 +65,11 @@ protected function removeRelationRecursivelyFromDatabaseIncludingNonReferencedNo // also remove outbound reference relations $this->getDatabaseConnection()->executeStatement( ' - DELETE n, r FROM ' . $this->getTableNamePrefix() . '_node n - LEFT JOIN ' . $this->getTableNamePrefix() . '_referencerelation r + DELETE n, r FROM ' . $this->tableNames->node() . ' n + LEFT JOIN ' . $this->tableNames->referenceRelation() . ' r ON r.nodeanchorpoint = n.relationanchorpoint LEFT JOIN - ' . $this->getTableNamePrefix() . '_hierarchyrelation h + ' . $this->tableNames->hierarchyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint WHERE n.relationanchorpoint = :anchorPointForNode diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeVariation.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeVariation.php index 27b2322512e..15f11b3ff1a 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeVariation.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeVariation.php @@ -31,8 +31,6 @@ trait NodeVariation { abstract protected function getProjectionContentGraph(): ProjectionContentGraph; - abstract protected function getTableNamePrefix(): string; - /** * @param NodeSpecializationVariantWasCreated $event * @throws \Exception @@ -68,7 +66,7 @@ private function whenNodeSpecializationVariantWasCreated(NodeSpecializationVaria $hierarchyRelation->assignNewChildNode( $specializedNode->relationAnchorPoint, $this->getDatabaseConnection(), - $this->tableNamePrefix + $this->tableNames ); unset($uncoveredDimensionSpacePoints[$hierarchyRelation->dimensionSpacePointHash]); } @@ -120,7 +118,7 @@ private function whenNodeSpecializationVariantWasCreated(NodeSpecializationVaria ), NodeTags::create(SubtreeTags::createEmpty(), $parentSubtreeTags->all()), ); - $hierarchyRelation->addToDatabase($this->getDatabaseConnection(), $this->getTableNamePrefix()); + $hierarchyRelation->addToDatabase($this->getDatabaseConnection(), $this->tableNames); } } @@ -135,7 +133,7 @@ private function whenNodeSpecializationVariantWasCreated(NodeSpecializationVaria $specializedNode->relationAnchorPoint, null, $this->getDatabaseConnection(), - $this->getTableNamePrefix() + $this->tableNames ); } @@ -189,7 +187,7 @@ public function whenNodeGeneralizationVariantWasCreated(NodeGeneralizationVarian $existingIngoingHierarchyRelation->assignNewChildNode( $generalizedNode->relationAnchorPoint, $this->getDatabaseConnection(), - $this->tableNamePrefix + $this->tableNames ); $unassignedIngoingDimensionSpacePoints = $unassignedIngoingDimensionSpacePoints->getDifference( new DimensionSpacePointSet([ @@ -209,7 +207,7 @@ public function whenNodeGeneralizationVariantWasCreated(NodeGeneralizationVarian $generalizedNode->relationAnchorPoint, null, $this->getDatabaseConnection(), - $this->getTableNamePrefix() + $this->tableNames ); } @@ -301,7 +299,7 @@ public function whenNodePeerVariantWasCreated(NodePeerVariantWasCreated $event, $existingIngoingHierarchyRelation->assignNewChildNode( $peerNode->relationAnchorPoint, $this->getDatabaseConnection(), - $this->tableNamePrefix + $this->tableNames ); $unassignedIngoingDimensionSpacePoints = $unassignedIngoingDimensionSpacePoints->getDifference( new DimensionSpacePointSet([ @@ -321,7 +319,7 @@ public function whenNodePeerVariantWasCreated(NodePeerVariantWasCreated $event, $peerNode->relationAnchorPoint, null, $this->getDatabaseConnection(), - $this->getTableNamePrefix() + $this->tableNames ); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php index 25887ab2575..2564a0a36ef 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php @@ -21,21 +21,19 @@ */ trait SubtreeTagging { - abstract protected function getTableNamePrefix(): string; - /** * @throws \Throwable */ private function whenSubtreeWasTagged(SubtreeWasTagged $event): void { $this->getDatabaseConnection()->executeStatement(' - UPDATE ' . $this->getTableNamePrefix() . '_hierarchyrelation h + UPDATE ' . $this->tableNames->hierarchyRelation() . ' h SET h.subtreetags = JSON_INSERT(h.subtreetags, :tagPath, null) WHERE h.childnodeanchor IN ( WITH RECURSIVE cte (id) AS ( SELECT ch.childnodeanchor - FROM ' . $this->getTableNamePrefix() . '_hierarchyrelation ch - INNER JOIN ' . $this->getTableNamePrefix() . '_node n ON n.relationanchorpoint = ch.parentnodeanchor + FROM ' . $this->tableNames->hierarchyRelation() . ' ch + INNER JOIN ' . $this->tableNames->node() . ' n ON n.relationanchorpoint = ch.parentnodeanchor WHERE n.nodeaggregateid = :nodeAggregateId AND ch.contentstreamid = :contentStreamId @@ -46,7 +44,7 @@ private function whenSubtreeWasTagged(SubtreeWasTagged $event): void dh.childnodeanchor FROM cte - JOIN ' . $this->getTableNamePrefix() . '_hierarchyrelation dh ON dh.parentnodeanchor = cte.id + JOIN ' . $this->tableNames->hierarchyRelation() . ' dh ON dh.parentnodeanchor = cte.id WHERE NOT JSON_CONTAINS_PATH(dh.subtreetags, \'one\', :tagPath) ) @@ -64,8 +62,8 @@ private function whenSubtreeWasTagged(SubtreeWasTagged $event): void ]); $this->getDatabaseConnection()->executeStatement(' - UPDATE ' . $this->getTableNamePrefix() . '_hierarchyrelation h - INNER JOIN ' . $this->getTableNamePrefix() . '_node n ON n.relationanchorpoint = h.childnodeanchor + UPDATE ' . $this->tableNames->hierarchyRelation() . ' h + INNER JOIN ' . $this->tableNames->node() . ' n ON n.relationanchorpoint = h.childnodeanchor SET h.subtreetags = JSON_SET(h.subtreetags, :tagPath, true) WHERE n.nodeaggregateid = :nodeAggregateId @@ -87,15 +85,15 @@ private function whenSubtreeWasTagged(SubtreeWasTagged $event): void private function whenSubtreeWasUntagged(SubtreeWasUntagged $event): void { $this->getDatabaseConnection()->executeStatement(' - UPDATE ' . $this->getTableNamePrefix() . '_hierarchyrelation h - INNER JOIN ' . $this->getTableNamePrefix() . '_hierarchyrelation ph ON ph.childnodeanchor = h.parentnodeanchor + UPDATE ' . $this->tableNames->hierarchyRelation() . ' h + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' ph ON ph.childnodeanchor = h.parentnodeanchor SET h.subtreetags = IF(( SELECT JSON_CONTAINS_PATH(ph.subtreetags, \'one\', :tagPath) FROM - ' . $this->getTableNamePrefix() . '_hierarchyrelation ph - INNER JOIN ' . $this->getTableNamePrefix() . '_hierarchyrelation ch ON ch.parentnodeanchor = ph.childnodeanchor - INNER JOIN ' . $this->getTableNamePrefix() . '_node n ON n.relationanchorpoint = ch.childnodeanchor + ' . $this->tableNames->hierarchyRelation() . ' ph + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' ch ON ch.parentnodeanchor = ph.childnodeanchor + INNER JOIN ' . $this->tableNames->node() . ' n ON n.relationanchorpoint = ch.childnodeanchor WHERE n.nodeaggregateid = :nodeAggregateId AND ph.contentstreamid = :contentStreamId @@ -105,8 +103,8 @@ private function whenSubtreeWasUntagged(SubtreeWasUntagged $event): void WHERE h.childnodeanchor IN ( WITH RECURSIVE cte (id) AS ( SELECT ch.childnodeanchor - FROM ' . $this->getTableNamePrefix() . '_hierarchyrelation ch - INNER JOIN ' . $this->getTableNamePrefix() . '_node n ON n.relationanchorpoint = ch.childnodeanchor + FROM ' . $this->tableNames->hierarchyRelation() . ' ch + INNER JOIN ' . $this->tableNames->node() . ' n ON n.relationanchorpoint = ch.childnodeanchor WHERE n.nodeaggregateid = :nodeAggregateId AND ch.contentstreamid = :contentStreamId @@ -116,7 +114,7 @@ private function whenSubtreeWasUntagged(SubtreeWasUntagged $event): void dh.childnodeanchor FROM cte - JOIN ' . $this->getTableNamePrefix() . '_hierarchyrelation dh ON dh.parentnodeanchor = cte.id + JOIN ' . $this->tableNames->hierarchyRelation() . ' dh ON dh.parentnodeanchor = cte.id WHERE JSON_EXTRACT(dh.subtreetags, :tagPath) != TRUE ) @@ -140,14 +138,14 @@ private function moveSubtreeTags( DimensionSpacePoint $coveredDimensionSpacePoint ): void { $this->getDatabaseConnection()->executeStatement(' - UPDATE ' . $this->getTableNamePrefix() . '_hierarchyrelation h, + UPDATE ' . $this->tableNames->hierarchyRelation() . ' h, ( WITH RECURSIVE cte AS ( SELECT JSON_KEYS(th.subtreetags) subtreeTagsToInherit, th.childnodeanchor FROM - ' . $this->getTableNamePrefix() . '_hierarchyrelation th - INNER JOIN ' . $this->getTableNamePrefix() . '_node tn ON tn.relationanchorpoint = th.childnodeanchor + ' . $this->tableNames->hierarchyRelation() . ' th + INNER JOIN ' . $this->tableNames->node() . ' tn ON tn.relationanchorpoint = th.childnodeanchor WHERE tn.nodeaggregateid = :newParentNodeAggregateId AND th.contentstreamid = :contentStreamId @@ -164,7 +162,7 @@ private function moveSubtreeTags( dh.childnodeanchor FROM cte - JOIN ' . $this->getTableNamePrefix() . '_hierarchyrelation dh + JOIN ' . $this->tableNames->hierarchyRelation() . ' dh ON dh.parentnodeanchor = cte.childnodeanchor AND dh.contentstreamid = :contentStreamId @@ -198,7 +196,7 @@ private function subtreeTagsForHierarchyRelation(ContentStreamId $contentStreamI return NodeTags::createEmpty(); } $subtreeTagsJson = $this->getDatabaseConnection()->fetchOne(' - SELECT h.subtreetags FROM ' . $this->getTableNamePrefix() . '_hierarchyrelation h + SELECT h.subtreetags FROM ' . $this->tableNames->hierarchyRelation() . ' h WHERE h.childnodeanchor = :parentNodeAnchorPoint AND h.contentstreamid = :contentStreamId diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php index 8bc02f1e7d3..f829f87a936 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php @@ -15,6 +15,7 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection; use Doctrine\DBAL\Connection; +use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphTableNames; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\DimensionSpacePointsRepository; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeTags; @@ -43,15 +44,15 @@ public function __construct( /** * @param Connection $databaseConnection */ - public function addToDatabase(Connection $databaseConnection, string $tableNamePrefix): void + public function addToDatabase(Connection $databaseConnection, ContentGraphTableNames $tableNames): void { $databaseConnection->transactional(function ($databaseConnection) use ( - $tableNamePrefix + $tableNames ) { - $dimensionSpacePoints = new DimensionSpacePointsRepository($databaseConnection, $tableNamePrefix); + $dimensionSpacePoints = new DimensionSpacePointsRepository($databaseConnection, $tableNames); $dimensionSpacePoints->insertDimensionSpacePoint($this->dimensionSpacePoint); - $databaseConnection->insert($tableNamePrefix . '_hierarchyrelation', [ + $databaseConnection->insert($tableNames->hierarchyRelation(), [ 'parentnodeanchor' => $this->parentNodeAnchor->value, 'childnodeanchor' => $this->childNodeAnchor->value, 'name' => $this->name?->value, @@ -66,9 +67,9 @@ public function addToDatabase(Connection $databaseConnection, string $tableNameP /** * @param Connection $databaseConnection */ - public function removeFromDatabase(Connection $databaseConnection, string $tableNamePrefix): void + public function removeFromDatabase(Connection $databaseConnection, ContentGraphTableNames $tableNames): void { - $databaseConnection->delete($tableNamePrefix . '_hierarchyrelation', $this->getDatabaseId()); + $databaseConnection->delete($tableNames->hierarchyRelation(), $this->getDatabaseId()); } /** @@ -78,10 +79,10 @@ public function removeFromDatabase(Connection $databaseConnection, string $table public function assignNewChildNode( NodeRelationAnchorPoint $childAnchorPoint, Connection $databaseConnection, - string $tableNamePrefix + ContentGraphTableNames $tableNames ): void { $databaseConnection->update( - $tableNamePrefix . '_hierarchyrelation', + $tableNames->hierarchyRelation(), [ 'childnodeanchor' => $childAnchorPoint->value ], @@ -96,7 +97,7 @@ public function assignNewParentNode( NodeRelationAnchorPoint $parentAnchorPoint, ?int $position, Connection $databaseConnection, - string $tableNamePrefix + ContentGraphTableNames $tableNames ): void { $data = [ 'parentnodeanchor' => $parentAnchorPoint->value @@ -105,16 +106,16 @@ public function assignNewParentNode( $data['position'] = $position; } $databaseConnection->update( - $tableNamePrefix . '_hierarchyrelation', + $tableNames->hierarchyRelation(), $data, $this->getDatabaseId() ); } - public function assignNewPosition(int $position, Connection $databaseConnection, string $tableNamePrefix): void + public function assignNewPosition(int $position, Connection $databaseConnection, ContentGraphTableNames $tableNames): void { $databaseConnection->update( - $tableNamePrefix . '_hierarchyrelation', + $tableNames->hierarchyRelation(), [ 'position' => $position ], diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php index f9bfb9f6d7d..6ce216ba365 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php @@ -16,6 +16,7 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\Types\Types; +use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphTableNames; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\DimensionSpacePointsRepository; use Neos\ContentRepository\Core\Feature\NodeModification\Dto\SerializedPropertyValues; use Neos\ContentRepository\Core\NodeType\NodeTypeName; @@ -47,13 +48,12 @@ public function __construct( } /** - * @param Connection $databaseConnection * @throws \Doctrine\DBAL\DBALException */ - public function updateToDatabase(Connection $databaseConnection, string $tableNamePrefix): void + public function updateToDatabase(Connection $databaseConnection, ContentGraphTableNames $tableNames): void { $databaseConnection->update( - $tableNamePrefix . '_node', + $tableNames->node(), [ 'nodeaggregateid' => $this->nodeAggregateId->value, 'origindimensionspacepointhash' => $this->originDimensionSpacePointHash, @@ -73,18 +73,6 @@ public function updateToDatabase(Connection $databaseConnection, string $tableNa ); } - /** - * @param Connection $databaseConnection - * @throws \Doctrine\DBAL\DBALException - * @throws \Doctrine\DBAL\Exception\InvalidArgumentException - */ - public function removeFromDatabase(Connection $databaseConnection, string $tableNamePrefix): void - { - $databaseConnection->delete($tableNamePrefix . '_node', [ - 'relationanchorpoint' => $this->relationAnchorPoint->value - ]); - } - /** * @param array $databaseRow * @throws \Exception @@ -112,22 +100,12 @@ public static function fromDatabaseRow(array $databaseRow): self /** * Insert a node record with the given data and return it. * - * @param Connection $databaseConnection - * @param string $tableNamePrefix - * @param NodeAggregateId $nodeAggregateId * @param array $originDimensionSpacePoint - * @param string $originDimensionSpacePointHash - * @param SerializedPropertyValues $properties - * @param NodeTypeName $nodeTypeName - * @param NodeAggregateClassification $classification - * @param NodeName|null $nodeName - * @param Timestamps $timestamps - * @return self * @throws \Doctrine\DBAL\Exception */ public static function createNewInDatabase( Connection $databaseConnection, - string $tableNamePrefix, + ContentGraphTableNames $tableNames, NodeAggregateId $nodeAggregateId, array $originDimensionSpacePoint, string $originDimensionSpacePointHash, @@ -139,7 +117,7 @@ public static function createNewInDatabase( Timestamps $timestamps, ): self { $relationAnchorPoint = $databaseConnection->transactional(function ($databaseConnection) use ( - $tableNamePrefix, + $tableNames, $nodeAggregateId, $originDimensionSpacePoint, $originDimensionSpacePointHash, @@ -148,10 +126,10 @@ public static function createNewInDatabase( $classification, $timestamps ) { - $dimensionSpacePoints = new DimensionSpacePointsRepository($databaseConnection, $tableNamePrefix); + $dimensionSpacePoints = new DimensionSpacePointsRepository($databaseConnection, $tableNames); $dimensionSpacePoints->insertDimensionSpacePointByHashAndCoordinates($originDimensionSpacePointHash, $originDimensionSpacePoint); - $databaseConnection->insert($tableNamePrefix . '_node', [ + $databaseConnection->insert($tableNames->node(), [ 'nodeaggregateid' => $nodeAggregateId->value, 'origindimensionspacepointhash' => $originDimensionSpacePointHash, 'properties' => json_encode($properties), @@ -187,20 +165,16 @@ public static function createNewInDatabase( /** * Creates a copy of this NodeRecord with a new anchor point. * - * @param Connection $databaseConnection - * @param string $tableNamePrefix - * @param NodeRecord $copyFrom - * @return self * @throws \Doctrine\DBAL\Exception */ public static function createCopyFromNodeRecord( Connection $databaseConnection, - string $tableNamePrefix, + ContentGraphTableNames $tableNames, NodeRecord $copyFrom ): self { return self::createNewInDatabase( $databaseConnection, - $tableNamePrefix, + $tableNames, $copyFrom->nodeAggregateId, $copyFrom->originDimensionSpacePoint, $copyFrom->originDimensionSpacePointHash, diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ProjectionIntegrityViolationDetector.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ProjectionIntegrityViolationDetector.php index adb394fb6cd..2e99276d62b 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ProjectionIntegrityViolationDetector.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ProjectionIntegrityViolationDetector.php @@ -14,6 +14,7 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection; +use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphTableNames; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\Infrastructure\DbalClientInterface; @@ -33,7 +34,7 @@ final class ProjectionIntegrityViolationDetector implements ProjectionIntegrityV { public function __construct( private readonly DbalClientInterface $client, - private readonly string $tableNamePrefix + private readonly ContentGraphTableNames $tableNames ) { } @@ -45,9 +46,9 @@ public function hierarchyIntegrityIsProvided(): Result $result = new Result(); $disconnectedHierarchyRelationRecords = $this->client->getConnection()->executeQuery( - 'SELECT h.* FROM ' . $this->tableNamePrefix . '_hierarchyrelation h - LEFT JOIN ' . $this->tableNamePrefix . '_node p ON h.parentnodeanchor = p.relationanchorpoint - LEFT JOIN ' . $this->tableNamePrefix . '_node c ON h.childnodeanchor = c.relationanchorpoint + 'SELECT h.* FROM ' . $this->tableNames->hierarchyRelation() . ' h + LEFT JOIN ' . $this->tableNames->node() . ' p ON h.parentnodeanchor = p.relationanchorpoint + LEFT JOIN ' . $this->tableNames->node() . ' c ON h.childnodeanchor = c.relationanchorpoint WHERE h.parentnodeanchor != :rootNodeAnchor AND ( p.relationanchorpoint IS NULL @@ -67,7 +68,7 @@ public function hierarchyIntegrityIsProvided(): Result } $invalidlyHashedHierarchyRelationRecords = $this->client->getConnection()->executeQuery( - 'SELECT * FROM ' . $this->tableNamePrefix . '_hierarchyrelation h LEFT JOIN ' . $this->tableNamePrefix . '_dimensionspacepoints dsp ON dsp.hash = h.dimensionspacepointhash + 'SELECT * FROM ' . $this->tableNames->hierarchyRelation() . ' h LEFT JOIN ' . $this->tableNames->dimensionSpacePoints() . ' dsp ON dsp.hash = h.dimensionspacepointhash HAVING dsp.dimensionspacepoint IS NULL' )->fetchAllAssociative(); @@ -80,9 +81,9 @@ public function hierarchyIntegrityIsProvided(): Result } $hierarchyRelationRecordsAppearingMultipleTimes = $this->client->getConnection()->executeQuery( - 'SELECT COUNT(*) as uniquenessCounter, h.* FROM ' . $this->tableNamePrefix . '_hierarchyrelation h - LEFT JOIN ' . $this->tableNamePrefix . '_node p ON h.parentnodeanchor = p.relationanchorpoint - LEFT JOIN ' . $this->tableNamePrefix . '_node c ON h.childnodeanchor = c.relationanchorpoint + 'SELECT COUNT(*) as uniquenessCounter, h.* FROM ' . $this->tableNames->hierarchyRelation() . ' h + LEFT JOIN ' . $this->tableNames->node() . ' p ON h.parentnodeanchor = p.relationanchorpoint + LEFT JOIN ' . $this->tableNames->node() . ' c ON h.childnodeanchor = c.relationanchorpoint WHERE h.parentnodeanchor != :rootNodeAnchor GROUP BY p.nodeaggregateid, c.nodeaggregateid, h.dimensionspacepointhash, h.contentstreamid @@ -113,7 +114,7 @@ public function siblingsAreDistinctlySorted(): Result $ambiguouslySortedHierarchyRelationRecords = $this->client->getConnection()->executeQuery( 'SELECT *, COUNT(position) - FROM ' . $this->tableNamePrefix . '_hierarchyrelation + FROM ' . $this->tableNames->hierarchyRelation() . ' GROUP BY position, parentnodeanchor, contentstreamid, dimensionspacepointhash HAVING COUNT(position) > 1' ); @@ -127,7 +128,7 @@ public function siblingsAreDistinctlySorted(): Result foreach ($ambiguouslySortedHierarchyRelationRecords as $hierarchyRelationRecord) { $ambiguouslySortedNodeRecords = $this->client->getConnection()->executeQuery( 'SELECT nodeaggregateid - FROM ' . $this->tableNamePrefix . '_node + FROM ' . $this->tableNames->node() . ' WHERE relationanchorpoint = :relationAnchorPoint', [ 'relationAnchorPoint' => $hierarchyRelationRecord['childnodeanchor'] @@ -155,8 +156,8 @@ public function tetheredNodesAreNamed(): Result $result = new Result(); $unnamedTetheredNodeRecords = $this->client->getConnection()->executeQuery( 'SELECT n.nodeaggregateid, h.contentstreamid - FROM ' . $this->tableNamePrefix . '_node n - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h + FROM ' . $this->tableNames->node() . ' n + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint WHERE n.classification = :tethered AND h.name IS NULL @@ -192,8 +193,8 @@ public function subtreeTagsAreInherited(): Result 'SELECT ph.name FROM - ' . $this->tableNamePrefix . '_hierarchyrelation h - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation ph + ' . $this->tableNames->hierarchyRelation() . ' h + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' ph ON ph.childnodeanchor = h.parentnodeanchor AND ph.contentstreamid = h.contentstreamid AND ph.dimensionspacepointhash = h.dimensionspacepointhash @@ -222,9 +223,9 @@ public function referenceIntegrityIsProvided(): Result $result = new Result(); $referenceRelationRecordsDetachedFromSource = $this->client->getConnection()->executeQuery( - 'SELECT * FROM ' . $this->tableNamePrefix . '_referencerelation + 'SELECT * FROM ' . $this->tableNames->referenceRelation() . ' WHERE nodeanchorpoint NOT IN ( - SELECT relationanchorpoint FROM ' . $this->tableNamePrefix . '_node + SELECT relationanchorpoint FROM ' . $this->tableNames->node() . ' )' )->fetchAllAssociative(); @@ -240,13 +241,13 @@ public function referenceIntegrityIsProvided(): Result 'SELECT sh.contentstreamid AS contentstreamId, s.nodeaggregateid AS sourceNodeAggregateId, r.destinationnodeaggregateid AS destinationNodeAggregateId - FROM ' . $this->tableNamePrefix . '_referencerelation r - INNER JOIN ' . $this->tableNamePrefix . '_node s ON r.nodeanchorpoint = s.relationanchorpoint - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation sh + FROM ' . $this->tableNames->referenceRelation() . ' r + INNER JOIN ' . $this->tableNames->node() . ' s ON r.nodeanchorpoint = s.relationanchorpoint + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' sh ON r.nodeanchorpoint = sh.childnodeanchor LEFT JOIN ( - ' . $this->tableNamePrefix . '_node d - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation dh + ' . $this->tableNames->node() . ' d + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' dh ON d.relationanchorpoint = dh.childnodeanchor ) ON r.destinationnodeaggregateid = d.nodeaggregateid AND sh.contentstreamid = dh.contentstreamid @@ -291,7 +292,7 @@ public function allNodesAreConnectedToARootNodePerSubgraph(): Result SELECT h.childnodeanchor FROM - ' . $this->tableNamePrefix . '_hierarchyrelation h + ' . $this->tableNames->hierarchyRelation() . ' h WHERE h.parentnodeanchor = :rootAnchorPoint AND h.contentstreamid = :contentStreamId @@ -304,14 +305,14 @@ public function allNodesAreConnectedToARootNodePerSubgraph(): Result h.childnodeanchor FROM subgraph p - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' h on h.parentnodeanchor = p.childnodeanchor WHERE h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :dimensionSpacePointHash ) -SELECT nodeaggregateid FROM ' . $this->tableNamePrefix . '_node n -INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h +SELECT nodeaggregateid FROM ' . $this->tableNames->node() . ' n +INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint WHERE h.contentstreamid = :contentStreamId @@ -364,8 +365,8 @@ public function nodeAggregateIdsAreUniquePerSubgraph(): Result foreach ($this->findProjectedDimensionSpacePoints() as $dimensionSpacePoint) { $ambiguousNodeAggregateRecords = $this->client->getConnection()->executeQuery( 'SELECT n.nodeaggregateid, COUNT(n.relationanchorpoint) - FROM ' . $this->tableNamePrefix . '_node n - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h + FROM ' . $this->tableNames->node() . ' n + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint WHERE h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :dimensionSpacePointHash @@ -401,8 +402,8 @@ public function allNodesHaveAtMostOneParentPerSubgraph(): Result foreach ($this->findProjectedDimensionSpacePoints() as $dimensionSpacePoint) { $nodeRecordsWithMultipleParents = $this->client->getConnection()->executeQuery( 'SELECT c.nodeaggregateid - FROM ' . $this->tableNamePrefix . '_node c - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h + FROM ' . $this->tableNames->node() . ' c + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' h ON h.childnodeanchor = c.relationanchorpoint WHERE h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :dimensionSpacePointHash @@ -441,8 +442,8 @@ public function nodeAggregatesAreConsistentlyTypedPerContentStream(): Result ) as $nodeAggregateId ) { $nodeAggregateRecords = $this->client->getConnection()->executeQuery( - 'SELECT DISTINCT n.nodetypename FROM ' . $this->tableNamePrefix . '_node n - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h + 'SELECT DISTINCT n.nodetypename FROM ' . $this->tableNames->node() . ' n + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint WHERE h.contentstreamid = :contentStreamId AND n.nodeaggregateid = :nodeAggregateId', @@ -484,8 +485,8 @@ public function nodeAggregatesAreConsistentlyClassifiedPerContentStream(): Resul ) as $nodeAggregateId ) { $nodeAggregateRecords = $this->client->getConnection()->executeQuery( - 'SELECT DISTINCT n.classification FROM ' . $this->tableNamePrefix . '_node n - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h + 'SELECT DISTINCT n.classification FROM ' . $this->tableNames->node() . ' n + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint WHERE h.contentstreamid = :contentStreamId AND n.nodeaggregateid = :nodeAggregateId', @@ -523,10 +524,10 @@ public function childNodeCoverageIsASubsetOfParentNodeCoverage(): Result foreach ($this->findProjectedContentStreamIds() as $contentStreamId) { $excessivelyCoveringNodeRecords = $this->client->getConnection()->executeQuery( 'SELECT n.nodeaggregateid, c.dimensionspacepointhash - FROM ' . $this->tableNamePrefix . '_hierarchyrelation c - INNER JOIN ' . $this->tableNamePrefix . '_node n + FROM ' . $this->tableNames->hierarchyRelation() . ' c + INNER JOIN ' . $this->tableNames->node() . ' n ON c.childnodeanchor = n.relationanchorpoint - LEFT JOIN ' . $this->tableNamePrefix . '_hierarchyrelation p + LEFT JOIN ' . $this->tableNames->hierarchyRelation() . ' p ON c.parentnodeanchor = p.childnodeanchor WHERE c.contentstreamid = :contentStreamId AND p.contentstreamid = :contentStreamId @@ -560,16 +561,16 @@ public function allNodesCoverTheirOrigin(): Result foreach ($this->findProjectedContentStreamIds() as $contentStreamId) { $nodeRecordsWithMissingOriginCoverage = $this->client->getConnection()->executeQuery( 'SELECT nodeaggregateid, origindimensionspacepointhash - FROM ' . $this->tableNamePrefix . '_node n - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h + FROM ' . $this->tableNames->node() . ' n + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint WHERE h.contentstreamid = :contentStreamId AND nodeaggregateid NOT IN ( -- this query finds all nodes whose origin *IS COVERED* by an incoming hierarchy relation. SELECT n.nodeaggregateid - FROM ' . $this->tableNamePrefix . '_node n - LEFT JOIN ' . $this->tableNamePrefix . '_hierarchyrelation p + FROM ' . $this->tableNames->node() . ' n + LEFT JOIN ' . $this->tableNames->hierarchyRelation() . ' p ON p.childnodeanchor = n.relationanchorpoint AND p.dimensionspacepointhash = n.origindimensionspacepointhash WHERE p.contentstreamid = :contentStreamId @@ -605,7 +606,7 @@ protected function findProjectedContentStreamIds(): iterable $connection = $this->client->getConnection(); $rows = $connection->executeQuery( - 'SELECT DISTINCT contentstreamid FROM ' . $this->tableNamePrefix . '_hierarchyrelation' + 'SELECT DISTINCT contentstreamid FROM ' . $this->tableNames->hierarchyRelation() )->fetchAllAssociative(); return array_map(function (array $row) { @@ -621,7 +622,7 @@ protected function findProjectedContentStreamIds(): iterable protected function findProjectedDimensionSpacePoints(): DimensionSpacePointSet { $records = $this->client->getConnection()->executeQuery( - 'SELECT dimensionspacepoint FROM ' . $this->tableNamePrefix . '_dimensionspacepoints' + 'SELECT dimensionspacepoint FROM ' . $this->tableNames->dimensionSpacePoints() )->fetchAllAssociative(); $records = array_map(function (array $record) { @@ -639,7 +640,7 @@ protected function findProjectedNodeAggregateIdsInContentStream( ContentStreamId $contentStreamId ): array { $records = $this->client->getConnection()->executeQuery( - 'SELECT DISTINCT nodeaggregateid FROM ' . $this->tableNamePrefix . '_node' + 'SELECT DISTINCT nodeaggregateid FROM ' . $this->tableNames->node() )->fetchAllAssociative(); return array_map(function (array $record) { diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index 4781ae1a82e..a11b50f9b29 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -19,8 +19,8 @@ use Doctrine\DBAL\Exception as DBALException; use Doctrine\DBAL\Query\QueryBuilder; use Doctrine\DBAL\Result; -use Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphProjection; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRelationAnchorPoint; +use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphTableNames; +use Neos\ContentGraph\DoctrineDbalAdapter\NodeQueryBuilder; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; @@ -40,6 +40,7 @@ use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** * The Doctrine DBAL adapter content graph @@ -64,6 +65,8 @@ */ final class ContentGraph implements ContentGraphInterface { + private readonly NodeQueryBuilder $nodeQueryBuilder; + /** * @var array */ @@ -74,27 +77,30 @@ public function __construct( private readonly NodeFactory $nodeFactory, private readonly ContentRepositoryId $contentRepositoryId, private readonly NodeTypeManager $nodeTypeManager, - private readonly string $tableNamePrefix + private readonly ContentGraphTableNames $tableNames, + public readonly WorkspaceName $workspaceName, + public readonly ContentStreamId $contentStreamId ) { + $this->nodeQueryBuilder = new NodeQueryBuilder($this->client->getConnection(), $this->tableNames); } - final public function getSubgraph( - ContentStreamId $contentStreamId, + public function getSubgraph( DimensionSpacePoint $dimensionSpacePoint, VisibilityConstraints $visibilityConstraints ): ContentSubgraphInterface { - $index = $contentStreamId->value . '-' . $dimensionSpacePoint->hash . '-' . $visibilityConstraints->getHash(); + $index = $dimensionSpacePoint->hash . '-' . $visibilityConstraints->getHash(); if (!isset($this->subgraphs[$index])) { $this->subgraphs[$index] = new ContentSubgraphWithRuntimeCaches( new ContentSubgraph( $this->contentRepositoryId, - $contentStreamId, + $this->workspaceName, + $this->contentStreamId, $dimensionSpacePoint, $visibilityConstraints, $this->client, $this->nodeFactory, $this->nodeTypeManager, - $this->tableNamePrefix + $this->tableNames ) ); } @@ -106,11 +112,9 @@ final public function getSubgraph( * @throws RootNodeAggregateDoesNotExist */ public function findRootNodeAggregateByType( - ContentStreamId $contentStreamId, NodeTypeName $nodeTypeName ): NodeAggregate { $rootNodeAggregates = $this->findRootNodeAggregates( - $contentStreamId, FindRootNodeAggregatesFilter::create(nodeTypeName: $nodeTypeName) ); @@ -127,7 +131,6 @@ public function findRootNodeAggregateByType( } $rootNodeAggregate = $rootNodeAggregates->first(); - if ($rootNodeAggregate === null) { throw RootNodeAggregateDoesNotExist::butWasExpectedTo($nodeTypeName); } @@ -136,66 +139,39 @@ public function findRootNodeAggregateByType( } public function findRootNodeAggregates( - ContentStreamId $contentStreamId, FindRootNodeAggregatesFilter $filter, ): NodeAggregates { - $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.contentstreamid, h.name, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') - ->from($this->tableNamePrefix . '_node', 'n') - ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->innerJoin('h', $this->tableNamePrefix . '_dimensionspacepoints', 'dsp', 'dsp.hash = h.dimensionspacepointhash') - ->where('h.contentstreamid = :contentStreamId') - ->andWhere('h.parentnodeanchor = :rootEdgeParentAnchorId') - ->setParameters([ - 'contentStreamId' => $contentStreamId->value, - 'rootEdgeParentAnchorId' => NodeRelationAnchorPoint::forRootEdge()->value, - ]); - - if ($filter->nodeTypeName !== null) { - $queryBuilder - ->andWhere('n.nodetypename = :nodeTypeName') - ->setParameter('nodeTypeName', $filter->nodeTypeName->value); - } - return NodeAggregates::fromArray(iterator_to_array($this->mapQueryBuilderToNodeAggregates($queryBuilder, $contentStreamId))); + $rootNodeAggregateQueryBuilder = $this->nodeQueryBuilder->buildFindRootNodeAggregatesQuery($this->contentStreamId, $filter); + return NodeAggregates::fromArray(iterator_to_array($this->mapQueryBuilderToNodeAggregates($rootNodeAggregateQueryBuilder))); } public function findNodeAggregatesByType( - ContentStreamId $contentStreamId, NodeTypeName $nodeTypeName ): iterable { - $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.contentstreamid, h.name, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') - ->from($this->tableNamePrefix . '_node', 'n') - ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->innerJoin('h', $this->tableNamePrefix . '_dimensionspacepoints', 'dsp', 'dsp.hash = h.dimensionspacepointhash') - ->where('h.contentstreamid = :contentStreamId') + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeAggregateQuery(); + $queryBuilder ->andWhere('n.nodetypename = :nodeTypeName') ->setParameters([ - 'contentStreamId' => $contentStreamId->value, + 'contentStreamId' => $this->contentStreamId->value, 'nodeTypeName' => $nodeTypeName->value, ]); - return $this->mapQueryBuilderToNodeAggregates($queryBuilder, $contentStreamId); + return $this->mapQueryBuilderToNodeAggregates($queryBuilder); } public function findNodeAggregateById( - ContentStreamId $contentStreamId, NodeAggregateId $nodeAggregateId ): ?NodeAggregate { - $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.name, h.contentstreamid, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') - ->from($this->tableNamePrefix . '_hierarchyrelation', 'h') - ->innerJoin('h', $this->tableNamePrefix . '_node', 'n', 'n.relationanchorpoint = h.childnodeanchor') - ->innerJoin('h', $this->tableNamePrefix . '_dimensionspacepoints', 'dsp', 'dsp.hash = h.dimensionspacepointhash') - ->where('n.nodeaggregateid = :nodeAggregateId') - ->andWhere('h.contentstreamid = :contentStreamId') + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeAggregateQuery() + ->andWhere('n.nodeaggregateid = :nodeAggregateId') + ->orderBy('n.relationanchorpoint', 'DESC') ->setParameters([ 'nodeAggregateId' => $nodeAggregateId->value, - 'contentStreamId' => $contentStreamId->value + 'contentStreamId' => $this->contentStreamId->value ]); return $this->nodeFactory->mapNodeRowsToNodeAggregate( $this->fetchRows($queryBuilder), - $contentStreamId, + $this->contentStreamId, VisibilityConstraints::withoutRestrictions() ); } @@ -204,37 +180,38 @@ public function findNodeAggregateById( * @return iterable */ public function findParentNodeAggregates( - ContentStreamId $contentStreamId, NodeAggregateId $childNodeAggregateId ): iterable { - $queryBuilder = $this->createQueryBuilder() - ->select('pn.*, ph.name, ph.contentstreamid, ph.subtreetags, pdsp.dimensionspacepoint AS covereddimensionspacepoint') - ->from($this->tableNamePrefix . '_node', 'pn') - ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'ph', 'ph.childnodeanchor = pn.relationanchorpoint') - ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('ch', $this->tableNamePrefix . '_node', 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') - ->innerJoin('ph', $this->tableNamePrefix . '_dimensionspacepoints', 'pdsp', 'pdsp.hash = ph.dimensionspacepointhash') - ->where('cn.nodeaggregateid = :nodeAggregateId') - ->andWhere('ph.contentstreamid = :contentStreamId') + $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') ->andWhere('ch.contentstreamid = :contentStreamId') + ->andWhere('cn.nodeaggregateid = :nodeAggregateId') ->setParameters([ 'nodeAggregateId' => $childNodeAggregateId->value, - 'contentStreamId' => $contentStreamId->value + 'contentStreamId' => $this->contentStreamId->value ]); - return $this->mapQueryBuilderToNodeAggregates($queryBuilder, $contentStreamId); + return $this->mapQueryBuilderToNodeAggregates($queryBuilder); } - public function findParentNodeAggregateByChildOriginDimensionSpacePoint( - ContentStreamId $contentStreamId, - NodeAggregateId $childNodeAggregateId, - OriginDimensionSpacePoint $childOriginDimensionSpacePoint - ): ?NodeAggregate { + /** + * @return iterable + */ + public function findChildNodeAggregates( + NodeAggregateId $parentNodeAggregateId + ): iterable { + $queryBuilder = $this->nodeQueryBuilder->buildChildNodeAggregateQuery($parentNodeAggregateId, $this->contentStreamId); + return $this->mapQueryBuilderToNodeAggregates($queryBuilder); + } + + public function findParentNodeAggregateByChildOriginDimensionSpacePoint(NodeAggregateId $childNodeAggregateId, OriginDimensionSpacePoint $childOriginDimensionSpacePoint): ?NodeAggregate + { $subQueryBuilder = $this->createQueryBuilder() ->select('pn.nodeaggregateid') - ->from($this->tableNamePrefix . '_node', 'pn') - ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('ch', $this->tableNamePrefix . '_node', 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') + ->from($this->nodeQueryBuilder->tableNames->node(), 'pn') + ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('ch', $this->nodeQueryBuilder->tableNames->node(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') ->where('ch.contentstreamid = :contentStreamId') ->andWhere('ch.dimensionspacepointhash = :childOriginDimensionSpacePointHash') ->andWhere('cn.nodeaggregateid = :childNodeAggregateId') @@ -242,75 +219,41 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint( $queryBuilder = $this->createQueryBuilder() ->select('n.*, h.name, h.contentstreamid, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') - ->from($this->tableNamePrefix . '_node', 'n') - ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->innerJoin('h', $this->tableNamePrefix . '_dimensionspacepoints', 'dsp', 'dsp.hash = h.dimensionspacepointhash') + ->from($this->nodeQueryBuilder->tableNames->node(), 'n') + ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('h', $this->nodeQueryBuilder->tableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') ->where('n.nodeaggregateid = (' . $subQueryBuilder->getSQL() . ')') ->andWhere('h.contentstreamid = :contentStreamId') ->setParameters([ - 'contentStreamId' => $contentStreamId->value, + 'contentStreamId' => $this->contentStreamId->value, 'childNodeAggregateId' => $childNodeAggregateId->value, 'childOriginDimensionSpacePointHash' => $childOriginDimensionSpacePoint->hash, ]); return $this->nodeFactory->mapNodeRowsToNodeAggregate( $this->fetchRows($queryBuilder), - $contentStreamId, + $this->contentStreamId, VisibilityConstraints::withoutRestrictions() ); } - /** - * @return iterable - */ - public function findChildNodeAggregates( - ContentStreamId $contentStreamId, - NodeAggregateId $parentNodeAggregateId - ): iterable { - $queryBuilder = $this->buildChildNodeAggregateQuery($parentNodeAggregateId, $contentStreamId); - return $this->mapQueryBuilderToNodeAggregates($queryBuilder, $contentStreamId); - } - - /** - * @return iterable - */ - public function findChildNodeAggregatesByName( - ContentStreamId $contentStreamId, - NodeAggregateId $parentNodeAggregateId, - NodeName $name - ): iterable { - $queryBuilder = $this->buildChildNodeAggregateQuery($parentNodeAggregateId, $contentStreamId) - ->andWhere('ch.name = :relationName') - ->setParameter('relationName', $name->value); - return $this->mapQueryBuilderToNodeAggregates($queryBuilder, $contentStreamId); - } - - /** - * @return iterable - */ - public function findTetheredChildNodeAggregates( - ContentStreamId $contentStreamId, - NodeAggregateId $parentNodeAggregateId - ): iterable { - $queryBuilder = $this->buildChildNodeAggregateQuery($parentNodeAggregateId, $contentStreamId) + public function findTetheredChildNodeAggregates(NodeAggregateId $parentNodeAggregateId): iterable + { + $queryBuilder = $this->nodeQueryBuilder->buildChildNodeAggregateQuery($parentNodeAggregateId, $this->contentStreamId) ->andWhere('cn.classification = :tetheredClassification') ->setParameter('tetheredClassification', NodeAggregateClassification::CLASSIFICATION_TETHERED->value); - return $this->mapQueryBuilderToNodeAggregates($queryBuilder, $contentStreamId); + + return $this->mapQueryBuilderToNodeAggregates($queryBuilder); } - public function getDimensionSpacePointsOccupiedByChildNodeName( - ContentStreamId $contentStreamId, - NodeName $nodeName, - NodeAggregateId $parentNodeAggregateId, - OriginDimensionSpacePoint $parentNodeOriginDimensionSpacePoint, - DimensionSpacePointSet $dimensionSpacePointsToCheck - ): DimensionSpacePointSet { + public function getDimensionSpacePointsOccupiedByChildNodeName(NodeName $nodeName, NodeAggregateId $parentNodeAggregateId, OriginDimensionSpacePoint $parentNodeOriginDimensionSpacePoint, DimensionSpacePointSet $dimensionSpacePointsToCheck): DimensionSpacePointSet + { $queryBuilder = $this->createQueryBuilder() ->select('dsp.dimensionspacepoint, h.dimensionspacepointhash') - ->from($this->tableNamePrefix . '_hierarchyrelation', 'h') - ->innerJoin('h', $this->tableNamePrefix . '_node', 'n', 'n.relationanchorpoint = h.parentnodeanchor') - ->innerJoin('h', $this->tableNamePrefix . '_dimensionspacepoints', 'dsp', 'dsp.hash = h.dimensionspacepointhash') - ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'ph', 'ph.childnodeanchor = n.relationanchorpoint') + ->from($this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h') + ->innerJoin('h', $this->nodeQueryBuilder->tableNames->node(), 'n', 'n.relationanchorpoint = h.parentnodeanchor') + ->innerJoin('h', $this->nodeQueryBuilder->tableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') + ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ph', 'ph.childnodeanchor = n.relationanchorpoint') ->where('n.nodeaggregateid = :parentNodeAggregateId') ->andWhere('n.origindimensionspacepointhash = :parentNodeOriginDimensionSpacePointHash') ->andWhere('ph.contentstreamid = :contentStreamId') @@ -320,7 +263,7 @@ public function getDimensionSpacePointsOccupiedByChildNodeName( ->setParameters([ 'parentNodeAggregateId' => $parentNodeAggregateId->value, 'parentNodeOriginDimensionSpacePointHash' => $parentNodeOriginDimensionSpacePoint->hash, - 'contentStreamId' => $contentStreamId->value, + 'contentStreamId' => $this->contentStreamId->value, 'dimensionSpacePointHashes' => $dimensionSpacePointsToCheck->getPointHashes(), 'nodeName' => $nodeName->value ], [ @@ -330,14 +273,29 @@ public function getDimensionSpacePointsOccupiedByChildNodeName( foreach ($this->fetchRows($queryBuilder) as $hierarchyRelationData) { $dimensionSpacePoints[$hierarchyRelationData['dimensionspacepointhash']] = DimensionSpacePoint::fromJsonString($hierarchyRelationData['dimensionspacepoint']); } + return new DimensionSpacePointSet($dimensionSpacePoints); } + /** + * @return iterable + */ + public function findChildNodeAggregatesByName( + NodeAggregateId $parentNodeAggregateId, + NodeName $name + ): iterable { + $queryBuilder = $this->nodeQueryBuilder->buildChildNodeAggregateQuery($parentNodeAggregateId, $this->contentStreamId) + ->andWhere('ch.name = :relationName') + ->setParameter('relationName', $name->value); + + return $this->mapQueryBuilderToNodeAggregates($queryBuilder); + } + public function countNodes(): int { $queryBuilder = $this->createQueryBuilder() ->select('COUNT(*)') - ->from($this->tableNamePrefix . '_node'); + ->from($this->nodeQueryBuilder->tableNames->node()); $result = $queryBuilder->execute(); if (!$result instanceof Result) { throw new \RuntimeException(sprintf('Failed to count nodes. Expected result to be of type %s, got: %s', Result::class, get_debug_type($result)), 1701444550); @@ -351,38 +309,10 @@ public function countNodes(): int public function findUsedNodeTypeNames(): iterable { - $rows = $this->fetchRows($this->createQueryBuilder() - ->select('DISTINCT nodetypename') - ->from($this->tableNamePrefix . '_node')); - return array_map(static fn (array $row) => NodeTypeName::fromString($row['nodetypename']), $rows); - } - - /** - * @return ContentSubgraphWithRuntimeCaches[] - * @internal only used for {@see DoctrineDbalContentGraphProjection} - */ - public function getSubgraphs(): array - { - return $this->subgraphs; - } - - private function buildChildNodeAggregateQuery(NodeAggregateId $parentNodeAggregateId, ContentStreamId $contentStreamId): QueryBuilder - { - return $this->createQueryBuilder() - ->select('cn.*, ch.name, ch.contentstreamid, ch.subtreetags, cdsp.dimensionspacepoint AS covereddimensionspacepoint') - ->from($this->tableNamePrefix . '_node', 'pn') - ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'ph', 'ph.childnodeanchor = pn.relationanchorpoint') - ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('ch', $this->tableNamePrefix . '_dimensionspacepoints', 'cdsp', 'cdsp.hash = ch.dimensionspacepointhash') - ->innerJoin('ch', $this->tableNamePrefix . '_node', 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') - ->where('pn.nodeaggregateid = :parentNodeAggregateId') - ->andWhere('ph.contentstreamid = :contentStreamId') - ->andWhere('ch.contentstreamid = :contentStreamId') - ->orderBy('ch.position') - ->setParameters([ - 'parentNodeAggregateId' => $parentNodeAggregateId->value, - 'contentStreamId' => $contentStreamId->value, - ]); + return array_map( + static fn (array $row) => NodeTypeName::fromString($row['nodetypename']), + $this->fetchRows($this->nodeQueryBuilder->buildfindUsedNodeTypeNamesQuery()) + ); } private function createQueryBuilder(): QueryBuilder @@ -394,11 +324,11 @@ private function createQueryBuilder(): QueryBuilder * @param QueryBuilder $queryBuilder * @return iterable */ - private function mapQueryBuilderToNodeAggregates(QueryBuilder $queryBuilder, ContentStreamId $contentStreamId): iterable + private function mapQueryBuilderToNodeAggregates(QueryBuilder $queryBuilder): iterable { return $this->nodeFactory->mapNodeRowsToNodeAggregates( $this->fetchRows($queryBuilder), - $contentStreamId, + $this->contentStreamId, VisibilityConstraints::withoutRestrictions() ); } @@ -418,4 +348,16 @@ private function fetchRows(QueryBuilder $queryBuilder): array throw new \RuntimeException(sprintf('Failed to fetch rows from database: %s', $e->getMessage()), 1701444358, $e); } } + + /** The workspace this content graph is operating on */ + public function getWorkspaceName(): WorkspaceName + { + return $this->workspaceName; + } + + /** @internal The content stream id where the workspace name points to for this instance */ + public function getContentStreamId(): ContentStreamId + { + return $this->contentStreamId; + } } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php index c1a278822d7..a8c43d988aa 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php @@ -14,12 +14,12 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository; -use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver\Exception as DbalDriverException; use Doctrine\DBAL\Exception as DbalException; use Doctrine\DBAL\ForwardCompatibility\Result; -use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Query\QueryBuilder; +use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphTableNames; +use Neos\ContentGraph\DoctrineDbalAdapter\NodeQueryBuilder; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\Infrastructure\DbalClientInterface; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; @@ -42,28 +42,14 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindSubtreeFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindSucceedingSiblingNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\NodeType\ExpandedNodeTypeCriteria; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\NodeType\NodeTypeCriteria; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\Ordering\Ordering; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\Ordering\OrderingDirection; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\Ordering\TimestampField; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\Pagination\Pagination; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\AndCriteria; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\NegateCriteria; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\OrCriteria; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\PropertyValueContains; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\PropertyValueCriteriaInterface; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\PropertyValueEndsWith; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\PropertyValueEquals; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\PropertyValueGreaterThan; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\PropertyValueGreaterThanOrEqual; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\PropertyValueLessThan; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\PropertyValueLessThanOrEqual; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\PropertyValueStartsWith; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\NodePath; use Neos\ContentRepository\Core\Projection\ContentGraph\Nodes; use Neos\ContentRepository\Core\Projection\ContentGraph\References; -use Neos\ContentRepository\Core\Projection\ContentGraph\SearchTerm; use Neos\ContentRepository\Core\Projection\ContentGraph\Subtree; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; @@ -72,6 +58,7 @@ use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\ContentRepository\Core\SharedModel\Node\PropertyName; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** * The content subgraph application repository @@ -101,18 +88,21 @@ */ final class ContentSubgraph implements ContentSubgraphInterface { - private int $dynamicParameterCount = 0; + private readonly NodeQueryBuilder $nodeQueryBuilder; public function __construct( private readonly ContentRepositoryId $contentRepositoryId, + /** @phpstan-ignore-next-line */ + private readonly WorkspaceName $workspaceName, private readonly ContentStreamId $contentStreamId, private readonly DimensionSpacePoint $dimensionSpacePoint, private readonly VisibilityConstraints $visibilityConstraints, private readonly DbalClientInterface $client, private readonly NodeFactory $nodeFactory, private readonly NodeTypeManager $nodeTypeManager, - private readonly string $tableNamePrefix + ContentGraphTableNames $tableNames ) { + $this->nodeQueryBuilder = new NodeQueryBuilder($this->client->getConnection(), $tableNames); } public function getIdentity(): ContentSubgraphIdentity @@ -168,45 +158,24 @@ public function countBackReferences(NodeAggregateId $nodeAggregateId, CountBackR public function findNodeById(NodeAggregateId $nodeAggregateId): ?Node { - $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.name, h.subtreetags') - ->from($this->tableNamePrefix . '_node', 'n') - ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->where('n.nodeaggregateid = :nodeAggregateId')->setParameter('nodeAggregateId', $nodeAggregateId->value) - ->andWhere('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->contentStreamId->value) - ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash); + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeQuery($this->contentStreamId, $this->dimensionSpacePoint) + ->andWhere('n.nodeaggregateid = :nodeAggregateId')->setParameter('nodeAggregateId', $nodeAggregateId->value); $this->addSubtreeTagConstraints($queryBuilder); return $this->fetchNode($queryBuilder); } public function findRootNodeByType(NodeTypeName $nodeTypeName): ?Node { - $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.name, h.subtreetags') - ->from($this->tableNamePrefix . '_node', 'n') - ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->where('n.nodetypename = :nodeTypeName')->setParameter('nodeTypeName', $nodeTypeName->value) - ->andWhere('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->contentStreamId->value) - ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) - ->andWhere('n.classification = :nodeAggregateClassification') - ->setParameter('nodeAggregateClassification', NodeAggregateClassification::CLASSIFICATION_ROOT->value); + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeQuery($this->contentStreamId, $this->dimensionSpacePoint) + ->andWhere('n.nodetypename = :nodeTypeName')->setParameter('nodeTypeName', $nodeTypeName->value) + ->andWhere('n.classification = :nodeAggregateClassification')->setParameter('nodeAggregateClassification', NodeAggregateClassification::CLASSIFICATION_ROOT->value); $this->addSubtreeTagConstraints($queryBuilder); return $this->fetchNode($queryBuilder); } public function findParentNode(NodeAggregateId $childNodeAggregateId): ?Node { - $queryBuilder = $this->createQueryBuilder() - ->select('pn.*, ch.name, ch.subtreetags') - ->from($this->tableNamePrefix . '_node', 'pn') - ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'ph', 'ph.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('pn', $this->tableNamePrefix . '_node', 'cn', 'cn.relationanchorpoint = ph.childnodeanchor') - ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'ch', 'ch.childnodeanchor = pn.relationanchorpoint') - ->where('cn.nodeaggregateid = :childNodeAggregateId')->setParameter('childNodeAggregateId', $childNodeAggregateId->value) - ->andWhere('ph.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->contentStreamId->value) - ->andWhere('ch.contentstreamid = :contentStreamId') - ->andWhere('ph.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) - ->andWhere('ch.dimensionspacepointhash = :dimensionSpacePointHash'); + $queryBuilder = $this->nodeQueryBuilder->buildBasicParentNodeQuery($childNodeAggregateId, $this->contentStreamId, $this->dimensionSpacePoint); $this->addSubtreeTagConstraints($queryBuilder, 'ph'); return $this->fetchNode($queryBuilder); } @@ -238,14 +207,7 @@ public function findNodeByAbsolutePath(AbsoluteNodePath $path): ?Node */ private function findChildNodeConnectedThroughEdgeName(NodeAggregateId $parentNodeAggregateId, NodeName $nodeName): ?Node { - $queryBuilder = $this->createQueryBuilder() - ->select('cn.*, h.name, h.subtreetags') - ->from($this->tableNamePrefix . '_node', 'pn') - ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('pn', $this->tableNamePrefix . '_node', 'cn', 'cn.relationanchorpoint = h.childnodeanchor') - ->where('pn.nodeaggregateid = :parentNodeAggregateId')->setParameter('parentNodeAggregateId', $parentNodeAggregateId->value) - ->andWhere('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->contentStreamId->value) - ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) + $queryBuilder = $this->nodeQueryBuilder->buildBasicChildNodesQuery($parentNodeAggregateId, $this->contentStreamId, $this->dimensionSpacePoint) ->andWhere('h.name = :edgeName')->setParameter('edgeName', $nodeName->value); $this->addSubtreeTagConstraints($queryBuilder); return $this->fetchNode($queryBuilder); @@ -291,8 +253,8 @@ public function findSubtree(NodeAggregateId $entryNodeAggregateId, FindSubtreeFi $queryBuilderInitial = $this->createQueryBuilder() // @see https://mariadb.com/kb/en/library/recursive-common-table-expressions-overview/#cast-to-avoid-data-truncation ->select('n.*, h.name, h.subtreetags, CAST("ROOT" AS CHAR(50)) AS parentNodeAggregateId, 0 AS level, 0 AS position') - ->from($this->tableNamePrefix . '_node', 'n') - ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->from($this->nodeQueryBuilder->tableNames->node(), 'n') + ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('n.nodeaggregateid = :entryNodeAggregateId'); @@ -301,15 +263,15 @@ public function findSubtree(NodeAggregateId $entryNodeAggregateId, FindSubtreeFi $queryBuilderRecursive = $this->createQueryBuilder() ->select('c.*, h.name, h.subtreetags, p.nodeaggregateid AS parentNodeAggregateId, p.level + 1 AS level, h.position') ->from('tree', 'p') - ->innerJoin('p', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.parentnodeanchor = p.relationanchorpoint') - ->innerJoin('p', $this->tableNamePrefix . '_node', 'c', 'c.relationanchorpoint = h.childnodeanchor') + ->innerJoin('p', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.parentnodeanchor = p.relationanchorpoint') + ->innerJoin('p', $this->nodeQueryBuilder->tableNames->node(), 'c', 'c.relationanchorpoint = h.childnodeanchor') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); if ($filter->maximumLevels !== null) { $queryBuilderRecursive->andWhere('p.level < :maximumLevels')->setParameter('maximumLevels', $filter->maximumLevels); } if ($filter->nodeTypes !== null) { - $this->addNodeTypeCriteria($queryBuilderRecursive, $filter->nodeTypes, 'c'); + $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilderRecursive, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager), 'c'); } $this->addSubtreeTagConstraints($queryBuilderRecursive); @@ -383,9 +345,9 @@ public function findClosestNode(NodeAggregateId $entryNodeAggregateId, FindClose { $queryBuilderInitial = $this->createQueryBuilder() ->select('n.*, ph.name, ph.subtreetags, ph.parentnodeanchor') - ->from($this->tableNamePrefix . '_node', 'n') + ->from($this->nodeQueryBuilder->tableNames->node(), 'n') // we need to join with the hierarchy relation, because we need the node name. - ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'ph', 'n.relationanchorpoint = ph.childnodeanchor') + ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ph', 'n.relationanchorpoint = ph.childnodeanchor') ->andWhere('ph.contentstreamid = :contentStreamId') ->andWhere('ph.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('n.nodeaggregateid = :entryNodeAggregateId'); @@ -394,21 +356,15 @@ public function findClosestNode(NodeAggregateId $entryNodeAggregateId, FindClose $queryBuilderRecursive = $this->createQueryBuilder() ->select('pn.*, h.name, h.subtreetags, h.parentnodeanchor') ->from('ancestry', 'cn') - ->innerJoin('cn', $this->tableNamePrefix . '_node', 'pn', 'pn.relationanchorpoint = cn.parentnodeanchor') - ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = pn.relationanchorpoint') + ->innerJoin('cn', $this->nodeQueryBuilder->tableNames->node(), 'pn', 'pn.relationanchorpoint = cn.parentnodeanchor') + ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = pn.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); $this->addSubtreeTagConstraints($queryBuilderRecursive); - $queryBuilderCte = $this->createQueryBuilder() - ->select('*') - ->from('ancestry', 'pn') - ->setMaxResults(1) - ->setParameter('contentStreamId', $this->contentStreamId->value) - ->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) - ->setParameter('entryNodeAggregateId', $entryNodeAggregateId->value); + $queryBuilderCte = $this->nodeQueryBuilder->buildBasicNodesCteQuery($entryNodeAggregateId, $this->contentStreamId, $this->dimensionSpacePoint); if ($filter->nodeTypes !== null) { - $this->addNodeTypeCriteria($queryBuilderCte, $filter->nodeTypes, 'pn'); + $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilderCte, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager), 'pn'); } $nodeRows = $this->fetchCteResults( $queryBuilderInitial, @@ -446,21 +402,18 @@ public function countDescendantNodes(NodeAggregateId $entryNodeAggregateId, Coun public function countNodes(): int { - $queryBuilder = $this->createQueryBuilder() - ->select('COUNT(*)') - ->from($this->tableNamePrefix . '_node', 'n') - ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->where('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->contentStreamId->value) - ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash); + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeQuery($this->contentStreamId, $this->dimensionSpacePoint, 'n', 'COUNT(*)'); try { $result = $this->executeQuery($queryBuilder)->fetchOne(); - if (!is_int($result)) { - throw new \RuntimeException(sprintf('Expected result to be of type integer but got: %s', get_debug_type($result)), 1678366902); - } - return $result; } catch (DbalDriverException | DbalException $e) { throw new \RuntimeException(sprintf('Failed to count all nodes: %s', $e->getMessage()), 1678364741, $e); } + + if (!is_int($result)) { + throw new \RuntimeException(sprintf('Expected result to be of type integer but got: %s', get_debug_type($result)), 1678366902); + } + + return $result; } public function jsonSerialize(): ContentSubgraphIdentity @@ -488,12 +441,6 @@ private function createQueryBuilder(): QueryBuilder return $this->client->getConnection()->createQueryBuilder(); } - private function createUniqueParameterName(): string - { - return 'param_' . (++$this->dynamicParameterCount); - } - - private function addSubtreeTagConstraints(QueryBuilder $queryBuilder, string $hierarchyRelationTableAlias = 'h'): void { $hierarchyRelationTablePrefix = $hierarchyRelationTableAlias === '' ? '' : $hierarchyRelationTableAlias . '.'; @@ -504,119 +451,17 @@ private function addSubtreeTagConstraints(QueryBuilder $queryBuilder, string $hi } } - private function addNodeTypeCriteria(QueryBuilder $queryBuilder, NodeTypeCriteria $nodeTypeCriteria, string $nodeTableAlias = 'n'): void - { - $nodeTablePrefix = $nodeTableAlias === '' ? '' : $nodeTableAlias . '.'; - $constraintsWithSubNodeTypes = ExpandedNodeTypeCriteria::create($nodeTypeCriteria, $this->nodeTypeManager); - $allowanceQueryPart = ''; - if (!$constraintsWithSubNodeTypes->explicitlyAllowedNodeTypeNames->isEmpty()) { - $allowanceQueryPart = $queryBuilder->expr()->in($nodeTablePrefix . 'nodetypename', ':explicitlyAllowedNodeTypeNames'); - $queryBuilder->setParameter('explicitlyAllowedNodeTypeNames', $constraintsWithSubNodeTypes->explicitlyAllowedNodeTypeNames->toStringArray(), Connection::PARAM_STR_ARRAY); - } - $denyQueryPart = ''; - if (!$constraintsWithSubNodeTypes->explicitlyDisallowedNodeTypeNames->isEmpty()) { - $denyQueryPart = $queryBuilder->expr()->notIn($nodeTablePrefix . 'nodetypename', ':explicitlyDisallowedNodeTypeNames'); - $queryBuilder->setParameter('explicitlyDisallowedNodeTypeNames', $constraintsWithSubNodeTypes->explicitlyDisallowedNodeTypeNames->toStringArray(), Connection::PARAM_STR_ARRAY); - } - if ($allowanceQueryPart && $denyQueryPart) { - if ($constraintsWithSubNodeTypes->isWildCardAllowed) { - $queryBuilder->andWhere($queryBuilder->expr()->or($allowanceQueryPart, $denyQueryPart)); - } else { - $queryBuilder->andWhere($queryBuilder->expr()->and($allowanceQueryPart, $denyQueryPart)); - } - } elseif ($allowanceQueryPart && !$constraintsWithSubNodeTypes->isWildCardAllowed) { - $queryBuilder->andWhere($allowanceQueryPart); - } elseif ($denyQueryPart) { - $queryBuilder->andWhere($denyQueryPart); - } - } - - private function addSearchTermConstraints(QueryBuilder $queryBuilder, SearchTerm $searchTerm, string $nodeTableAlias = 'n'): void - { - $queryBuilder->andWhere('JSON_SEARCH(' . $nodeTableAlias . '.properties, "one", :searchTermPattern, NULL, "$.*.value") IS NOT NULL')->setParameter('searchTermPattern', '%' . $searchTerm->term . '%'); - } - - private function addPropertyValueConstraints(QueryBuilder $queryBuilder, PropertyValueCriteriaInterface $propertyValue, string $nodeTableAlias = 'n'): void - { - $queryBuilder->andWhere($this->propertyValueConstraints($queryBuilder, $propertyValue, $nodeTableAlias)); - } - - private function propertyValueConstraints(QueryBuilder $queryBuilder, PropertyValueCriteriaInterface $propertyValue, string $nodeTableAlias): string - { - return match ($propertyValue::class) { - AndCriteria::class => (string)$queryBuilder->expr()->and($this->propertyValueConstraints($queryBuilder, $propertyValue->criteria1, $nodeTableAlias), $this->propertyValueConstraints($queryBuilder, $propertyValue->criteria2, $nodeTableAlias)), - NegateCriteria::class => 'NOT (' . $this->propertyValueConstraints($queryBuilder, $propertyValue->criteria, $nodeTableAlias) . ')', - OrCriteria::class => (string)$queryBuilder->expr()->or($this->propertyValueConstraints($queryBuilder, $propertyValue->criteria1, $nodeTableAlias), $this->propertyValueConstraints($queryBuilder, $propertyValue->criteria2, $nodeTableAlias)), - PropertyValueContains::class => $this->searchPropertyValueStatement($queryBuilder, $propertyValue->propertyName, '%' . $propertyValue->value . '%', $nodeTableAlias, $propertyValue->caseSensitive), - PropertyValueEndsWith::class => $this->searchPropertyValueStatement($queryBuilder, $propertyValue->propertyName, '%' . $propertyValue->value, $nodeTableAlias, $propertyValue->caseSensitive), - PropertyValueEquals::class => is_string($propertyValue->value) ? $this->searchPropertyValueStatement($queryBuilder, $propertyValue->propertyName, $propertyValue->value, $nodeTableAlias, $propertyValue->caseSensitive) : $this->comparePropertyValueStatement($queryBuilder, $propertyValue->propertyName, $propertyValue->value, '=', $nodeTableAlias), - PropertyValueGreaterThan::class => $this->comparePropertyValueStatement($queryBuilder, $propertyValue->propertyName, $propertyValue->value, '>', $nodeTableAlias), - PropertyValueGreaterThanOrEqual::class => $this->comparePropertyValueStatement($queryBuilder, $propertyValue->propertyName, $propertyValue->value, '>=', $nodeTableAlias), - PropertyValueLessThan::class => $this->comparePropertyValueStatement($queryBuilder, $propertyValue->propertyName, $propertyValue->value, '<', $nodeTableAlias), - PropertyValueLessThanOrEqual::class => $this->comparePropertyValueStatement($queryBuilder, $propertyValue->propertyName, $propertyValue->value, '<=', $nodeTableAlias), - PropertyValueStartsWith::class => $this->searchPropertyValueStatement($queryBuilder, $propertyValue->propertyName, $propertyValue->value . '%', $nodeTableAlias, $propertyValue->caseSensitive), - default => throw new \InvalidArgumentException(sprintf('Invalid/unsupported property value criteria "%s"', get_debug_type($propertyValue)), 1679561062), - }; - } - - private function comparePropertyValueStatement(QueryBuilder $queryBuilder, PropertyName $propertyName, string|int|float|bool $value, string $operator, string $nodeTableAlias): string - { - $paramName = $this->createUniqueParameterName(); - $paramType = match (gettype($value)) { - 'boolean' => ParameterType::BOOLEAN, - 'integer' => ParameterType::INTEGER, - default => ParameterType::STRING, - }; - $queryBuilder->setParameter($paramName, $value, $paramType); - return $this->extractPropertyValue($propertyName, $nodeTableAlias) . ' ' . $operator . ' :' . $paramName; - } - - private function extractPropertyValue(PropertyName $propertyName, string $nodeTableAlias): string - { - try { - $escapedPropertyName = addslashes(json_encode($propertyName->value, JSON_THROW_ON_ERROR)); - } catch (\JsonException $e) { - throw new \RuntimeException(sprintf('Failed to escape property name: %s', $e->getMessage()), 1679394579, $e); - } - return 'JSON_EXTRACT(' . $nodeTableAlias . '.properties, \'$.' . $escapedPropertyName . '.value\')'; - } - - private function searchPropertyValueStatement(QueryBuilder $queryBuilder, PropertyName $propertyName, string|bool|int|float $value, string $nodeTableAlias, bool $caseSensitive): string - { - try { - $escapedPropertyName = addslashes(json_encode($propertyName->value, JSON_THROW_ON_ERROR)); - } catch (\JsonException $e) { - throw new \RuntimeException(sprintf('Failed to escape property name: %s', $e->getMessage()), 1679394579, $e); - } - if (is_bool($value)) { - return 'JSON_SEARCH(' . $nodeTableAlias . '.properties, \'one\', \'' . ($value ? 'true' : 'false') . '\', NULL, \'$.' . $escapedPropertyName . '.value\') IS NOT NULL'; - } - $paramName = $this->createUniqueParameterName(); - $queryBuilder->setParameter($paramName, $value); - if ($caseSensitive) { - return 'JSON_SEARCH(' . $nodeTableAlias . '.properties COLLATE utf8mb4_bin, \'one\', :' . $paramName . ' COLLATE utf8mb4_bin, NULL, \'$.' . $escapedPropertyName . '.value\') IS NOT NULL'; - } - return 'JSON_SEARCH(' . $nodeTableAlias . '.properties, \'one\', :' . $paramName . ', NULL, \'$.' . $escapedPropertyName . '.value\') IS NOT NULL'; - } - private function buildChildNodesQuery(NodeAggregateId $parentNodeAggregateId, FindChildNodesFilter|CountChildNodesFilter $filter): QueryBuilder { - $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.name, h.subtreetags') - ->from($this->tableNamePrefix . '_node', 'pn') - ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('pn', $this->tableNamePrefix . '_node', 'n', 'h.childnodeanchor = n.relationanchorpoint') - ->where('pn.nodeaggregateid = :parentNodeAggregateId')->setParameter('parentNodeAggregateId', $parentNodeAggregateId->value) - ->andWhere('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->contentStreamId->value) - ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash); + $queryBuilder = $this->nodeQueryBuilder->buildBasicChildNodesQuery($parentNodeAggregateId, $this->contentStreamId, $this->dimensionSpacePoint); if ($filter->nodeTypes !== null) { - $this->addNodeTypeCriteria($queryBuilder, $filter->nodeTypes); + $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilder, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager)); } if ($filter->searchTerm !== null) { - $this->addSearchTermConstraints($queryBuilder, $filter->searchTerm); + $this->nodeQueryBuilder->addSearchTermConstraints($queryBuilder, $filter->searchTerm); } if ($filter->propertyValue !== null) { - $this->addPropertyValueConstraints($queryBuilder, $filter->propertyValue); + $this->nodeQueryBuilder->addPropertyValueConstraints($queryBuilder, $filter->propertyValue); } $this->addSubtreeTagConstraints($queryBuilder); return $queryBuilder; @@ -628,11 +473,11 @@ private function buildReferencesQuery(bool $backReferences, NodeAggregateId $nod $destinationTablePrefix = $backReferences ? 's' : 'd'; $queryBuilder = $this->createQueryBuilder() ->select("{$destinationTablePrefix}n.*, {$destinationTablePrefix}h.name, {$destinationTablePrefix}h.subtreetags, r.name AS referencename, r.properties AS referenceproperties") - ->from($this->tableNamePrefix . '_hierarchyrelation', 'sh') - ->innerJoin('sh', $this->tableNamePrefix . '_node', 'sn', 'sn.relationanchorpoint = sh.childnodeanchor') - ->innerJoin('sh', $this->tableNamePrefix . '_referencerelation', 'r', 'r.nodeanchorpoint = sn.relationanchorpoint') - ->innerJoin('sh', $this->tableNamePrefix . '_node', 'dn', 'dn.nodeaggregateid = r.destinationnodeaggregateid') - ->innerJoin('sh', $this->tableNamePrefix . '_hierarchyrelation', 'dh', 'dh.childnodeanchor = dn.relationanchorpoint') + ->from($this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'sh') + ->innerJoin('sh', $this->nodeQueryBuilder->tableNames->node(), 'sn', 'sn.relationanchorpoint = sh.childnodeanchor') + ->innerJoin('sh', $this->nodeQueryBuilder->tableNames->referenceRelation(), 'r', 'r.nodeanchorpoint = sn.relationanchorpoint') + ->innerJoin('sh', $this->nodeQueryBuilder->tableNames->node(), 'dn', 'dn.nodeaggregateid = r.destinationnodeaggregateid') + ->innerJoin('sh', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'dh', 'dh.childnodeanchor = dn.relationanchorpoint') ->where("{$sourceTablePrefix}n.nodeaggregateid = :nodeAggregateId")->setParameter('nodeAggregateId', $nodeAggregateId->value) ->andWhere('dh.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) ->andWhere('sh.dimensionspacepointhash = :dimensionSpacePointHash') @@ -641,22 +486,19 @@ private function buildReferencesQuery(bool $backReferences, NodeAggregateId $nod $this->addSubtreeTagConstraints($queryBuilder, 'dh'); $this->addSubtreeTagConstraints($queryBuilder, 'sh'); if ($filter->nodeTypes !== null) { - $this->addNodeTypeCriteria($queryBuilder, $filter->nodeTypes, "{$destinationTablePrefix}n"); + $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilder, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager), "{$destinationTablePrefix}n"); } if ($filter->nodeSearchTerm !== null) { - $this->addSearchTermConstraints($queryBuilder, $filter->nodeSearchTerm, "{$destinationTablePrefix}n"); + $this->nodeQueryBuilder->addSearchTermConstraints($queryBuilder, $filter->nodeSearchTerm, "{$destinationTablePrefix}n"); } if ($filter->nodePropertyValue !== null) { - $this->addPropertyValueConstraints($queryBuilder, $filter->nodePropertyValue, "{$destinationTablePrefix}n"); + $this->nodeQueryBuilder->addPropertyValueConstraints($queryBuilder, $filter->nodePropertyValue, "{$destinationTablePrefix}n"); } if ($filter->referenceSearchTerm !== null) { - $this->addSearchTermConstraints($queryBuilder, $filter->referenceSearchTerm, 'r'); + $this->nodeQueryBuilder->addSearchTermConstraints($queryBuilder, $filter->referenceSearchTerm, 'r'); } if ($filter->referencePropertyValue !== null) { - $this->addPropertyValueConstraints($queryBuilder, $filter->referencePropertyValue, 'r'); - } - if ($filter->nodePropertyValue !== null) { - $this->addPropertyValueConstraints($queryBuilder, $filter->nodePropertyValue, "{$destinationTablePrefix}n"); + $this->nodeQueryBuilder->addPropertyValueConstraints($queryBuilder, $filter->referencePropertyValue, 'r'); } if ($filter->referenceName !== null) { $queryBuilder->andWhere('r.name = :referenceName')->setParameter('referenceName', $filter->referenceName->value); @@ -678,42 +520,17 @@ private function buildReferencesQuery(bool $backReferences, NodeAggregateId $nod private function buildSiblingsQuery(bool $preceding, NodeAggregateId $siblingNodeAggregateId, FindPrecedingSiblingNodesFilter|FindSucceedingSiblingNodesFilter $filter): QueryBuilder { - $parentNodeAnchorSubQuery = $this->createQueryBuilder() - ->select('sh.parentnodeanchor') - ->from($this->tableNamePrefix . '_hierarchyrelation', 'sh') - ->innerJoin('sh', $this->tableNamePrefix . '_node', 'sn', 'sn.relationanchorpoint = sh.childnodeanchor') - ->where('sn.nodeaggregateid = :siblingNodeAggregateId') - ->andWhere('sh.contentstreamid = :contentStreamId') - ->andWhere('sh.dimensionspacepointhash = :dimensionSpacePointHash'); - - $siblingPositionSubQuery = $this->createQueryBuilder() - ->select('sh.position') - ->from($this->tableNamePrefix . '_hierarchyrelation', 'sh') - ->innerJoin('sh', $this->tableNamePrefix . '_node', 'sn', 'sn.relationanchorpoint = sh.childnodeanchor') - ->where('sn.nodeaggregateid = :siblingNodeAggregateId') - ->andWhere('sh.contentstreamid = :contentStreamId') - ->andWhere('sh.dimensionspacepointhash = :dimensionSpacePointHash'); - - $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.name, h.subtreetags') - ->from($this->tableNamePrefix . '_node', 'n') - ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->where('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->contentStreamId->value) - ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) - ->andWhere('h.parentnodeanchor = (' . $parentNodeAnchorSubQuery->getSQL() . ')') - ->andWhere('n.nodeaggregateid != :siblingNodeAggregateId')->setParameter('siblingNodeAggregateId', $siblingNodeAggregateId->value) - ->andWhere('h.position ' . ($preceding ? '<' : '>') . ' (' . $siblingPositionSubQuery->getSQL() . ')') - ->orderBy('h.position', $preceding ? 'DESC' : 'ASC'); + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeSiblingsQuery($preceding, $siblingNodeAggregateId, $this->contentStreamId, $this->dimensionSpacePoint); $this->addSubtreeTagConstraints($queryBuilder); if ($filter->nodeTypes !== null) { - $this->addNodeTypeCriteria($queryBuilder, $filter->nodeTypes); + $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilder, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager)); } if ($filter->searchTerm !== null) { - $this->addSearchTermConstraints($queryBuilder, $filter->searchTerm); + $this->nodeQueryBuilder->addSearchTermConstraints($queryBuilder, $filter->searchTerm); } if ($filter->propertyValue !== null) { - $this->addPropertyValueConstraints($queryBuilder, $filter->propertyValue); + $this->nodeQueryBuilder->addPropertyValueConstraints($queryBuilder, $filter->propertyValue); } if ($filter->pagination !== null) { $this->applyPagination($queryBuilder, $filter->pagination); @@ -728,11 +545,11 @@ private function buildAncestorNodesQueries(NodeAggregateId $entryNodeAggregateId { $queryBuilderInitial = $this->createQueryBuilder() ->select('n.*, ph.name, ph.subtreetags, ph.parentnodeanchor') - ->from($this->tableNamePrefix . '_node', 'n') + ->from($this->nodeQueryBuilder->tableNames->node(), 'n') // we need to join with the hierarchy relation, because we need the node name. - ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'ch', 'ch.parentnodeanchor = n.relationanchorpoint') - ->innerJoin('ch', $this->tableNamePrefix . '_node', 'c', 'c.relationanchorpoint = ch.childnodeanchor') - ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'ph', 'n.relationanchorpoint = ph.childnodeanchor') + ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ch', 'ch.parentnodeanchor = n.relationanchorpoint') + ->innerJoin('ch', $this->nodeQueryBuilder->tableNames->node(), 'c', 'c.relationanchorpoint = ch.childnodeanchor') + ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ph', 'n.relationanchorpoint = ph.childnodeanchor') ->where('ch.contentstreamid = :contentStreamId') ->andWhere('ch.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('ph.contentstreamid = :contentStreamId') @@ -744,20 +561,15 @@ private function buildAncestorNodesQueries(NodeAggregateId $entryNodeAggregateId $queryBuilderRecursive = $this->createQueryBuilder() ->select('pn.*, h.name, h.subtreetags, h.parentnodeanchor') ->from('ancestry', 'cn') - ->innerJoin('cn', $this->tableNamePrefix . '_node', 'pn', 'pn.relationanchorpoint = cn.parentnodeanchor') - ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = pn.relationanchorpoint') + ->innerJoin('cn', $this->nodeQueryBuilder->tableNames->node(), 'pn', 'pn.relationanchorpoint = cn.parentnodeanchor') + ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = pn.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); $this->addSubtreeTagConstraints($queryBuilderRecursive); - $queryBuilderCte = $this->createQueryBuilder() - ->select('*') - ->from('ancestry', 'pn') - ->setParameter('contentStreamId', $this->contentStreamId->value) - ->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) - ->setParameter('entryNodeAggregateId', $entryNodeAggregateId->value); + $queryBuilderCte = $this->nodeQueryBuilder->buildBasicNodesCteQuery($entryNodeAggregateId, $this->contentStreamId, $this->dimensionSpacePoint); if ($filter->nodeTypes !== null) { - $this->addNodeTypeCriteria($queryBuilderCte, $filter->nodeTypes, 'pn'); + $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilderCte, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager), 'pn'); } return compact('queryBuilderInitial', 'queryBuilderRecursive', 'queryBuilderCte'); } @@ -770,11 +582,11 @@ private function buildDescendantNodesQueries(NodeAggregateId $entryNodeAggregate $queryBuilderInitial = $this->createQueryBuilder() // @see https://mariadb.com/kb/en/library/recursive-common-table-expressions-overview/#cast-to-avoid-data-truncation ->select('n.*, h.name, h.subtreetags, CAST("ROOT" AS CHAR(50)) AS parentNodeAggregateId, 0 AS level, 0 AS position') - ->from($this->tableNamePrefix . '_node', 'n') + ->from($this->nodeQueryBuilder->tableNames->node(), 'n') // we need to join with the hierarchy relation, because we need the node name. - ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->innerJoin('n', $this->tableNamePrefix . '_node', 'p', 'p.relationanchorpoint = h.parentnodeanchor') - ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'ph', 'ph.childnodeanchor = p.relationanchorpoint') + ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('n', $this->nodeQueryBuilder->tableNames->node(), 'p', 'p.relationanchorpoint = h.parentnodeanchor') + ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ph', 'ph.childnodeanchor = p.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('ph.contentstreamid = :contentStreamId') @@ -785,26 +597,21 @@ private function buildDescendantNodesQueries(NodeAggregateId $entryNodeAggregate $queryBuilderRecursive = $this->createQueryBuilder() ->select('cn.*, h.name, h.subtreetags, pn.nodeaggregateid AS parentNodeAggregateId, pn.level + 1 AS level, h.position') ->from('tree', 'pn') - ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('pn', $this->tableNamePrefix . '_node', 'cn', 'cn.relationanchorpoint = h.childnodeanchor') + ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->node(), 'cn', 'cn.relationanchorpoint = h.childnodeanchor') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); $this->addSubtreeTagConstraints($queryBuilderRecursive); - $queryBuilderCte = $this->createQueryBuilder() - ->select('*') - ->from('tree', 'n') - ->setParameter('contentStreamId', $this->contentStreamId->value) - ->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) - ->setParameter('entryNodeAggregateId', $entryNodeAggregateId->value); + $queryBuilderCte = $this->nodeQueryBuilder->buildBasicNodesCteQuery($entryNodeAggregateId, $this->contentStreamId, $this->dimensionSpacePoint, 'tree', 'n'); if ($filter->nodeTypes !== null) { - $this->addNodeTypeCriteria($queryBuilderCte, $filter->nodeTypes); + $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilderCte, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager)); } if ($filter->searchTerm !== null) { - $this->addSearchTermConstraints($queryBuilderCte, $filter->searchTerm); + $this->nodeQueryBuilder->addSearchTermConstraints($queryBuilderCte, $filter->searchTerm); } if ($filter->propertyValue !== null) { - $this->addPropertyValueConstraints($queryBuilderCte, $filter->propertyValue); + $this->nodeQueryBuilder->addPropertyValueConstraints($queryBuilderCte, $filter->propertyValue); } return compact('queryBuilderInitial', 'queryBuilderRecursive', 'queryBuilderCte'); } @@ -817,7 +624,7 @@ private function applyOrdering(QueryBuilder $queryBuilder, Ordering $ordering, s OrderingDirection::DESCENDING => 'DESC', }; if ($orderingField->field instanceof PropertyName) { - $queryBuilder->addOrderBy($this->extractPropertyValue($orderingField->field, $nodeTableAlias), $order); + $queryBuilder->addOrderBy($this->nodeQueryBuilder->extractPropertyValue($orderingField->field, $nodeTableAlias), $order); } else { $timestampColumnName = match ($orderingField->field) { TimestampField::CREATED => 'created', @@ -832,9 +639,7 @@ private function applyOrdering(QueryBuilder $queryBuilder, Ordering $ordering, s private function applyPagination(QueryBuilder $queryBuilder, Pagination $pagination): void { - $queryBuilder - ->setMaxResults($pagination->limit) - ->setFirstResult($pagination->offset); + $queryBuilder->setMaxResults($pagination->limit)->setFirstResult($pagination->offset); } /** diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/DimensionSpacePointsRepository.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/DimensionSpacePointsRepository.php index 1a409b5abc6..6b7632f46bd 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/DimensionSpacePointsRepository.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/DimensionSpacePointsRepository.php @@ -15,6 +15,7 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository; use Doctrine\DBAL\Connection; +use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphTableNames; use Neos\ContentRepository\Core\DimensionSpace\AbstractDimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; @@ -32,7 +33,7 @@ final class DimensionSpacePointsRepository public function __construct( private readonly Connection $databaseConnection, - private readonly string $tableNamePrefix + private readonly ContentGraphTableNames $tableNames ) { } @@ -81,7 +82,7 @@ public function getOriginDimensionSpacePointByHash(string $hash): OriginDimensio private function writeDimensionSpacePoint(string $hash, string $dimensionSpacePointCoordinatesJson): void { $this->databaseConnection->executeStatement( - 'INSERT IGNORE INTO ' . $this->tableNamePrefix . '_dimensionspacepoints (hash, dimensionspacepoint) VALUES (:dimensionspacepointhash, :dimensionspacepoint)', + 'INSERT IGNORE INTO ' . $this->tableNames->dimensionSpacePoints() . ' (hash, dimensionspacepoint) VALUES (:dimensionspacepointhash, :dimensionspacepoint)', [ 'dimensionspacepointhash' => $hash, 'dimensionspacepoint' => $dimensionSpacePointCoordinatesJson @@ -96,7 +97,7 @@ private function getCoordinatesByHashFromRuntimeCache(string $hash): ?string private function fillRuntimeCacheFromDatabase(): void { - $allDimensionSpacePoints = $this->databaseConnection->fetchAllAssociative('SELECT hash, dimensionspacepoint FROM ' . $this->tableNamePrefix . '_dimensionspacepoints'); + $allDimensionSpacePoints = $this->databaseConnection->fetchAllAssociative('SELECT hash, dimensionspacepoint FROM ' . $this->tableNames->dimensionSpacePoints()); foreach ($allDimensionSpacePoints as $dimensionSpacePointRow) { $this->dimensionspacePointsRuntimeCache[(string)$dimensionSpacePointRow['hash']] = (string)$dimensionSpacePointRow['dimensionspacepoint']; } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php index 02e6b63e5b9..f41e6591b88 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php @@ -17,6 +17,7 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Driver\Exception; +use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphTableNames; use Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphProjection; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\HierarchyRelation; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRecord; @@ -30,8 +31,8 @@ use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; /** - * The read only content graph for use by the {@see GraphProjector}. This is the class for low-level operations - * within the projector, where implementation details of the graph structure are known. + * The read only content graph for use by the {@see DoctrineDbalContentGraphProjection}. This is the class for low-level operations + * within the projection, where implementation details of the graph structure are known. * * This is NO PUBLIC API in any way. * @@ -41,7 +42,7 @@ class ProjectionContentGraph { public function __construct( private readonly DbalClientInterface $client, - private readonly string $tableNamePrefix + private readonly ContentGraphTableNames $tableNames ) { } @@ -71,11 +72,11 @@ public function findParentNode( : $originDimensionSpacePoint->hash ]; $nodeRow = $this->getDatabaseConnection()->executeQuery( - 'SELECT p.*, ph.contentstreamid, ph.name, ph.subtreetags, dsp.dimensionspacepoint AS origindimensionspacepoint FROM ' . $this->tableNamePrefix . '_node p - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation ph ON ph.childnodeanchor = p.relationanchorpoint - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation ch ON ch.parentnodeanchor = p.relationanchorpoint - INNER JOIN ' . $this->tableNamePrefix . '_node c ON ch.childnodeanchor = c.relationanchorpoint - INNER JOIN ' . $this->tableNamePrefix . '_dimensionspacepoints dsp ON p.origindimensionspacepointhash = dsp.hash + 'SELECT p.*, ph.contentstreamid, ph.name, ph.subtreetags, dsp.dimensionspacepoint AS origindimensionspacepoint FROM ' . $this->tableNames->node() . ' p + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' ph ON ph.childnodeanchor = p.relationanchorpoint + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' ch ON ch.parentnodeanchor = p.relationanchorpoint + INNER JOIN ' . $this->tableNames->node() . ' c ON ch.childnodeanchor = c.relationanchorpoint + INNER JOIN ' . $this->tableNames->dimensionSpacePoints() . ' dsp ON p.origindimensionspacepointhash = dsp.hash WHERE c.nodeaggregateid = :childNodeAggregateId AND c.origindimensionspacepointhash = :originDimensionSpacePointHash AND ph.contentstreamid = :contentStreamId @@ -102,9 +103,9 @@ public function findNodeInAggregate( DimensionSpacePoint $coveredDimensionSpacePoint ): ?NodeRecord { $nodeRow = $this->getDatabaseConnection()->executeQuery( - 'SELECT n.*, h.name, h.subtreetags, dsp.dimensionspacepoint AS origindimensionspacepoint FROM ' . $this->tableNamePrefix . '_node n - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h ON h.childnodeanchor = n.relationanchorpoint - INNER JOIN ' . $this->tableNamePrefix . '_dimensionspacepoints dsp ON n.origindimensionspacepointhash = dsp.hash + 'SELECT n.*, h.name, h.subtreetags, dsp.dimensionspacepoint AS origindimensionspacepoint FROM ' . $this->tableNames->node() . ' n + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint + INNER JOIN ' . $this->tableNames->dimensionSpacePoints() . ' dsp ON n.origindimensionspacepointhash = dsp.hash WHERE n.nodeaggregateid = :nodeAggregateId AND h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :dimensionSpacePointHash', @@ -118,35 +119,6 @@ public function findNodeInAggregate( return $nodeRow ? NodeRecord::fromDatabaseRow($nodeRow) : null; } - /** - * @param ContentStreamId $contentStreamId - * @param NodeAggregateId $nodeAggregateId - * @param OriginDimensionSpacePoint $originDimensionSpacePoint - * @return NodeRecord|null - * @throws \Exception - */ - public function findNodeByIds( - ContentStreamId $contentStreamId, - NodeAggregateId $nodeAggregateId, - OriginDimensionSpacePoint $originDimensionSpacePoint - ): ?NodeRecord { - $nodeRow = $this->getDatabaseConnection()->executeQuery( - 'SELECT n.*, h.name, h.subtreetags, dsp.dimensionspacepoint AS origindimensionspacepoint FROM ' . $this->tableNamePrefix . '_node n - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h ON h.childnodeanchor = n.relationanchorpoint - INNER JOIN ' . $this->tableNamePrefix . '_dimensionspacepoints dsp ON n.origindimensionspacepointhash = dsp.hash - WHERE n.nodeaggregateid = :nodeAggregateId - AND n.origindimensionspacepointhash = :originDimensionSpacePointHash - AND h.contentstreamid = :contentStreamId', - [ - 'contentStreamId' => $contentStreamId->value, - 'nodeAggregateId' => $nodeAggregateId->value, - 'originDimensionSpacePointHash' => $originDimensionSpacePoint->hash - ] - )->fetchAssociative(); - - return $nodeRow ? NodeRecord::fromDatabaseRow($nodeRow) : null; - } - /** * @param NodeAggregateId $nodeAggregateId * @param OriginDimensionSpacePoint $originDimensionSpacePoint @@ -160,8 +132,8 @@ public function getAnchorPointForNodeAndOriginDimensionSpacePointAndContentStrea ContentStreamId $contentStreamId ): ?NodeRelationAnchorPoint { $rows = $this->getDatabaseConnection()->executeQuery( - 'SELECT DISTINCT n.relationanchorpoint FROM ' . $this->tableNamePrefix . '_node n - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h ON h.childnodeanchor = n.relationanchorpoint + 'SELECT DISTINCT n.relationanchorpoint FROM ' . $this->tableNames->node() . ' n + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint WHERE n.nodeaggregateid = :nodeAggregateId AND n.origindimensionspacepointhash = :originDimensionSpacePointHash AND h.contentstreamid = :contentStreamId', @@ -195,8 +167,8 @@ public function getAnchorPointsForNodeAggregateInContentStream( ContentStreamId $contentStreamId ): iterable { $rows = $this->getDatabaseConnection()->executeQuery( - 'SELECT DISTINCT n.relationanchorpoint FROM ' . $this->tableNamePrefix . '_node n - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h ON h.childnodeanchor = n.relationanchorpoint + 'SELECT DISTINCT n.relationanchorpoint FROM ' . $this->tableNames->node() . ' n + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint WHERE n.nodeaggregateid = :nodeAggregateId AND h.contentstreamid = :contentStreamId', [ @@ -219,8 +191,8 @@ public function getAnchorPointsForNodeAggregateInContentStream( public function getNodeByAnchorPoint(NodeRelationAnchorPoint $nodeRelationAnchorPoint): ?NodeRecord { $nodeRow = $this->getDatabaseConnection()->executeQuery( - 'SELECT n.*, dsp.dimensionspacepoint AS origindimensionspacepoint FROM ' . $this->tableNamePrefix . '_node n - INNER JOIN ' . $this->tableNamePrefix . '_dimensionspacepoints dsp ON n.origindimensionspacepointhash = dsp.hash + 'SELECT n.*, dsp.dimensionspacepoint AS origindimensionspacepoint FROM ' . $this->tableNames->node() . ' n + INNER JOIN ' . $this->tableNames->dimensionSpacePoints() . ' dsp ON n.origindimensionspacepointhash = dsp.hash WHERE n.relationanchorpoint = :relationAnchorPoint', [ 'relationAnchorPoint' => $nodeRelationAnchorPoint->value, @@ -255,7 +227,7 @@ public function determineHierarchyRelationPosition( if ($succeedingSiblingAnchorPoint) { /** @var array $succeedingSiblingRelation */ $succeedingSiblingRelation = $this->getDatabaseConnection()->executeQuery( - 'SELECT h.* FROM ' . $this->tableNamePrefix . '_hierarchyrelation h + 'SELECT h.* FROM ' . $this->tableNames->hierarchyRelation() . ' h WHERE h.childnodeanchor = :succeedingSiblingAnchorPoint AND h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :dimensionSpacePointHash', @@ -270,7 +242,7 @@ public function determineHierarchyRelationPosition( $parentAnchorPoint = NodeRelationAnchorPoint::fromInteger($succeedingSiblingRelation['parentnodeanchor']); $precedingSiblingData = $this->getDatabaseConnection()->executeQuery( - 'SELECT MAX(h.position) AS position FROM ' . $this->tableNamePrefix . '_hierarchyrelation h + 'SELECT MAX(h.position) AS position FROM ' . $this->tableNames->hierarchyRelation() . ' h WHERE h.parentnodeanchor = :anchorPoint AND h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :dimensionSpacePointHash @@ -296,7 +268,7 @@ public function determineHierarchyRelationPosition( if (!$parentAnchorPoint) { /** @var array $childHierarchyRelationData */ $childHierarchyRelationData = $this->getDatabaseConnection()->executeQuery( - 'SELECT h.parentnodeanchor FROM ' . $this->tableNamePrefix . '_hierarchyrelation h + 'SELECT h.parentnodeanchor FROM ' . $this->tableNames->hierarchyRelation() . ' h WHERE h.childnodeanchor = :childAnchorPoint AND h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :dimensionSpacePointHash', @@ -311,7 +283,7 @@ public function determineHierarchyRelationPosition( ); } $rightmostSucceedingSiblingRelationData = $this->getDatabaseConnection()->executeQuery( - 'SELECT MAX(h.position) AS position FROM ' . $this->tableNamePrefix . '_hierarchyrelation h + 'SELECT MAX(h.position) AS position FROM ' . $this->tableNames->hierarchyRelation() . ' h WHERE h.parentnodeanchor = :parentAnchorPoint AND h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :dimensionSpacePointHash', @@ -348,7 +320,7 @@ public function getOutgoingHierarchyRelationsForNodeAndSubgraph( $relations = []; foreach ( $this->getDatabaseConnection()->executeQuery( - 'SELECT h.* FROM ' . $this->tableNamePrefix . '_hierarchyrelation h + 'SELECT h.* FROM ' . $this->tableNames->hierarchyRelation() . ' h WHERE h.parentnodeanchor = :parentAnchorPoint AND h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :dimensionSpacePointHash', @@ -380,7 +352,7 @@ public function getIngoingHierarchyRelationsForNodeAndSubgraph( $relations = []; foreach ( $this->getDatabaseConnection()->executeQuery( - 'SELECT h.* FROM ' . $this->tableNamePrefix . '_hierarchyrelation h + 'SELECT h.* FROM ' . $this->tableNames->hierarchyRelation() . ' h WHERE h.childnodeanchor = :childAnchorPoint AND h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :dimensionSpacePointHash', @@ -410,7 +382,7 @@ public function findIngoingHierarchyRelationsForNode( DimensionSpacePointSet $restrictToSet = null ): array { $relations = []; - $query = 'SELECT h.* FROM ' . $this->tableNamePrefix . '_hierarchyrelation h + $query = 'SELECT h.* FROM ' . $this->tableNames->hierarchyRelation() . ' h WHERE h.childnodeanchor = :childAnchorPoint AND h.contentstreamid = :contentStreamId'; $parameters = [ @@ -448,7 +420,7 @@ public function findOutgoingHierarchyRelationsForNode( DimensionSpacePointSet $restrictToSet = null ): array { $relations = []; - $query = 'SELECT h.* FROM ' . $this->tableNamePrefix . '_hierarchyrelation h + $query = 'SELECT h.* FROM ' . $this->tableNames->hierarchyRelation() . ' h WHERE h.parentnodeanchor = :parentAnchorPoint AND h.contentstreamid = :contentStreamId'; $parameters = [ @@ -488,8 +460,8 @@ public function findOutgoingHierarchyRelationsForNodeAggregate( $relations = []; foreach ( $this->getDatabaseConnection()->executeQuery( - 'SELECT h.* FROM ' . $this->tableNamePrefix . '_hierarchyrelation h - INNER JOIN ' . $this->tableNamePrefix . '_node n ON h.parentnodeanchor = n.relationanchorpoint + 'SELECT h.* FROM ' . $this->tableNames->hierarchyRelation() . ' h + INNER JOIN ' . $this->tableNames->node() . ' n ON h.parentnodeanchor = n.relationanchorpoint WHERE n.nodeaggregateid = :nodeAggregateId AND h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash IN (:dimensionSpacePointHashes)', @@ -523,8 +495,8 @@ public function findIngoingHierarchyRelationsForNodeAggregate( ): array { $relations = []; - $query = 'SELECT h.* FROM ' . $this->tableNamePrefix . '_hierarchyrelation h - INNER JOIN ' . $this->tableNamePrefix . '_node n ON h.childnodeanchor = n.relationanchorpoint + $query = 'SELECT h.* FROM ' . $this->tableNames->hierarchyRelation() . ' h + INNER JOIN ' . $this->tableNames->node() . ' n ON h.childnodeanchor = n.relationanchorpoint WHERE n.nodeaggregateid = :nodeAggregateId AND h.contentstreamid = :contentStreamId'; $parameters = [ @@ -561,7 +533,7 @@ public function getAllContentStreamIdsAnchorPointIsContainedIn( foreach ( $this->getDatabaseConnection()->executeQuery( 'SELECT DISTINCT h.contentstreamid - FROM ' . $this->tableNamePrefix . '_hierarchyrelation h + FROM ' . $this->tableNames->hierarchyRelation() . ' h WHERE h.childnodeanchor = :nodeRelationAnchorPoint', [ 'nodeRelationAnchorPoint' => $nodeRelationAnchorPoint->value, @@ -574,85 +546,12 @@ public function getAllContentStreamIdsAnchorPointIsContainedIn( return $contentStreamIds; } - /** - * Finds all descendant node aggregate ids, indexed by dimension space point hash - * - * @param ContentStreamId $contentStreamId - * @param NodeAggregateId $entryNodeAggregateId - * @param DimensionSpacePointSet $affectedDimensionSpacePoints - * @return array|NodeAggregateId[][] - * @throws DBALException - */ - public function findDescendantNodeAggregateIds( - ContentStreamId $contentStreamId, - NodeAggregateId $entryNodeAggregateId, - DimensionSpacePointSet $affectedDimensionSpacePoints - ): array { - $rows = $this->getDatabaseConnection()->executeQuery( - ' - -- ProjectionContentGraph::findDescendantNodeAggregateIds - - WITH RECURSIVE nestedNodes AS ( - -- -------------------------------- - -- INITIAL query: select the root nodes - -- -------------------------------- - SELECT - n.nodeaggregateid, - n.relationanchorpoint, - h.dimensionspacepointhash - FROM - ' . $this->tableNamePrefix . '_node n - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h - on h.childnodeanchor = n.relationanchorpoint - WHERE n.nodeaggregateid = :entryNodeAggregateId - AND h.contentstreamid = :contentStreamId - AND h.dimensionspacepointhash IN (:affectedDimensionSpacePointHashes) - - UNION - -- -------------------------------- - -- RECURSIVE query: do one "child" query step - -- -------------------------------- - SELECT - c.nodeaggregateid, - c.relationanchorpoint, - h.dimensionspacepointhash - FROM - nestedNodes p - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h - on h.parentnodeanchor = p.relationanchorpoint - INNER JOIN ' . $this->tableNamePrefix . '_node c - on h.childnodeanchor = c.relationanchorpoint - WHERE - h.contentstreamid = :contentStreamId - AND h.dimensionspacepointhash IN (:affectedDimensionSpacePointHashes) - ) - select nodeaggregateid, dimensionspacepointhash from nestedNodes - ', - [ - 'entryNodeAggregateId' => $entryNodeAggregateId->value, - 'contentStreamId' => $contentStreamId->value, - 'affectedDimensionSpacePointHashes' => $affectedDimensionSpacePoints->getPointHashes() - ], - [ - 'affectedDimensionSpacePointHashes' => Connection::PARAM_STR_ARRAY - ] - )->fetchAllAssociative(); - - $nodeAggregateIds = []; - foreach ($rows as $row) { - $nodeAggregateIds[$row['nodeaggregateid']][$row['dimensionspacepointhash']] - = NodeAggregateId::fromString($row['nodeaggregateid']); - } - - return $nodeAggregateIds; - } - /** * @param array $rawData */ protected function mapRawDataToHierarchyRelation(array $rawData): HierarchyRelation { - $dimensionspacepointRaw = $this->client->getConnection()->fetchOne('SELECT dimensionspacepoint FROM ' . $this->tableNamePrefix . '_dimensionspacepoints WHERE hash = :hash', ['hash' => $rawData['dimensionspacepointhash']]); + $dimensionspacepointRaw = $this->client->getConnection()->fetchOne('SELECT dimensionspacepoint FROM ' . $this->tableNames->dimensionSpacePoints() . ' WHERE hash = :hash', ['hash' => $rawData['dimensionspacepointhash']]); return new HierarchyRelation( NodeRelationAnchorPoint::fromInteger((int)$rawData['parentnodeanchor']), diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php new file mode 100644 index 00000000000..9fd82b24be9 --- /dev/null +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php @@ -0,0 +1,272 @@ +createQueryBuilder() + ->select('n.*, h.contentstreamid, h.name, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') + ->from($this->tableNames->node(), 'n') + ->innerJoin('n', $this->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('h', $this->tableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') + ->where('h.contentstreamid = :contentStreamId'); + + return $queryBuilder; + } + + public function buildChildNodeAggregateQuery(NodeAggregateId $parentNodeAggregateId, ContentStreamId $contentStreamId): QueryBuilder + { + return $this->createQueryBuilder() + ->select('cn.*, ch.name, ch.contentstreamid, ch.subtreetags, cdsp.dimensionspacepoint AS covereddimensionspacepoint') + ->from($this->tableNames->node(), 'pn') + ->innerJoin('pn', $this->tableNames->hierarchyRelation(), 'ph', 'ph.childnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->tableNames->hierarchyRelation(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('ch', $this->tableNames->dimensionSpacePoints(), 'cdsp', 'cdsp.hash = ch.dimensionspacepointhash') + ->innerJoin('ch', $this->tableNames->node(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') + ->where('pn.nodeaggregateid = :parentNodeAggregateId') + ->andWhere('ph.contentstreamid = :contentStreamId') + ->andWhere('ch.contentstreamid = :contentStreamId') + ->orderBy('ch.position') + ->setParameters([ + 'parentNodeAggregateId' => $parentNodeAggregateId->value, + 'contentStreamId' => $contentStreamId->value, + ]); + } + + public function buildFindRootNodeAggregatesQuery(ContentStreamId $contentStreamId, FindRootNodeAggregatesFilter $filter): QueryBuilder + { + $queryBuilder = $this->buildBasicNodeAggregateQuery() + ->andWhere('h.parentnodeanchor = :rootEdgeParentAnchorId') + ->setParameters([ + 'contentStreamId' => $contentStreamId->value, + 'rootEdgeParentAnchorId' => NodeRelationAnchorPoint::forRootEdge()->value, + ]); + + if ($filter->nodeTypeName !== null) { + $queryBuilder->andWhere('n.nodetypename = :nodeTypeName')->setParameter('nodeTypeName', $filter->nodeTypeName->value); + } + + return $queryBuilder; + } + + public function buildBasicNodeQuery(ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint, string $nodeTableAlias = 'n', string $select = 'n.*, h.name, h.subtreetags'): QueryBuilder + { + return $this->createQueryBuilder() + ->select($select) + ->from($this->tableNames->node(), $nodeTableAlias) + ->innerJoin($nodeTableAlias, $this->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = ' . $nodeTableAlias . '.relationanchorpoint') + ->where('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $contentStreamId->value) + ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); + } + + public function buildBasicChildNodesQuery(NodeAggregateId $parentNodeAggregateId, ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder + { + return $this->createQueryBuilder() + ->select('n.*, h.name, h.subtreetags') + ->from($this->tableNames->node(), 'pn') + ->innerJoin('pn', $this->tableNames->hierarchyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->tableNames->node(), 'n', 'h.childnodeanchor = n.relationanchorpoint') + ->where('pn.nodeaggregateid = :parentNodeAggregateId')->setParameter('parentNodeAggregateId', $parentNodeAggregateId->value) + ->andWhere('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $contentStreamId->value) + ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); + } + + public function buildBasicParentNodeQuery(NodeAggregateId $childNodeAggregateId, ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder + { + return $this->createQueryBuilder() + ->select('pn.*, ch.name, ch.subtreetags') + ->from($this->tableNames->node(), 'pn') + ->innerJoin('pn', $this->tableNames->hierarchyRelation(), 'ph', 'ph.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->tableNames->node(), 'cn', 'cn.relationanchorpoint = ph.childnodeanchor') + ->innerJoin('pn', $this->tableNames->hierarchyRelation(), 'ch', 'ch.childnodeanchor = pn.relationanchorpoint') + ->where('cn.nodeaggregateid = :childNodeAggregateId')->setParameter('childNodeAggregateId', $childNodeAggregateId->value) + ->andWhere('ph.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $contentStreamId->value) + ->andWhere('ch.contentstreamid = :contentStreamId') + ->andWhere('ph.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash) + ->andWhere('ch.dimensionspacepointhash = :dimensionSpacePointHash'); + } + + public function buildBasicNodeSiblingsQuery(bool $preceding, NodeAggregateId $siblingNodeAggregateId, ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder + { + $sharedSubQuery = $this->createQueryBuilder() + ->from($this->tableNames->hierarchyRelation(), 'sh') + ->innerJoin('sh', $this->tableNames->node(), 'sn', 'sn.relationanchorpoint = sh.childnodeanchor') + ->where('sn.nodeaggregateid = :siblingNodeAggregateId') + ->andWhere('sh.contentstreamid = :contentStreamId') + ->andWhere('sh.dimensionspacepointhash = :dimensionSpacePointHash'); + + $parentNodeAnchorSubQuery = (clone $sharedSubQuery)->select('sh.parentnodeanchor'); + $siblingPositionSubQuery = (clone $sharedSubQuery)->select('sh.position'); + + return $this->buildBasicNodeQuery($contentStreamId, $dimensionSpacePoint) + ->andWhere('h.parentnodeanchor = (' . $parentNodeAnchorSubQuery->getSQL() . ')') + ->andWhere('n.nodeaggregateid != :siblingNodeAggregateId')->setParameter('siblingNodeAggregateId', $siblingNodeAggregateId->value) + ->andWhere('h.position ' . ($preceding ? '<' : '>') . ' (' . $siblingPositionSubQuery->getSQL() . ')') + ->orderBy('h.position', $preceding ? 'DESC' : 'ASC'); + } + + public function buildBasicNodesCteQuery(NodeAggregateId $entryNodeAggregateId, ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint, string $cteName = 'ancestry', string $cteAlias = 'pn'): QueryBuilder + { + return $this->createQueryBuilder() + ->select('*') + ->from($cteName, $cteAlias) + ->setParameter('contentStreamId', $contentStreamId->value) + ->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash) + ->setParameter('entryNodeAggregateId', $entryNodeAggregateId->value); + } + + public function addNodeTypeCriteria(QueryBuilder $queryBuilder, ExpandedNodeTypeCriteria $constraintsWithSubNodeTypes, string $nodeTableAlias = 'n'): void + { + $nodeTablePrefix = $nodeTableAlias === '' ? '' : $nodeTableAlias . '.'; + $allowanceQueryPart = ''; + if (!$constraintsWithSubNodeTypes->explicitlyAllowedNodeTypeNames->isEmpty()) { + $allowanceQueryPart = $queryBuilder->expr()->in($nodeTablePrefix . 'nodetypename', ':explicitlyAllowedNodeTypeNames'); + $queryBuilder->setParameter('explicitlyAllowedNodeTypeNames', $constraintsWithSubNodeTypes->explicitlyAllowedNodeTypeNames->toStringArray(), Connection::PARAM_STR_ARRAY); + } + $denyQueryPart = ''; + if (!$constraintsWithSubNodeTypes->explicitlyDisallowedNodeTypeNames->isEmpty()) { + $denyQueryPart = $queryBuilder->expr()->notIn($nodeTablePrefix . 'nodetypename', ':explicitlyDisallowedNodeTypeNames'); + $queryBuilder->setParameter('explicitlyDisallowedNodeTypeNames', $constraintsWithSubNodeTypes->explicitlyDisallowedNodeTypeNames->toStringArray(), Connection::PARAM_STR_ARRAY); + } + if ($allowanceQueryPart && $denyQueryPart) { + if ($constraintsWithSubNodeTypes->isWildCardAllowed) { + $queryBuilder->andWhere($queryBuilder->expr()->or($allowanceQueryPart, $denyQueryPart)); + } else { + $queryBuilder->andWhere($queryBuilder->expr()->and($allowanceQueryPart, $denyQueryPart)); + } + } elseif ($allowanceQueryPart && !$constraintsWithSubNodeTypes->isWildCardAllowed) { + $queryBuilder->andWhere($allowanceQueryPart); + } elseif ($denyQueryPart) { + $queryBuilder->andWhere($denyQueryPart); + } + } + + public function addSearchTermConstraints(QueryBuilder $queryBuilder, SearchTerm $searchTerm, string $nodeTableAlias = 'n'): void + { + $queryBuilder->andWhere('JSON_SEARCH(' . $nodeTableAlias . '.properties, "one", :searchTermPattern, NULL, "$.*.value") IS NOT NULL')->setParameter('searchTermPattern', '%' . $searchTerm->term . '%'); + } + + public function addPropertyValueConstraints(QueryBuilder $queryBuilder, PropertyValueCriteriaInterface $propertyValue, string $nodeTableAlias = 'n'): void + { + $queryBuilder->andWhere($this->propertyValueConstraints($queryBuilder, $propertyValue, $nodeTableAlias)); + } + + private function propertyValueConstraints(QueryBuilder $queryBuilder, PropertyValueCriteriaInterface $propertyValue, string $nodeTableAlias): string + { + return match ($propertyValue::class) { + AndCriteria::class => (string)$queryBuilder->expr()->and($this->propertyValueConstraints($queryBuilder, $propertyValue->criteria1, $nodeTableAlias), $this->propertyValueConstraints($queryBuilder, $propertyValue->criteria2, $nodeTableAlias)), + NegateCriteria::class => 'NOT (' . $this->propertyValueConstraints($queryBuilder, $propertyValue->criteria, $nodeTableAlias) . ')', + OrCriteria::class => (string)$queryBuilder->expr()->or($this->propertyValueConstraints($queryBuilder, $propertyValue->criteria1, $nodeTableAlias), $this->propertyValueConstraints($queryBuilder, $propertyValue->criteria2, $nodeTableAlias)), + PropertyValueContains::class => $this->searchPropertyValueStatement($queryBuilder, $propertyValue->propertyName, '%' . $propertyValue->value . '%', $nodeTableAlias, $propertyValue->caseSensitive), + PropertyValueEndsWith::class => $this->searchPropertyValueStatement($queryBuilder, $propertyValue->propertyName, '%' . $propertyValue->value, $nodeTableAlias, $propertyValue->caseSensitive), + PropertyValueEquals::class => is_string($propertyValue->value) ? $this->searchPropertyValueStatement($queryBuilder, $propertyValue->propertyName, $propertyValue->value, $nodeTableAlias, $propertyValue->caseSensitive) : $this->comparePropertyValueStatement($queryBuilder, $propertyValue->propertyName, $propertyValue->value, '=', $nodeTableAlias), + PropertyValueGreaterThan::class => $this->comparePropertyValueStatement($queryBuilder, $propertyValue->propertyName, $propertyValue->value, '>', $nodeTableAlias), + PropertyValueGreaterThanOrEqual::class => $this->comparePropertyValueStatement($queryBuilder, $propertyValue->propertyName, $propertyValue->value, '>=', $nodeTableAlias), + PropertyValueLessThan::class => $this->comparePropertyValueStatement($queryBuilder, $propertyValue->propertyName, $propertyValue->value, '<', $nodeTableAlias), + PropertyValueLessThanOrEqual::class => $this->comparePropertyValueStatement($queryBuilder, $propertyValue->propertyName, $propertyValue->value, '<=', $nodeTableAlias), + PropertyValueStartsWith::class => $this->searchPropertyValueStatement($queryBuilder, $propertyValue->propertyName, $propertyValue->value . '%', $nodeTableAlias, $propertyValue->caseSensitive), + default => throw new \InvalidArgumentException(sprintf('Invalid/unsupported property value criteria "%s"', get_debug_type($propertyValue)), 1679561062), + }; + } + + private function comparePropertyValueStatement(QueryBuilder $queryBuilder, PropertyName $propertyName, string|int|float|bool $value, string $operator, string $nodeTableAlias): string + { + $paramName = $this->createUniqueParameterName(); + $paramType = match (gettype($value)) { + 'boolean' => ParameterType::BOOLEAN, + 'integer' => ParameterType::INTEGER, + default => ParameterType::STRING, + }; + $queryBuilder->setParameter($paramName, $value, $paramType); + + return $this->extractPropertyValue($propertyName, $nodeTableAlias) . ' ' . $operator . ' :' . $paramName; + } + + public function extractPropertyValue(PropertyName $propertyName, string $nodeTableAlias): string + { + try { + $escapedPropertyName = addslashes(json_encode($propertyName->value, JSON_THROW_ON_ERROR)); + } catch (\JsonException $e) { + throw new \RuntimeException(sprintf('Failed to escape property name: %s', $e->getMessage()), 1679394579, $e); + } + + return 'JSON_EXTRACT(' . $nodeTableAlias . '.properties, \'$.' . $escapedPropertyName . '.value\')'; + } + + private function searchPropertyValueStatement(QueryBuilder $queryBuilder, PropertyName $propertyName, string|bool|int|float $value, string $nodeTableAlias, bool $caseSensitive): string + { + try { + $escapedPropertyName = addslashes(json_encode($propertyName->value, JSON_THROW_ON_ERROR)); + } catch (\JsonException $e) { + throw new \RuntimeException(sprintf('Failed to escape property name: %s', $e->getMessage()), 1679394579, $e); + } + if (is_bool($value)) { + return 'JSON_SEARCH(' . $nodeTableAlias . '.properties, \'one\', \'' . ($value ? 'true' : 'false') . '\', NULL, \'$.' . $escapedPropertyName . '.value\') IS NOT NULL'; + } + $paramName = $this->createUniqueParameterName(); + $queryBuilder->setParameter($paramName, $value); + if ($caseSensitive) { + return 'JSON_SEARCH(' . $nodeTableAlias . '.properties COLLATE utf8mb4_bin, \'one\', :' . $paramName . ' COLLATE utf8mb4_bin, NULL, \'$.' . $escapedPropertyName . '.value\') IS NOT NULL'; + } + + return 'JSON_SEARCH(' . $nodeTableAlias . '.properties, \'one\', :' . $paramName . ', NULL, \'$.' . $escapedPropertyName . '.value\') IS NOT NULL'; + } + + /** + * @return QueryBuilder + * @throws DBALException + */ + public function buildfindUsedNodeTypeNamesQuery(): QueryBuilder + { + return $this->createQueryBuilder() + ->select('DISTINCT nodetypename') + ->from($this->tableNames->node()); + } + + private function createQueryBuilder(): QueryBuilder + { + return $this->connection->createQueryBuilder(); + } + + private function createUniqueParameterName(): string + { + return 'param_' . str_replace('-', '', UuidFactory::create()); + } +} diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperGraphFactory.php b/Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperGraphFactory.php new file mode 100644 index 00000000000..ebb74ab5035 --- /dev/null +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperGraphFactory.php @@ -0,0 +1,62 @@ +contentRepositoryId->value, + 'Workspace' + )); + + $row = $this->databaseClient->getConnection()->executeQuery( + ' + SELECT * FROM ' . $tableName . ' + WHERE workspaceName = :workspaceName + ', + [ + 'workspaceName' => $workspaceName->value, + ] + )->fetchAssociative(); + + if ($row === false) { + throw new ContentStreamDoesNotExistYet('The workspace "' . $workspaceName->value . '" does not exist.', 1714839710); + } + + return $this->buildForWorkspaceAndContentStream($workspaceName, ContentStreamId::fromString($row['currentcontentstreamid'])); + } + + public function buildForWorkspaceAndContentStream(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphInterface + { + return new ContentHyperGraph($this->databaseClient, $this->nodeFactory, $this->contentRepositoryId, $this->nodeTypeManager, $this->tableNamePrefix, $workspaceName, $contentStreamId); + } +} diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Projection/HypergraphProjection.php b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Projection/HypergraphProjection.php index 114df60ed58..794b625b1cc 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Projection/HypergraphProjection.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Projection/HypergraphProjection.php @@ -26,9 +26,8 @@ use Neos\ContentGraph\PostgreSQLAdapter\Domain\Projection\Feature\NodeVariation; use Neos\ContentGraph\PostgreSQLAdapter\Domain\Projection\Feature\SubtreeTagging; use Neos\ContentGraph\PostgreSQLAdapter\Domain\Projection\SchemaBuilder\HypergraphSchemaBuilder; -use Neos\ContentGraph\PostgreSQLAdapter\Domain\Repository\ContentHypergraph; -use Neos\ContentGraph\PostgreSQLAdapter\Domain\Repository\NodeFactory; use Neos\ContentGraph\PostgreSQLAdapter\Infrastructure\PostgresDbalClientInterface; +use Neos\ContentRepository\Core\ContentGraphFinder; use Neos\ContentRepository\Core\EventStore\EventInterface; use Neos\ContentRepository\Core\Feature\ContentStreamForking\Event\ContentStreamWasForked; use Neos\ContentRepository\Core\Feature\NodeCreation\Event\NodeAggregateWithNodeWasCreated; @@ -45,18 +44,16 @@ use Neos\ContentRepository\Core\Feature\SubtreeTagging\Event\SubtreeWasUntagged; use Neos\ContentRepository\Core\Infrastructure\DbalCheckpointStorage; use Neos\ContentRepository\Core\Infrastructure\DbalSchemaDiff; -use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\Projection\CheckpointStorageStatusType; use Neos\ContentRepository\Core\Projection\ProjectionInterface; use Neos\ContentRepository\Core\Projection\ProjectionStatus; -use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\EventStore\Model\Event\SequenceNumber; use Neos\EventStore\Model\EventEnvelope; /** * The alternate reality-aware hypergraph projector for the PostgreSQL backend via Doctrine DBAL * - * @implements ProjectionInterface + * @implements ProjectionInterface * @internal the parent Content Graph is public */ final class HypergraphProjection implements ProjectionInterface @@ -71,20 +68,13 @@ final class HypergraphProjection implements ProjectionInterface use NodeTypeChange; use NodeVariation; - /** - * @var ContentHypergraph|null Cache for the content graph returned by {@see getState()}, - * so that always the same instance is returned - */ - private ?ContentHypergraph $contentHypergraph = null; private DbalCheckpointStorage $checkpointStorage; private ProjectionHypergraph $projectionHypergraph; public function __construct( private readonly PostgresDbalClientInterface $databaseClient, - private readonly NodeFactory $nodeFactory, - private readonly ContentRepositoryId $contentRepositoryId, - private readonly NodeTypeManager $nodeTypeManager, private readonly string $tableNamePrefix, + private readonly ContentGraphFinder $contentGraphFinder ) { $this->projectionHypergraph = new ProjectionHypergraph($this->databaseClient, $this->tableNamePrefix); $this->checkpointStorage = new DbalCheckpointStorage( @@ -159,11 +149,6 @@ public function reset(): void $this->checkpointStorage->acquireLock(); $this->checkpointStorage->updateAndReleaseLock(SequenceNumber::none()); - - //$contentGraph = $this->getState(); - //foreach ($contentGraph->getSubgraphs() as $subgraph) { - // $subgraph->inMemoryCache->enable(); - //} } private function truncateDatabaseTables(): void @@ -242,18 +227,9 @@ public function getCheckpointStorage(): DbalCheckpointStorage return $this->checkpointStorage; } - public function getState(): ContentHypergraph + public function getState(): ContentGraphFinder { - if (!$this->contentHypergraph) { - $this->contentHypergraph = new ContentHypergraph( - $this->databaseClient, - $this->nodeFactory, - $this->contentRepositoryId, - $this->nodeTypeManager, - $this->tableNamePrefix - ); - } - return $this->contentHypergraph; + return $this->contentGraphFinder; } protected function getProjectionHypergraph(): ProjectionHypergraph diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php index 49f9b7af5be..1ff5fbb1dbc 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php @@ -35,6 +35,7 @@ use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** * The PostgreSQL adapter content hypergraph @@ -59,22 +60,23 @@ public function __construct( NodeFactory $nodeFactory, private readonly ContentRepositoryId $contentRepositoryId, private readonly NodeTypeManager $nodeTypeManager, - private readonly string $tableNamePrefix + private readonly string $tableNamePrefix, + public readonly WorkspaceName $workspaceName, + public readonly ContentStreamId $contentStreamId ) { $this->databaseClient = $databaseClient; $this->nodeFactory = $nodeFactory; } public function getSubgraph( - ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint, VisibilityConstraints $visibilityConstraints ): ContentSubgraphInterface { - $index = $contentStreamId->value . '-' . $dimensionSpacePoint->hash . '-' . $visibilityConstraints->getHash(); + $index = $this->contentStreamId->value . '-' . $dimensionSpacePoint->hash . '-' . $visibilityConstraints->getHash(); if (!isset($this->subhypergraphs[$index])) { $this->subhypergraphs[$index] = new ContentSubhypergraph( $this->contentRepositoryId, - $contentStreamId, + $this->contentStreamId, $dimensionSpacePoint, $visibilityConstraints, $this->databaseClient, @@ -88,11 +90,9 @@ public function getSubgraph( } public function findRootNodeAggregateByType( - ContentStreamId $contentStreamId, NodeTypeName $nodeTypeName ): NodeAggregate { $rootNodeAggregates = $this->findRootNodeAggregates( - $contentStreamId, FindRootNodeAggregatesFilter::create(nodeTypeName: $nodeTypeName) ); @@ -118,7 +118,6 @@ public function findRootNodeAggregateByType( } public function findRootNodeAggregates( - ContentStreamId $contentStreamId, FindRootNodeAggregatesFilter $filter, ): NodeAggregates { throw new \BadMethodCallException('method findRootNodeAggregates is not implemented yet.', 1645782874); @@ -128,17 +127,15 @@ public function findRootNodeAggregates( * @return \Iterator */ public function findNodeAggregatesByType( - ContentStreamId $contentStreamId, NodeTypeName $nodeTypeName ): \Iterator { return new \Generator(); } public function findNodeAggregateById( - ContentStreamId $contentStreamId, NodeAggregateId $nodeAggregateId ): ?NodeAggregate { - $query = HypergraphQuery::create($contentStreamId, $this->tableNamePrefix, true); + $query = HypergraphQuery::create($this->contentStreamId, $this->tableNamePrefix, true); $query = $query->withNodeAggregateId($nodeAggregateId); $nodeRows = $query->execute($this->getDatabaseConnection())->fetchAllAssociative(); @@ -150,7 +147,6 @@ public function findNodeAggregateById( } public function findParentNodeAggregateByChildOriginDimensionSpacePoint( - ContentStreamId $contentStreamId, NodeAggregateId $childNodeAggregateId, OriginDimensionSpacePoint $childOriginDimensionSpacePoint ): ?NodeAggregate { @@ -172,7 +168,7 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint( AND ch.contentstreamid = :contentStreamId )'; $parameters = [ - 'contentStreamId' => $contentStreamId->value, + 'contentStreamId' => $this->contentStreamId->value, 'childNodeAggregateId' => $childNodeAggregateId->value, 'childOriginDimensionSpacePointHash' => $childOriginDimensionSpacePoint->hash ]; @@ -192,10 +188,9 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint( * @return iterable */ public function findParentNodeAggregates( - ContentStreamId $contentStreamId, NodeAggregateId $childNodeAggregateId ): iterable { - $query = HypergraphParentQuery::create($contentStreamId, $this->tableNamePrefix); + $query = HypergraphParentQuery::create($this->contentStreamId, $this->tableNamePrefix); $query = $query->withChildNodeAggregateId($childNodeAggregateId); $nodeRows = $query->execute($this->getDatabaseConnection())->fetchAllAssociative(); @@ -210,11 +205,10 @@ public function findParentNodeAggregates( * @return iterable */ public function findChildNodeAggregates( - ContentStreamId $contentStreamId, NodeAggregateId $parentNodeAggregateId ): iterable { $query = HypergraphChildQuery::create( - $contentStreamId, + $this->contentStreamId, $parentNodeAggregateId, $this->tableNamePrefix ); @@ -231,12 +225,11 @@ public function findChildNodeAggregates( * @return iterable */ public function findChildNodeAggregatesByName( - ContentStreamId $contentStreamId, NodeAggregateId $parentNodeAggregateId, NodeName $name ): iterable { $query = HypergraphChildQuery::create( - $contentStreamId, + $this->contentStreamId, $parentNodeAggregateId, $this->tableNamePrefix ); @@ -254,11 +247,10 @@ public function findChildNodeAggregatesByName( * @return iterable */ public function findTetheredChildNodeAggregates( - ContentStreamId $contentStreamId, NodeAggregateId $parentNodeAggregateId ): iterable { $query = HypergraphChildQuery::create( - $contentStreamId, + $this->contentStreamId, $parentNodeAggregateId, $this->tableNamePrefix ); @@ -270,14 +262,13 @@ public function findTetheredChildNodeAggregates( } public function getDimensionSpacePointsOccupiedByChildNodeName( - ContentStreamId $contentStreamId, NodeName $nodeName, NodeAggregateId $parentNodeAggregateId, OriginDimensionSpacePoint $parentNodeOriginDimensionSpacePoint, DimensionSpacePointSet $dimensionSpacePointsToCheck ): DimensionSpacePointSet { $query = HypergraphChildQuery::create( - $contentStreamId, + $this->contentStreamId, $parentNodeAggregateId, $this->tableNamePrefix, ['ch.dimensionspacepoint, ch.dimensionspacepointhash'] @@ -318,4 +309,14 @@ private function getDatabaseConnection(): DatabaseConnection { return $this->databaseClient->getConnection(); } + + public function getWorkspaceName(): WorkspaceName + { + return $this->workspaceName; + } + + public function getContentStreamId(): ContentStreamId + { + return $this->contentStreamId; + } } diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/HypergraphProjectionFactory.php b/Neos.ContentGraph.PostgreSQLAdapter/src/HypergraphProjectionFactory.php index 71c076400b6..3f8189c6d01 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/HypergraphProjectionFactory.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/HypergraphProjectionFactory.php @@ -7,6 +7,7 @@ use Neos\ContentGraph\PostgreSQLAdapter\Domain\Projection\HypergraphProjection; use Neos\ContentGraph\PostgreSQLAdapter\Domain\Repository\NodeFactory; use Neos\ContentGraph\PostgreSQLAdapter\Infrastructure\PostgresDbalClientInterface; +use Neos\ContentRepository\Core\ContentGraphFinder; use Neos\ContentRepository\Core\Factory\ProjectionFactoryDependencies; use Neos\ContentRepository\Core\Projection\ProjectionFactoryInterface; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; @@ -36,16 +37,16 @@ public function build( $projectionFactoryDependencies->contentRepositoryId ); - return new HypergraphProjection( - $this->dbalClient, - new NodeFactory( - $projectionFactoryDependencies->contentRepositoryId, - $projectionFactoryDependencies->nodeTypeManager, - $projectionFactoryDependencies->propertyConverter - ), + $nodeFactory = new NodeFactory( $projectionFactoryDependencies->contentRepositoryId, $projectionFactoryDependencies->nodeTypeManager, - $tableNamePrefix + $projectionFactoryDependencies->propertyConverter + ); + + return new HypergraphProjection( + $this->dbalClient, + $tableNamePrefix, + new ContentGraphFinder(new ContentHyperGraphFactory($this->dbalClient, $nodeFactory, $projectionFactoryDependencies->contentRepositoryId, $projectionFactoryDependencies->nodeTypeManager, $tableNamePrefix)) ); } } diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/01-RootNodeCreation/01-CreateRootNodeAggregateWithNode_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/01-RootNodeCreation/01-CreateRootNodeAggregateWithNode_ConstraintChecks.feature index b1ff005357c..d7c4a1716df 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/01-RootNodeCreation/01-CreateRootNodeAggregateWithNode_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/01-RootNodeCreation/01-CreateRootNodeAggregateWithNode_ConstraintChecks.feature @@ -40,7 +40,7 @@ Feature: Create a root node aggregate | workspaceName | "i-do-not-exist" | | nodeAggregateId | "nody-mc-nodeface" | | nodeTypeName | "Neos.ContentRepository:Root" | - Then the last command should have thrown an exception of type "ContentStreamDoesNotExistYet" + Then the last command should have thrown an exception of type "WorkspaceDoesNotExist" Scenario: Try to create a root node aggregate in a closed content stream: When the command CloseContentStream is executed with payload: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/01-CreateNodeAggregateWithNode_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/01-CreateNodeAggregateWithNode_ConstraintChecks.feature index 7ca3ef355a9..da3862003de 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/01-CreateNodeAggregateWithNode_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/01-CreateNodeAggregateWithNode_ConstraintChecks.feature @@ -54,7 +54,7 @@ Feature: Create node aggregate with node | parentNodeAggregateId | "lady-eleonode-rootford" | | nodeName | "document" | - Then the last command should have thrown an exception of type "ContentStreamDoesNotExistYet" + Then the last command should have thrown an exception of type "WorkspaceDoesNotExist" Scenario: Try to create a node aggregate in a workspace whose content stream is closed: When the command CloseContentStream is executed with payload: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/03-NodeVariation/01-CreateNodeVariant_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/03-NodeVariation/01-CreateNodeVariant_ConstraintChecks.feature index d4d2be10e79..2105bfd50e9 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/03-NodeVariation/01-CreateNodeVariant_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/03-NodeVariation/01-CreateNodeVariant_ConstraintChecks.feature @@ -48,7 +48,7 @@ Feature: Create node variant | nodeAggregateId | "sir-david-nodenborough" | | sourceOrigin | {"market":"CH", "language":"gsw"} | | targetOrigin | {"market":"DE", "language":"de"} | - Then the last command should have thrown an exception of type "ContentStreamDoesNotExistYet" + Then the last command should have thrown an exception of type "WorkspaceDoesNotExist" Scenario: Try to create a variant in a workspace that does not exist When the command CloseContentStream is executed with payload: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/04-NodeModification/01-SetNodeProperties_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/04-NodeModification/01-SetNodeProperties_ConstraintChecks.feature index 2ac617f4d44..ab674a8bf39 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/04-NodeModification/01-SetNodeProperties_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/04-NodeModification/01-SetNodeProperties_ConstraintChecks.feature @@ -45,7 +45,7 @@ Feature: Set node properties: Constraint checks | nodeAggregateId | "nody-mc-nodeface" | | originDimensionSpacePoint | {"language":"de"} | | propertyValues | {"text":"New text"} | - Then the last command should have thrown an exception of type "ContentStreamDoesNotExistYet" + Then the last command should have thrown an exception of type "WorkspaceDoesNotExist" Scenario: Try to set properties in a workspace whose content stream is closed When the command CloseContentStream is executed with payload: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/01-SetNodeReferences_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/01-SetNodeReferences_ConstraintChecks.feature index c374e45a337..2c4aefd3639 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/01-SetNodeReferences_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/01-SetNodeReferences_ConstraintChecks.feature @@ -77,7 +77,7 @@ Feature: Constraint checks on SetNodeReferences | sourceNodeAggregateId | "source-nodandaise" | | referenceName | "referenceProperty" | | references | [{"target":"anthony-destinode"}] | - Then the last command should have thrown an exception of type "ContentStreamDoesNotExistYet" with code 1710407870 + Then the last command should have thrown an exception of type "WorkspaceDoesNotExist" with code 1513924741 # checks for sourceNodeAggregateId Scenario: Try to reference nodes in a non-existent node aggregate diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/01-DisableNodeAggregate_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/01-DisableNodeAggregate_ConstraintChecks.feature index 82db6e57770..c1be0796564 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/01-DisableNodeAggregate_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/01-DisableNodeAggregate_ConstraintChecks.feature @@ -39,7 +39,7 @@ Feature: Constraint checks on node aggregate disabling | workspaceName | "i-do-not-exist" | | nodeAggregateId | "sir-david-nodenborough" | | nodeVariantSelectionStrategy | "allVariants" | - Then the last command should have thrown an exception of type "ContentStreamDoesNotExistYet" + Then the last command should have thrown an exception of type "WorkspaceDoesNotExist" Scenario: Try to disable a node aggregate in a workspace whose content stream is closed When the command CloseContentStream is executed with payload: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/04-EnableNodeAggregate_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/04-EnableNodeAggregate_ConstraintChecks.feature index 44b5734afd4..311d3d0d487 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/04-EnableNodeAggregate_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/04-EnableNodeAggregate_ConstraintChecks.feature @@ -39,7 +39,7 @@ Feature: Enable a node aggregate | workspaceName | "i-do-not-exist" | | nodeAggregateId | "sir-david-nodenborough" | | nodeVariantSelectionStrategy | "allVariants" | - Then the last command should have thrown an exception of type "ContentStreamDoesNotExistYet" + Then the last command should have thrown an exception of type "WorkspaceDoesNotExist" Scenario: Try to enable a non-existing node aggregate When the command EnableNodeAggregate is executed with payload and exceptions are caught: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/01-RemoveNodeAggregate_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/01-RemoveNodeAggregate_ConstraintChecks.feature index bc0798286f2..301e1937186 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/01-RemoveNodeAggregate_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/01-RemoveNodeAggregate_ConstraintChecks.feature @@ -44,7 +44,7 @@ Feature: Remove NodeAggregate | nodeAggregateId | "sir-david-nodenborough" | | coveredDimensionSpacePoint | {"language":"de"} | | nodeVariantSelectionStrategy | "allVariants" | - Then the last command should have thrown an exception of type "ContentStreamDoesNotExistYet" + Then the last command should have thrown an exception of type "WorkspaceDoesNotExist" Scenario: Try to remove a node aggregate in a workspace whose content stream is closed When the command CloseContentStream is executed with payload: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature index 47ae7031caf..721f5a667ec 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature @@ -60,7 +60,7 @@ Feature: Move node to a new parent / within the current parent before a sibling | nodeAggregateId | "sir-david-nodenborough" | | dimensionSpacePoint | {"example": "source"} | | relationDistributionStrategy | "scatter" | - Then the last command should have thrown an exception of type "ContentStreamDoesNotExistYet" + Then the last command should have thrown an exception of type "WorkspaceDoesNotExist" Scenario: Try to move a node in a workspace whose content stream is closed: When the command CloseContentStream is executed with payload: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRenaming/01_ChangeNodeAggregateName_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRenaming/01_ChangeNodeAggregateName_ConstraintChecks.feature index 542dbc3fb26..fe721eb01a0 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRenaming/01_ChangeNodeAggregateName_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRenaming/01_ChangeNodeAggregateName_ConstraintChecks.feature @@ -42,7 +42,7 @@ Feature: Change node name | workspaceName | "i-do-not-exist" | | nodeAggregateId | "sir-david-nodenborough" | | newNodeName | "new-name" | - Then the last command should have thrown an exception of type "ContentStreamDoesNotExistYet" + Then the last command should have thrown an exception of type "WorkspaceDoesNotExist" Scenario: Try to rename a non-existing node aggregate When the command ChangeNodeAggregateName is executed with payload and exceptions are caught: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRetyping/ChangeNodeAggregateType_BasicErrorCases.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRetyping/ChangeNodeAggregateType_BasicErrorCases.feature index 597ab0036b9..9414ce67343 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRetyping/ChangeNodeAggregateType_BasicErrorCases.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRetyping/ChangeNodeAggregateType_BasicErrorCases.feature @@ -74,7 +74,7 @@ Feature: Change node aggregate type - basic error cases | nodeAggregateId | "sir-david-nodenborough" | | newNodeTypeName | "Neos.ContentRepository.Testing:ChildOfNodeTypeA" | | strategy | "happypath" | - Then the last command should have thrown an exception of type "ContentStreamDoesNotExistYet" + Then the last command should have thrown an exception of type "WorkspaceDoesNotExist" Scenario: Try to change the type on a non-existing node aggregate When the command ChangeNodeAggregateType was published with payload and exceptions are caught: diff --git a/Neos.ContentRepository.Core/Classes/CommandHandler/CommandBus.php b/Neos.ContentRepository.Core/Classes/CommandHandler/CommandBus.php index 1ce93c44e86..b77ce854cf6 100644 --- a/Neos.ContentRepository.Core/Classes/CommandHandler/CommandBus.php +++ b/Neos.ContentRepository.Core/Classes/CommandHandler/CommandBus.php @@ -4,6 +4,7 @@ namespace Neos\ContentRepository\Core\CommandHandler; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\EventStore\EventsToPublish; @@ -25,12 +26,12 @@ public function __construct(CommandHandlerInterface ...$handlers) $this->handlers = $handlers; } - public function handle(CommandInterface $command, ContentRepository $contentRepository): EventsToPublish + public function handle(CommandInterface $command, CommandHandlingDependencies $commandHandlingDependencies): EventsToPublish { // TODO fail if multiple handlers can handle the same command foreach ($this->handlers as $handler) { if ($handler->canHandle($command)) { - return $handler->handle($command, $contentRepository); + return $handler->handle($command, $commandHandlingDependencies); } } throw new \RuntimeException(sprintf('No handler found for Command "%s"', get_debug_type($command)), 1649582778); diff --git a/Neos.ContentRepository.Core/Classes/CommandHandler/CommandHandlerInterface.php b/Neos.ContentRepository.Core/Classes/CommandHandler/CommandHandlerInterface.php index 4a8ca5a5b5f..e190337c370 100644 --- a/Neos.ContentRepository.Core/Classes/CommandHandler/CommandHandlerInterface.php +++ b/Neos.ContentRepository.Core/Classes/CommandHandler/CommandHandlerInterface.php @@ -4,6 +4,7 @@ namespace Neos\ContentRepository\Core\CommandHandler; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\EventStore\EventsToPublish; @@ -18,5 +19,5 @@ interface CommandHandlerInterface { public function canHandle(CommandInterface $command): bool; - public function handle(CommandInterface $command, ContentRepository $contentRepository): EventsToPublish; + public function handle(CommandInterface $command, CommandHandlingDependencies $commandHandlingDependencies): EventsToPublish; } diff --git a/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php b/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php new file mode 100644 index 00000000000..c0b5270cc37 --- /dev/null +++ b/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php @@ -0,0 +1,95 @@ +value to ContentGraphInterface + * @var array + */ + private array $overridenContentGraphInstances = []; + + public function __construct(private readonly ContentRepository $contentRepository) + { + } + + public function handle(CommandInterface $command): CommandResult + { + return $this->contentRepository->handle($command); + } + + public function getWorkspaceFinder(): WorkspaceFinder + { + return $this->contentRepository->getWorkspaceFinder(); + } + + public function getContentStreamFinder(): ContentStreamFinder + { + return $this->contentRepository->getContentStreamFinder(); + } + + /** + * @throws WorkspaceDoesNotExist if the workspace does not exist + */ + public function getContentGraph(WorkspaceName $workspaceName): ContentGraphInterface + { + if (isset($this->overridenContentGraphInstances[$workspaceName->value])) { + return $this->overridenContentGraphInstances[$workspaceName->value]; + } + + return $this->contentRepository->getContentGraph($workspaceName); + } + + /** + * Stateful (dirty) override of the chosen ContentStreamId for a given workspace, it applies within the given closure. + * Implementations must ensure that requesting the contentStreamId for this workspace will resolve to the given + * override ContentStreamId and vice versa resolving the WorkspaceName from this ContentStreamId should result in the + * given WorkspaceName within the closure. + * + * @internal Used in write operations applying commands to a contentstream that will have WorkspaceName in the future + * but doesn't have one yet. + */ + public function overrideContentStreamId(WorkspaceName $workspaceName, ContentStreamId $contentStreamId, \Closure $fn): void + { + if (isset($this->overridenContentGraphInstances[$workspaceName->value])) { + throw new \RuntimeException('Contentstream override for this workspace already in effect, nesting not allowed.', 1715170938); + } + + $contentGraph = $this->contentRepository->projectionState(ContentGraphFinder::class)->getByWorkspaceNameAndContentStreamId($workspaceName, $contentStreamId); + $this->overridenContentGraphInstances[$workspaceName->value] = $contentGraph; + + try { + $fn(); + } finally { + unset($this->overridenContentGraphInstances[$workspaceName->value]); + } + } +} diff --git a/Neos.ContentRepository.Core/Classes/ContentGraphFactoryInterface.php b/Neos.ContentRepository.Core/Classes/ContentGraphFactoryInterface.php new file mode 100644 index 00000000000..2a766579751 --- /dev/null +++ b/Neos.ContentRepository.Core/Classes/ContentGraphFactoryInterface.php @@ -0,0 +1,35 @@ + + */ + private array $contentGraphInstances = []; + + public function __construct( + private readonly ContentGraphFactoryInterface $contentGraphFactory + ) { + } + + /** + * The default way to get a content graph to operate on. + * The currently assigned ContentStreamId for the given Workspace is resolved internally. + * + * @throws WorkspaceDoesNotExist if the provided workspace does not resolve to an existing content stream + * @see ContentRepository::getContentGraph() + */ + public function getByWorkspaceName(WorkspaceName $workspaceName): ContentGraphInterface + { + if (isset($this->contentGraphInstances[$workspaceName->value])) { + return $this->contentGraphInstances[$workspaceName->value]; + } + + $this->contentGraphInstances[$workspaceName->value] = $this->contentGraphFactory->buildForWorkspace($workspaceName); + return $this->contentGraphInstances[$workspaceName->value]; + } + + /** + * To release all held instances, in case a workspace/content stream relation needs to be reset + * + * @internal Should only be needed after write operations (which should take care on their own) + */ + public function forgetInstances(): void + { + $this->contentGraphInstances = []; + } + + /** + * For testing we allow getting an instance set by both parameters, effectively overriding the relationship at will + * + * @param WorkspaceName $workspaceName + * @param ContentStreamId $contentStreamId + * @internal Only for testing + */ + public function getByWorkspaceNameAndContentStreamId(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphInterface + { + return $this->contentGraphFactory->buildForWorkspaceAndContentStream($workspaceName, $contentStreamId); + } +} diff --git a/Neos.ContentRepository.Core/Classes/ContentRepository.php b/Neos.ContentRepository.Core/Classes/ContentRepository.php index 180e9fd0066..f6fc32cbc0e 100644 --- a/Neos.ContentRepository.Core/Classes/ContentRepository.php +++ b/Neos.ContentRepository.Core/Classes/ContentRepository.php @@ -38,7 +38,9 @@ use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceFinder; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryStatus; +use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepository\Core\SharedModel\User\UserIdProviderInterface; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\EventStore\EventStoreInterface; use Neos\EventStore\Model\Event\EventMetadata; use Neos\EventStore\Model\EventEnvelope; @@ -63,6 +65,8 @@ final class ContentRepository */ private array $projectionStateCache; + private CommandHandlingDependencies $commandHandlingDependencies; + /** * @internal use the {@see ContentRepositoryFactory::getOrBuild()} to instantiate @@ -80,6 +84,7 @@ public function __construct( private readonly UserIdProviderInterface $userIdProvider, private readonly ClockInterface $clock, ) { + $this->commandHandlingDependencies = new CommandHandlingDependencies($this); } /** @@ -96,7 +101,7 @@ public function handle(CommandInterface $command): CommandResult { // the commands only calculate which events they want to have published, but do not do the // publishing themselves - $eventsToPublish = $this->commandBus->handle($command, $this); + $eventsToPublish = $this->commandBus->handle($command, $this->commandHandlingDependencies); // TODO meaningful exception message $initiatingUserId = $this->userIdProvider->getUserId(); @@ -234,9 +239,12 @@ public function getNodeTypeManager(): NodeTypeManager return $this->nodeTypeManager; } - public function getContentGraph(): ContentGraphInterface + /** + * @throws WorkspaceDoesNotExist if the workspace does not exist + */ + public function getContentGraph(WorkspaceName $workspaceName): ContentGraphInterface { - return $this->projectionState(ContentGraphInterface::class); + return $this->projectionState(ContentGraphFinder::class)->getByWorkspaceName($workspaceName); } public function getWorkspaceFinder(): WorkspaceFinder diff --git a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php index b23f23851c0..bba7d784579 100644 --- a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php +++ b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php @@ -120,6 +120,7 @@ public function getOrBuild(): ContentRepository public function buildService( ContentRepositoryServiceFactoryInterface $serviceFactory ): ContentRepositoryServiceInterface { + $serviceFactoryDependencies = ContentRepositoryServiceFactoryDependencies::create( $this->projectionFactoryDependencies, $this->getOrBuild(), @@ -144,16 +145,16 @@ private function buildCommandBus(): CommandBus $this->projectionFactoryDependencies->nodeTypeManager, $this->projectionFactoryDependencies->contentDimensionZookeeper, $this->projectionFactoryDependencies->interDimensionalVariationGraph, - $this->projectionFactoryDependencies->propertyConverter + $this->projectionFactoryDependencies->propertyConverter, ), new DimensionSpaceCommandHandler( $this->projectionFactoryDependencies->contentDimensionZookeeper, - $this->projectionFactoryDependencies->interDimensionalVariationGraph + $this->projectionFactoryDependencies->interDimensionalVariationGraph, ), new NodeDuplicationCommandHandler( $this->projectionFactoryDependencies->nodeTypeManager, $this->projectionFactoryDependencies->contentDimensionZookeeper, - $this->projectionFactoryDependencies->interDimensionalVariationGraph + $this->projectionFactoryDependencies->interDimensionalVariationGraph, ) ); } diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php index f83f1cf83ae..01635643acb 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php @@ -14,7 +14,7 @@ namespace Neos\ContentRepository\Core\Feature\Common; -use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\Exception\DimensionSpacePointNotFound; @@ -27,6 +27,7 @@ use Neos\ContentRepository\Core\NodeType\NodeType; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\NodeType\NodeTypeName; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindChildNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindPrecedingSiblingNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindSucceedingSiblingNodesFilter; @@ -80,16 +81,18 @@ abstract protected function getAllowedDimensionSubspace(): DimensionSpacePointSe */ protected function requireContentStream( WorkspaceName $workspaceName, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): ContentStreamId { - $contentStreamId = ContentStreamIdOverride::resolveContentStreamIdForWorkspace($contentRepository, $workspaceName); - if (!$contentRepository->getContentStreamFinder()->hasContentStream($contentStreamId)) { + $contentStreamId = $commandHandlingDependencies->getContentGraph($workspaceName)->getContentStreamId(); + $state = $commandHandlingDependencies->getContentStreamFinder()->findStateForContentStream($contentStreamId); + if ($state === null) { throw new ContentStreamDoesNotExistYet( - 'Content stream "' . $contentStreamId->value . '" does not exist yet.', + 'Content stream for "' . $workspaceName->value . '" does not exist yet.', 1521386692 ); } - if ($contentRepository->getContentStreamFinder()->findStateForContentStream($contentStreamId) === ContentStreamState::STATE_CLOSED) { + + if ($state === ContentStreamState::STATE_CLOSED) { throw new ContentStreamIsClosed( 'Content stream "' . $contentStreamId->value . '" is closed.', 1710260081 @@ -156,19 +159,16 @@ protected function requireNodeTypeToNotBeOfTypeRoot(NodeType $nodeType): void } protected function requireRootNodeTypeToBeUnoccupied( - NodeTypeName $nodeTypeName, - ContentStreamId $contentStreamId, - ContentRepository $contentRepository + ContentGraphInterface $contentGraph, + NodeTypeName $nodeTypeName ): void { try { - $rootNodeAggregate = $contentRepository->getContentGraph()->findRootNodeAggregateByType( - $contentStreamId, - $nodeTypeName - ); - throw RootNodeAggregateTypeIsAlreadyOccupied::butWasExpectedNotTo($nodeTypeName); - } catch (RootNodeAggregateDoesNotExist $exception) { - // all is well + $contentGraph->findRootNodeAggregateByType($nodeTypeName); + } catch (RootNodeAggregateDoesNotExist $_) { + return; } + + throw RootNodeAggregateTypeIsAlreadyOccupied::butWasExpectedNotTo($nodeTypeName); } /** @@ -259,24 +259,22 @@ protected function requireNodeTypeToAllowNumberOfReferencesInReference(Serialize /** * NodeType and NodeName must belong together to the same node, which is the to-be-checked one. * - * @param ContentStreamId $contentStreamId + * @param ContentGraphInterface $contentGraph * @param NodeType $nodeType * @param NodeName|null $nodeName * @param array|NodeAggregateId[] $parentNodeAggregateIds * @throws NodeConstraintException */ protected function requireConstraintsImposedByAncestorsAreMet( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, NodeType $nodeType, ?NodeName $nodeName, - array $parentNodeAggregateIds, - ContentRepository $contentRepository + array $parentNodeAggregateIds ): void { foreach ($parentNodeAggregateIds as $parentNodeAggregateId) { $parentAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $parentNodeAggregateId, - $contentRepository + $contentGraph, + $parentNodeAggregateId ); if (!$parentAggregate->classification->isTethered()) { try { @@ -289,8 +287,7 @@ protected function requireConstraintsImposedByAncestorsAreMet( } foreach ( - $contentRepository->getContentGraph()->findParentNodeAggregates( - $contentStreamId, + $contentGraph->findParentNodeAggregates( $parentNodeAggregateId ) as $grandParentNodeAggregate ) { @@ -389,14 +386,9 @@ protected function areNodeTypeConstraintsImposedByGrandparentValid( ?NodeName $parentNodeName, NodeType $nodeType ): bool { - if ( - $parentNodeName + return !($parentNodeName && $grandParentsNodeType->hasTetheredNode($parentNodeName) - && !$this->getNodeTypeManager()->isNodeTypeAllowedAsChildToTetheredNode($grandParentsNodeType, $parentNodeName, $nodeType) - ) { - return false; - } - return true; + && !$this->getNodeTypeManager()->isNodeTypeAllowedAsChildToTetheredNode($grandParentsNodeType, $parentNodeName, $nodeType)); } /** @@ -404,12 +396,10 @@ protected function areNodeTypeConstraintsImposedByGrandparentValid( * @throws NodeAggregateCurrentlyDoesNotExist */ protected function requireProjectedNodeAggregate( - ContentStreamId $contentStreamId, - NodeAggregateId $nodeAggregateId, - ContentRepository $contentRepository + ContentGraphInterface $contentGraph, + NodeAggregateId $nodeAggregateId ): NodeAggregate { - $nodeAggregate = $contentRepository->getContentGraph()->findNodeAggregateById( - $contentStreamId, + $nodeAggregate = $contentGraph->findNodeAggregateById( $nodeAggregateId ); @@ -428,12 +418,10 @@ protected function requireProjectedNodeAggregate( * @throws NodeAggregateCurrentlyExists */ protected function requireProjectedNodeAggregateToNotExist( - ContentStreamId $contentStreamId, - NodeAggregateId $nodeAggregateId, - ContentRepository $contentRepository + ContentGraphInterface $contentGraph, + NodeAggregateId $nodeAggregateId ): void { - $nodeAggregate = $contentRepository->getContentGraph()->findNodeAggregateById( - $contentStreamId, + $nodeAggregate = $contentGraph->findNodeAggregateById( $nodeAggregateId ); @@ -449,14 +437,12 @@ protected function requireProjectedNodeAggregateToNotExist( * @throws NodeAggregateCurrentlyDoesNotExist */ public function requireProjectedParentNodeAggregate( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, NodeAggregateId $childNodeAggregateId, - OriginDimensionSpacePoint $childOriginDimensionSpacePoint, - ContentRepository $contentRepository + OriginDimensionSpacePoint $childOriginDimensionSpacePoint ): NodeAggregate { - $parentNodeAggregate = $contentRepository->getContentGraph() + $parentNodeAggregate = $contentGraph ->findParentNodeAggregateByChildOriginDimensionSpacePoint( - $contentStreamId, $childNodeAggregateId, $childOriginDimensionSpacePoint ); @@ -465,7 +451,7 @@ public function requireProjectedParentNodeAggregate( throw new NodeAggregateCurrentlyDoesNotExist( 'Parent node aggregate for ' . $childNodeAggregateId->value . ' does currently not exist in origin dimension space point ' . $childOriginDimensionSpacePoint->toJson() - . ' and content stream ' . $contentStreamId->value, + . ' and workspace ' . $contentGraph->getWorkspaceName()->value, 1645368685 ); } @@ -484,7 +470,7 @@ protected function requireNodeAggregateToCoverDimensionSpacePoint( throw new NodeAggregateDoesCurrentlyNotCoverDimensionSpacePoint( 'Node aggregate "' . $nodeAggregate->nodeAggregateId->value . '" does currently not cover dimension space point ' - . json_encode($dimensionSpacePoint) . '.', + . json_encode($dimensionSpacePoint, JSON_THROW_ON_ERROR) . '.', 1541678877 ); } @@ -536,10 +522,9 @@ protected function requireNodeAggregateToBeUntethered(NodeAggregate $nodeAggrega * @throws NodeAggregateIsDescendant */ protected function requireNodeAggregateToNotBeDescendant( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, NodeAggregate $nodeAggregate, - NodeAggregate $referenceNodeAggregate, - ContentRepository $contentRepository + NodeAggregate $referenceNodeAggregate ): void { if ($nodeAggregate->nodeAggregateId->equals($referenceNodeAggregate->nodeAggregateId)) { throw new NodeAggregateIsDescendant( @@ -549,16 +534,14 @@ protected function requireNodeAggregateToNotBeDescendant( ); } foreach ( - $contentRepository->getContentGraph()->findChildNodeAggregates( - $contentStreamId, + $contentGraph->findChildNodeAggregates( $referenceNodeAggregate->nodeAggregateId ) as $childReferenceNodeAggregate ) { $this->requireNodeAggregateToNotBeDescendant( - $contentStreamId, + $contentGraph, $nodeAggregate, - $childReferenceNodeAggregate, - $contentRepository + $childReferenceNodeAggregate ); } } @@ -567,14 +550,12 @@ protected function requireNodeAggregateToNotBeDescendant( * @throws NodeAggregateIsNoSibling */ protected function requireNodeAggregateToBeSibling( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, NodeAggregateId $referenceNodeAggregateId, NodeAggregateId $siblingNodeAggregateId, DimensionSpacePoint $dimensionSpacePoint, - ContentRepository $contentRepository, ): void { - $succeedingSiblings = $contentRepository->getContentGraph()->getSubgraph( - $contentStreamId, + $succeedingSiblings = $contentGraph->getSubgraph( $dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() )->findSucceedingSiblingNodes($referenceNodeAggregateId, FindSucceedingSiblingNodesFilter::create()); @@ -582,8 +563,7 @@ protected function requireNodeAggregateToBeSibling( return; } - $precedingSiblings = $contentRepository->getContentGraph()->getSubgraph( - $contentStreamId, + $precedingSiblings = $contentGraph->getSubgraph( $dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() )->findPrecedingSiblingNodes($referenceNodeAggregateId, FindPrecedingSiblingNodesFilter::create()); @@ -602,14 +582,12 @@ protected function requireNodeAggregateToBeSibling( * @throws NodeAggregateIsNoChild */ protected function requireNodeAggregateToBeChild( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, NodeAggregateId $childNodeAggregateId, NodeAggregateId $parentNodeAggregateId, DimensionSpacePoint $dimensionSpacePoint, - ContentRepository $contentRepository, ): void { - $childNodes = $contentRepository->getContentGraph()->getSubgraph( - $contentStreamId, + $childNodes = $contentGraph->getSubgraph( $dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() )->findChildNodes($parentNodeAggregateId, FindChildNodesFilter::create()); @@ -628,19 +606,17 @@ protected function requireNodeAggregateToBeChild( * @throws NodeNameIsAlreadyOccupied */ protected function requireNodeNameToBeUnoccupied( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, ?NodeName $nodeName, NodeAggregateId $parentNodeAggregateId, OriginDimensionSpacePoint $parentOriginDimensionSpacePoint, - DimensionSpacePointSet $dimensionSpacePoints, - ContentRepository $contentRepository + DimensionSpacePointSet $dimensionSpacePoints ): void { if ($nodeName === null) { return; } - $dimensionSpacePointsOccupiedByChildNodeName = $contentRepository->getContentGraph() + $dimensionSpacePointsOccupiedByChildNodeName = $contentGraph ->getDimensionSpacePointsOccupiedByChildNodeName( - $contentStreamId, $nodeName, $parentNodeAggregateId, $parentOriginDimensionSpacePoint, @@ -659,17 +635,16 @@ protected function requireNodeNameToBeUnoccupied( * @throws NodeNameIsAlreadyCovered */ protected function requireNodeNameToBeUncovered( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, ?NodeName $nodeName, NodeAggregateId $parentNodeAggregateId, - DimensionSpacePointSet $dimensionSpacePointsToBeCovered, - ContentRepository $contentRepository + DimensionSpacePointSet $dimensionSpacePointsToBeCovered ): void { if ($nodeName === null) { return; } - $childNodeAggregates = $contentRepository->getContentGraph()->findChildNodeAggregatesByName( - $contentStreamId, + + $childNodeAggregates = $contentGraph->findChildNodeAggregatesByName( $parentNodeAggregateId, $nodeName ); @@ -696,7 +671,7 @@ protected function requireNodeAggregateToOccupyDimensionSpacePoint( ): void { if (!$nodeAggregate->occupiesDimensionSpacePoint($originDimensionSpacePoint)) { throw new DimensionSpacePointIsNotYetOccupied( - 'Dimension space point ' . json_encode($originDimensionSpacePoint) + 'Dimension space point ' . json_encode($originDimensionSpacePoint, JSON_PARTIAL_OUTPUT_ON_ERROR) . ' is not yet occupied by node aggregate "' . $nodeAggregate->nodeAggregateId->value . '"', 1552595396 ); @@ -712,7 +687,7 @@ protected function requireNodeAggregateToNotOccupyDimensionSpacePoint( ): void { if ($nodeAggregate->occupiesDimensionSpacePoint($originDimensionSpacePoint)) { throw new DimensionSpacePointIsAlreadyOccupied( - 'Dimension space point ' . json_encode($originDimensionSpacePoint) + 'Dimension space point ' . json_encode($originDimensionSpacePoint, JSON_PARTIAL_OUTPUT_ON_ERROR) . ' is already occupied by node aggregate "' . $nodeAggregate->nodeAggregateId->value . '"', 1552595441 ); @@ -756,12 +731,11 @@ protected function validateReferenceProperties( protected function getExpectedVersionOfContentStream( ContentStreamId $contentStreamId, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): ExpectedVersion { + return ExpectedVersion::fromVersion( - $contentRepository->getContentStreamFinder() - ->findVersionForContentStream($contentStreamId) - ->unwrap() + $commandHandlingDependencies->getContentStreamFinder()->findVersionForContentStream($contentStreamId)->unwrap() ); } } diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/ContentStreamIdOverride.php b/Neos.ContentRepository.Core/Classes/Feature/Common/ContentStreamIdOverride.php deleted file mode 100644 index 96bf09b3a80..00000000000 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/ContentStreamIdOverride.php +++ /dev/null @@ -1,67 +0,0 @@ -getWorkspaceFinder()->findOneByName($workspaceName)?->currentContentStreamId; - - if (!$contentStreamId) { - throw new ContentStreamDoesNotExistYet( - 'Content stream for workspace "' . $workspaceName->value . '" does not exist yet.', - 1710407870 - ); - } - - return $contentStreamId; - } -} diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/NodeCreationInternals.php b/Neos.ContentRepository.Core/Classes/Feature/Common/NodeCreationInternals.php index 62c1f351f64..ef78ca2d0da 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/NodeCreationInternals.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/NodeCreationInternals.php @@ -14,13 +14,12 @@ namespace Neos\ContentRepository\Core\Feature\Common; -use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindSucceedingSiblingNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; /** * @internal implementation details of command handlers @@ -41,30 +40,21 @@ trait NodeCreationInternals * operates on the explicitly set succeeding sibling instead of the node itself. */ private function resolveInterdimensionalSiblingsForCreation( - ContentRepository $contentRepository, - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, NodeAggregateId $requestedSucceedingSiblingNodeAggregateId, OriginDimensionSpacePoint $sourceOrigin, DimensionSpacePointSet $coveredDimensionSpacePoints, ): InterdimensionalSiblings { - $originSubgraph = $contentRepository->getContentGraph()->getSubgraph( - $contentStreamId, - $sourceOrigin->toDimensionSpacePoint(), - VisibilityConstraints::withoutRestrictions() - ); - $originAlternativeSucceedingSiblings = $originSubgraph->findSucceedingSiblingNodes( + $subGraph = $contentGraph->getSubgraph($sourceOrigin->toDimensionSpacePoint(), VisibilityConstraints::withoutRestrictions()); + $originAlternativeSucceedingSiblings = $subGraph->findSucceedingSiblingNodes( $requestedSucceedingSiblingNodeAggregateId, FindSucceedingSiblingNodesFilter::create() ); $interdimensionalSiblings = []; foreach ($coveredDimensionSpacePoints as $coveredDimensionSpacePoint) { - $variantSubgraph = $contentRepository->getContentGraph()->getSubgraph( - $contentStreamId, - $coveredDimensionSpacePoint, - VisibilityConstraints::withoutRestrictions() - ); - $variantSucceedingSibling = $variantSubgraph->findNodeById($requestedSucceedingSiblingNodeAggregateId); + $subGraph = $contentGraph->getSubgraph($coveredDimensionSpacePoint, VisibilityConstraints::withoutRestrictions()); + $variantSucceedingSibling = $subGraph->findNodeById($requestedSucceedingSiblingNodeAggregateId); if ($variantSucceedingSibling) { // a) happy path, the explicitly requested succeeding sibling also exists in this dimension space point $interdimensionalSiblings[] = new InterdimensionalSibling( @@ -76,7 +66,7 @@ private function resolveInterdimensionalSiblingsForCreation( // check the other siblings succeeding in the origin dimension space point foreach ($originAlternativeSucceedingSiblings as $originSibling) { - $alternativeVariantSucceedingSibling = $variantSubgraph->findNodeById($originSibling->nodeAggregateId); + $alternativeVariantSucceedingSibling = $subGraph->findNodeById($originSibling->nodeAggregateId); if (!$alternativeVariantSucceedingSibling) { continue; } diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/NodeVariationInternals.php b/Neos.ContentRepository.Core/Classes/Feature/Common/NodeVariationInternals.php index 54b8d0a60e2..08aaa79770a 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/NodeVariationInternals.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/NodeVariationInternals.php @@ -23,12 +23,12 @@ use Neos\ContentRepository\Core\Feature\NodeVariation\Event\NodeGeneralizationVariantWasCreated; use Neos\ContentRepository\Core\Feature\NodeVariation\Event\NodePeerVariantWasCreated; use Neos\ContentRepository\Core\Feature\NodeVariation\Event\NodeSpecializationVariantWasCreated; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindSucceedingSiblingNodesFilter; -use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** * @internal implementation details of command handlers @@ -38,11 +38,10 @@ trait NodeVariationInternals abstract protected function getInterDimensionalVariationGraph(): DimensionSpace\InterDimensionalVariationGraph; protected function createEventsForVariations( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $targetOrigin, - NodeAggregate $nodeAggregate, - ContentRepository $contentRepository + NodeAggregate $nodeAggregate ): Events { return match ( $this->getInterDimensionalVariationGraph()->getVariantType( @@ -51,45 +50,40 @@ protected function createEventsForVariations( ) ) { DimensionSpace\VariantType::TYPE_SPECIALIZATION => $this->handleCreateNodeSpecializationVariant( - $contentStreamId, + $contentGraph, $sourceOrigin, $targetOrigin, - $nodeAggregate, - $contentRepository + $nodeAggregate ), DimensionSpace\VariantType::TYPE_GENERALIZATION => $this->handleCreateNodeGeneralizationVariant( - $contentStreamId, + $contentGraph, $sourceOrigin, $targetOrigin, - $nodeAggregate, - $contentRepository + $nodeAggregate ), default => $this->handleCreateNodePeerVariant( - $contentStreamId, + $contentGraph, $sourceOrigin, $targetOrigin, - $nodeAggregate, - $contentRepository + $nodeAggregate ), }; } protected function handleCreateNodeSpecializationVariant( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $targetOrigin, - NodeAggregate $nodeAggregate, - ContentRepository $contentRepository + NodeAggregate $nodeAggregate ): Events { $specializationVisibility = $this->calculateEffectiveVisibility($targetOrigin, $nodeAggregate); $events = $this->collectNodeSpecializationVariantsThatWillHaveBeenCreated( - $contentStreamId, + $contentGraph, $sourceOrigin, $targetOrigin, $nodeAggregate, $specializationVisibility, - [], - $contentRepository + [] ); return Events::fromArray($events); @@ -100,22 +94,20 @@ protected function handleCreateNodeSpecializationVariant( * @return array */ protected function collectNodeSpecializationVariantsThatWillHaveBeenCreated( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $targetOrigin, NodeAggregate $nodeAggregate, DimensionSpacePointSet $specializationVisibility, - array $events, - ContentRepository $contentRepository + array $events ): array { $events[] = new NodeSpecializationVariantWasCreated( - $contentStreamId, + $contentGraph->getContentStreamId(), $nodeAggregate->nodeAggregateId, $sourceOrigin, $targetOrigin, $this->resolveInterdimensionalSiblings( - $contentRepository, - $contentStreamId, + $contentGraph, $nodeAggregate->nodeAggregateId, $sourceOrigin, $specializationVisibility @@ -123,19 +115,17 @@ protected function collectNodeSpecializationVariantsThatWillHaveBeenCreated( ); foreach ( - $contentRepository->getContentGraph()->findTetheredChildNodeAggregates( - $contentStreamId, + $contentGraph->findTetheredChildNodeAggregates( $nodeAggregate->nodeAggregateId ) as $tetheredChildNodeAggregate ) { $events = $this->collectNodeSpecializationVariantsThatWillHaveBeenCreated( - $contentStreamId, + $contentGraph, $sourceOrigin, $targetOrigin, $tetheredChildNodeAggregate, $specializationVisibility, - $events, - $contentRepository + $events ); } @@ -143,21 +133,19 @@ protected function collectNodeSpecializationVariantsThatWillHaveBeenCreated( } protected function handleCreateNodeGeneralizationVariant( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $targetOrigin, - NodeAggregate $nodeAggregate, - ContentRepository $contentRepository + NodeAggregate $nodeAggregate ): Events { $generalizationVisibility = $this->calculateEffectiveVisibility($targetOrigin, $nodeAggregate); $events = $this->collectNodeGeneralizationVariantsThatWillHaveBeenCreated( - $contentStreamId, + $contentGraph, $sourceOrigin, $targetOrigin, $nodeAggregate, $generalizationVisibility, - [], - $contentRepository + [] ); return Events::fromArray($events); @@ -168,22 +156,20 @@ protected function handleCreateNodeGeneralizationVariant( * @return array */ protected function collectNodeGeneralizationVariantsThatWillHaveBeenCreated( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $targetOrigin, NodeAggregate $nodeAggregate, DimensionSpacePointSet $generalizationVisibility, - array $events, - ContentRepository $contentRepository + array $events ): array { $events[] = new NodeGeneralizationVariantWasCreated( - $contentStreamId, + $contentGraph->getContentStreamId(), $nodeAggregate->nodeAggregateId, $sourceOrigin, $targetOrigin, $this->resolveInterdimensionalSiblings( - $contentRepository, - $contentStreamId, + $contentGraph, $nodeAggregate->nodeAggregateId, $sourceOrigin, $generalizationVisibility @@ -191,19 +177,17 @@ protected function collectNodeGeneralizationVariantsThatWillHaveBeenCreated( ); foreach ( - $contentRepository->getContentGraph()->findTetheredChildNodeAggregates( - $contentStreamId, + $contentGraph->findTetheredChildNodeAggregates( $nodeAggregate->nodeAggregateId ) as $tetheredChildNodeAggregate ) { $events = $this->collectNodeGeneralizationVariantsThatWillHaveBeenCreated( - $contentStreamId, + $contentGraph, $sourceOrigin, $targetOrigin, $tetheredChildNodeAggregate, $generalizationVisibility, - $events, - $contentRepository + $events ); } @@ -211,21 +195,19 @@ protected function collectNodeGeneralizationVariantsThatWillHaveBeenCreated( } protected function handleCreateNodePeerVariant( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $targetOrigin, - NodeAggregate $nodeAggregate, - ContentRepository $contentRepository + NodeAggregate $nodeAggregate ): Events { $peerVisibility = $this->calculateEffectiveVisibility($targetOrigin, $nodeAggregate); $events = $this->collectNodePeerVariantsThatWillHaveBeenCreated( - $contentStreamId, + $contentGraph, $sourceOrigin, $targetOrigin, $nodeAggregate, $peerVisibility, - [], - $contentRepository + [] ); return Events::fromArray($events); @@ -236,22 +218,20 @@ protected function handleCreateNodePeerVariant( * @return array */ protected function collectNodePeerVariantsThatWillHaveBeenCreated( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $targetOrigin, NodeAggregate $nodeAggregate, DimensionSpacePointSet $peerVisibility, - array $events, - ContentRepository $contentRepository + array $events ): array { $events[] = new NodePeerVariantWasCreated( - $contentStreamId, + $contentGraph->getContentStreamId(), $nodeAggregate->nodeAggregateId, $sourceOrigin, $targetOrigin, $this->resolveInterdimensionalSiblings( - $contentRepository, - $contentStreamId, + $contentGraph, $nodeAggregate->nodeAggregateId, $sourceOrigin, $peerVisibility @@ -259,19 +239,17 @@ protected function collectNodePeerVariantsThatWillHaveBeenCreated( ); foreach ( - $contentRepository->getContentGraph()->findTetheredChildNodeAggregates( - $contentStreamId, + $contentGraph->findTetheredChildNodeAggregates( $nodeAggregate->nodeAggregateId ) as $tetheredChildNodeAggregate ) { $events = $this->collectNodePeerVariantsThatWillHaveBeenCreated( - $contentStreamId, + $contentGraph, $sourceOrigin, $targetOrigin, $tetheredChildNodeAggregate, $peerVisibility, - $events, - $contentRepository + $events ); } @@ -291,33 +269,20 @@ protected function collectNodePeerVariantsThatWillHaveBeenCreated( * except this operates on the to-be-varied node itself instead of an explicitly set succeeding sibling */ private function resolveInterdimensionalSiblings( - ContentRepository $contentRepository, - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, NodeAggregateId $varyingNodeAggregateId, OriginDimensionSpacePoint $sourceOrigin, DimensionSpacePointSet $variantCoverage, ): InterdimensionalSiblings { - $originSubgraph = $contentRepository->getContentGraph()->getSubgraph( - $contentStreamId, - $sourceOrigin->toDimensionSpacePoint(), - VisibilityConstraints::withoutRestrictions() - ); - $originSiblings = $originSubgraph->findSucceedingSiblingNodes( - $varyingNodeAggregateId, - FindSucceedingSiblingNodesFilter::create() - ); + $originSiblings = $contentGraph + ->getSubgraph($sourceOrigin->toDimensionSpacePoint(), VisibilityConstraints::withoutRestrictions()) + ->findSucceedingSiblingNodes($varyingNodeAggregateId, FindSucceedingSiblingNodesFilter::create()); $interdimensionalSiblings = []; foreach ($variantCoverage as $variantDimensionSpacePoint) { - $variantSubgraph = $contentRepository->getContentGraph()->getSubgraph( - $contentStreamId, - $variantDimensionSpacePoint, - VisibilityConstraints::withoutRestrictions() - ); - // check the siblings succeeding in the origin dimension space point foreach ($originSiblings as $originSibling) { - $variantSibling = $variantSubgraph->findNodeById($originSibling->nodeAggregateId); + $variantSibling = $contentGraph->getSubgraph($variantDimensionSpacePoint, VisibilityConstraints::withoutRestrictions())->findNodeById($originSibling->nodeAggregateId); if (!$variantSibling) { continue; } diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/TetheredNodeInternals.php b/Neos.ContentRepository.Core/Classes/Feature/Common/TetheredNodeInternals.php index 5d2a01fce2d..abe3e5b73ef 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/TetheredNodeInternals.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/TetheredNodeInternals.php @@ -14,7 +14,6 @@ * source code. */ -use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\EventStore\Events; use Neos\ContentRepository\Core\Feature\NodeCreation\Event\NodeAggregateWithNodeWasCreated; @@ -23,6 +22,7 @@ use Neos\ContentRepository\Core\Infrastructure\Property\PropertyConverter; use Neos\ContentRepository\Core\NodeType\NodeType; use Neos\ContentRepository\Core\NodeType\NodeTypeName; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateClassification; @@ -40,11 +40,10 @@ trait TetheredNodeInternals abstract protected function getPropertyConverter(): PropertyConverter; abstract protected function createEventsForVariations( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $targetOrigin, - NodeAggregate $nodeAggregate, - ContentRepository $contentRepository + NodeAggregate $nodeAggregate ): Events; /** @@ -56,15 +55,14 @@ abstract protected function createEventsForVariations( * @throws \Exception */ protected function createEventsForMissingTetheredNode( + ContentGraphInterface $contentGraph, NodeAggregate $parentNodeAggregate, OriginDimensionSpacePoint $originDimensionSpacePoint, NodeName $tetheredNodeName, ?NodeAggregateId $tetheredNodeAggregateId, - NodeType $expectedTetheredNodeType, - ContentRepository $contentRepository + NodeType $expectedTetheredNodeType ): Events { - $childNodeAggregates = $contentRepository->getContentGraph()->findChildNodeAggregatesByName( - $parentNodeAggregate->contentStreamId, + $childNodeAggregates = $contentGraph->findChildNodeAggregatesByName( $parentNodeAggregate->nodeAggregateId, $tetheredNodeName ); @@ -149,11 +147,10 @@ protected function createEventsForMissingTetheredNode( } /** @var Node $childNodeSource Node aggregates are never empty */ return $this->createEventsForVariations( - $parentNodeAggregate->contentStreamId, + $contentGraph, $childNodeSource->originDimensionSpacePoint, $originDimensionSpacePoint, - $parentNodeAggregate, - $contentRepository + $parentNodeAggregate ); } else { throw new \RuntimeException( diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php index 86fe2b2ac22..2c9d9160ccd 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php @@ -16,6 +16,7 @@ use Neos\ContentRepository\Core\CommandHandler\CommandHandlerInterface; use Neos\ContentRepository\Core\CommandHandler\CommandInterface; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\EventStore\Events; use Neos\ContentRepository\Core\EventStore\EventsToPublish; @@ -49,14 +50,14 @@ public function canHandle(CommandInterface $command): bool return method_exists($this, 'handle' . (new \ReflectionClass($command))->getShortName()); } - public function handle(CommandInterface $command, ContentRepository $contentRepository): EventsToPublish + public function handle(CommandInterface $command, CommandHandlingDependencies $commandHandlingDependencies): EventsToPublish { return match ($command::class) { - CreateContentStream::class => $this->handleCreateContentStream($command, $contentRepository), - CloseContentStream::class => $this->handleCloseContentStream($command, $contentRepository), - ReopenContentStream::class => $this->handleReopenContentStream($command, $contentRepository), - ForkContentStream::class => $this->handleForkContentStream($command, $contentRepository), - RemoveContentStream::class => $this->handleRemoveContentStream($command, $contentRepository), + CreateContentStream::class => $this->handleCreateContentStream($command, $commandHandlingDependencies), + CloseContentStream::class => $this->handleCloseContentStream($command, $commandHandlingDependencies), + ReopenContentStream::class => $this->handleReopenContentStream($command, $commandHandlingDependencies), + ForkContentStream::class => $this->handleForkContentStream($command, $commandHandlingDependencies), + RemoveContentStream::class => $this->handleRemoveContentStream($command, $commandHandlingDependencies), default => throw new \DomainException('Cannot handle commands of class ' . get_class($command), 1710408206), }; } @@ -66,9 +67,9 @@ public function handle(CommandInterface $command, ContentRepository $contentRepo */ private function handleCreateContentStream( CreateContentStream $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { - $this->requireContentStreamToNotExistYet($command->contentStreamId, $contentRepository); + $this->requireContentStreamToNotExistYet($command->contentStreamId, $commandHandlingDependencies); $streamName = ContentStreamEventStreamName::fromContentStreamId($command->contentStreamId) ->getEventStreamName(); @@ -85,11 +86,11 @@ private function handleCreateContentStream( private function handleCloseContentStream( CloseContentStream $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { - $this->requireContentStreamToExist($command->contentStreamId, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($command->contentStreamId, $contentRepository); - $this->requireContentStreamToNotBeClosed($command->contentStreamId, $contentRepository); + $this->requireContentStreamToExist($command->contentStreamId, $commandHandlingDependencies); + $expectedVersion = $this->getExpectedVersionOfContentStream($command->contentStreamId, $commandHandlingDependencies); + $this->requireContentStreamToNotBeClosed($command->contentStreamId, $commandHandlingDependencies); $streamName = ContentStreamEventStreamName::fromContentStreamId($command->contentStreamId)->getEventStreamName(); return new EventsToPublish( @@ -105,11 +106,11 @@ private function handleCloseContentStream( private function handleReopenContentStream( ReopenContentStream $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { - $this->requireContentStreamToExist($command->contentStreamId, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($command->contentStreamId, $contentRepository); - $this->requireContentStreamToBeClosed($command->contentStreamId, $contentRepository); + $this->requireContentStreamToExist($command->contentStreamId, $commandHandlingDependencies); + $expectedVersion = $this->getExpectedVersionOfContentStream($command->contentStreamId, $commandHandlingDependencies); + $this->requireContentStreamToBeClosed($command->contentStreamId, $commandHandlingDependencies); $streamName = ContentStreamEventStreamName::fromContentStreamId($command->contentStreamId)->getEventStreamName(); return new EventsToPublish( @@ -130,14 +131,13 @@ private function handleReopenContentStream( */ private function handleForkContentStream( ForkContentStream $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $this->requireContentStreamToExist($command->sourceContentStreamId, $contentRepository); - $this->requireContentStreamToNotBeClosed($command->sourceContentStreamId, $contentRepository); - $this->requireContentStreamToNotExistYet($command->newContentStreamId, $contentRepository); + $this->requireContentStreamToExist($command->sourceContentStreamId, $commandHandlingDependencies); + $this->requireContentStreamToNotBeClosed($command->sourceContentStreamId, $commandHandlingDependencies); + $this->requireContentStreamToNotExistYet($command->newContentStreamId, $commandHandlingDependencies); - $sourceContentStreamVersion = $contentRepository->getContentStreamFinder() - ->findVersionForContentStream($command->sourceContentStreamId); + $sourceContentStreamVersion = $commandHandlingDependencies->getContentStreamFinder()->findVersionForContentStream($command->sourceContentStreamId); $streamName = ContentStreamEventStreamName::fromContentStreamId($command->newContentStreamId) ->getEventStreamName(); @@ -158,10 +158,10 @@ private function handleForkContentStream( private function handleRemoveContentStream( RemoveContentStream $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $this->requireContentStreamToExist($command->contentStreamId, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($command->contentStreamId, $contentRepository); + $this->requireContentStreamToExist($command->contentStreamId, $commandHandlingDependencies); + $expectedVersion = $this->getExpectedVersionOfContentStream($command->contentStreamId, $commandHandlingDependencies); $streamName = ContentStreamEventStreamName::fromContentStreamId( $command->contentStreamId @@ -180,13 +180,14 @@ private function handleRemoveContentStream( /** * @param ContentStreamId $contentStreamId + * @param CommandHandlingDependencies $commandHandlingDependencies * @throws ContentStreamAlreadyExists */ protected function requireContentStreamToNotExistYet( ContentStreamId $contentStreamId, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): void { - if ($contentRepository->getContentStreamFinder()->hasContentStream($contentStreamId)) { + if ($commandHandlingDependencies->getContentStreamFinder()->hasContentStream($contentStreamId)) { throw new ContentStreamAlreadyExists( 'Content stream "' . $contentStreamId->value . '" already exists.', 1521386345 @@ -196,13 +197,15 @@ protected function requireContentStreamToNotExistYet( /** * @param ContentStreamId $contentStreamId + * @param CommandHandlingDependencies $commandHandlingDependencies * @throws ContentStreamDoesNotExistYet */ protected function requireContentStreamToExist( ContentStreamId $contentStreamId, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): void { - if (!$contentRepository->getContentStreamFinder()->hasContentStream($contentStreamId)) { + $maybeVersion = $commandHandlingDependencies->getContentStreamFinder()->findVersionForContentStream($contentStreamId); + if ($maybeVersion->isNothing()) { throw new ContentStreamDoesNotExistYet( 'Content stream "' . $contentStreamId->value . '" does not exist yet.', 1521386692 @@ -212,9 +215,10 @@ protected function requireContentStreamToExist( protected function requireContentStreamToNotBeClosed( ContentStreamId $contentStreamId, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): void { - if ($contentRepository->getContentStreamFinder()->findStateForContentStream($contentStreamId) === ContentStreamState::STATE_CLOSED) { + $contentStreamState = $commandHandlingDependencies->getContentStreamFinder()->findStateForContentStream($contentStreamId); + if ($contentStreamState === ContentStreamState::STATE_CLOSED) { throw new ContentStreamIsClosed( 'Content stream "' . $contentStreamId->value . '" is closed.', 1710260081 @@ -224,9 +228,10 @@ protected function requireContentStreamToNotBeClosed( protected function requireContentStreamToBeClosed( ContentStreamId $contentStreamId, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): void { - if ($contentRepository->getContentStreamFinder()->findStateForContentStream($contentStreamId) !== ContentStreamState::STATE_CLOSED) { + $contentStreamState = $commandHandlingDependencies->getContentStreamFinder()->findStateForContentStream($contentStreamId); + if ($contentStreamState !== ContentStreamState::STATE_CLOSED) { throw new ContentStreamIsNotClosed( 'Content stream "' . $contentStreamId->value . '" is not closed.', 1710405911 @@ -236,11 +241,11 @@ protected function requireContentStreamToBeClosed( protected function getExpectedVersionOfContentStream( ContentStreamId $contentStreamId, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): ExpectedVersion { + $maybeVersion = $commandHandlingDependencies->getContentStreamFinder()->findVersionForContentStream($contentStreamId); return ExpectedVersion::fromVersion( - $contentRepository->getContentStreamFinder() - ->findVersionForContentStream($contentStreamId) + $maybeVersion ->unwrap() ); } diff --git a/Neos.ContentRepository.Core/Classes/Feature/DimensionSpaceAdjustment/DimensionSpaceCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/DimensionSpaceAdjustment/DimensionSpaceCommandHandler.php index ca0b66861b4..dadb48a00cf 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/DimensionSpaceAdjustment/DimensionSpaceCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/DimensionSpaceAdjustment/DimensionSpaceCommandHandler.php @@ -16,6 +16,7 @@ use Neos\ContentRepository\Core\CommandHandler\CommandHandlerInterface; use Neos\ContentRepository\Core\CommandHandler\CommandInterface; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\DimensionSpace\ContentDimensionZookeeper; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; @@ -33,9 +34,6 @@ use Neos\ContentRepository\Core\Feature\DimensionSpaceAdjustment\Exception\DimensionSpacePointAlreadyExists; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; -use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\EventStore\Model\EventStream\ExpectedVersion; /** @@ -54,27 +52,26 @@ public function canHandle(CommandInterface $command): bool return method_exists($this, 'handle' . (new \ReflectionClass($command))->getShortName()); } - public function handle(CommandInterface $command, ContentRepository $contentRepository): EventsToPublish + public function handle(CommandInterface $command, CommandHandlingDependencies $commandHandlingDependencies): EventsToPublish { /** @phpstan-ignore-next-line */ return match ($command::class) { - MoveDimensionSpacePoint::class => $this->handleMoveDimensionSpacePoint($command, $contentRepository), - AddDimensionShineThrough::class => $this->handleAddDimensionShineThrough($command, $contentRepository), + MoveDimensionSpacePoint::class => $this->handleMoveDimensionSpacePoint($command, $commandHandlingDependencies), + AddDimensionShineThrough::class => $this->handleAddDimensionShineThrough($command, $commandHandlingDependencies), }; } private function handleMoveDimensionSpacePoint( MoveDimensionSpacePoint $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $contentStreamId = $this->requireContentStreamForWorkspaceName($command->workspaceName, $contentRepository); - $streamName = ContentStreamEventStreamName::fromContentStreamId($contentStreamId) + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $streamName = ContentStreamEventStreamName::fromContentStreamId($contentGraph->getContentStreamId()) ->getEventStreamName(); self::requireDimensionSpacePointToBeEmptyInContentStream( - $command->target, - $contentStreamId, - $contentRepository->getContentGraph() + $contentGraph, + $command->target ); $this->requireDimensionSpacePointToExist($command->target); @@ -82,7 +79,7 @@ private function handleMoveDimensionSpacePoint( $streamName, Events::with( new DimensionSpacePointWasMoved( - $contentStreamId, + $contentGraph->getContentStreamId(), $command->source, $command->target ), @@ -93,16 +90,15 @@ private function handleMoveDimensionSpacePoint( private function handleAddDimensionShineThrough( AddDimensionShineThrough $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $contentStreamId = $this->requireContentStreamForWorkspaceName($command->workspaceName, $contentRepository); - $streamName = ContentStreamEventStreamName::fromContentStreamId($contentStreamId) + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $streamName = ContentStreamEventStreamName::fromContentStreamId($contentGraph->getContentStreamId()) ->getEventStreamName(); self::requireDimensionSpacePointToBeEmptyInContentStream( - $command->target, - $contentStreamId, - $contentRepository->getContentGraph() + $contentGraph, + $command->target ); $this->requireDimensionSpacePointToExist($command->target); @@ -112,7 +108,7 @@ private function handleAddDimensionShineThrough( $streamName, Events::with( new DimensionShineThroughWasAdded( - $contentStreamId, + $contentGraph->getContentStreamId(), $command->source, $command->target ) @@ -133,19 +129,14 @@ protected function requireDimensionSpacePointToExist(DimensionSpacePoint $dimens } private static function requireDimensionSpacePointToBeEmptyInContentStream( - DimensionSpacePoint $dimensionSpacePoint, - ContentStreamId $contentStreamId, - ContentGraphInterface $contentGraph + ContentGraphInterface $contentGraph, + DimensionSpacePoint $dimensionSpacePoint ): void { - $subgraph = $contentGraph->getSubgraph( - $contentStreamId, - $dimensionSpacePoint, - VisibilityConstraints::withoutRestrictions() - ); - if ($subgraph->countNodes() > 0) { + $hasNodes = $contentGraph->getSubgraph($dimensionSpacePoint, VisibilityConstraints::withoutRestrictions())->countNodes(); + if ($hasNodes > 0) { throw new DimensionSpacePointAlreadyExists(sprintf( 'the content stream %s already contained nodes in dimension space point %s - this is not allowed.', - $contentStreamId->value, + $contentGraph->getContentStreamId()->value, $dimensionSpacePoint->toJson(), ), 1612898126); } @@ -164,23 +155,4 @@ private function requireDimensionSpacePointToBeSpecialization( throw DimensionSpacePointIsNoSpecialization::butWasSupposedToBe($target, $source); } } - - /** - * @throws ContentStreamDoesNotExistYet - */ - protected function requireContentStreamForWorkspaceName( - WorkspaceName $workspaceName, - ContentRepository $contentRepository - ): ContentStreamId { - $contentStreamId = $contentRepository->getWorkspaceFinder()->findOneByName($workspaceName) - ?->currentContentStreamId; - if (!$contentStreamId || !$contentRepository->getContentStreamFinder()->hasContentStream($contentStreamId)) { - throw new ContentStreamDoesNotExistYet( - 'Content stream "' . $contentStreamId?->value . '" does not exist yet.', - 1521386692 - ); - } - - return $contentStreamId; - } } diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php index 27b49905cb6..3830cf78e29 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php @@ -16,6 +16,7 @@ use Neos\ContentRepository\Core\CommandHandler\CommandHandlerInterface; use Neos\ContentRepository\Core\CommandHandler\CommandInterface; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\DimensionSpace; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; @@ -72,73 +73,51 @@ final class NodeAggregateCommandHandler implements CommandHandlerInterface use NodeVariation; use TetheredNodeInternals; - /** - * Used for constraint checks against the current outside configuration state of node types - */ - private NodeTypeManager $nodeTypeManager; - - /** - * Used for variation resolution from the current outside state of content dimensions - */ - private DimensionSpace\InterDimensionalVariationGraph $interDimensionalVariationGraph; - - /** - * Used for constraint checks against the current outside configuration state of content dimensions - */ - private DimensionSpace\ContentDimensionZookeeper $contentDimensionZookeeper; - - protected PropertyConverter $propertyConverter; - /** * can be disabled in {@see NodeAggregateCommandHandler::withoutAncestorNodeTypeConstraintChecks()} */ private bool $ancestorNodeTypeConstraintChecksEnabled = true; public function __construct( - NodeTypeManager $nodeTypeManager, - DimensionSpace\ContentDimensionZookeeper $contentDimensionZookeeper, - DimensionSpace\InterDimensionalVariationGraph $interDimensionalVariationGraph, - PropertyConverter $propertyConverter + private readonly NodeTypeManager $nodeTypeManager, + private readonly DimensionSpace\ContentDimensionZookeeper $contentDimensionZookeeper, + private readonly DimensionSpace\InterDimensionalVariationGraph $interDimensionalVariationGraph, + private readonly PropertyConverter $propertyConverter, ) { - $this->nodeTypeManager = $nodeTypeManager; - $this->contentDimensionZookeeper = $contentDimensionZookeeper; - $this->interDimensionalVariationGraph = $interDimensionalVariationGraph; - $this->propertyConverter = $propertyConverter; } - public function canHandle(CommandInterface $command): bool { return method_exists($this, 'handle' . (new \ReflectionClass($command))->getShortName()); } - public function handle(CommandInterface $command, ContentRepository $contentRepository): EventsToPublish + public function handle(CommandInterface $command, CommandHandlingDependencies $commandHandlingDependencies): EventsToPublish { /** @phpstan-ignore-next-line */ return match ($command::class) { - SetNodeProperties::class => $this->handleSetNodeProperties($command, $contentRepository), + SetNodeProperties::class => $this->handleSetNodeProperties($command, $commandHandlingDependencies), SetSerializedNodeProperties::class - => $this->handleSetSerializedNodeProperties($command, $contentRepository), - SetNodeReferences::class => $this->handleSetNodeReferences($command, $contentRepository), + => $this->handleSetSerializedNodeProperties($command, $commandHandlingDependencies), + SetNodeReferences::class => $this->handleSetNodeReferences($command, $commandHandlingDependencies), SetSerializedNodeReferences::class - => $this->handleSetSerializedNodeReferences($command, $contentRepository), - ChangeNodeAggregateType::class => $this->handleChangeNodeAggregateType($command, $contentRepository), - RemoveNodeAggregate::class => $this->handleRemoveNodeAggregate($command, $contentRepository), + => $this->handleSetSerializedNodeReferences($command, $commandHandlingDependencies), + ChangeNodeAggregateType::class => $this->handleChangeNodeAggregateType($command, $commandHandlingDependencies), + RemoveNodeAggregate::class => $this->handleRemoveNodeAggregate($command, $commandHandlingDependencies), CreateNodeAggregateWithNode::class - => $this->handleCreateNodeAggregateWithNode($command, $contentRepository), + => $this->handleCreateNodeAggregateWithNode($command, $commandHandlingDependencies), CreateNodeAggregateWithNodeAndSerializedProperties::class - => $this->handleCreateNodeAggregateWithNodeAndSerializedProperties($command, $contentRepository), - MoveNodeAggregate::class => $this->handleMoveNodeAggregate($command, $contentRepository), - CreateNodeVariant::class => $this->handleCreateNodeVariant($command, $contentRepository), + => $this->handleCreateNodeAggregateWithNodeAndSerializedProperties($command, $commandHandlingDependencies), + MoveNodeAggregate::class => $this->handleMoveNodeAggregate($command, $commandHandlingDependencies), + CreateNodeVariant::class => $this->handleCreateNodeVariant($command, $commandHandlingDependencies), CreateRootNodeAggregateWithNode::class - => $this->handleCreateRootNodeAggregateWithNode($command, $contentRepository), + => $this->handleCreateRootNodeAggregateWithNode($command, $commandHandlingDependencies), UpdateRootNodeAggregateDimensions::class - => $this->handleUpdateRootNodeAggregateDimensions($command, $contentRepository), - DisableNodeAggregate::class => $this->handleDisableNodeAggregate($command, $contentRepository), - EnableNodeAggregate::class => $this->handleEnableNodeAggregate($command, $contentRepository), - TagSubtree::class => $this->handleTagSubtree($command, $contentRepository), - UntagSubtree::class => $this->handleUntagSubtree($command, $contentRepository), - ChangeNodeAggregateName::class => $this->handleChangeNodeAggregateName($command, $contentRepository), + => $this->handleUpdateRootNodeAggregateDimensions($command, $commandHandlingDependencies), + DisableNodeAggregate::class => $this->handleDisableNodeAggregate($command, $commandHandlingDependencies), + EnableNodeAggregate::class => $this->handleEnableNodeAggregate($command, $commandHandlingDependencies), + TagSubtree::class => $this->handleTagSubtree($command, $commandHandlingDependencies), + UntagSubtree::class => $this->handleUntagSubtree($command, $commandHandlingDependencies), + ChangeNodeAggregateName::class => $this->handleChangeNodeAggregateName($command, $commandHandlingDependencies), }; } diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php b/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php index 72ecce321d3..35c9a8589b9 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php @@ -14,7 +14,7 @@ namespace Neos\ContentRepository\Core\Feature\NodeCreation; -use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\DimensionSpace; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\EventStore\Events; @@ -34,6 +34,7 @@ use Neos\ContentRepository\Core\NodeType\NodeType; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\NodeType\NodeTypeName; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\NodePath; use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet; use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeNotFoundException; @@ -42,7 +43,6 @@ use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\ContentRepository\Core\SharedModel\Node\PropertyName; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; /** * @internal implementation detail of Command Handlers @@ -69,7 +69,7 @@ abstract protected function getNodeTypeManager(): NodeTypeManager; private function handleCreateNodeAggregateWithNode( CreateNodeAggregateWithNode $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { $this->requireNodeType($command->nodeTypeName); $this->validateProperties($command->initialPropertyValues, $command->nodeTypeName); @@ -91,7 +91,7 @@ private function handleCreateNodeAggregateWithNode( $lowLevelCommand = $lowLevelCommand->withTetheredDescendantNodeAggregateIds($command->tetheredDescendantNodeAggregateIds); } - return $this->handleCreateNodeAggregateWithNodeAndSerializedProperties($lowLevelCommand, $contentRepository); + return $this->handleCreateNodeAggregateWithNodeAndSerializedProperties($lowLevelCommand, $commandHandlingDependencies); } private function validateProperties(?PropertyValuesToWrite $propertyValues, NodeTypeName $nodeTypeName): void @@ -124,10 +124,11 @@ private function validateProperties(?PropertyValuesToWrite $propertyValues, Node */ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( CreateNodeAggregateWithNodeAndSerializedProperties $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $this->requireContentStream($command->workspaceName, $commandHandlingDependencies); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $commandHandlingDependencies); $this->requireDimensionSpacePointToExist($command->originDimensionSpacePoint->toDimensionSpacePoint()); $nodeType = $this->requireNodeType($command->nodeTypeName); $this->requireNodeTypeToNotBeAbstract($nodeType); @@ -136,28 +137,24 @@ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( $this->requireTetheredDescendantNodeTypesToNotBeOfTypeRoot($nodeType); if ($this->areAncestorNodeTypeConstraintChecksEnabled()) { $this->requireConstraintsImposedByAncestorsAreMet( - $contentStreamId, + $contentGraph, $nodeType, $command->nodeName, - [$command->parentNodeAggregateId], - $contentRepository + [$command->parentNodeAggregateId] ); } $this->requireProjectedNodeAggregateToNotExist( - $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $contentGraph, + $command->nodeAggregateId ); $parentNodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->parentNodeAggregateId, - $contentRepository + $contentGraph, + $command->parentNodeAggregateId ); if ($command->succeedingSiblingNodeAggregateId) { $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->succeedingSiblingNodeAggregateId, - $contentRepository + $contentGraph, + $command->succeedingSiblingNodeAggregateId ); } $this->requireNodeAggregateToCoverDimensionSpacePoint( @@ -172,14 +169,13 @@ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( ); if ($command->nodeName) { $this->requireNodeNameToBeUnoccupied( - $contentStreamId, + $contentGraph, $command->nodeName, $command->parentNodeAggregateId, $parentNodeAggregate->classification->isRoot() ? DimensionSpace\OriginDimensionSpacePoint::createWithoutDimensions() : $command->originDimensionSpacePoint, $coveredDimensionSpacePoints, - $contentRepository ); } @@ -195,9 +191,8 @@ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( $descendantNodeAggregateIds->getNodeAggregateIds() as $descendantNodeAggregateId ) { $this->requireProjectedNodeAggregateToNotExist( - $contentStreamId, - $descendantNodeAggregateId, - $contentRepository + $contentGraph, + $descendantNodeAggregateId ); } @@ -206,26 +201,25 @@ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( $events = [ $this->createRegularWithNode( + $contentGraph, $command, - $contentStreamId, $coveredDimensionSpacePoints, - $initialPropertyValues, - $contentRepository + $initialPropertyValues ) ]; array_push($events, ...iterator_to_array($this->handleTetheredChildNodes( $command, - $contentStreamId, + $contentGraph, $nodeType, $coveredDimensionSpacePoints, $command->nodeAggregateId, $descendantNodeAggregateIds, - null, + null ))); return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId) + ContentStreamEventStreamName::fromContentStreamId($contentGraph->getContentStreamId()) ->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand($command, Events::fromArray($events)), $expectedVersion @@ -233,21 +227,19 @@ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( } private function createRegularWithNode( + ContentGraphInterface $contentGraph, CreateNodeAggregateWithNodeAndSerializedProperties $command, - ContentStreamId $contentStreamId, DimensionSpacePointSet $coveredDimensionSpacePoints, SerializedPropertyValues $initialPropertyValues, - ContentRepository $contentRepository, ): NodeAggregateWithNodeWasCreated { return new NodeAggregateWithNodeWasCreated( - $contentStreamId, + $contentGraph->getContentStreamId(), $command->nodeAggregateId, $command->nodeTypeName, $command->originDimensionSpacePoint, $command->succeedingSiblingNodeAggregateId ? $this->resolveInterdimensionalSiblingsForCreation( - $contentRepository, - $contentStreamId, + $contentGraph, $command->succeedingSiblingNodeAggregateId, $command->originDimensionSpacePoint, $coveredDimensionSpacePoints @@ -266,12 +258,12 @@ private function createRegularWithNode( */ private function handleTetheredChildNodes( CreateNodeAggregateWithNodeAndSerializedProperties $command, - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, NodeType $nodeType, DimensionSpacePointSet $coveredDimensionSpacePoints, NodeAggregateId $parentNodeAggregateId, NodeAggregateIdsByNodePaths $nodeAggregateIds, - ?NodePath $nodePath, + ?NodePath $nodePath ): Events { $events = []; foreach ($this->getNodeTypeManager()->getTetheredNodesConfigurationForNodeType($nodeType) as $rawNodeName => $childNodeType) { @@ -285,7 +277,7 @@ private function handleTetheredChildNodes( $initialPropertyValues = SerializedPropertyValues::defaultFromNodeType($childNodeType, $this->getPropertyConverter()); $events[] = new NodeAggregateWithNodeWasCreated( - $contentStreamId, + $contentGraph->getContentStreamId(), $childNodeAggregateId, $childNodeType->name, $command->originDimensionSpacePoint, @@ -298,12 +290,12 @@ private function handleTetheredChildNodes( array_push($events, ...iterator_to_array($this->handleTetheredChildNodes( $command, - $contentStreamId, + $contentGraph, $childNodeType, $coveredDimensionSpacePoints, $childNodeAggregateId, $nodeAggregateIds, - $childNodePath, + $childNodePath ))); } diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeDisabling/NodeDisabling.php b/Neos.ContentRepository.Core/Classes/Feature/NodeDisabling/NodeDisabling.php index eca096a52c7..c74d9fce8c0 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeDisabling/NodeDisabling.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeDisabling/NodeDisabling.php @@ -14,7 +14,7 @@ * source code. */ -use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\DimensionSpace; use Neos\ContentRepository\Core\DimensionSpace\Exception\DimensionSpacePointNotFound; use Neos\ContentRepository\Core\EventStore\Events; @@ -27,7 +27,6 @@ use Neos\ContentRepository\Core\Feature\SubtreeTagging\Event\SubtreeWasTagged; use Neos\ContentRepository\Core\Feature\SubtreeTagging\Event\SubtreeWasUntagged; use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet; -use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregateCurrentlyDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregatesTypeIsAmbiguous; /** @@ -38,23 +37,21 @@ trait NodeDisabling abstract protected function getInterDimensionalVariationGraph(): DimensionSpace\InterDimensionalVariationGraph; /** + * @param DisableNodeAggregate $command + * @param CommandHandlingDependencies $commandHandlingDependencies * @return EventsToPublish - * @throws ContentStreamDoesNotExistYet - * @throws DimensionSpacePointNotFound - * @throws NodeAggregateCurrentlyDoesNotExist - * @throws NodeAggregatesTypeIsAmbiguous */ private function handleDisableNodeAggregate( DisableNodeAggregate $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $this->requireContentStream($command->workspaceName, $commandHandlingDependencies); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $commandHandlingDependencies); $this->requireDimensionSpacePointToExist($command->coveredDimensionSpacePoint); $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $contentGraph, + $command->nodeAggregateId ); $this->requireNodeAggregateToCoverDimensionSpacePoint( $nodeAggregate, @@ -74,7 +71,7 @@ private function handleDisableNodeAggregate( $events = Events::with( new SubtreeWasTagged( - $contentStreamId, + $contentGraph->getContentStreamId(), $command->nodeAggregateId, $affectedDimensionSpacePoints, SubtreeTag::disabled(), @@ -82,7 +79,7 @@ private function handleDisableNodeAggregate( ); return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId) + ContentStreamEventStreamName::fromContentStreamId($contentGraph->getContentStreamId()) ->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand( $command, @@ -94,6 +91,7 @@ private function handleDisableNodeAggregate( /** * @param EnableNodeAggregate $command + * @param CommandHandlingDependencies $commandHandlingDependencies * @return EventsToPublish * @throws ContentStreamDoesNotExistYet * @throws DimensionSpacePointNotFound @@ -101,15 +99,14 @@ private function handleDisableNodeAggregate( */ public function handleEnableNodeAggregate( EnableNodeAggregate $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $commandHandlingDependencies); $this->requireDimensionSpacePointToExist($command->coveredDimensionSpacePoint); $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $contentGraph, + $command->nodeAggregateId ); $this->requireNodeAggregateToCoverDimensionSpacePoint( $nodeAggregate, @@ -129,7 +126,7 @@ public function handleEnableNodeAggregate( $events = Events::with( new SubtreeWasUntagged( - $contentStreamId, + $contentGraph->getContentStreamId(), $command->nodeAggregateId, $affectedDimensionSpacePoints, SubtreeTag::disabled(), @@ -137,7 +134,7 @@ public function handleEnableNodeAggregate( ); return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId)->getEventStreamName(), + ContentStreamEventStreamName::fromContentStreamId($contentGraph->getContentStreamId())->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand($command, $events), $expectedVersion ); diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php index acac660dfbd..b538bb99f6a 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php @@ -14,6 +14,8 @@ namespace Neos\ContentRepository\Core\Feature\NodeDuplication; +use Neos\ContentRepository\Core\CommandHandlingDependencies; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\CommandHandler\CommandHandlerInterface; use Neos\ContentRepository\Core\CommandHandler\CommandInterface; use Neos\ContentRepository\Core\ContentRepository; @@ -35,7 +37,7 @@ use Neos\ContentRepository\Core\SharedModel\Exception\NodeConstraintException; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** * @internal from userland, you'll use ContentRepository::handle to dispatch commands @@ -67,11 +69,11 @@ public function canHandle(CommandInterface $command): bool return method_exists($this, 'handle' . (new \ReflectionClass($command))->getShortName()); } - public function handle(CommandInterface $command, ContentRepository $contentRepository): EventsToPublish + public function handle(CommandInterface $command, CommandHandlingDependencies $commandHandlingDependencies): EventsToPublish { /** @phpstan-ignore-next-line */ return match ($command::class) { - CopyNodesRecursively::class => $this->handleCopyNodesRecursively($command, $contentRepository), + CopyNodesRecursively::class => $this->handleCopyNodesRecursively($command, $commandHandlingDependencies), }; } @@ -80,11 +82,11 @@ public function handle(CommandInterface $command, ContentRepository $contentRepo */ private function handleCopyNodesRecursively( CopyNodesRecursively $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { // Basic constraints (Content Stream / Dimension Space Point / Node Type of to-be-inserted root node) - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $commandHandlingDependencies); $this->requireDimensionSpacePointToExist( $command->targetDimensionSpacePoint->toDimensionSpacePoint() ); @@ -95,31 +97,27 @@ private function handleCopyNodesRecursively( // NOTE: we only check this for the *root* node of the to-be-inserted structure; and not for its // children (as we want to create the structure as-is; assuming it was already valid beforehand). $this->requireConstraintsImposedByAncestorsAreMet( - $contentStreamId, + $contentGraph, $nodeType, $command->targetNodeName, - [$command->targetParentNodeAggregateId], - $contentRepository + [$command->targetParentNodeAggregateId] ); // Constraint: The new nodeAggregateIds are not allowed to exist yet. $this->requireNewNodeAggregateIdsToNotExist( - $contentStreamId, - $command->nodeAggregateIdMapping, - $contentRepository + $contentGraph, + $command->nodeAggregateIdMapping ); // Constraint: the parent node must exist in the command's DimensionSpacePoint as well $parentNodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->targetParentNodeAggregateId, - $contentRepository + $contentGraph, + $command->targetParentNodeAggregateId ); if ($command->targetSucceedingSiblingNodeAggregateId) { $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->targetSucceedingSiblingNodeAggregateId, - $contentRepository + $contentGraph, + $command->targetSucceedingSiblingNodeAggregateId ); } $this->requireNodeAggregateToCoverDimensionSpacePoint( @@ -139,21 +137,20 @@ private function handleCopyNodesRecursively( // Constraint: The node name must be free in all these dimension space points if ($command->targetNodeName) { $this->requireNodeNameToBeUnoccupied( - $contentStreamId, + $contentGraph, $command->targetNodeName, $command->targetParentNodeAggregateId, $parentNodeAggregate->classification->isRoot() ? OriginDimensionSpacePoint::createWithoutDimensions() : $command->targetDimensionSpacePoint, $coveredDimensionSpacePoints, - $contentRepository ); } // Now, we can start creating the recursive structure. $events = []; $this->createEventsForNodeToInsert( - $contentStreamId, + $contentGraph, $command->targetDimensionSpacePoint, $coveredDimensionSpacePoints, $command->targetParentNodeAggregateId, @@ -161,13 +158,12 @@ private function handleCopyNodesRecursively( $command->targetNodeName, $command->nodeTreeToInsert, $command->nodeAggregateIdMapping, - $contentRepository, $events ); return new EventsToPublish( ContentStreamEventStreamName::fromContentStreamId( - $contentStreamId + $contentGraph->getContentStreamId() )->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand( $command, @@ -178,15 +174,13 @@ private function handleCopyNodesRecursively( } private function requireNewNodeAggregateIdsToNotExist( - ContentStreamId $contentStreamId, - Dto\NodeAggregateIdMapping $nodeAggregateIdMapping, - ContentRepository $contentRepository + ContentGraphInterface $contentGraph, + Dto\NodeAggregateIdMapping $nodeAggregateIdMapping ): void { foreach ($nodeAggregateIdMapping->getAllNewNodeAggregateIds() as $nodeAggregateId) { $this->requireProjectedNodeAggregateToNotExist( - $contentStreamId, - $nodeAggregateId, - $contentRepository + $contentGraph, + $nodeAggregateId ); } } @@ -195,7 +189,7 @@ private function requireNewNodeAggregateIdsToNotExist( * @param array $events */ private function createEventsForNodeToInsert( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, OriginDimensionSpacePoint $originDimensionSpacePoint, DimensionSpacePointSet $coveredDimensionSpacePoints, NodeAggregateId $targetParentNodeAggregateId, @@ -203,11 +197,10 @@ private function createEventsForNodeToInsert( ?NodeName $targetNodeName, NodeSubtreeSnapshot $nodeToInsert, Dto\NodeAggregateIdMapping $nodeAggregateIdMapping, - ContentRepository $contentRepository, array &$events, ): void { $events[] = new NodeAggregateWithNodeWasCreated( - $contentStreamId, + $contentGraph->getContentStreamId(), $nodeAggregateIdMapping->getNewNodeAggregateId( $nodeToInsert->nodeAggregateId ) ?: NodeAggregateId::create(), @@ -215,8 +208,7 @@ private function createEventsForNodeToInsert( $originDimensionSpacePoint, $targetSucceedingSiblingNodeAggregateId ? $this->resolveInterdimensionalSiblingsForCreation( - $contentRepository, - $contentStreamId, + $contentGraph, $targetSucceedingSiblingNodeAggregateId, $originDimensionSpacePoint, $coveredDimensionSpacePoints @@ -230,7 +222,7 @@ private function createEventsForNodeToInsert( foreach ($nodeToInsert->childNodes as $childNodeToInsert) { $this->createEventsForNodeToInsert( - $contentStreamId, + $contentGraph, $originDimensionSpacePoint, $coveredDimensionSpacePoints, // the just-inserted node becomes the new parent node ID @@ -242,7 +234,6 @@ private function createEventsForNodeToInsert( $childNodeToInsert->nodeName, $childNodeToInsert, $nodeAggregateIdMapping, - $contentRepository, $events ); } diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeModification/NodeModification.php b/Neos.ContentRepository.Core/Classes/Feature/NodeModification/NodeModification.php index 49d71efa330..ec347b252fd 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeModification/NodeModification.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeModification/NodeModification.php @@ -14,7 +14,7 @@ namespace Neos\ContentRepository\Core\Feature\NodeModification; -use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\EventStore\Events; use Neos\ContentRepository\Core\EventStore\EventsToPublish; use Neos\ContentRepository\Core\Feature\Common\NodeAggregateEventPublisher; @@ -26,10 +26,10 @@ use Neos\ContentRepository\Core\Feature\NodeModification\Event\NodePropertiesWereSet; use Neos\ContentRepository\Core\NodeType\NodeType; use Neos\ContentRepository\Core\NodeType\NodeTypeName; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\PropertyNames; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; /** * @internal implementation detail of Command Handlers @@ -39,21 +39,20 @@ trait NodeModification abstract protected function requireNodeType(NodeTypeName $nodeTypeName): NodeType; abstract protected function requireProjectedNodeAggregate( - ContentStreamId $contentStreamId, - NodeAggregateId $nodeAggregateId, - ContentRepository $contentRepository + ContentGraphInterface $contentGraph, + NodeAggregateId $nodeAggregateId ): NodeAggregate; private function handleSetNodeProperties( SetNodeProperties $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); + $this->requireContentStream($command->workspaceName, $commandHandlingDependencies); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); $this->requireDimensionSpacePointToExist($command->originDimensionSpacePoint->toDimensionSpacePoint()); $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $contentGraph, + $command->nodeAggregateId ); $this->requireNodeAggregateToNotBeRoot($nodeAggregate); $nodeTypeName = $nodeAggregate->nodeTypeName; @@ -71,20 +70,19 @@ private function handleSetNodeProperties( $command->propertyValues->getPropertiesToUnset() ); - return $this->handleSetSerializedNodeProperties($lowLevelCommand, $contentRepository); + return $this->handleSetSerializedNodeProperties($lowLevelCommand, $commandHandlingDependencies); } private function handleSetSerializedNodeProperties( SetSerializedNodeProperties $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $commandHandlingDependencies); // Check if node exists $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $contentGraph, + $command->nodeAggregateId ); $nodeType = $this->requireNodeType($nodeAggregate->nodeTypeName); $this->requireNodeAggregateToOccupyDimensionSpacePoint($nodeAggregate, $command->originDimensionSpacePoint); @@ -98,7 +96,7 @@ private function handleSetSerializedNodeProperties( ); foreach ($affectedOrigins as $affectedOrigin) { $events[] = new NodePropertiesWereSet( - $contentStreamId, + $contentGraph->getContentStreamId(), $command->nodeAggregateId, $affectedOrigin, $nodeAggregate->getCoverageByOccupant($affectedOrigin), @@ -117,7 +115,7 @@ private function handleSetSerializedNodeProperties( ); foreach ($affectedOrigins as $affectedOrigin) { $events[] = new NodePropertiesWereSet( - $contentStreamId, + $contentGraph->getContentStreamId(), $command->nodeAggregateId, $affectedOrigin, $nodeAggregate->getCoverageByOccupant($affectedOrigin), @@ -130,7 +128,7 @@ private function handleSetSerializedNodeProperties( $events = $this->mergeSplitEvents($events); return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId) + ContentStreamEventStreamName::fromContentStreamId($contentGraph->getContentStreamId()) ->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand( $command, @@ -152,7 +150,7 @@ private function splitPropertiesToUnsetByScope(PropertyNames $propertiesToUnset, } return array_map( - fn(array $propertyValues): PropertyNames => PropertyNames::fromArray($propertyValues), + PropertyNames::fromArray(...), $propertiesToUnsetByScope ); } diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php index b164a54d2a0..1869aca61de 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php @@ -14,7 +14,7 @@ namespace Neos\ContentRepository\Core\Feature\NodeMove; -use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\DimensionSpace; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; @@ -28,6 +28,7 @@ use Neos\ContentRepository\Core\Feature\NodeMove\Command\MoveNodeAggregate; use Neos\ContentRepository\Core\Feature\NodeMove\Dto\RelationDistributionStrategy; use Neos\ContentRepository\Core\Feature\NodeMove\Event\NodeAggregateWasMoved; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindPrecedingSiblingNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindSucceedingSiblingNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\Pagination\Pagination; @@ -40,7 +41,6 @@ use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregateIsNoSibling; use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregatesTypeIsAmbiguous; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; /** * @internal implementation detail of Command Handlers @@ -52,25 +52,22 @@ abstract protected function getInterDimensionalVariationGraph(): DimensionSpace\ abstract protected function areAncestorNodeTypeConstraintChecksEnabled(): bool; abstract protected function requireProjectedNodeAggregate( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, NodeAggregateId $nodeAggregateId, - ContentRepository $contentRepository ): NodeAggregate; abstract protected function requireNodeAggregateToBeSibling( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, NodeAggregateId $referenceNodeAggregateId, NodeAggregateId $siblingNodeAggregateId, DimensionSpacePoint $dimensionSpacePoint, - ContentRepository $contentRepository, ): void; abstract protected function requireNodeAggregateToBeChild( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, NodeAggregateId $childNodeAggregateId, - NodeAggregateId $parentNodAggregateId, + NodeAggregateId $parentNodeAggregateId, DimensionSpacePoint $dimensionSpacePoint, - ContentRepository $contentRepository, ): void; /** @@ -84,15 +81,15 @@ abstract protected function requireNodeAggregateToBeChild( */ private function handleMoveNodeAggregate( MoveNodeAggregate $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $contentStreamId = $this->requireContentStream($command->workspaceName, $commandHandlingDependencies); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $commandHandlingDependencies); $this->requireDimensionSpacePointToExist($command->dimensionSpacePoint); $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraph, $command->nodeAggregateId, - $contentRepository ); $this->requireNodeAggregateToNotBeRoot($nodeAggregate); $this->requireNodeAggregateToBeUntethered($nodeAggregate); @@ -106,27 +103,24 @@ private function handleMoveNodeAggregate( if ($command->newParentNodeAggregateId) { $this->requireConstraintsImposedByAncestorsAreMet( - $contentStreamId, + $contentGraph, $this->requireNodeType($nodeAggregate->nodeTypeName), $nodeAggregate->nodeName, [$command->newParentNodeAggregateId], - $contentRepository ); $newParentNodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraph, $command->newParentNodeAggregateId, - $contentRepository ); $this->requireNodeNameToBeUncovered( - $contentStreamId, + $contentGraph, $nodeAggregate->nodeName, $command->newParentNodeAggregateId, // We need to check all covered DSPs of the parent node aggregate to prevent siblings // with different node aggregate IDs but the same name $newParentNodeAggregate->coveredDimensionSpacePoints, - $contentRepository ); $this->requireNodeAggregateToCoverDimensionSpacePoints( @@ -135,58 +129,51 @@ private function handleMoveNodeAggregate( ); $this->requireNodeAggregateToNotBeDescendant( - $contentStreamId, + $contentGraph, $newParentNodeAggregate, $nodeAggregate, - $contentRepository ); } if ($command->newPrecedingSiblingNodeAggregateId) { $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraph, $command->newPrecedingSiblingNodeAggregateId, - $contentRepository ); if ($command->newParentNodeAggregateId) { $this->requireNodeAggregateToBeChild( - $contentStreamId, + $contentGraph, $command->newPrecedingSiblingNodeAggregateId, $command->newParentNodeAggregateId, $command->dimensionSpacePoint, - $contentRepository ); } else { $this->requireNodeAggregateToBeSibling( - $contentStreamId, + $contentGraph, $command->nodeAggregateId, $command->newPrecedingSiblingNodeAggregateId, $command->dimensionSpacePoint, - $contentRepository ); } } if ($command->newSucceedingSiblingNodeAggregateId) { $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraph, $command->newSucceedingSiblingNodeAggregateId, - $contentRepository ); if ($command->newParentNodeAggregateId) { $this->requireNodeAggregateToBeChild( - $contentStreamId, + $contentGraph, $command->newSucceedingSiblingNodeAggregateId, $command->newParentNodeAggregateId, $command->dimensionSpacePoint, - $contentRepository ); } else { $this->requireNodeAggregateToBeSibling( - $contentStreamId, + $contentGraph, $command->nodeAggregateId, $command->newSucceedingSiblingNodeAggregateId, $command->dimensionSpacePoint, - $contentRepository ); } } @@ -197,16 +184,15 @@ private function handleMoveNodeAggregate( $command->nodeAggregateId, $command->newParentNodeAggregateId, $this->resolveInterdimensionalSiblingsForMove( - $contentStreamId, + $contentGraph, $command->dimensionSpacePoint, $affectedDimensionSpacePoints, $command->nodeAggregateId, $command->newParentNodeAggregateId, $command->newSucceedingSiblingNodeAggregateId, $command->newPrecedingSiblingNodeAggregateId, - $command->newParentNodeAggregateId !== null - || $command->newSucceedingSiblingNodeAggregateId === null && $command->newPrecedingSiblingNodeAggregateId === null, - $contentRepository + ($command->newParentNodeAggregateId !== null) + || (($command->newSucceedingSiblingNodeAggregateId === null) && ($command->newPrecedingSiblingNodeAggregateId === null)), ) ) ); @@ -250,7 +236,7 @@ private function resolveAffectedDimensionSpacePointSet( * False when no new parent is set, which will result in the node not being moved */ private function resolveInterdimensionalSiblingsForMove( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, DimensionSpacePoint $selectedDimensionSpacePoint, DimensionSpacePointSet $affectedDimensionSpacePoints, NodeAggregateId $nodeAggregateId, @@ -258,10 +244,8 @@ private function resolveInterdimensionalSiblingsForMove( ?NodeAggregateId $succeedingSiblingId, ?NodeAggregateId $precedingSiblingId, bool $completeSet, - ContentRepository $contentRepository, ): InterdimensionalSiblings { - $selectedSubgraph = $contentRepository->getContentGraph()->getSubgraph( - $contentStreamId, + $selectedSubgraph = $contentGraph->getSubgraph( $selectedDimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ); @@ -280,8 +264,7 @@ private function resolveInterdimensionalSiblingsForMove( $interdimensionalSiblings = []; foreach ($affectedDimensionSpacePoints as $dimensionSpacePoint) { - $variantSubgraph = $contentRepository->getContentGraph()->getSubgraph( - $contentStreamId, + $variantSubgraph = $contentGraph->getSubgraph( $dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ); diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/NodeReferencing.php b/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/NodeReferencing.php index 507e5c6bb6c..9134d910691 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/NodeReferencing.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/NodeReferencing.php @@ -14,7 +14,7 @@ namespace Neos\ContentRepository\Core\Feature\NodeReferencing; -use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\EventStore\Events; use Neos\ContentRepository\Core\EventStore\EventsToPublish; use Neos\ContentRepository\Core\Feature\Common\ConstraintChecks; @@ -26,9 +26,10 @@ use Neos\ContentRepository\Core\Feature\NodeReferencing\Dto\NodeReferenceToWrite; use Neos\ContentRepository\Core\Feature\NodeReferencing\Dto\SerializedNodeReference; use Neos\ContentRepository\Core\Feature\NodeReferencing\Event\NodeReferencesWereSet; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; +use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; /** * @internal implementation detail of Command Handlers @@ -38,22 +39,21 @@ trait NodeReferencing use ConstraintChecks; abstract protected function requireProjectedNodeAggregate( - ContentStreamId $contentStreamId, - NodeAggregateId $nodeAggregateId, - ContentRepository $contentRepository + ContentGraphInterface $contentGraph, + NodeAggregateId $nodeAggregateId ): NodeAggregate; private function handleSetNodeReferences( SetNodeReferences $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); + $this->requireContentStream($command->workspaceName, $commandHandlingDependencies); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); $this->requireDimensionSpacePointToExist($command->sourceOriginDimensionSpacePoint->toDimensionSpacePoint()); $sourceNodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->sourceNodeAggregateId, - $contentRepository + $contentGraph, + $command->sourceNodeAggregateId ); $this->requireNodeAggregateToNotBeRoot($sourceNodeAggregate); $nodeTypeName = $sourceNodeAggregate->nodeTypeName; @@ -88,25 +88,24 @@ private function handleSetNodeReferences( )), ); - return $this->handleSetSerializedNodeReferences($lowLevelCommand, $contentRepository); + return $this->handleSetSerializedNodeReferences($lowLevelCommand, $commandHandlingDependencies); } /** - * @throws \Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet + * @throws ContentStreamDoesNotExistYet */ private function handleSetSerializedNodeReferences( SetSerializedNodeReferences $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $commandHandlingDependencies); $this->requireDimensionSpacePointToExist( $command->sourceOriginDimensionSpacePoint->toDimensionSpacePoint() ); $sourceNodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->sourceNodeAggregateId, - $contentRepository + $contentGraph, + $command->sourceNodeAggregateId ); $this->requireNodeAggregateToNotBeRoot($sourceNodeAggregate); $this->requireNodeAggregateToOccupyDimensionSpacePoint( @@ -124,9 +123,8 @@ private function handleSetSerializedNodeReferences( foreach ($command->references as $reference) { assert($reference instanceof SerializedNodeReference); $destinationNodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $reference->targetNodeAggregateId, - $contentRepository + $contentGraph, + $reference->targetNodeAggregateId ); $this->requireNodeAggregateToNotBeRoot($destinationNodeAggregate); $this->requireNodeAggregateToCoverDimensionSpacePoint( @@ -152,7 +150,7 @@ private function handleSetSerializedNodeReferences( $events = Events::with( new NodeReferencesWereSet( - $contentStreamId, + $contentGraph->getContentStreamId(), $command->sourceNodeAggregateId, $affectedOrigins, $command->referenceName, @@ -161,7 +159,7 @@ private function handleSetSerializedNodeReferences( ); return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId) + ContentStreamEventStreamName::fromContentStreamId($contentGraph->getContentStreamId()) ->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand( $command, diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeRemoval/Command/RemoveNodeAggregate.php b/Neos.ContentRepository.Core/Classes/Feature/NodeRemoval/Command/RemoveNodeAggregate.php index f169088ff73..085af255b8c 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeRemoval/Command/RemoveNodeAggregate.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeRemoval/Command/RemoveNodeAggregate.php @@ -21,7 +21,6 @@ use Neos\ContentRepository\Core\Feature\WorkspacePublication\Dto\NodeIdToPublishOrDiscard; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeVariantSelectionStrategy; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeRemoval/NodeRemoval.php b/Neos.ContentRepository.Core/Classes/Feature/NodeRemoval/NodeRemoval.php index 446f7b58e45..41a8c5c7eed 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeRemoval/NodeRemoval.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeRemoval/NodeRemoval.php @@ -14,7 +14,7 @@ namespace Neos\ContentRepository\Core\Feature\NodeRemoval; -use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\DimensionSpace; use Neos\ContentRepository\Core\DimensionSpace\Exception\DimensionSpacePointNotFound; use Neos\ContentRepository\Core\EventStore\Events; @@ -47,14 +47,14 @@ abstract protected function areAncestorNodeTypeConstraintChecksEnabled(): bool; */ private function handleRemoveNodeAggregate( RemoveNodeAggregate $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $this->requireContentStream($command->workspaceName, $commandHandlingDependencies); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $commandHandlingDependencies); $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $contentGraph, + $command->nodeAggregateId ); $this->requireDimensionSpacePointToExist($command->coveredDimensionSpacePoint); $this->requireNodeAggregateNotToBeTethered($nodeAggregate); @@ -64,15 +64,14 @@ private function handleRemoveNodeAggregate( ); if ($command->removalAttachmentPoint instanceof NodeAggregateId) { $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->removalAttachmentPoint, - $contentRepository + $contentGraph, + $command->removalAttachmentPoint ); } $events = Events::with( new NodeAggregateWasRemoved( - $contentStreamId, + $contentGraph->getContentStreamId(), $command->nodeAggregateId, $command->nodeVariantSelectionStrategy->resolveAffectedOriginDimensionSpacePoints( $nodeAggregate->getOccupationByCovered($command->coveredDimensionSpacePoint), @@ -89,7 +88,7 @@ private function handleRemoveNodeAggregate( ); return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId) + ContentStreamEventStreamName::fromContentStreamId($contentGraph->getContentStreamId()) ->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand( $command, diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php b/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php index 40403f523ba..930d1294f1d 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php @@ -14,7 +14,7 @@ namespace Neos\ContentRepository\Core\Feature\NodeRenaming; -use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\EventStore\Events; use Neos\ContentRepository\Core\EventStore\EventsToPublish; use Neos\ContentRepository\Core\Feature\Common\ConstraintChecks; @@ -30,40 +30,38 @@ trait NodeRenaming { use ConstraintChecks; - private function handleChangeNodeAggregateName(ChangeNodeAggregateName $command, ContentRepository $contentRepository): EventsToPublish + private function handleChangeNodeAggregateName(ChangeNodeAggregateName $command, CommandHandlingDependencies $commandHandlingDependencies): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $commandHandlingDependencies); $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $contentGraph, + $command->nodeAggregateId ); $this->requireNodeAggregateToNotBeRoot($nodeAggregate, 'and Root Node Aggregates cannot be renamed'); $this->requireNodeAggregateToBeUntethered($nodeAggregate); - foreach ($contentRepository->getContentGraph()->findParentNodeAggregates($contentStreamId, $command->nodeAggregateId) as $parentNodeAggregate) { + foreach ($contentGraph->findParentNodeAggregates($command->nodeAggregateId) as $parentNodeAggregate) { foreach ($parentNodeAggregate->occupiedDimensionSpacePoints as $occupiedParentDimensionSpacePoint) { $this->requireNodeNameToBeUnoccupied( - $contentStreamId, + $contentGraph, $command->newNodeName, $parentNodeAggregate->nodeAggregateId, $occupiedParentDimensionSpacePoint, - $parentNodeAggregate->coveredDimensionSpacePoints, - $contentRepository + $parentNodeAggregate->coveredDimensionSpacePoints ); } } $events = Events::with( new NodeAggregateNameWasChanged( - $contentStreamId, + $contentGraph->getContentStreamId(), $command->nodeAggregateId, $command->newNodeName, ), ); return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId)->getEventStreamName(), + ContentStreamEventStreamName::fromContentStreamId($contentGraph->getContentStreamId())->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand( $command, $events diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php b/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php index 25e60f27449..341d64437a1 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php @@ -14,7 +14,7 @@ namespace Neos\ContentRepository\Core\Feature\NodeTypeChange; -use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePointSet; @@ -28,6 +28,7 @@ use Neos\ContentRepository\Core\Feature\NodeTypeChange\Event\NodeAggregateTypeWasChanged; use Neos\ContentRepository\Core\NodeType\NodeType; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; use Neos\ContentRepository\Core\Projection\ContentGraph\NodePath; @@ -38,7 +39,6 @@ use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeNotFoundException; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; /** @codingStandardsIgnoreStart */ /** @codingStandardsIgnoreEnd */ @@ -51,17 +51,15 @@ trait NodeTypeChange abstract protected function getNodeTypeManager(): NodeTypeManager; abstract protected function requireProjectedNodeAggregate( - ContentStreamId $contentStreamId, - NodeAggregateId $nodeAggregateId, - ContentRepository $contentRepository + ContentGraphInterface $contentRepository, + NodeAggregateId $nodeAggregateId ): NodeAggregate; abstract protected function requireConstraintsImposedByAncestorsAreMet( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, NodeType $nodeType, ?NodeName $nodeName, - array $parentNodeAggregateIds, - ContentRepository $contentRepository + array $parentNodeAggregateIds ): void; abstract protected function requireNodeTypeConstraintsImposedByParentToBeMet( @@ -89,12 +87,12 @@ abstract protected function areNodeTypeConstraintsImposedByGrandparentValid( ): bool; abstract protected function createEventsForMissingTetheredNode( + ContentGraphInterface $contentGraph, NodeAggregate $parentNodeAggregate, OriginDimensionSpacePoint $originDimensionSpacePoint, NodeName $tetheredNodeName, NodeAggregateId $tetheredNodeAggregateId, - NodeType $expectedTetheredNodeType, - ContentRepository $contentRepository + NodeType $expectedTetheredNodeType ): Events; /** @@ -102,22 +100,22 @@ abstract protected function createEventsForMissingTetheredNode( * @throws NodeConstraintException * @throws NodeTypeNotFoundException * @throws NodeAggregatesTypeIsAmbiguous + * @throws \Exception */ private function handleChangeNodeAggregateType( ChangeNodeAggregateType $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { /************** * Constraint checks **************/ // existence of content stream, node type and node aggregate - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $commandHandlingDependencies); $newNodeType = $this->requireNodeType($command->newNodeTypeName); $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $contentGraph, + $command->nodeAggregateId ); // node type detail checks @@ -126,25 +124,23 @@ private function handleChangeNodeAggregateType( $this->requireTetheredDescendantNodeTypesToNotBeOfTypeRoot($newNodeType); // the new node type must be allowed at this position in the tree - $parentNodeAggregates = $contentRepository->getContentGraph()->findParentNodeAggregates( - $nodeAggregate->contentStreamId, + $parentNodeAggregates = $contentGraph->findParentNodeAggregates( $nodeAggregate->nodeAggregateId ); foreach ($parentNodeAggregates as $parentNodeAggregate) { assert($parentNodeAggregate instanceof NodeAggregate); $this->requireConstraintsImposedByAncestorsAreMet( - $contentStreamId, + $contentGraph, $newNodeType, $nodeAggregate->nodeName, - [$parentNodeAggregate->nodeAggregateId], - $contentRepository + [$parentNodeAggregate->nodeAggregateId] ); } /** @codingStandardsIgnoreStart */ match ($command->strategy) { NodeAggregateTypeChangeChildConstraintConflictResolutionStrategy::STRATEGY_HAPPY_PATH - => $this->requireConstraintsImposedByHappyPathStrategyAreMet($nodeAggregate, $newNodeType, $contentRepository), + => $this->requireConstraintsImposedByHappyPathStrategyAreMet($contentGraph, $nodeAggregate, $newNodeType), NodeAggregateTypeChangeChildConstraintConflictResolutionStrategy::STRATEGY_DELETE => null }; /** @codingStandardsIgnoreStop */ @@ -165,7 +161,7 @@ private function handleChangeNodeAggregateType( **************/ $events = [ new NodeAggregateTypeWasChanged( - $contentStreamId, + $contentGraph->getContentStreamId(), $command->nodeAggregateId, $command->newNodeTypeName ), @@ -174,14 +170,14 @@ private function handleChangeNodeAggregateType( // remove disallowed nodes if ($command->strategy === NodeAggregateTypeChangeChildConstraintConflictResolutionStrategy::STRATEGY_DELETE) { array_push($events, ...iterator_to_array($this->deleteDisallowedNodesWhenChangingNodeType( + $contentGraph, $nodeAggregate, - $newNodeType, - $contentRepository + $newNodeType ))); array_push($events, ...iterator_to_array($this->deleteObsoleteTetheredNodesWhenChangingNodeType( + $contentGraph, $nodeAggregate, - $newNodeType, - $contentRepository + $newNodeType ))); } @@ -192,33 +188,32 @@ private function handleChangeNodeAggregateType( foreach ($expectedTetheredNodes as $serializedTetheredNodeName => $expectedTetheredNodeType) { $tetheredNodeName = NodeName::fromString($serializedTetheredNodeName); - $subgraph = $contentRepository->getContentGraph()->getSubgraph( - $node->subgraphIdentity->contentStreamId, + $tetheredNode = $contentGraph->getSubgraph( $node->originDimensionSpacePoint->toDimensionSpacePoint(), VisibilityConstraints::withoutRestrictions() - ); - $tetheredNode = $subgraph->findNodeByPath( + )->findNodeByPath( $tetheredNodeName, - $node->nodeAggregateId + $node->nodeAggregateId, ); + if ($tetheredNode === null) { $tetheredNodeAggregateId = $command->tetheredDescendantNodeAggregateIds ->getNodeAggregateId(NodePath::fromString($tetheredNodeName->value)) ?: NodeAggregateId::create(); array_push($events, ...iterator_to_array($this->createEventsForMissingTetheredNode( + $contentGraph, $nodeAggregate, $node->originDimensionSpacePoint, $tetheredNodeName, $tetheredNodeAggregateId, - $expectedTetheredNodeType, - $contentRepository + $expectedTetheredNodeType ))); } } } return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId)->getEventStreamName(), + ContentStreamEventStreamName::fromContentStreamId($contentGraph->getContentStreamId())->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand( $command, Events::fromArray($events), @@ -234,14 +229,13 @@ private function handleChangeNodeAggregateType( * @throws NodeConstraintException|NodeTypeNotFoundException */ private function requireConstraintsImposedByHappyPathStrategyAreMet( + ContentGraphInterface $contentGraph, NodeAggregate $nodeAggregate, - NodeType $newNodeType, - ContentRepository $contentRepository + NodeType $newNodeType ): void { // if we have children, we need to check whether they are still allowed // after we changed the node type of the $nodeAggregate to $newNodeType. - $childNodeAggregates = $contentRepository->getContentGraph()->findChildNodeAggregates( - $nodeAggregate->contentStreamId, + $childNodeAggregates = $contentGraph->findChildNodeAggregates( $nodeAggregate->nodeAggregateId ); foreach ($childNodeAggregates as $childNodeAggregate) { @@ -256,11 +250,9 @@ private function requireConstraintsImposedByHappyPathStrategyAreMet( // we do not need to test for grandparents here, as we did not modify the grandparents. // Thus, if it was allowed before, it is allowed now. - // additionally, we need to look one level down to the grandchildren as well // - as it could happen that these are affected by our constraint checks as well. - $grandchildNodeAggregates = $contentRepository->getContentGraph()->findChildNodeAggregates( - $childNodeAggregate->contentStreamId, + $grandchildNodeAggregates = $contentGraph->findChildNodeAggregates( $childNodeAggregate->nodeAggregateId ); foreach ($grandchildNodeAggregates as $grandchildNodeAggregate) { @@ -282,15 +274,14 @@ private function requireConstraintsImposedByHappyPathStrategyAreMet( * needs to be modified as well (as they are structurally the same) */ private function deleteDisallowedNodesWhenChangingNodeType( + ContentGraphInterface $contentGraph, NodeAggregate $nodeAggregate, - NodeType $newNodeType, - ContentRepository $contentRepository + NodeType $newNodeType ): Events { $events = []; // if we have children, we need to check whether they are still allowed // after we changed the node type of the $nodeAggregate to $newNodeType. - $childNodeAggregates = $contentRepository->getContentGraph()->findChildNodeAggregates( - $nodeAggregate->contentStreamId, + $childNodeAggregates = $contentGraph->findChildNodeAggregates( $nodeAggregate->nodeAggregateId ); foreach ($childNodeAggregates as $childNodeAggregate) { @@ -308,9 +299,9 @@ private function deleteDisallowedNodesWhenChangingNodeType( // this aggregate (or parts thereof) are DISALLOWED according to constraints. // We now need to find out which edges we need to remove, $dimensionSpacePointsToBeRemoved = $this->findDimensionSpacePointsConnectingParentAndChildAggregate( + $contentGraph, $nodeAggregate, - $childNodeAggregate, - $contentRepository + $childNodeAggregate ); // AND REMOVE THEM $events[] = $this->removeNodeInDimensionSpacePointSet( @@ -321,13 +312,9 @@ private function deleteDisallowedNodesWhenChangingNodeType( // we do not need to test for grandparents here, as we did not modify the grandparents. // Thus, if it was allowed before, it is allowed now. - // additionally, we need to look one level down to the grandchildren as well // - as it could happen that these are affected by our constraint checks as well. - $grandchildNodeAggregates = $contentRepository->getContentGraph()->findChildNodeAggregates( - $childNodeAggregate->contentStreamId, - $childNodeAggregate->nodeAggregateId - ); + $grandchildNodeAggregates = $contentGraph->findChildNodeAggregates($childNodeAggregate->nodeAggregateId); foreach ($grandchildNodeAggregates as $grandchildNodeAggregate) { /* @var $grandchildNodeAggregate NodeAggregate */ // we do not need to test for the parent of grandchild (=child), @@ -344,9 +331,9 @@ private function deleteDisallowedNodesWhenChangingNodeType( // this aggregate (or parts thereof) are DISALLOWED according to constraints. // We now need to find out which edges we need to remove, $dimensionSpacePointsToBeRemoved = $this->findDimensionSpacePointsConnectingParentAndChildAggregate( + $contentGraph, $childNodeAggregate, - $grandchildNodeAggregate, - $contentRepository + $grandchildNodeAggregate ); // AND REMOVE THEM $events[] = $this->removeNodeInDimensionSpacePointSet( @@ -361,18 +348,15 @@ private function deleteDisallowedNodesWhenChangingNodeType( } private function deleteObsoleteTetheredNodesWhenChangingNodeType( + ContentGraphInterface $contentGraph, NodeAggregate $nodeAggregate, - NodeType $newNodeType, - ContentRepository $contentRepository + NodeType $newNodeType ): Events { $expectedTetheredNodes = $this->getNodeTypeManager()->getTetheredNodesConfigurationForNodeType($newNodeType); $events = []; // find disallowed tethered nodes - $tetheredNodeAggregates = $contentRepository->getContentGraph()->findTetheredChildNodeAggregates( - $nodeAggregate->contentStreamId, - $nodeAggregate->nodeAggregateId - ); + $tetheredNodeAggregates = $contentGraph->findTetheredChildNodeAggregates($nodeAggregate->nodeAggregateId); foreach ($tetheredNodeAggregates as $tetheredNodeAggregate) { /* @var $tetheredNodeAggregate NodeAggregate */ @@ -380,9 +364,9 @@ private function deleteObsoleteTetheredNodesWhenChangingNodeType( // this aggregate (or parts thereof) are DISALLOWED according to constraints. // We now need to find out which edges we need to remove, $dimensionSpacePointsToBeRemoved = $this->findDimensionSpacePointsConnectingParentAndChildAggregate( + $contentGraph, $nodeAggregate, - $tetheredNodeAggregate, - $contentRepository + $tetheredNodeAggregate ); // AND REMOVE THEM $events[] = $this->removeNodeInDimensionSpacePointSet( @@ -419,18 +403,15 @@ private function deleteObsoleteTetheredNodesWhenChangingNodeType( * we originated from) */ private function findDimensionSpacePointsConnectingParentAndChildAggregate( + ContentGraphInterface $contentGraph, NodeAggregate $parentNodeAggregate, - NodeAggregate $childNodeAggregate, - ContentRepository $contentRepository + NodeAggregate $childNodeAggregate ): DimensionSpacePointSet { $points = []; foreach ($childNodeAggregate->coveredDimensionSpacePoints as $coveredDimensionSpacePoint) { - $subgraph = $contentRepository->getContentGraph()->getSubgraph( - $childNodeAggregate->contentStreamId, - $coveredDimensionSpacePoint, - VisibilityConstraints::withoutRestrictions() + $parentNode = $contentGraph->getSubgraph($coveredDimensionSpacePoint, VisibilityConstraints::withoutRestrictions())->findParentNode( + $childNodeAggregate->nodeAggregateId ); - $parentNode = $subgraph->findParentNode($childNodeAggregate->nodeAggregateId); if ( $parentNode && $parentNode->nodeAggregateId->equals($parentNodeAggregate->nodeAggregateId) diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeVariation/Command/CreateNodeVariant.php b/Neos.ContentRepository.Core/Classes/Feature/NodeVariation/Command/CreateNodeVariant.php index c842644e845..001f9bd66e9 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeVariation/Command/CreateNodeVariant.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeVariation/Command/CreateNodeVariant.php @@ -20,7 +20,6 @@ use Neos\ContentRepository\Core\Feature\Common\RebasableToOtherWorkspaceInterface; use Neos\ContentRepository\Core\Feature\WorkspacePublication\Dto\NodeIdToPublishOrDiscard; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeVariation/NodeVariation.php b/Neos.ContentRepository.Core/Classes/Feature/NodeVariation/NodeVariation.php index 7df4a3acb5b..fe9a58f7d3e 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeVariation/NodeVariation.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeVariation/NodeVariation.php @@ -14,7 +14,7 @@ namespace Neos\ContentRepository\Core\Feature\NodeVariation; -use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\DimensionSpace\Exception\DimensionSpacePointNotFound; use Neos\ContentRepository\Core\EventStore\EventsToPublish; use Neos\ContentRepository\Core\Feature\Common\ConstraintChecks; @@ -48,14 +48,14 @@ trait NodeVariation */ private function handleCreateNodeVariant( CreateNodeVariant $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $this->requireContentStream($command->workspaceName, $commandHandlingDependencies); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $commandHandlingDependencies); $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $contentGraph, + $command->nodeAggregateId ); // we do this check first, because it gives a more meaningful error message on what you need to do. // we cannot use sentences with "." because the UI will only print the 1st sentence :/ @@ -66,10 +66,9 @@ private function handleCreateNodeVariant( $this->requireNodeAggregateToOccupyDimensionSpacePoint($nodeAggregate, $command->sourceOrigin); $this->requireNodeAggregateToNotOccupyDimensionSpacePoint($nodeAggregate, $command->targetOrigin); $parentNodeAggregate = $this->requireProjectedParentNodeAggregate( - $contentStreamId, + $contentGraph, $command->nodeAggregateId, - $command->sourceOrigin, - $contentRepository + $command->sourceOrigin ); $this->requireNodeAggregateToCoverDimensionSpacePoint( $parentNodeAggregate, @@ -77,15 +76,14 @@ private function handleCreateNodeVariant( ); $events = $this->createEventsForVariations( - $contentStreamId, + $contentGraph, $command->sourceOrigin, $command->targetOrigin, - $nodeAggregate, - $contentRepository + $nodeAggregate ); return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId)->getEventStreamName(), + ContentStreamEventStreamName::fromContentStreamId($contentGraph->getContentStreamId())->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand( $command, $events diff --git a/Neos.ContentRepository.Core/Classes/Feature/RootNodeCreation/RootNodeHandling.php b/Neos.ContentRepository.Core/Classes/Feature/RootNodeCreation/RootNodeHandling.php index 0fb102fe6ef..8bd2e3cb142 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/RootNodeCreation/RootNodeHandling.php +++ b/Neos.ContentRepository.Core/Classes/Feature/RootNodeCreation/RootNodeHandling.php @@ -14,7 +14,7 @@ namespace Neos\ContentRepository\Core\Feature\RootNodeCreation; -use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\EventStore\Events; @@ -59,6 +59,7 @@ abstract protected function requireNodeTypeToBeOfTypeRoot(NodeType $nodeType): v /** * @param CreateRootNodeAggregateWithNode $command + * @param CommandHandlingDependencies $commandHandlingDependencies * @return EventsToPublish * @throws ContentStreamDoesNotExistYet * @throws NodeAggregateCurrentlyExists @@ -68,22 +69,21 @@ abstract protected function requireNodeTypeToBeOfTypeRoot(NodeType $nodeType): v */ private function handleCreateRootNodeAggregateWithNode( CreateRootNodeAggregateWithNode $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $this->requireContentStream($command->workspaceName, $commandHandlingDependencies); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $commandHandlingDependencies); $this->requireProjectedNodeAggregateToNotExist( - $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $contentGraph, + $command->nodeAggregateId ); $nodeType = $this->requireNodeType($command->nodeTypeName); $this->requireNodeTypeToNotBeAbstract($nodeType); $this->requireNodeTypeToBeOfTypeRoot($nodeType); $this->requireRootNodeTypeToBeUnoccupied( - $nodeType->name, - $contentStreamId, - $contentRepository + $contentGraph, + $nodeType->name ); $descendantNodeAggregateIds = $command->tetheredDescendantNodeAggregateIds->completeForNodeOfType( @@ -97,25 +97,24 @@ private function handleCreateRootNodeAggregateWithNode( $events = [ $this->createRootWithNode( $command, - $contentStreamId, + $contentGraph->getContentStreamId(), $this->getAllowedDimensionSubspace() ) ]; foreach ($this->getInterDimensionalVariationGraph()->getRootGeneralizations() as $rootGeneralization) { array_push($events, ...iterator_to_array($this->handleTetheredRootChildNodes( - $contentStreamId, + $contentGraph->getContentStreamId(), $nodeType, OriginDimensionSpacePoint::fromDimensionSpacePoint($rootGeneralization), $this->getInterDimensionalVariationGraph()->getSpecializationSet($rootGeneralization, true), $command->nodeAggregateId, $command->tetheredDescendantNodeAggregateIds, - null, - $contentRepository + null ))); } - $contentStreamEventStream = ContentStreamEventStreamName::fromContentStreamId($contentStreamId); + $contentStreamEventStream = ContentStreamEventStreamName::fromContentStreamId($contentGraph->getContentStreamId()); return new EventsToPublish( $contentStreamEventStream->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand( @@ -146,14 +145,13 @@ private function createRootWithNode( */ private function handleUpdateRootNodeAggregateDimensions( UpdateRootNodeAggregateDimensions $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $commandHandlingDependencies); $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $contentGraph, + $command->nodeAggregateId ); if (!$nodeAggregate->classification->isRoot()) { throw new NodeAggregateIsNotRoot('The node aggregate ' . $nodeAggregate->nodeAggregateId->value . ' is not classified as root, but should be for command UpdateRootNodeAggregateDimensions.', 1678647355); @@ -161,14 +159,14 @@ private function handleUpdateRootNodeAggregateDimensions( $events = Events::with( new RootNodeAggregateDimensionsWereUpdated( - $contentStreamId, + $contentGraph->getContentStreamId(), $command->nodeAggregateId, $this->getAllowedDimensionSubspace() ) ); $contentStreamEventStream = ContentStreamEventStreamName::fromContentStreamId( - $contentStreamId + $contentGraph->getContentStreamId() ); return new EventsToPublish( $contentStreamEventStream->getEventStreamName(), @@ -191,8 +189,7 @@ private function handleTetheredRootChildNodes( DimensionSpacePointSet $coveredDimensionSpacePoints, NodeAggregateId $parentNodeAggregateId, NodeAggregateIdsByNodePaths $nodeAggregateIdsByNodePath, - ?NodePath $nodePath, - ContentRepository $contentRepository, + ?NodePath $nodePath ): Events { $events = []; foreach ($this->getNodeTypeManager()->getTetheredNodesConfigurationForNodeType($nodeType) as $rawNodeName => $childNodeType) { @@ -223,8 +220,7 @@ private function handleTetheredRootChildNodes( $coveredDimensionSpacePoints, $childNodeAggregateId, $nodeAggregateIdsByNodePath, - $childNodePath, - $contentRepository + $childNodePath ))); } diff --git a/Neos.ContentRepository.Core/Classes/Feature/SubtreeTagging/SubtreeTagging.php b/Neos.ContentRepository.Core/Classes/Feature/SubtreeTagging/SubtreeTagging.php index 05f3c58248e..d16e300dfe5 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/SubtreeTagging/SubtreeTagging.php +++ b/Neos.ContentRepository.Core/Classes/Feature/SubtreeTagging/SubtreeTagging.php @@ -14,7 +14,7 @@ * source code. */ -use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\DimensionSpace; use Neos\ContentRepository\Core\EventStore\Events; use Neos\ContentRepository\Core\EventStore\EventsToPublish; @@ -36,11 +36,11 @@ trait SubtreeTagging abstract protected function getInterDimensionalVariationGraph(): DimensionSpace\InterDimensionalVariationGraph; - private function handleTagSubtree(TagSubtree $command, ContentRepository $contentRepository): EventsToPublish + private function handleTagSubtree(TagSubtree $command, CommandHandlingDependencies $commandHandlingDependencies): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); $this->requireDimensionSpacePointToExist($command->coveredDimensionSpacePoint); - $nodeAggregate = $this->requireProjectedNodeAggregate($contentStreamId, $command->nodeAggregateId, $contentRepository); + $nodeAggregate = $this->requireProjectedNodeAggregate($contentGraph, $command->nodeAggregateId); $this->requireNodeAggregateToCoverDimensionSpacePoint( $nodeAggregate, $command->coveredDimensionSpacePoint @@ -60,7 +60,7 @@ private function handleTagSubtree(TagSubtree $command, ContentRepository $conten $events = Events::with( new SubtreeWasTagged( - $contentStreamId, + $contentGraph->getContentStreamId(), $command->nodeAggregateId, $affectedDimensionSpacePoints, $command->tag, @@ -68,7 +68,7 @@ private function handleTagSubtree(TagSubtree $command, ContentRepository $conten ); return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId) + ContentStreamEventStreamName::fromContentStreamId($contentGraph->getContentStreamId()) ->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand( $command, @@ -78,14 +78,13 @@ private function handleTagSubtree(TagSubtree $command, ContentRepository $conten ); } - public function handleUntagSubtree(UntagSubtree $command, ContentRepository $contentRepository): EventsToPublish + public function handleUntagSubtree(UntagSubtree $command, CommandHandlingDependencies $commandHandlingDependencies): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); $this->requireDimensionSpacePointToExist($command->coveredDimensionSpacePoint); $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $contentGraph, + $command->nodeAggregateId ); $this->requireNodeAggregateToCoverDimensionSpacePoint( $nodeAggregate, @@ -106,7 +105,7 @@ public function handleUntagSubtree(UntagSubtree $command, ContentRepository $con $events = Events::with( new SubtreeWasUntagged( - $contentStreamId, + $contentGraph->getContentStreamId(), $command->nodeAggregateId, $affectedDimensionSpacePoints, $command->tag, @@ -114,7 +113,7 @@ public function handleUntagSubtree(UntagSubtree $command, ContentRepository $con ); return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId)->getEventStreamName(), + ContentStreamEventStreamName::fromContentStreamId($contentGraph->getContentStreamId())->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand($command, $events), ExpectedVersion::ANY() ); diff --git a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php index f03b3a20633..d4de04228a2 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php @@ -17,6 +17,7 @@ use Neos\ContentRepository\Core\CommandHandler\CommandHandlerInterface; use Neos\ContentRepository\Core\CommandHandler\CommandInterface; use Neos\ContentRepository\Core\CommandHandler\CommandResult; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\EventStore\DecoratedEvent; use Neos\ContentRepository\Core\EventStore\EventInterface; @@ -24,7 +25,6 @@ use Neos\ContentRepository\Core\EventStore\EventPersister; use Neos\ContentRepository\Core\EventStore\Events; use Neos\ContentRepository\Core\EventStore\EventsToPublish; -use Neos\ContentRepository\Core\Feature\Common\ContentStreamIdOverride; use Neos\ContentRepository\Core\Feature\Common\MatchableWithNodeIdToPublishOrDiscardInterface; use Neos\ContentRepository\Core\Feature\Common\PublishableToOtherContentStreamsInterface; use Neos\ContentRepository\Core\Feature\Common\RebasableToOtherWorkspaceInterface; @@ -68,6 +68,7 @@ use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Event\WorkspaceWasRebased; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Exception\WorkspaceRebaseFailed; use Neos\ContentRepository\Core\Projection\Workspace\Workspace; +use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceFinder; use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamAlreadyExists; use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet; use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; @@ -97,21 +98,21 @@ public function canHandle(CommandInterface $command): bool return method_exists($this, 'handle' . (new \ReflectionClass($command))->getShortName()); } - public function handle(CommandInterface $command, ContentRepository $contentRepository): EventsToPublish + public function handle(CommandInterface $command, CommandHandlingDependencies $commandHandlingDependencies): EventsToPublish { /** @phpstan-ignore-next-line */ return match ($command::class) { - CreateWorkspace::class => $this->handleCreateWorkspace($command, $contentRepository), - RenameWorkspace::class => $this->handleRenameWorkspace($command, $contentRepository), - CreateRootWorkspace::class => $this->handleCreateRootWorkspace($command, $contentRepository), - PublishWorkspace::class => $this->handlePublishWorkspace($command, $contentRepository), - RebaseWorkspace::class => $this->handleRebaseWorkspace($command, $contentRepository), - PublishIndividualNodesFromWorkspace::class => $this->handlePublishIndividualNodesFromWorkspace($command, $contentRepository), - DiscardIndividualNodesFromWorkspace::class => $this->handleDiscardIndividualNodesFromWorkspace($command, $contentRepository), - DiscardWorkspace::class => $this->handleDiscardWorkspace($command, $contentRepository), - DeleteWorkspace::class => $this->handleDeleteWorkspace($command, $contentRepository), - ChangeWorkspaceOwner::class => $this->handleChangeWorkspaceOwner($command, $contentRepository), - ChangeBaseWorkspace::class => $this->handleChangeBaseWorkspace($command, $contentRepository), + CreateWorkspace::class => $this->handleCreateWorkspace($command, $commandHandlingDependencies), + RenameWorkspace::class => $this->handleRenameWorkspace($command, $commandHandlingDependencies), + CreateRootWorkspace::class => $this->handleCreateRootWorkspace($command, $commandHandlingDependencies), + PublishWorkspace::class => $this->handlePublishWorkspace($command, $commandHandlingDependencies), + RebaseWorkspace::class => $this->handleRebaseWorkspace($command, $commandHandlingDependencies), + PublishIndividualNodesFromWorkspace::class => $this->handlePublishIndividualNodesFromWorkspace($command, $commandHandlingDependencies), + DiscardIndividualNodesFromWorkspace::class => $this->handleDiscardIndividualNodesFromWorkspace($command, $commandHandlingDependencies), + DiscardWorkspace::class => $this->handleDiscardWorkspace($command, $commandHandlingDependencies), + DeleteWorkspace::class => $this->handleDeleteWorkspace($command, $commandHandlingDependencies), + ChangeWorkspaceOwner::class => $this->handleChangeWorkspaceOwner($command, $commandHandlingDependencies), + ChangeBaseWorkspace::class => $this->handleChangeBaseWorkspace($command, $commandHandlingDependencies), }; } @@ -123,17 +124,11 @@ public function handle(CommandInterface $command, ContentRepository $contentRepo */ private function handleCreateWorkspace( CreateWorkspace $command, - ContentRepository $contentRepository, + CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { - $existingWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName($command->workspaceName); - if ($existingWorkspace !== null) { - throw new WorkspaceAlreadyExists(sprintf( - 'The workspace %s already exists', - $command->workspaceName->value - ), 1505830958921); - } + $this->requireWorkspaceToNotExist($command->workspaceName, $commandHandlingDependencies); - $baseWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName($command->baseWorkspaceName); + $baseWorkspace = $commandHandlingDependencies->getWorkspaceFinder()->findOneByName($command->baseWorkspaceName); if ($baseWorkspace === null) { throw new BaseWorkspaceDoesNotExist(sprintf( 'The workspace %s (base workspace of %s) does not exist', @@ -142,11 +137,12 @@ private function handleCreateWorkspace( ), 1513890708); } + $baseWorkspaceContentGraph = $commandHandlingDependencies->getContentGraph($command->baseWorkspaceName); // When the workspace is created, we first have to fork the content stream - $contentRepository->handle( + $commandHandlingDependencies->handle( ForkContentStream::create( $command->newContentStreamId, - $baseWorkspace->currentContentStreamId, + $baseWorkspaceContentGraph->getContentStreamId(), ) )->block(); @@ -171,9 +167,11 @@ private function handleCreateWorkspace( /** * @throws WorkspaceDoesNotExist */ - private function handleRenameWorkspace(RenameWorkspace $command, ContentRepository $contentRepository): EventsToPublish - { - $this->requireWorkspace($command->workspaceName, $contentRepository); + private function handleRenameWorkspace( + RenameWorkspace $command, + CommandHandlingDependencies $commandHandlingDependencies + ): EventsToPublish { + $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies->getWorkspaceFinder()); $events = Events::with( new WorkspaceWasRenamed( @@ -198,18 +196,12 @@ private function handleRenameWorkspace(RenameWorkspace $command, ContentReposito */ private function handleCreateRootWorkspace( CreateRootWorkspace $command, - ContentRepository $contentRepository, + CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { - $existingWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName($command->workspaceName); - if ($existingWorkspace !== null) { - throw new WorkspaceAlreadyExists(sprintf( - 'The workspace %s already exists', - $command->workspaceName->value - ), 1505848624450); - } + $this->requireWorkspaceToNotExist($command->workspaceName, $commandHandlingDependencies); $newContentStreamId = $command->newContentStreamId; - $contentRepository->handle( + $commandHandlingDependencies->handle( CreateContentStream::create( $newContentStreamId, ) @@ -242,10 +234,10 @@ private function handleCreateRootWorkspace( */ private function handlePublishWorkspace( PublishWorkspace $command, - ContentRepository $contentRepository, + CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository); - $baseWorkspace = $this->requireBaseWorkspace($workspace, $contentRepository); + $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies->getWorkspaceFinder()); + $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies->getWorkspaceFinder()); $this->publishContentStream( $workspace->currentContentStreamId, @@ -253,7 +245,7 @@ private function handlePublishWorkspace( )?->block(); // After publishing a workspace, we need to again fork from Base. - $contentRepository->handle( + $commandHandlingDependencies->handle( ForkContentStream::create( $command->newContentStreamId, $baseWorkspace->currentContentStreamId, @@ -269,6 +261,7 @@ private function handlePublishWorkspace( $workspace->currentContentStreamId, ) ); + // if we got so far without an Exception, we can switch the Workspace's active Content stream. return new EventsToPublish( $streamName, @@ -356,25 +349,25 @@ private function publishContentStream( */ private function handleRebaseWorkspace( RebaseWorkspace $command, - ContentRepository $contentRepository, + CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository); - $baseWorkspace = $this->requireBaseWorkspace($workspace, $contentRepository); + $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies->getWorkspaceFinder()); + $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies->getWorkspaceFinder()); $oldWorkspaceContentStreamId = $workspace->currentContentStreamId; - $oldWorkspaceContentStreamIdState = $contentRepository->getContentStreamFinder() + $oldWorkspaceContentStreamIdState = $commandHandlingDependencies->getContentStreamFinder() ->findStateForContentStream($oldWorkspaceContentStreamId); if ($oldWorkspaceContentStreamIdState === null) { throw new \DomainException('Cannot rebase a workspace with a stateless content stream', 1711718314); } // 0) close old content stream - $contentRepository->handle( + $commandHandlingDependencies->handle( CloseContentStream::create($oldWorkspaceContentStreamId) )->block(); // 1) fork a new content stream $rebasedContentStreamId = $command->rebasedContentStreamId; - $contentRepository->handle( + $commandHandlingDependencies->handle( ForkContentStream::create( $command->rebasedContentStreamId, $baseWorkspace->currentContentStreamId, @@ -389,13 +382,15 @@ private function handleRebaseWorkspace( // 2) extract the commands from the to-be-rebased content stream; and applies them on the new content stream $originalCommands = $this->extractCommandsFromContentStreamMetadata($workspaceContentStreamName); $commandsThatFailed = new CommandsThatFailedDuringRebase(); - ContentStreamIdOverride::applyContentStreamIdToClosure( + $commandHandlingDependencies->overrideContentStreamId( + $command->workspaceName, $command->rebasedContentStreamId, - function () use ($originalCommands, $contentRepository, &$commandsThatFailed): void { + function () use ($originalCommands, $commandHandlingDependencies, &$commandsThatFailed): void { foreach ($originalCommands as $sequenceNumber => $originalCommand) { // We no longer need to adjust commands as the workspace stays the same try { - $contentRepository->handle($originalCommand)->block(); + $commandHandlingDependencies->handle($originalCommand)->block(); + // if we came this far, we know the command was applied successfully. } catch (\Exception $e) { $commandsThatFailed = $commandsThatFailed->add( new CommandThatFailedDuringRebase( @@ -424,23 +419,23 @@ function () use ($originalCommands, $contentRepository, &$commandsThatFailed): v $events, ExpectedVersion::ANY() ); - } else { - // 3.E) In case of an exception, reopen the old content stream... - $contentRepository->handle( - ReopenContentStream::create( - $oldWorkspaceContentStreamId, - $oldWorkspaceContentStreamIdState, - ) - )->block(); + } - // ... remove the newly created one... - $contentRepository->handle(RemoveContentStream::create( - $rebasedContentStreamId - ))->block(); + // 3.E) In case of an exception, reopen the old content stream... + $commandHandlingDependencies->handle( + ReopenContentStream::create( + $oldWorkspaceContentStreamId, + $oldWorkspaceContentStreamIdState, + ) + )->block(); - // ...and throw an exception that contains all the information about what exactly failed - throw new WorkspaceRebaseFailed($commandsThatFailed, 'Rebase failed', 1711713880); - } + // ... remove the newly created one... + $commandHandlingDependencies->handle(RemoveContentStream::create( + $rebasedContentStreamId + ))->block(); + + // ...and throw an exception that contains all the information about what exactly failed + throw new WorkspaceRebaseFailed($commandsThatFailed, 'Rebase failed', 1711713880); } /** @@ -488,19 +483,20 @@ private function extractCommandsFromContentStreamMetadata( */ private function handlePublishIndividualNodesFromWorkspace( PublishIndividualNodesFromWorkspace $command, - ContentRepository $contentRepository, + CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies->getWorkspaceFinder()); $oldWorkspaceContentStreamId = $workspace->currentContentStreamId; - $oldWorkspaceContentStreamIdState = $contentRepository->getContentStreamFinder()->findStateForContentStream($oldWorkspaceContentStreamId); + $oldWorkspaceContentStreamIdState = $commandHandlingDependencies->getContentStreamFinder()->findStateForContentStream($oldWorkspaceContentStreamId); if ($oldWorkspaceContentStreamIdState === null) { throw new \DomainException('Cannot publish nodes on a workspace with a stateless content stream', 1710410114); } - $baseWorkspace = $this->requireBaseWorkspace($workspace, $contentRepository); + $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies->getWorkspaceFinder()); // 1) close old content stream - $contentRepository->handle( - CloseContentStream::create($oldWorkspaceContentStreamId) + $commandHandlingDependencies->handle( + CloseContentStream::create($contentGraph->getContentStreamId()) ); // 2) separate commands in two parts - the ones MATCHING the nodes from the command, and the REST @@ -512,7 +508,7 @@ private function handlePublishIndividualNodesFromWorkspace( /** @var array $remainingCommands */ // 3) fork a new contentStream, based on the base WS, and apply MATCHING - $contentRepository->handle( + $commandHandlingDependencies->handle( ForkContentStream::create( $command->contentStreamIdForMatchingPart, $baseWorkspace->currentContentStreamId, @@ -521,9 +517,10 @@ private function handlePublishIndividualNodesFromWorkspace( try { // 4) using the new content stream, apply the matching commands - ContentStreamIdOverride::applyContentStreamIdToClosure( + $commandHandlingDependencies->overrideContentStreamId( + $baseWorkspace->workspaceName, $command->contentStreamIdForMatchingPart, - function () use ($matchingCommands, $contentRepository, $baseWorkspace): void { + function () use ($matchingCommands, $commandHandlingDependencies, $baseWorkspace): void { foreach ($matchingCommands as $matchingCommand) { if (!($matchingCommand instanceof RebasableToOtherWorkspaceInterface)) { throw new \RuntimeException( @@ -532,7 +529,7 @@ function () use ($matchingCommands, $contentRepository, $baseWorkspace): void { ); } - $contentRepository->handle($matchingCommand->createCopyForWorkspace( + $commandHandlingDependencies->handle($matchingCommand->createCopyForWorkspace( $baseWorkspace->workspaceName, ))->block(); } @@ -546,37 +543,39 @@ function () use ($matchingCommands, $contentRepository, $baseWorkspace): void { )?->block(); // 6) fork a new content stream, based on the base WS, and apply REST - $contentRepository->handle( + $commandHandlingDependencies->handle( ForkContentStream::create( $command->contentStreamIdForRemainingPart, $baseWorkspace->currentContentStreamId ) )->block(); + // 7) apply REMAINING commands to the workspace's new content stream - ContentStreamIdOverride::applyContentStreamIdToClosure( + $commandHandlingDependencies->overrideContentStreamId( + $command->workspaceName, $command->contentStreamIdForRemainingPart, - function () use ($contentRepository, $remainingCommands) { + function () use ($commandHandlingDependencies, $remainingCommands) { foreach ($remainingCommands as $remainingCommand) { - $contentRepository->handle($remainingCommand)->block(); + $commandHandlingDependencies->handle($remainingCommand)->block(); } } ); } catch (\Exception $exception) { // 4.E) In case of an exception, reopen the old content stream and remove the newly created - $contentRepository->handle( + $commandHandlingDependencies->handle( ReopenContentStream::create( $oldWorkspaceContentStreamId, $oldWorkspaceContentStreamIdState, ) )->block(); - $contentRepository->handle(RemoveContentStream::create( + $commandHandlingDependencies->handle(RemoveContentStream::create( $command->contentStreamIdForMatchingPart ))->block(); try { - $contentRepository->handle(RemoveContentStream::create( + $commandHandlingDependencies->handle(RemoveContentStream::create( $command->contentStreamIdForRemainingPart ))->block(); } catch (ContentStreamDoesNotExistYet $contentStreamDoesNotExistYet) { @@ -588,10 +587,10 @@ function () use ($contentRepository, $remainingCommands) { // 8) to avoid dangling content streams, we need to remove our temporary content stream (whose events // have already been published) as well as the old one - $contentRepository->handle(RemoveContentStream::create( + $commandHandlingDependencies->handle(RemoveContentStream::create( $command->contentStreamIdForMatchingPart )); - $contentRepository->handle(RemoveContentStream::create( + $commandHandlingDependencies->handle(RemoveContentStream::create( $oldWorkspaceContentStreamId )); @@ -624,18 +623,19 @@ function () use ($contentRepository, $remainingCommands) { */ private function handleDiscardIndividualNodesFromWorkspace( DiscardIndividualNodesFromWorkspace $command, - ContentRepository $contentRepository, + CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository); - $oldWorkspaceContentStreamId = $workspace->currentContentStreamId; - $oldWorkspaceContentStreamIdState = $contentRepository->getContentStreamFinder()->findStateForContentStream($oldWorkspaceContentStreamId); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies->getWorkspaceFinder()); + $oldWorkspaceContentStreamId = $contentGraph->getContentStreamId(); + $oldWorkspaceContentStreamIdState = $commandHandlingDependencies->getContentStreamFinder()->findStateForContentStream($contentGraph->getContentStreamId()); if ($oldWorkspaceContentStreamIdState === null) { throw new \DomainException('Cannot discard nodes on a workspace with a stateless content stream', 1710408112); } - $baseWorkspace = $this->requireBaseWorkspace($workspace, $contentRepository); + $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies->getWorkspaceFinder()); // 1) close old content stream - $contentRepository->handle( + $commandHandlingDependencies->handle( CloseContentStream::create($oldWorkspaceContentStreamId) )->block(); @@ -648,7 +648,7 @@ private function handleDiscardIndividualNodesFromWorkspace( $this->separateMatchingAndRemainingCommands($command, $workspace, $commandsToDiscard, $commandsToKeep); // 3) fork a new contentStream, based on the base WS, and apply the commands to keep - $contentRepository->handle( + $commandHandlingDependencies->handle( ForkContentStream::create( $command->newContentStreamId, $baseWorkspace->currentContentStreamId, @@ -657,9 +657,10 @@ private function handleDiscardIndividualNodesFromWorkspace( // 4) using the new content stream, apply the commands to keep try { - ContentStreamIdOverride::applyContentStreamIdToClosure( + $commandHandlingDependencies->overrideContentStreamId( + $baseWorkspace->workspaceName, $command->newContentStreamId, - function () use ($commandsToKeep, $contentRepository, $baseWorkspace): void { + function () use ($commandsToKeep, $commandHandlingDependencies, $baseWorkspace): void { foreach ($commandsToKeep as $matchingCommand) { if (!($matchingCommand instanceof RebasableToOtherWorkspaceInterface)) { throw new \RuntimeException( @@ -668,7 +669,7 @@ function () use ($commandsToKeep, $contentRepository, $baseWorkspace): void { ); } - $contentRepository->handle($matchingCommand->createCopyForWorkspace( + $commandHandlingDependencies->handle($matchingCommand->createCopyForWorkspace( $baseWorkspace->workspaceName, ))->block(); } @@ -676,14 +677,14 @@ function () use ($commandsToKeep, $contentRepository, $baseWorkspace): void { ); } catch (\Exception $exception) { // 4.E) In case of an exception, reopen the old content stream and remove the newly created - $contentRepository->handle( + $commandHandlingDependencies->handle( ReopenContentStream::create( $oldWorkspaceContentStreamId, $oldWorkspaceContentStreamIdState, ) )->block(); - $contentRepository->handle(RemoveContentStream::create( + $commandHandlingDependencies->handle(RemoveContentStream::create( $command->newContentStreamId ))->block(); @@ -691,7 +692,7 @@ function () use ($commandsToKeep, $contentRepository, $baseWorkspace): void { } // 5) If everything worked, to avoid dangling content streams, we need to remove the old content stream - $contentRepository->handle(RemoveContentStream::create( + $commandHandlingDependencies->handle(RemoveContentStream::create( $oldWorkspaceContentStreamId ))->block(); @@ -767,13 +768,13 @@ private function commandMatchesAtLeastOneNode( */ private function handleDiscardWorkspace( DiscardWorkspace $command, - ContentRepository $contentRepository, + CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository); - $baseWorkspace = $this->requireBaseWorkspace($workspace, $contentRepository); + $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies->getWorkspaceFinder()); + $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies->getWorkspaceFinder()); $newContentStream = $command->newContentStreamId; - $contentRepository->handle( + $commandHandlingDependencies->handle( ForkContentStream::create( $newContentStream, $baseWorkspace->currentContentStreamId, @@ -809,17 +810,16 @@ private function handleDiscardWorkspace( */ private function handleChangeBaseWorkspace( ChangeBaseWorkspace $command, - ContentRepository $contentRepository, + CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository); + $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies->getWorkspaceFinder()); $this->requireEmptyWorkspace($workspace); - $this->requireBaseWorkspace($workspace, $contentRepository); - - $baseWorkspace = $this->requireWorkspace($command->baseWorkspaceName, $contentRepository); + $this->requireBaseWorkspace($workspace, $commandHandlingDependencies->getWorkspaceFinder()); + $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies->getWorkspaceFinder()); - $this->requireNonCircularRelationBetweenWorkspaces($workspace, $baseWorkspace, $contentRepository); + $this->requireNonCircularRelationBetweenWorkspaces($workspace, $baseWorkspace, $commandHandlingDependencies->getWorkspaceFinder()); - $contentRepository->handle( + $commandHandlingDependencies->handle( ForkContentStream::create( $command->newContentStreamId, $baseWorkspace->currentContentStreamId, @@ -847,11 +847,11 @@ private function handleChangeBaseWorkspace( */ private function handleDeleteWorkspace( DeleteWorkspace $command, - ContentRepository $contentRepository, + CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository); + $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies->getWorkspaceFinder()); - $contentRepository->handle( + $commandHandlingDependencies->handle( RemoveContentStream::create( $workspace->currentContentStreamId ) @@ -876,9 +876,9 @@ private function handleDeleteWorkspace( */ private function handleChangeWorkspaceOwner( ChangeWorkspaceOwner $command, - ContentRepository $contentRepository, + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $this->requireWorkspace($command->workspaceName, $contentRepository); + $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies->getWorkspaceFinder()); $events = Events::with( new WorkspaceOwnerWasChanged( @@ -895,12 +895,27 @@ private function handleChangeWorkspaceOwner( ); } + private function requireWorkspaceToNotExist(WorkspaceName $workspaceName, CommandHandlingDependencies $commandHandlingDependencies): void + { + try { + $commandHandlingDependencies->getContentGraph($workspaceName); + } catch (WorkspaceDoesNotExist) { + // Desired outcome + return; + } + + throw new WorkspaceAlreadyExists(sprintf( + 'The workspace %s already exists', + $workspaceName->value + ), 1715341085); + } + /** * @throws WorkspaceDoesNotExist */ - private function requireWorkspace(WorkspaceName $workspaceName, ContentRepository $contentRepository): Workspace + private function requireWorkspace(WorkspaceName $workspaceName, WorkspaceFinder $workspaceFinder): Workspace { - $workspace = $contentRepository->getWorkspaceFinder()->findOneByName($workspaceName); + $workspace = $workspaceFinder->findOneByName($workspaceName); if (is_null($workspace)) { throw WorkspaceDoesNotExist::butWasSupposedTo($workspaceName); } @@ -912,14 +927,21 @@ private function requireWorkspace(WorkspaceName $workspaceName, ContentRepositor * @throws WorkspaceHasNoBaseWorkspaceName * @throws BaseWorkspaceDoesNotExist */ - private function requireBaseWorkspace(Workspace $workspace, ContentRepository $contentRepository): Workspace - { + private function requireBaseWorkspace( + Workspace $workspace, + WorkspaceFinder $workspaceFinder + ): Workspace { if (is_null($workspace->baseWorkspaceName)) { throw WorkspaceHasNoBaseWorkspaceName::butWasSupposedTo($workspace->workspaceName); } - $baseWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName($workspace->baseWorkspaceName); - if ($baseWorkspace === null) { + try { + $baseWorkspace = $workspaceFinder->findOneByName($workspace->baseWorkspaceName); + } catch (WorkspaceDoesNotExist $_) { + $baseWorkspace = null; + } + + if (is_null($baseWorkspace)) { throw BaseWorkspaceDoesNotExist::butWasSupposedTo($workspace->workspaceName); } @@ -930,18 +952,18 @@ private function requireBaseWorkspace(Workspace $workspace, ContentRepository $c * @throws BaseWorkspaceEqualsWorkspaceException * @throws CircularRelationBetweenWorkspacesException */ - private function requireNonCircularRelationBetweenWorkspaces(Workspace $workspace, Workspace $baseWorkspace, ContentRepository $contentRepository): void + private function requireNonCircularRelationBetweenWorkspaces(Workspace $workspace, Workspace $baseWorkspace, WorkspaceFinder $workspaceFinder): void { if ($workspace->workspaceName->equals($baseWorkspace->workspaceName)) { throw new BaseWorkspaceEqualsWorkspaceException(sprintf('The base workspace of the target must be different from the given workspace "%s".', $workspace->workspaceName->value)); } $nextBaseWorkspace = $baseWorkspace; - while ($nextBaseWorkspace?->baseWorkspaceName !== null) { + while ($nextBaseWorkspace->baseWorkspaceName !== null) { if ($workspace->workspaceName->equals($nextBaseWorkspace->baseWorkspaceName)) { throw new CircularRelationBetweenWorkspacesException(sprintf('The workspace "%s" is already on the path of the target workspace "%s".', $workspace->workspaceName->value, $baseWorkspace->workspaceName->value)); } - $nextBaseWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName($nextBaseWorkspace->baseWorkspaceName); + $nextBaseWorkspace = $this->requireBaseWorkspace($workspace, $workspaceFinder); } } diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php index 4d8d7e4a4a7..5ecb1bd025d 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php @@ -24,6 +24,7 @@ use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** * This is the MAIN ENTRY POINT for the Content Repository. This class exists only @@ -40,13 +41,11 @@ interface ContentGraphInterface extends ProjectionStateInterface * @api main API method of ContentGraph */ public function getSubgraph( - ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint, VisibilityConstraints $visibilityConstraints ): ContentSubgraphInterface; /** - * @api * Throws exception if no root aggregate found, because a Content Repository needs at least * one root node to function. * @@ -54,9 +53,9 @@ public function getSubgraph( * as this would lead to nondeterministic results in your code. * * @throws RootNodeAggregateDoesNotExist + * @api */ public function findRootNodeAggregateByType( - ContentStreamId $contentStreamId, NodeTypeName $nodeTypeName ): NodeAggregate; @@ -64,7 +63,6 @@ public function findRootNodeAggregateByType( * @api */ public function findRootNodeAggregates( - ContentStreamId $contentStreamId, Filter\FindRootNodeAggregatesFilter $filter, ): NodeAggregates; @@ -73,7 +71,6 @@ public function findRootNodeAggregates( * @api */ public function findNodeAggregatesByType( - ContentStreamId $contentStreamId, NodeTypeName $nodeTypeName ): iterable; @@ -82,7 +79,6 @@ public function findNodeAggregatesByType( * @api */ public function findNodeAggregateById( - ContentStreamId $contentStreamId, NodeAggregateId $nodeAggregateId ): ?NodeAggregate; @@ -98,7 +94,6 @@ public function findUsedNodeTypeNames(): iterable; * @internal only for consumption inside the Command Handler */ public function findParentNodeAggregateByChildOriginDimensionSpacePoint( - ContentStreamId $contentStreamId, NodeAggregateId $childNodeAggregateId, OriginDimensionSpacePoint $childOriginDimensionSpacePoint ): ?NodeAggregate; @@ -108,7 +103,6 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint( * @internal only for consumption inside the Command Handler */ public function findParentNodeAggregates( - ContentStreamId $contentStreamId, NodeAggregateId $childNodeAggregateId ): iterable; @@ -117,7 +111,6 @@ public function findParentNodeAggregates( * @internal only for consumption inside the Command Handler */ public function findChildNodeAggregates( - ContentStreamId $contentStreamId, NodeAggregateId $parentNodeAggregateId ): iterable; @@ -129,7 +122,6 @@ public function findChildNodeAggregates( * @internal only for consumption inside the Command Handler */ public function findChildNodeAggregatesByName( - ContentStreamId $contentStreamId, NodeAggregateId $parentNodeAggregateId, NodeName $name ): iterable; @@ -139,7 +131,6 @@ public function findChildNodeAggregatesByName( * @internal only for consumption inside the Command Handler */ public function findTetheredChildNodeAggregates( - ContentStreamId $contentStreamId, NodeAggregateId $parentNodeAggregateId ): iterable; @@ -147,7 +138,6 @@ public function findTetheredChildNodeAggregates( * @internal only for consumption inside the Command Handler */ public function getDimensionSpacePointsOccupiedByChildNodeName( - ContentStreamId $contentStreamId, NodeName $nodeName, NodeAggregateId $parentNodeAggregateId, OriginDimensionSpacePoint $parentNodeOriginDimensionSpacePoint, @@ -155,7 +145,15 @@ public function getDimensionSpacePointsOccupiedByChildNodeName( ): DimensionSpacePointSet; /** + * Provides the total number of projected nodes regardless of workspace or content stream. + * * @internal only for consumption in testcases */ public function countNodes(): int; + + /** The workspace this content graph is operating on */ + public function getWorkspaceName(): WorkspaceName; + + /** @internal The content stream id where the workspace name points to for this instance */ + public function getContentStreamId(): ContentStreamId; } diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphProjection.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphProjection.php index 018bcb8414d..01de943a906 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphProjection.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphProjection.php @@ -4,6 +4,7 @@ namespace Neos\ContentRepository\Core\Projection\ContentGraph; +use Neos\ContentRepository\Core\ContentGraphFinder; use Neos\ContentRepository\Core\EventStore\EventInterface; use Neos\ContentRepository\Core\Projection\CheckpointStorageInterface; use Neos\ContentRepository\Core\Projection\ProjectionInterface; @@ -12,16 +13,16 @@ use Neos\EventStore\Model\EventEnvelope; /** - * @implements ProjectionInterface + * @implements ProjectionInterface * @api people load this projection class name to access the Content Graph */ final class ContentGraphProjection implements ProjectionInterface, WithMarkStaleInterface { /** - * @param WithMarkStaleInterface&ProjectionInterface $projectionImplementation + * @param ProjectionInterface $projectionImplementation */ public function __construct( - private readonly ProjectionInterface&WithMarkStaleInterface $projectionImplementation + private readonly ProjectionInterface $projectionImplementation ) { } @@ -45,7 +46,7 @@ public function canHandle(EventInterface $event): bool return $this->projectionImplementation->canHandle($event); } - public function getState(): ContentGraphInterface + public function getState(): ContentGraphFinder { return $this->projectionImplementation->getState(); } @@ -62,6 +63,10 @@ public function getCheckpointStorage(): CheckpointStorageInterface public function markStale(): void { - $this->projectionImplementation->markStale(); + if ($this->projectionImplementation instanceof WithMarkStaleInterface) { + $this->projectionImplementation->markStale(); + } + + $this->getState()->forgetInstances(); } } diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentSubgraphInterface.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentSubgraphInterface.php index 0d1f7d3f8f5..79a1427ec93 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentSubgraphInterface.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentSubgraphInterface.php @@ -33,7 +33,7 @@ * From the central Content Repository instance, you can fetch the singleton * {@see ContentGraphInterface}. There, you can call * {@see ContentGraphInterface::getSubgraph()} and pass in - * the {@see ContentStreamId}, {@see DimensionSpacePoint} and + * the {@see DimensionSpacePoint} and * {@see VisibilityConstraints} you want to have. * * diff --git a/Neos.ContentRepository.Core/Classes/Service/ContentRepositoryBootstrapper.php b/Neos.ContentRepository.Core/Classes/Service/ContentRepositoryBootstrapper.php index 0b65f505f1c..5a138b0df7e 100644 --- a/Neos.ContentRepository.Core/Classes/Service/ContentRepositoryBootstrapper.php +++ b/Neos.ContentRepository.Core/Classes/Service/ContentRepositoryBootstrapper.php @@ -70,9 +70,7 @@ public function getOrCreateRootNodeAggregate( NodeTypeName $rootNodeTypeName ): NodeAggregateId { try { - $contentStreamId = $workspace->currentContentStreamId; - return $this->contentRepository->getContentGraph()->findRootNodeAggregateByType( - $contentStreamId, + return $this->contentRepository->getContentGraph($workspace->workspaceName)->findRootNodeAggregateByType( $rootNodeTypeName )->nodeAggregateId; diff --git a/Neos.ContentRepository.Export/Tests/Behavior/Features/Bootstrap/CrImportExportTrait.php b/Neos.ContentRepository.Export/Tests/Behavior/Features/Bootstrap/CrImportExportTrait.php index 12a2152bbb6..2b1ce7fd096 100644 --- a/Neos.ContentRepository.Export/Tests/Behavior/Features/Bootstrap/CrImportExportTrait.php +++ b/Neos.ContentRepository.Export/Tests/Behavior/Features/Bootstrap/CrImportExportTrait.php @@ -18,7 +18,6 @@ use Behat\Gherkin\Node\TableNode; use League\Flysystem\Filesystem; use League\Flysystem\InMemory\InMemoryFilesystemAdapter; -use Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphProjectionFactory; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryDependencies; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; @@ -206,11 +205,4 @@ public function iExpectAMigrationErrorWithTheMessage(PyStringNode $expectedMessa * @return T */ abstract private function getObject(string $className): object; - - protected function getTableNamePrefix(): string - { - return DoctrineDbalContentGraphProjectionFactory::graphProjectionTableNamePrefix( - $this->currentContentRepository->id - ); - } } diff --git a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Bootstrap/FeatureContext.php b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Bootstrap/FeatureContext.php index ff9511a1011..dd5302406c3 100644 --- a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Bootstrap/FeatureContext.php +++ b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Bootstrap/FeatureContext.php @@ -18,6 +18,7 @@ use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepository\Export\Asset\AssetExporter; use Neos\ContentRepository\Export\Asset\AssetLoaderInterface; use Neos\ContentRepository\Export\Asset\ResourceLoaderInterface; @@ -123,10 +124,14 @@ public function iRunTheEventMigration(string $contentStream = null): void { $nodeTypeManager = $this->currentContentRepository->getNodeTypeManager(); $propertyMapper = $this->getObject(PropertyMapper::class); - $contentGraph = $this->currentContentRepository->getContentGraph(); - $nodeFactory = (new \ReflectionClass($contentGraph)) + $contentGraphFinder = $this->currentContentRepository->projectionState(\Neos\ContentRepository\Core\ContentGraphFinder::class); + // FIXME: Dirty + $contentGraphFactory = (new \ReflectionClass($contentGraphFinder)) + ->getProperty('contentGraphFactory') + ->getValue($contentGraphFinder); + $nodeFactory = (new \ReflectionClass($contentGraphFactory)) ->getProperty('nodeFactory') - ->getValue($contentGraph); + ->getValue($contentGraphFactory); $propertyConverter = (new \ReflectionClass($nodeFactory)) ->getProperty('propertyConverter') ->getValue($nodeFactory); diff --git a/Neos.ContentRepository.NodeMigration/src/NodeMigrationService.php b/Neos.ContentRepository.NodeMigration/src/NodeMigrationService.php index 8bd7c065860..23b18fc4bee 100644 --- a/Neos.ContentRepository.NodeMigration/src/NodeMigrationService.php +++ b/Neos.ContentRepository.NodeMigration/src/NodeMigrationService.php @@ -130,10 +130,10 @@ protected function executeSubMigrationAndBlock( if ($transformations->containsGlobal()) { $transformations->executeGlobalAndBlock($workspaceNameForWriting); } elseif ($transformations->containsNodeAggregateBased()) { - foreach ($this->contentRepository->getContentGraph()->findUsedNodeTypeNames() as $nodeTypeName) { + $contentGraph = $this->contentRepository->getContentGraph($workspaceForReading->workspaceName); + foreach ($contentGraph->findUsedNodeTypeNames() as $nodeTypeName) { foreach ( - $this->contentRepository->getContentGraph()->findNodeAggregatesByType( - $workspaceForReading->currentContentStreamId, + $contentGraph->findNodeAggregatesByType( $nodeTypeName ) as $nodeAggregate ) { @@ -143,10 +143,10 @@ protected function executeSubMigrationAndBlock( } } } elseif ($transformations->containsNodeBased()) { - foreach ($this->contentRepository->getContentGraph()->findUsedNodeTypeNames() as $nodeTypeName) { + $contentGraph = $this->contentRepository->getContentGraph($workspaceForReading->workspaceName); + foreach ($contentGraph->findUsedNodeTypeNames() as $nodeTypeName) { foreach ( - $this->contentRepository->getContentGraph()->findNodeAggregatesByType( - $workspaceForReading->currentContentStreamId, + $contentGraph->findNodeAggregatesByType( $nodeTypeName ) as $nodeAggregate ) { diff --git a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/DisallowedChildNodeAdjustment.php b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/DisallowedChildNodeAdjustment.php index c715e22748c..5f602315ddd 100644 --- a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/DisallowedChildNodeAdjustment.php +++ b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/DisallowedChildNodeAdjustment.php @@ -24,7 +24,6 @@ class DisallowedChildNodeAdjustment use RemoveNodeAggregateTrait; public function __construct( - private readonly ContentRepository $contentRepository, private readonly ProjectedNodeIterator $projectedNodeIterator, private readonly NodeTypeManager $nodeTypeManager, ) { @@ -51,8 +50,7 @@ public function findAdjustmentsForNodeType(NodeTypeName $nodeTypeName): \Generat // as it can happen that the constraint is only violated in e.g. "AT", but not in "DE". // Then, we only want to remove the single edge. foreach ($nodeAggregate->coveredDimensionSpacePoints as $coveredDimensionSpacePoint) { - $subgraph = $this->contentRepository->getContentGraph()->getSubgraph( - $nodeAggregate->contentStreamId, + $subgraph = $this->projectedNodeIterator->contentGraph->getSubgraph( $coveredDimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ); @@ -68,7 +66,7 @@ public function findAdjustmentsForNodeType(NodeTypeName $nodeTypeName): \Generat $parentNodeType = $this->nodeTypeManager->getNodeType($parentNode->nodeTypeName); if ($parentNodeType) { $allowedByParent = $parentNodeType->allowsChildNodeType($nodeType) - || $nodeAggregate->nodeName && $parentNodeType->hasTetheredNode($nodeAggregate->nodeName); + || ($nodeAggregate->nodeName && $parentNodeType->hasTetheredNode($nodeAggregate->nodeName)); } } diff --git a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/ProjectedNodeIterator.php b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/ProjectedNodeIterator.php index e7548998909..69e50487315 100644 --- a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/ProjectedNodeIterator.php +++ b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/ProjectedNodeIterator.php @@ -8,8 +8,6 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceFinder; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** * Low-Level helper service, iterating over the "real" Nodes in the Live workspace; that is, the nodes, @@ -22,13 +20,10 @@ */ class ProjectedNodeIterator { - protected WorkspaceFinder $workspaceFinder; - protected ContentGraphInterface $contentGraph; - - public function __construct(WorkspaceFinder $workspaceFinder, ContentGraphInterface $contentGraph) + public function __construct( + public readonly ContentGraphInterface $contentGraph + ) { - $this->workspaceFinder = $workspaceFinder; - $this->contentGraph = $contentGraph; } /** @@ -37,18 +32,9 @@ public function __construct(WorkspaceFinder $workspaceFinder, ContentGraphInterf */ public function nodeAggregatesOfType(NodeTypeName $nodeTypeName): iterable { - $contentStreamId = $this->findLiveContentStream(); - $nodeAggregates = $this->contentGraph->findNodeAggregatesByType($contentStreamId, $nodeTypeName); + $nodeAggregates = $this->contentGraph->findNodeAggregatesByType($nodeTypeName); foreach ($nodeAggregates as $nodeAggregate) { yield $nodeAggregate; } } - - private function findLiveContentStream(): ContentStreamId - { - $liveWorkspace = $this->workspaceFinder->findOneByName(WorkspaceName::forLive()); - assert($liveWorkspace !== null, 'Live workspace not found'); - - return $liveWorkspace->currentContentStreamId; - } } diff --git a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php index d0f0479a9ba..e80974464f0 100644 --- a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php +++ b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php @@ -18,11 +18,13 @@ use Neos\ContentRepository\Core\NodeType\NodeType; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\NodeType\NodeTypeName; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindChildNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\EventStore\Model\EventStream\ExpectedVersion; class TetheredNodeAdjustments @@ -53,6 +55,8 @@ public function findAdjustmentsForNodeType(NodeTypeName $nodeTypeName): \Generat $expectedTetheredNodes = $this->nodeTypeManager->getTetheredNodesConfigurationForNodeType($nodeType); foreach ($this->projectedNodeIterator->nodeAggregatesOfType($nodeTypeName) as $nodeAggregate) { + // TODO: We should use $nodeAggregate->workspaceName as soon as it's available + $contentGraph = $this->contentRepository->getContentGraph(WorkspaceName::forLive()); // find missing tethered nodes $foundMissingOrDisallowedTetheredNodes = false; $originDimensionSpacePoints = $nodeType->isOfType(NodeTypeName::ROOT_NODE_TYPE_NAME) @@ -65,14 +69,12 @@ public function findAdjustmentsForNodeType(NodeTypeName $nodeTypeName): \Generat foreach ($expectedTetheredNodes as $tetheredNodeName => $expectedTetheredNodeType) { $tetheredNodeName = NodeName::fromString($tetheredNodeName); - $subgraph = $this->contentRepository->getContentGraph()->getSubgraph( - $nodeAggregate->contentStreamId, + $tetheredNode = $contentGraph->getSubgraph( $originDimensionSpacePoint->toDimensionSpacePoint(), VisibilityConstraints::withoutRestrictions() - ); - $tetheredNode = $subgraph->findNodeByPath( + )->findNodeByPath( $tetheredNodeName, - $nodeAggregate->nodeAggregateId, + $nodeAggregate->nodeAggregateId ); if ($tetheredNode === null) { $foundMissingOrDisallowedTetheredNodes = true; @@ -84,14 +86,14 @@ public function findAdjustmentsForNodeType(NodeTypeName $nodeTypeName): \Generat $nodeAggregate->nodeAggregateId, StructureAdjustment::TETHERED_NODE_MISSING, 'The tethered child node "' . $tetheredNodeName->value . '" is missing.', - function () use ($nodeAggregate, $originDimensionSpacePoint, $tetheredNodeName, $expectedTetheredNodeType) { + function () use ($nodeAggregate, $originDimensionSpacePoint, $tetheredNodeName, $expectedTetheredNodeType, $contentGraph) { $events = $this->createEventsForMissingTetheredNode( + $contentGraph, $nodeAggregate, $originDimensionSpacePoint, $tetheredNodeName, null, - $expectedTetheredNodeType, - $this->contentRepository + $expectedTetheredNodeType ); $streamName = ContentStreamEventStreamName::fromContentStreamId($nodeAggregate->contentStreamId); @@ -110,8 +112,7 @@ function () use ($nodeAggregate, $originDimensionSpacePoint, $tetheredNodeName, } // find disallowed tethered nodes - $tetheredNodeAggregates = $this->contentRepository->getContentGraph()->findTetheredChildNodeAggregates( - $nodeAggregate->contentStreamId, + $tetheredNodeAggregates = $contentGraph->findTetheredChildNodeAggregates( $nodeAggregate->nodeAggregateId ); foreach ($tetheredNodeAggregates as $tetheredNodeAggregate) { @@ -133,12 +134,7 @@ function () use ($tetheredNodeAggregate) { // find wrongly ordered tethered nodes if ($foundMissingOrDisallowedTetheredNodes === false) { foreach ($originDimensionSpacePoints as $originDimensionSpacePoint) { - $subgraph = $this->contentRepository->getContentGraph()->getSubgraph( - $nodeAggregate->contentStreamId, - $originDimensionSpacePoint->toDimensionSpacePoint(), - VisibilityConstraints::withoutRestrictions() - ); - $childNodes = $subgraph->findChildNodes($nodeAggregate->nodeAggregateId, FindChildNodesFilter::create()); + $childNodes = $contentGraph->getSubgraph($originDimensionSpacePoint->toDimensionSpacePoint(), VisibilityConstraints::withoutRestrictions())->findChildNodes($nodeAggregate->nodeAggregateId, FindChildNodesFilter::create()); /** is indexed by node name, and the value is the tethered node itself */ $actualTetheredChildNodes = []; diff --git a/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentService.php b/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentService.php index 3f50d52afe7..f1b0222d607 100644 --- a/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentService.php +++ b/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentService.php @@ -12,6 +12,7 @@ use Neos\ContentRepository\Core\Infrastructure\Property\PropertyConverter; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\NodeType\NodeTypeName; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepository\StructureAdjustment\Adjustment\DimensionAdjustment; use Neos\ContentRepository\StructureAdjustment\Adjustment\DisallowedChildNodeAdjustment; use Neos\ContentRepository\StructureAdjustment\Adjustment\ProjectedNodeIterator; @@ -33,11 +34,10 @@ public function __construct( private readonly EventPersister $eventPersister, NodeTypeManager $nodeTypeManager, InterDimensionalVariationGraph $interDimensionalVariationGraph, - PropertyConverter $propertyConverter + PropertyConverter $propertyConverter, ) { $projectedNodeIterator = new ProjectedNodeIterator( - $contentRepository->getWorkspaceFinder(), - $contentRepository->getContentGraph(), + $contentRepository->getContentGraph(WorkspaceName::forLive()), ); $this->tetheredNodeAdjustments = new TetheredNodeAdjustments( @@ -53,7 +53,6 @@ public function __construct( $nodeTypeManager ); $this->disallowedChildNodeAdjustment = new DisallowedChildNodeAdjustment( - $this->contentRepository, $projectedNodeIterator, $nodeTypeManager ); @@ -73,7 +72,7 @@ public function __construct( */ public function findAllAdjustments(): \Generator { - foreach ($this->contentRepository->getContentGraph()->findUsedNodeTypeNames() as $nodeTypeName) { + foreach ($this->contentRepository->getContentGraph(WorkspaceName::forLive())->findUsedNodeTypeNames() as $nodeTypeName) { yield from $this->findAdjustmentsForNodeType($nodeTypeName); } } diff --git a/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentServiceFactory.php b/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentServiceFactory.php index 3363a2ec7fb..b9e75abeaff 100644 --- a/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentServiceFactory.php +++ b/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentServiceFactory.php @@ -19,7 +19,7 @@ public function build(ContentRepositoryServiceFactoryDependencies $serviceFactor $serviceFactoryDependencies->eventPersister, $serviceFactoryDependencies->nodeTypeManager, $serviceFactoryDependencies->interDimensionalVariationGraph, - $serviceFactoryDependencies->propertyConverter + $serviceFactoryDependencies->propertyConverter, ); } } diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php index a19eb219002..0639a9a35b5 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php @@ -15,6 +15,7 @@ namespace Neos\ContentRepository\TestSuite\Behavior\Features\Bootstrap; use Neos\ContentRepository\Core\CommandHandler\CommandResult; +use Neos\ContentRepository\Core\ContentGraphFinder; use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface; @@ -160,8 +161,13 @@ public function visibilityConstraintsAreSetTo(string $restrictionType): void public function getCurrentSubgraph(): ContentSubgraphInterface { - return $this->currentContentRepository->getContentGraph()->getSubgraph( - $this->currentContentStreamId, + $contentGraphFinder = $this->currentContentRepository->projectionState(ContentGraphFinder::class); + $contentGraphFinder->forgetInstances(); + if (isset($this->currentContentStreamId)) { + return $contentGraphFinder->getByWorkspaceNameAndContentStreamId($this->currentWorkspaceName, $this->currentContentStreamId)->getSubgraph($this->currentDimensionSpacePoint, $this->currentVisibilityConstraints); + } + + return $contentGraphFinder->getByWorkspaceName($this->currentWorkspaceName)->getSubgraph( $this->currentDimensionSpacePoint, $this->currentVisibilityConstraints ); diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php index 832cfcef5fa..11a35b4acac 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php @@ -195,7 +195,7 @@ public function workspaceHasStatus(string $rawWorkspaceName, string $status): vo */ public function iExpectTheGraphProjectionToConsistOfExactlyNodes(int $expectedNumberOfNodes): void { - $actualNumberOfNodes = $this->currentContentRepository->getContentGraph()->countNodes(); + $actualNumberOfNodes = $this->currentContentRepository->getContentGraph($this->currentWorkspaceName)->countNodes(); Assert::assertSame($expectedNumberOfNodes, $actualNumberOfNodes, 'Content graph consists of ' . $actualNumberOfNodes . ' nodes, expected were ' . $expectedNumberOfNodes . '.'); } @@ -262,8 +262,7 @@ protected function getRootNodeAggregateId(): ?NodeAggregateId } try { - return $this->currentContentRepository->getContentGraph()->findRootNodeAggregateByType( - $this->currentContentStreamId, + return $this->currentContentRepository->getContentGraph($this->currentWorkspaceName)->findRootNodeAggregateByType( NodeTypeName::fromString('Neos.Neos:Sites') )->nodeAggregateId; } catch (RootNodeAggregateDoesNotExist) { diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeCopying.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeCopying.php index f9c85562631..ae19812dc32 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeCopying.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeCopying.php @@ -39,8 +39,7 @@ abstract protected function readPayloadTable(TableNode $payloadTable): array; public function theCommandCopyNodesRecursivelyIsExecutedCopyingTheCurrentNodeAggregateWithPayload(TableNode $payloadTable): void { $commandArguments = $this->readPayloadTable($payloadTable); - $subgraph = $this->currentContentRepository->getContentGraph()->getSubgraph( - $this->currentContentStreamId, + $subgraph = $this->currentContentRepository->getContentGraph($this->currentWorkspaceName)->getSubgraph( $this->currentDimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ); diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeAggregateTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeAggregateTrait.php index b72d26e82c4..38ec865c887 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeAggregateTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeAggregateTrait.php @@ -42,7 +42,7 @@ public function iExpectTheNodeAggregateToExist(string $serializedNodeAggregateId { $nodeAggregateId = NodeAggregateId::fromString($serializedNodeAggregateId); $this->initializeCurrentNodeAggregate(function (ContentGraphInterface $contentGraph) use ($nodeAggregateId) { - $currentNodeAggregate = $contentGraph->findNodeAggregateById($this->currentContentStreamId, $nodeAggregateId); + $currentNodeAggregate = $contentGraph->findNodeAggregateById($nodeAggregateId); Assert::assertNotNull($currentNodeAggregate, sprintf('Node aggregate "%s" was not found in the current content stream "%s".', $nodeAggregateId->value, $this->currentContentStreamId->value)); return $currentNodeAggregate; }); @@ -50,7 +50,7 @@ public function iExpectTheNodeAggregateToExist(string $serializedNodeAggregateId protected function initializeCurrentNodeAggregate(callable $query): void { - $this->currentNodeAggregate = $query($this->currentContentRepository->getContentGraph()); + $this->currentNodeAggregate = $query($this->currentContentRepository->getContentGraph($this->currentWorkspaceName)); } /** @@ -159,8 +159,7 @@ public function iExpectThisNodeAggregateToHaveNoParentNodeAggregates(): void { $this->assertOnCurrentNodeAggregate(function (NodeAggregate $nodeAggregate) { Assert::assertEmpty( - iterator_to_array($this->currentContentRepository->getContentGraph()->findParentNodeAggregates( - $nodeAggregate->contentStreamId, + iterator_to_array($this->currentContentRepository->getContentGraph($this->currentWorkspaceName)->findParentNodeAggregates( $nodeAggregate->nodeAggregateId )), 'Did not expect parent node aggregates.' @@ -182,8 +181,7 @@ public function iExpectThisNodeAggregateToHaveTheParentNodeAggregates(string $se fn (NodeAggregate $parentNodeAggregate): string => $parentNodeAggregate->contentStreamId->value . ';' . $parentNodeAggregate->nodeAggregateId->value, iterator_to_array( - $this->currentContentRepository->getContentGraph()->findParentNodeAggregates( - $nodeAggregate->contentStreamId, + $this->currentContentRepository->getContentGraph($this->currentWorkspaceName)->findParentNodeAggregates( $nodeAggregate->nodeAggregateId ) ) @@ -203,8 +201,7 @@ public function iExpectThisNodeAggregateToHaveNoChildNodeAggregates(): void { $this->assertOnCurrentNodeAggregate(function (NodeAggregate $nodeAggregate) { Assert::assertEmpty( - iterator_to_array($this->currentContentRepository->getContentGraph()->findChildNodeAggregates( - $nodeAggregate->contentStreamId, + iterator_to_array($this->currentContentRepository->getContentGraph($this->currentWorkspaceName)->findChildNodeAggregates( $nodeAggregate->nodeAggregateId )), 'No child node aggregates were expected.' @@ -227,8 +224,7 @@ public function iExpectThisNodeAggregateToHaveTheChildNodeAggregates(string $ser $actualDiscriminators = array_values(array_map( fn (NodeAggregate $parentNodeAggregate): string => $parentNodeAggregate->contentStreamId->value . ':' . $parentNodeAggregate->nodeAggregateId->value, - iterator_to_array($this->currentContentRepository->getContentGraph()->findChildNodeAggregates( - $nodeAggregate->contentStreamId, + iterator_to_array($this->currentContentRepository->getContentGraph($this->currentWorkspaceName)->findChildNodeAggregates( $nodeAggregate->nodeAggregateId )) )); diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php index 5b6d14e9c82..2bfe0fae891 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php @@ -16,6 +16,7 @@ use Behat\Gherkin\Node\TableNode; use GuzzleHttp\Psr7\Uri; +use Neos\ContentRepository\Core\ContentGraphFinder; use Neos\ContentRepository\Core\Feature\SubtreeTagging\Dto\SubtreeTag; use Neos\ContentRepository\Core\NodeType\NodeTypeName; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; @@ -80,9 +81,9 @@ public function iGetTheNodeAtPath(string $serializedNodePath): void public function iExpectANodeIdentifiedByXToExistInTheContentGraph(string $serializedNodeDiscriminator): void { $nodeDiscriminator = NodeDiscriminator::fromShorthand($serializedNodeDiscriminator); + $this->currentContentStreamId = $nodeDiscriminator->contentStreamId; $this->initializeCurrentNodeFromContentGraph(function (ContentGraphInterface $contentGraph) use ($nodeDiscriminator) { $currentNodeAggregate = $contentGraph->findNodeAggregateById( - $nodeDiscriminator->contentStreamId, $nodeDiscriminator->nodeAggregateId ); Assert::assertTrue( @@ -275,7 +276,14 @@ public function iExpectThisNodeToExactlyInheritTheTags(string $expectedTagList): protected function initializeCurrentNodeFromContentGraph(callable $query): void { - $this->currentNode = $query($this->currentContentRepository->getContentGraph()); + $contentGraphFinder = $this->currentContentRepository->projectionState(ContentGraphFinder::class); + $contentGraphFinder->forgetInstances(); + if (isset($this->currentContentStreamId)) { + $contentGraph = $contentGraphFinder->getByWorkspaceNameAndContentStreamId($this->currentWorkspaceName, $this->currentContentStreamId); + } else { + $contentGraph = $this->currentContentRepository->getContentGraph($this->currentWorkspaceName); + } + $this->currentNode = $query($contentGraph); } protected function initializeCurrentNodeFromContentSubgraph(callable $query): void diff --git a/Neos.ContentRepositoryRegistry/Classes/Command/ContentCommandController.php b/Neos.ContentRepositoryRegistry/Classes/Command/ContentCommandController.php index d4b5c15dc61..87e8e0e936d 100644 --- a/Neos.ContentRepositoryRegistry/Classes/Command/ContentCommandController.php +++ b/Neos.ContentRepositoryRegistry/Classes/Command/ContentCommandController.php @@ -60,8 +60,7 @@ public function refreshRootNodeDimensionsCommand(string $contentRepository = 'de $this->outputLine('Refreshing root node dimensions in workspace %s (content repository %s)', [$workspaceInstance->workspaceName->value, $contentRepositoryId->value]); $this->outputLine('Resolved content stream %s', [$workspaceInstance->currentContentStreamId->value]); - $rootNodeAggregates = $contentRepositoryInstance->getContentGraph()->findRootNodeAggregates( - $workspaceInstance->currentContentStreamId, + $rootNodeAggregates = $contentRepositoryInstance->getContentGraph($workspaceInstance->workspaceName)->findRootNodeAggregates( FindRootNodeAggregatesFilter::create() ); @@ -153,14 +152,13 @@ public function createVariantsRecursivelyCommand(string $source, string $target, $this->outputLine('Creating %s to %s in workspace %s (content repository %s)', [$sourceSpacePoint->toJson(), $targetSpacePoint->toJson(), $workspaceInstance->workspaceName->value, $contentRepositoryId->value]); $this->outputLine('Resolved content stream %s', [$workspaceInstance->currentContentStreamId->value]); - $sourceSubgraph = $contentRepositoryInstance->getContentGraph()->getSubgraph( - $workspaceInstance->currentContentStreamId, + $sourceSubgraph = $contentRepositoryInstance->getContentGraph(WorkspaceName::fromString($workspace))->getSubgraph( $sourceSpacePoint, VisibilityConstraints::withoutRestrictions() ); - $rootNodeAggregates = $contentRepositoryInstance->getContentGraph() - ->findRootNodeAggregates($workspaceInstance->currentContentStreamId, FindRootNodeAggregatesFilter::create()); + $rootNodeAggregates = $contentRepositoryInstance->getContentGraph($workspaceInstance->workspaceName) + ->findRootNodeAggregates(FindRootNodeAggregatesFilter::create()); foreach ($rootNodeAggregates as $rootNodeAggregate) { diff --git a/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php b/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php index 25daca7cf84..a35269902f7 100644 --- a/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php +++ b/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php @@ -15,6 +15,7 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ProjectionCatchUpTriggerInterface; use Neos\ContentRepository\Core\Projection\ProjectionFactoryInterface; +use Neos\ContentRepository\Core\Projection\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\User\UserIdProviderInterface; use Neos\ContentRepositoryRegistry\Exception\ContentRepositoryNotFoundException; @@ -93,8 +94,12 @@ public function resetFactoryInstance(ContentRepositoryId $contentRepositoryId): public function subgraphForNode(Node $node): ContentSubgraphInterface { $contentRepository = $this->get($node->subgraphIdentity->contentRepositoryId); - return $contentRepository->getContentGraph()->getSubgraph( - $node->subgraphIdentity->contentStreamId, + + // FIXME: node->workspace + /** @var Workspace $workspace */ + $workspace = $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($node->subgraphIdentity->contentStreamId); + + return $contentRepository->getContentGraph($workspace->workspaceName)->getSubgraph( $node->subgraphIdentity->dimensionSpacePoint, $node->subgraphIdentity->visibilityConstraints ); @@ -157,7 +162,7 @@ private function buildFactory(ContentRepositoryId $contentRepositoryId): Content $this->buildProjectionsFactory($contentRepositoryId, $contentRepositorySettings), $this->buildProjectionCatchUpTrigger($contentRepositoryId, $contentRepositorySettings), $this->buildUserIdProvider($contentRepositoryId, $contentRepositorySettings), - $clock, + $clock ); } catch (\Exception $exception) { throw InvalidConfigurationException::fromException($contentRepositoryId, $exception); diff --git a/Neos.Media.Browser/Classes/Controller/UsageController.php b/Neos.Media.Browser/Classes/Controller/UsageController.php index e53cfa0df5a..2fd3b3f8ef8 100644 --- a/Neos.Media.Browser/Classes/Controller/UsageController.php +++ b/Neos.Media.Browser/Classes/Controller/UsageController.php @@ -98,7 +98,10 @@ public function relatedNodesAction(AssetInterface $asset) $contentRepository = $this->contentRepositoryRegistry->get($usage->getContentRepositoryId()); - $nodeAggregate = $contentRepository->getContentGraph()->findNodeAggregateById( + $workspace = $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($usage->getContentStreamId()); + + // FIXME: AssetUsageReference->workspaceName ? + $nodeAggregate = $contentRepository->getContentGraph($workspace->workspaceName)->findNodeAggregateById( $usage->getContentStreamId(), $usage->getNodeAggregateId() ); @@ -107,7 +110,7 @@ public function relatedNodesAction(AssetInterface $asset) } catch (NodeTypeNotFoundException $e) { $nodeType = null; } - $workspace = $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($usage->getContentStreamId()); + $accessible = $this->domainUserService->currentUserCanReadWorkspace($workspace); $inaccessibleRelation['nodeIdentifier'] = $usage->getNodeAggregateId()->value; @@ -121,7 +124,7 @@ public function relatedNodesAction(AssetInterface $asset) continue; } - $subgraph = $contentRepository->getContentGraph()->getSubgraph( + $subgraph = $contentRepository->getContentGraph($workspace->workspaceName)->getSubgraph( $usage->getContentStreamId(), $usage->getOriginDimensionSpacePoint()->toDimensionSpacePoint(), VisibilityConstraints::withoutRestrictions() diff --git a/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncService.php b/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncService.php index 02a77386db9..f73bf9865d5 100644 --- a/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncService.php +++ b/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncService.php @@ -4,8 +4,8 @@ namespace Neos\Neos\AssetUsage\Service; +use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; -use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\Media\Domain\Model\AssetInterface; use Neos\Media\Domain\Repository\AssetRepository; @@ -26,8 +26,8 @@ class AssetUsageSyncService implements ContentRepositoryServiceInterface private array $existingAssetsById = []; public function __construct( + private readonly ContentRepository $contentRepository, private readonly AssetUsageFinder $assetUsageFinder, - private readonly ContentGraphInterface $contentGraph, private readonly AssetRepository $assetRepository, private readonly AssetUsageRepository $assetUsageRepository, ) { @@ -55,8 +55,12 @@ public function isAssetUsageStillValid(AssetUsage $usage): bool } $dimensionSpacePoint = $usage->originDimensionSpacePoint->toDimensionSpacePoint(); - $subGraph = $this->contentGraph->getSubgraph( - $usage->contentStreamId, + // FIXME: AssetUsage->workspaceName ? + $workspace = $this->contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($usage->contentStreamId); + if (is_null($workspace)) { + return false; + } + $subGraph = $this->contentRepository->getContentGraph($workspace->workspaceName)->getSubgraph( $dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ); diff --git a/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncServiceFactory.php b/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncServiceFactory.php index 524ea542c12..2e798ecf25d 100644 --- a/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncServiceFactory.php +++ b/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncServiceFactory.php @@ -26,8 +26,8 @@ public function build( ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies, ): AssetUsageSyncService { return new AssetUsageSyncService( + $serviceFactoryDependencies->contentRepository, $serviceFactoryDependencies->contentRepository->projectionState(AssetUsageFinder::class), - $serviceFactoryDependencies->contentRepository->getContentGraph(), $this->assetRepository, $this->assetUsageRepositoryFactory->build($serviceFactoryDependencies->contentRepositoryId), ); diff --git a/Neos.Neos/Classes/Controller/Backend/ContentController.php b/Neos.Neos/Classes/Controller/Backend/ContentController.php index 707cca4d8e9..dd474086c97 100644 --- a/Neos.Neos/Classes/Controller/Backend/ContentController.php +++ b/Neos.Neos/Classes/Controller/Backend/ContentController.php @@ -152,9 +152,8 @@ public function uploadAssetAction(Asset $asset, string $metadata, string $node, $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); $nodeAddress = NodeAddressFactory::create($contentRepository)->createFromUriString($nodeAddressString); - $node = $contentRepository->getContentGraph() + $node = $contentRepository->getContentGraph($nodeAddress->workspaceName) ->getSubgraph( - $nodeAddress->contentStreamId, $nodeAddress->dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ) diff --git a/Neos.Neos/Classes/Controller/Frontend/NodeController.php b/Neos.Neos/Classes/Controller/Frontend/NodeController.php index 4bc115447ee..6f3a607f830 100644 --- a/Neos.Neos/Classes/Controller/Frontend/NodeController.php +++ b/Neos.Neos/Classes/Controller/Frontend/NodeController.php @@ -132,8 +132,7 @@ public function previewAction(string $node): void $nodeAddress = NodeAddressFactory::create($contentRepository)->createFromUriString($node); - $subgraph = $contentRepository->getContentGraph()->getSubgraph( - $nodeAddress->contentStreamId, + $subgraph = $contentRepository->getContentGraph($nodeAddress->workspaceName)->getSubgraph( $nodeAddress->dimensionSpacePoint, $visibilityConstraints ); @@ -201,8 +200,7 @@ public function showAction(string $node): void throw new NodeNotFoundException('The requested node isn\'t accessible to the current user', 1430218623); } - $subgraph = $contentRepository->getContentGraph()->getSubgraph( - $nodeAddress->contentStreamId, + $subgraph = $contentRepository->getContentGraph($nodeAddress->workspaceName)->getSubgraph( $nodeAddress->dimensionSpacePoint, VisibilityConstraints::frontend() ); diff --git a/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php b/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php index 24dd502fb69..5dca97ed900 100755 --- a/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php +++ b/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php @@ -185,8 +185,7 @@ public function updateSiteAction(Site $site, $newSiteNodeName) } try { - $sitesNode = $contentRepository->getContentGraph()->findRootNodeAggregateByType( - $liveWorkspace->currentContentStreamId, + $sitesNode = $contentRepository->getContentGraph($liveWorkspace->workspaceName)->findRootNodeAggregateByType( NodeTypeNameFactory::forSites() ); } catch (\Exception $exception) { @@ -208,8 +207,7 @@ public function updateSiteAction(Site $site, $newSiteNodeName) // technically, due to the name being the "identifier", there might be more than one :/ /** @var NodeAggregate[] $siteNodeAggregates */ /** @var Workspace $workspace */ - $siteNodeAggregates = $contentRepository->getContentGraph()->findChildNodeAggregatesByName( - $workspace->currentContentStreamId, + $siteNodeAggregates = $contentRepository->getContentGraph($workspace->workspaceName)->findChildNodeAggregatesByName( $sitesNode->nodeAggregateId, $site->getNodeName()->toNodeName() ); diff --git a/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php b/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php index 4db51ab429e..c603cd82dcf 100644 --- a/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php +++ b/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php @@ -457,8 +457,8 @@ public function rebaseAndRedirectAction(Node $targetNode, Workspace $targetWorks throw new \RuntimeException('No account is authenticated', 1710068880); } $personalWorkspaceName = WorkspaceNameBuilder::fromAccountIdentifier($currentAccount->getAccountIdentifier()); - $personalWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName($personalWorkspaceName); /** @var Workspace $personalWorkspace */ + $personalWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName($personalWorkspaceName); /** @todo do something else * if ($personalWorkspace !== $targetWorkspace) { @@ -750,8 +750,7 @@ protected function computeSiteChanges(Workspace $selectedWorkspace, ContentRepos ); foreach ($changes as $change) { - $contentStreamId = $change->contentStreamId; - + $workspaceName = $selectedWorkspace->workspaceName; if ($change->deleted) { // If we deleted a node, there is no way for us to anymore find the deleted node in the ContentStream // where the node was deleted. @@ -759,10 +758,9 @@ protected function computeSiteChanges(Workspace $selectedWorkspace, ContentRepos // // This is safe because the UI basically shows what would be removed once the deletion is published. $baseWorkspace = $this->getBaseWorkspaceWhenSureItExists($selectedWorkspace, $contentRepository); - $contentStreamId = $baseWorkspace->currentContentStreamId; + $workspaceName = $baseWorkspace->workspaceName; } - $subgraph = $contentRepository->getContentGraph()->getSubgraph( - $contentStreamId, + $subgraph = $contentRepository->getContentGraph($workspaceName)->getSubgraph( $change->originDimensionSpacePoint->toDimensionSpacePoint(), VisibilityConstraints::withoutRestrictions() ); @@ -872,17 +870,14 @@ protected function computeSiteChanges(Workspace $selectedWorkspace, ContentRepos */ protected function getOriginalNode( Node $modifiedNode, - ContentStreamId $baseContentStreamId, + WorkspaceName $workspaceName, ContentRepository $contentRepository, ): ?Node { - $baseSubgraph = $contentRepository->getContentGraph()->getSubgraph( - $baseContentStreamId, + $baseSubgraph = $contentRepository->getContentGraph($workspaceName)->getSubgraph( $modifiedNode->subgraphIdentity->dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ); - $node = $baseSubgraph->findNodeById($modifiedNode->nodeAggregateId); - - return $node; + return $baseSubgraph->findNodeById($modifiedNode->nodeAggregateId); } /** @@ -902,8 +897,7 @@ protected function renderContentChanges( $originalNode = null; if ($currentWorkspace !== null) { $baseWorkspace = $this->getBaseWorkspaceWhenSureItExists($currentWorkspace, $contentRepository); - $baseContentStreamId = $baseWorkspace->currentContentStreamId; - $originalNode = $this->getOriginalNode($changedNode, $baseContentStreamId, $contentRepository); + $originalNode = $this->getOriginalNode($changedNode, $baseWorkspace->workspaceName, $contentRepository); } @@ -914,7 +908,7 @@ protected function renderContentChanges( $renderer = new HtmlArrayRenderer(); foreach ($changedNode->properties as $propertyName => $changedPropertyValue) { if ( - $originalNode === null && empty($changedPropertyValue) + ($originalNode === null && empty($changedPropertyValue)) || ( isset($changeNodePropertiesDefaults[$propertyName]) && $changedPropertyValue === $changeNodePropertiesDefaults[$propertyName] diff --git a/Neos.Neos/Classes/Controller/Service/NodesController.php b/Neos.Neos/Classes/Controller/Service/NodesController.php index d8db9a2fcc7..bff9d28db9d 100644 --- a/Neos.Neos/Classes/Controller/Service/NodesController.php +++ b/Neos.Neos/Classes/Controller/Service/NodesController.php @@ -140,14 +140,12 @@ public function indexAction( 1645631728 ); } - $subgraph = $contentRepository->getContentGraph()->getSubgraph( - $workspace->currentContentStreamId, + $subgraph = $contentRepository->getContentGraph(WorkspaceName::fromString($workspaceName))->getSubgraph( DimensionSpacePoint::fromLegacyDimensionArray($dimensions), VisibilityConstraints::withoutRestrictions() // we are in a backend controller. ); } else { - $subgraph = $contentRepository->getContentGraph()->getSubgraph( - $nodeAddress->contentStreamId, + $subgraph = $contentRepository->getContentGraph($nodeAddress->workspaceName)->getSubgraph( $nodeAddress->dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() // we are in a backend controller. ); @@ -211,9 +209,8 @@ public function showAction(string $identifier, string $workspaceName = 'live', a assert($workspace instanceof Workspace); $dimensionSpacePoint = DimensionSpacePoint::fromLegacyDimensionArray($dimensions); - $subgraph = $contentRepository->getContentGraph() + $subgraph = $contentRepository->getContentGraph(WorkspaceName::fromString($workspaceName)) ->getSubgraph( - $workspace->currentContentStreamId, $dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ); @@ -223,7 +220,7 @@ public function showAction(string $identifier, string $workspaceName = 'live', a if ($node === null) { $this->addExistingNodeVariantInformationToResponse( $nodeAggregateId, - $workspace->currentContentStreamId, + $workspace->workspaceName, $dimensionSpacePoint, $contentRepository ); @@ -280,17 +277,16 @@ public function createAction( ->findOneByName(WorkspaceName::fromString($workspaceName)); assert($workspace instanceof Workspace); - $sourceSubgraph = $contentRepository->getContentGraph() + $contentGraph = $contentRepository->getContentGraph(WorkspaceName::fromString($workspaceName)); + $sourceSubgraph = $contentGraph ->getSubgraph( - $workspace->currentContentStreamId, DimensionSpacePoint::fromLegacyDimensionArray($sourceDimensions), VisibilityConstraints::withoutRestrictions() ); $targetDimensionSpacePoint = DimensionSpacePoint::fromLegacyDimensionArray($dimensions); - $targetSubgraph = $contentRepository->getContentGraph() + $targetSubgraph = $contentGraph ->getSubgraph( - $workspace->currentContentStreamId, $targetDimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ); @@ -323,13 +319,13 @@ public function createAction( */ protected function addExistingNodeVariantInformationToResponse( NodeAggregateId $identifier, - ContentStreamId $contentStreamId, + WorkspaceName $workspaceName, DimensionSpacePoint $dimensionSpacePoint, ContentRepository $contentRepository ): void { - $contentGraph = $contentRepository->getContentGraph(); + $contentGraph = $contentRepository->getContentGraph($workspaceName); $nodeTypeManager = $contentRepository->getNodeTypeManager(); - $nodeAggregate = $contentGraph->findNodeAggregateById($contentStreamId, $identifier); + $nodeAggregate = $contentGraph->findNodeAggregateById($identifier); if ($nodeAggregate && $nodeAggregate->coveredDimensionSpacePoints->count() > 0) { $this->response->setHttpHeader('X-Neos-Node-Exists-In-Other-Dimensions', 'true'); @@ -346,7 +342,7 @@ protected function addExistingNodeVariantInformationToResponse( $missingNodesOnRootline = 0; while ( $parentAggregate = self::firstNodeAggregate( - $contentGraph->findParentNodeAggregates($contentStreamId, $identifier) + $contentGraph->findParentNodeAggregates($identifier) ) ) { if (!$parentAggregate->coversDimensionSpacePoint($dimensionSpacePoint)) { @@ -382,6 +378,12 @@ private static function firstNodeAggregate(iterable $nodeAggregates): ?NodeAggre /** * Adopt (translate) the given node and parents that are not yet visible to the given context * + * @param WorkspaceName $workspaceName + * @param NodeAggregateId $nodeAggregateId + * @param ContentSubgraphInterface $sourceSubgraph + * @param ContentSubgraphInterface $targetSubgraph + * @param DimensionSpacePoint $targetDimensionSpacePoint + * @param ContentRepository $contentRepository * @param boolean $copyContent true if the content from the nodes that are translated should be copied * @return void */ diff --git a/Neos.Neos/Classes/Domain/Service/SiteNodeUtility.php b/Neos.Neos/Classes/Domain/Service/SiteNodeUtility.php index a9b503611e5..34614a86683 100644 --- a/Neos.Neos/Classes/Domain/Service/SiteNodeUtility.php +++ b/Neos.Neos/Classes/Domain/Service/SiteNodeUtility.php @@ -19,6 +19,7 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; use Neos\Neos\Domain\Model\Site; @@ -57,20 +58,19 @@ public function __construct( */ public function findSiteNodeBySite( Site $site, - ContentStreamId $contentStreamId, + WorkspaceName $workspaceName, DimensionSpacePoint $dimensionSpacePoint, VisibilityConstraints $visibilityConstraints ): Node { $contentRepository = $this->contentRepositoryRegistry->get($site->getConfiguration()->contentRepositoryId); - $subgraph = $contentRepository->getContentGraph()->getSubgraph( - $contentStreamId, + $contentGraph = $contentRepository->getContentGraph($workspaceName); + $subgraph = $contentGraph->getSubgraph( $dimensionSpacePoint, $visibilityConstraints, ); - $rootNodeAggregate = $contentRepository->getContentGraph()->findRootNodeAggregateByType( - $contentStreamId, + $rootNodeAggregate = $contentGraph->findRootNodeAggregateByType( NodeTypeNameFactory::forSites() ); $rootNode = $rootNodeAggregate->getNodeByCoveredDimensionSpacePoint($dimensionSpacePoint); diff --git a/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php b/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php index 7dbe858a5c5..33de72e820b 100644 --- a/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php +++ b/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php @@ -39,7 +39,7 @@ public function __construct( private ContentRepository $contentRepository, private InterDimensionalVariationGraph $interDimensionalVariationGraph, - private NodeTypeManager $nodeTypeManager + private NodeTypeManager $nodeTypeManager, ) { } @@ -54,15 +54,14 @@ public function removeSiteNode(SiteNodeName $siteNodeName): void 1651921482 ); } - $contentGraph = $this->contentRepository->getContentGraph(); foreach ($this->contentRepository->getWorkspaceFinder()->findAll() as $workspace) { + $contentGraph = $this->contentRepository->getContentGraph($workspace->workspaceName); $sitesNodeAggregate = $contentGraph->findRootNodeAggregateByType( - $workspace->currentContentStreamId, NodeTypeNameFactory::forSites() ); + $siteNodeAggregates = $contentGraph->findChildNodeAggregatesByName( - $workspace->currentContentStreamId, $sitesNodeAggregate->nodeAggregateId, $siteNodeName->toNodeName() ); @@ -99,8 +98,8 @@ public function createSiteNodeIfNotExists(Site $site, string $nodeTypeName): voi throw SiteNodeTypeIsInvalid::becauseItIsNotOfTypeSite(NodeTypeName::fromString($nodeTypeName)); } - $siteNodeAggregate = $this->contentRepository->getContentGraph()->findChildNodeAggregatesByName( - $liveWorkspace->currentContentStreamId, + $contentGraph = $this->contentRepository->getContentGraph($liveWorkspace->workspaceName); + $siteNodeAggregate = $contentGraph->findChildNodeAggregatesByName( $sitesNodeIdentifier, $site->getNodeName()->toNodeName(), ); diff --git a/Neos.Neos/Classes/Domain/Service/SiteServiceInternalsFactory.php b/Neos.Neos/Classes/Domain/Service/SiteServiceInternalsFactory.php index d47a105c5f1..123b324f754 100644 --- a/Neos.Neos/Classes/Domain/Service/SiteServiceInternalsFactory.php +++ b/Neos.Neos/Classes/Domain/Service/SiteServiceInternalsFactory.php @@ -27,7 +27,7 @@ public function build(ContentRepositoryServiceFactoryDependencies $serviceFactor return new SiteServiceInternals( $serviceFactoryDependencies->contentRepository, $serviceFactoryDependencies->interDimensionalVariationGraph, - $serviceFactoryDependencies->nodeTypeManager + $serviceFactoryDependencies->nodeTypeManager, ); } } diff --git a/Neos.Neos/Classes/Domain/Workspace/Workspace.php b/Neos.Neos/Classes/Domain/Workspace/Workspace.php index 670a4d9f7f7..814a897f158 100644 --- a/Neos.Neos/Classes/Domain/Workspace/Workspace.php +++ b/Neos.Neos/Classes/Domain/Workspace/Workspace.php @@ -262,8 +262,7 @@ private function requireNodeToBeOfType( NodeAggregateId $nodeAggregateId, NodeTypeName $nodeTypeName, ): void { - $nodeAggregate = $this->contentRepository->getContentGraph()->findNodeAggregateById( - $this->currentContentStreamId, + $nodeAggregate = $this->contentRepository->getContentGraph($this->name)->findNodeAggregateById( $nodeAggregateId, ); if (!$nodeAggregate instanceof NodeAggregate) { @@ -398,8 +397,7 @@ private function isChangePublishableWithinAncestorScope( } } - $subgraph = $this->contentRepository->getContentGraph()->getSubgraph( - $this->currentContentStreamId, + $subgraph = $this->contentRepository->getContentGraph($this->name)->getSubgraph( $change->originDimensionSpacePoint->toDimensionSpacePoint(), VisibilityConstraints::withoutRestrictions() ); diff --git a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php index 216818735ed..0924a557057 100644 --- a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php +++ b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php @@ -14,6 +14,7 @@ namespace Neos\Neos\Fusion\Cache; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ContentGraph; use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\NodeType\NodeType; use Neos\ContentRepository\Core\NodeType\NodeTypeName; @@ -91,28 +92,33 @@ private function collectTagsForChangeOnNodeAggregate( ContentStreamId $contentStreamId, NodeAggregateId $nodeAggregateId ): array { - $nodeAggregate = $contentRepository->getContentGraph()->findNodeAggregateById( - $contentStreamId, + $workspace = $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($contentStreamId); + if (is_null($workspace)) { + return []; + } + $contentGraph = $contentRepository->getContentGraph($workspace->workspaceName); + + + $nodeAggregate = $contentGraph->findNodeAggregateById( $nodeAggregateId ); if (!$nodeAggregate) { // Node Aggregate was removed in the meantime, so no need to clear caches on this one anymore. return []; } - $tagsToFlush = $this->collectTagsForChangeOnNodeIdentifier($contentRepository->id, $contentStreamId, $nodeAggregateId); + $tagsToFlush = $this->collectTagsForChangeOnNodeIdentifier($contentRepository->id, $contentGraph->getContentStreamId(), $nodeAggregateId); $tagsToFlush = array_merge($this->collectTagsForChangeOnNodeType( $nodeAggregate->nodeTypeName, $contentRepository->id, - $contentStreamId, + $contentGraph->getContentStreamId(), $nodeAggregateId, $contentRepository ), $tagsToFlush); $parentNodeAggregates = []; foreach ( - $contentRepository->getContentGraph()->findParentNodeAggregates( - $contentStreamId, + $contentGraph->findParentNodeAggregates( $nodeAggregateId ) as $parentNodeAggregate ) { @@ -146,8 +152,7 @@ private function collectTagsForChangeOnNodeAggregate( ); foreach ( - $contentRepository->getContentGraph()->findParentNodeAggregates( - $nodeAggregate->contentStreamId, + $contentGraph->findParentNodeAggregates( $nodeAggregate->nodeAggregateId ) as $parentNodeAggregate ) { diff --git a/Neos.Neos/Classes/Fusion/Cache/GraphProjectorCatchUpHookForCacheFlushing.php b/Neos.Neos/Classes/Fusion/Cache/GraphProjectorCatchUpHookForCacheFlushing.php index e26a5d526e9..76f9a024984 100644 --- a/Neos.Neos/Classes/Fusion/Cache/GraphProjectorCatchUpHookForCacheFlushing.php +++ b/Neos.Neos/Classes/Fusion/Cache/GraphProjectorCatchUpHookForCacheFlushing.php @@ -140,13 +140,17 @@ public function onBeforeEvent(EventInterface $eventInstance, EventEnvelope $even // cleared, leading to presumably duplicate nodes in the UI. || $eventInstance instanceof NodeAggregateWasMoved ) { - $nodeAggregate = $this->contentRepository->getContentGraph()->findNodeAggregateById( - $eventInstance->getContentStreamId(), + $workspace = $this->contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($eventInstance->getContentStreamId()); + if ($workspace === null) { + return; + } + // FIXME: EventInterface->workspaceName + $contentGraph = $this->contentRepository->getContentGraph($workspace->workspaceName); + $nodeAggregate = $contentGraph->findNodeAggregateById( $eventInstance->getNodeAggregateId() ); if ($nodeAggregate) { - $parentNodeAggregates = $this->contentRepository->getContentGraph()->findParentNodeAggregates( - $nodeAggregate->contentStreamId, + $parentNodeAggregates = $contentGraph->findParentNodeAggregates( $nodeAggregate->nodeAggregateId ); foreach ($parentNodeAggregates as $parentNodeAggregate) { @@ -173,8 +177,12 @@ public function onAfterEvent(EventInterface $eventInstance, EventEnvelope $event !($eventInstance instanceof NodeAggregateWasRemoved) && $eventInstance instanceof EmbedsContentStreamAndNodeAggregateId ) { - $nodeAggregate = $this->contentRepository->getContentGraph()->findNodeAggregateById( - $eventInstance->getContentStreamId(), + $workspace = $this->contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($eventInstance->getContentStreamId()); + if ($workspace === null) { + return; + } + // FIXME: EventInterface->workspaceName + $nodeAggregate = $this->contentRepository->getContentGraph($workspace->workspaceName)->findNodeAggregateById( $eventInstance->getNodeAggregateId() ); diff --git a/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php b/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php index 03ca0a2352f..462aad66e6c 100644 --- a/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php +++ b/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php @@ -81,8 +81,7 @@ private function tryDeserializeNode(array $serializedNode): ?Node return null; } - $subgraph = $contentRepository->getContentGraph()->getSubgraph( - $workspace->currentContentStreamId, + $subgraph = $contentRepository->getContentGraph($workspace->workspaceName)->getSubgraph( DimensionSpacePoint::fromArray($serializedNode['dimensionSpacePoint']), $workspace->isPublicWorkspace() ? VisibilityConstraints::frontend() diff --git a/Neos.Neos/Classes/Fusion/DimensionsMenuItemsImplementation.php b/Neos.Neos/Classes/Fusion/DimensionsMenuItemsImplementation.php index 5f2e128a252..42b514c6f8a 100644 --- a/Neos.Neos/Classes/Fusion/DimensionsMenuItemsImplementation.php +++ b/Neos.Neos/Classes/Fusion/DimensionsMenuItemsImplementation.php @@ -7,6 +7,7 @@ use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\Dimension\ContentDimensionId; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; @@ -58,15 +59,21 @@ protected function buildItems(): array $interDimensionalVariationGraph = $dimensionMenuItemsImplementationInternals->interDimensionalVariationGraph; $currentDimensionSpacePoint = $currentNode->subgraphIdentity->dimensionSpacePoint; $contentDimensionIdentifierToLimitTo = $this->getContentDimensionIdentifierToLimitTo(); + // FIXME: node->workspaceName + $workspace = $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($currentNode->subgraphIdentity->contentStreamId); + if (is_null($workspace)) { + return $menuItems; + } + $contentGraph = $contentRepository->getContentGraph($workspace->workspaceName); + foreach ($interDimensionalVariationGraph->getDimensionSpacePoints() as $dimensionSpacePoint) { $variant = null; if ($this->isDimensionSpacePointRelevant($dimensionSpacePoint)) { if ($dimensionSpacePoint->equals($currentDimensionSpacePoint)) { $variant = $currentNode; } else { - $variant = $contentRepository->getContentGraph() + $variant = $contentGraph ->getSubgraph( - $currentNode->subgraphIdentity->contentStreamId, $dimensionSpacePoint, $currentNode->subgraphIdentity->visibilityConstraints, ) @@ -79,7 +86,7 @@ protected function buildItems(): array $contentDimensionIdentifierToLimitTo, $currentNode->nodeAggregateId, $dimensionMenuItemsImplementationInternals, - $contentRepository + $contentGraph ); } @@ -147,7 +154,7 @@ protected function findClosestGeneralizationMatchingDimensionValue( ContentDimensionId $contentDimensionIdentifier, NodeAggregateId $nodeAggregateId, DimensionsMenuItemsImplementationInternals $dimensionMenuItemsImplementationInternals, - ContentRepository $contentRepository + ContentGraphInterface $contentGraph ): ?Node { $generalizations = $dimensionMenuItemsImplementationInternals->interDimensionalVariationGraph ->getWeightedGeneralizations($dimensionSpacePoint); @@ -157,11 +164,10 @@ protected function findClosestGeneralizationMatchingDimensionValue( $generalization->getCoordinate($contentDimensionIdentifier) === $dimensionSpacePoint->getCoordinate($contentDimensionIdentifier) ) { - $variant = $contentRepository->getContentGraph() + $variant = $contentGraph ->getSubgraph( - $this->currentNode->subgraphIdentity->contentStreamId, $generalization, - $this->currentNode->subgraphIdentity->visibilityConstraints, + $this->getCurrentNode()->subgraphIdentity->visibilityConstraints, ) ->findNodeById($nodeAggregateId); if ($variant) { diff --git a/Neos.Neos/Classes/Fusion/Helper/DimensionHelper.php b/Neos.Neos/Classes/Fusion/Helper/DimensionHelper.php index 15bafd94ac1..ddcf6d6b269 100644 --- a/Neos.Neos/Classes/Fusion/Helper/DimensionHelper.php +++ b/Neos.Neos/Classes/Fusion/Helper/DimensionHelper.php @@ -144,10 +144,14 @@ public function findVariantInDimension(Node $node, ContentDimensionId|string $di $contentDimensionValue = is_string($dimensionValue) ? new ContentDimensionValue($dimensionValue) : $dimensionValue; $contentRepository = $this->contentRepositoryRegistry->get($node->subgraphIdentity->contentRepositoryId); + $workspace = $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($node->subgraphIdentity->contentStreamId); + if (is_null($workspace)) { + return null; + } + // FIXME: node->workspaceName return $contentRepository - ->getContentGraph() + ->getContentGraph($workspace->workspaceName) ->getSubgraph( - $node->subgraphIdentity->contentStreamId, $node->subgraphIdentity->dimensionSpacePoint->vary($contentDimensionId, $contentDimensionValue->value), $node->subgraphIdentity->visibilityConstraints )->findNodeById($node->nodeAggregateId); diff --git a/Neos.Neos/Classes/Service/LinkingService.php b/Neos.Neos/Classes/Service/LinkingService.php index 445c1e62b63..74f1c01ca94 100644 --- a/Neos.Neos/Classes/Service/LinkingService.php +++ b/Neos.Neos/Classes/Service/LinkingService.php @@ -314,8 +314,7 @@ public function createNodeUri( $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); $nodeAddress = NodeAddressFactory::create($contentRepository)->createFromUriString($node); $workspace = $contentRepository->getWorkspaceFinder()->findOneByName($nodeAddress->workspaceName); - $subgraph = $contentRepository->getContentGraph()->getSubgraph( - $nodeAddress->contentStreamId, + $subgraph = $contentRepository->getContentGraph($nodeAddress->workspaceName)->getSubgraph( $nodeAddress->dimensionSpacePoint, $workspace && !$workspace->isPublicWorkspace() ? VisibilityConstraints::withoutRestrictions() diff --git a/Neos.Neos/Classes/TypeConverter/HackyNodeAddressToNodeConverter.php b/Neos.Neos/Classes/TypeConverter/HackyNodeAddressToNodeConverter.php index 50069aed402..9786c418fc9 100644 --- a/Neos.Neos/Classes/TypeConverter/HackyNodeAddressToNodeConverter.php +++ b/Neos.Neos/Classes/TypeConverter/HackyNodeAddressToNodeConverter.php @@ -80,9 +80,8 @@ public function convertFrom( $nodeAddressFactory = NodeAddressFactory::create($contentRepository); $nodeAddress = $nodeAddressFactory->createFromUriString($source); - $subgraph = $contentRepository->getContentGraph() + $subgraph = $contentRepository->getContentGraph($nodeAddress->workspaceName) ->getSubgraph( - $nodeAddress->contentStreamId, $nodeAddress->dimensionSpacePoint, $nodeAddress->isInLiveWorkspace() ? VisibilityConstraints::frontend() diff --git a/Neos.Neos/Classes/View/FusionExceptionView.php b/Neos.Neos/Classes/View/FusionExceptionView.php index eb675b7310d..6a0fa255278 100644 --- a/Neos.Neos/Classes/View/FusionExceptionView.php +++ b/Neos.Neos/Classes/View/FusionExceptionView.php @@ -119,7 +119,7 @@ public function render(): ResponseInterface|StreamInterface if ($liveWorkspace && $site) { $currentSiteNode = $this->siteNodeUtility->findSiteNodeBySite( $site, - $liveWorkspace->currentContentStreamId, + $liveWorkspace->workspaceName, $dimensionSpacePoint, VisibilityConstraints::frontend() ); diff --git a/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php b/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php index f75ef066aa9..dab61d2c975 100644 --- a/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php +++ b/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php @@ -277,9 +277,8 @@ public function render(): string } - $subgraph = $contentRepository->getContentGraph() + $subgraph = $contentRepository->getContentGraph($nodeAddress->workspaceName) ->getSubgraph( - $nodeAddress->contentStreamId, $nodeAddress->dimensionSpacePoint, $node->subgraphIdentity->visibilityConstraints ); @@ -370,8 +369,7 @@ private function resolveNodeAddressFromString( NodeAggregateId::fromString(\mb_substr($path, 7)) ); } - $subgraph = $contentRepository->getContentGraph()->getSubgraph( - $documentNodeAddress->contentStreamId, + $subgraph = $contentRepository->getContentGraph($documentNodeAddress->workspaceName)->getSubgraph( $documentNodeAddress->dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ); diff --git a/Neos.Neos/Classes/ViewHelpers/Uri/NodeViewHelper.php b/Neos.Neos/Classes/ViewHelpers/Uri/NodeViewHelper.php index 67438d4afb5..1b3ef235890 100644 --- a/Neos.Neos/Classes/ViewHelpers/Uri/NodeViewHelper.php +++ b/Neos.Neos/Classes/ViewHelpers/Uri/NodeViewHelper.php @@ -273,8 +273,7 @@ private function resolveNodeAddressFromString(string $path): ?NodeAddress NodeAggregateId::fromString(\mb_substr($path, 7)) ); } - $subgraph = $contentRepository->getContentGraph()->getSubgraph( - $documentNodeAddress->contentStreamId, + $subgraph = $contentRepository->getContentGraph($documentNodeAddress->workspaceName)->getSubgraph( $documentNodeAddress->dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ); diff --git a/Neos.Neos/Tests/Behavior/Features/Bootstrap/BrowserTrait.php b/Neos.Neos/Tests/Behavior/Features/Bootstrap/BrowserTrait.php index 030bde9ea19..8783cb9d480 100644 --- a/Neos.Neos/Tests/Behavior/Features/Bootstrap/BrowserTrait.php +++ b/Neos.Neos/Tests/Behavior/Features/Bootstrap/BrowserTrait.php @@ -113,7 +113,7 @@ public function getCurrentNodeAddresses(): array */ public function iGetTheNodeAddressForNodeAggregate(string $rawNodeAggregateId, $alias = 'DEFAULT') { - $subgraph = $this->currentContentRepository->getContentGraph()->getSubgraph($this->currentContentStreamId, $this->currentDimensionSpacePoint, $this->currentVisibilityConstraints); + $subgraph = $this->currentContentRepository->getContentGraph($this->currentWorkspaceName)->getSubgraph($this->currentDimensionSpacePoint, $this->currentVisibilityConstraints); $nodeAggregateId = NodeAggregateId::fromString($rawNodeAggregateId); $node = $subgraph->findNodeById($nodeAggregateId); Assert::assertNotNull($node, 'Did not find a node with aggregate id "' . $nodeAggregateId->value . '"'); @@ -136,7 +136,7 @@ public function iGetTheNodeAddressForNodeAggregate(string $rawNodeAggregateId, $ */ public function iGetTheNodeAddressForTheNodeAtPath(string $serializedNodePath, $alias = 'DEFAULT') { - $subgraph = $this->currentContentRepository->getContentGraph()->getSubgraph($this->currentContentStreamId, $this->currentDimensionSpacePoint, $this->currentVisibilityConstraints); + $subgraph = $this->currentContentRepository->getContentGraph($this->currentWorkspaceName)->getSubgraph($this->currentDimensionSpacePoint, $this->currentVisibilityConstraints); if (!$this->getRootNodeAggregateId()) { throw new \Exception('ERROR: rootNodeAggregateId needed for running this step. You need to use "the event RootNodeAggregateWithNodeWasCreated was published with payload" to create a root node..'); } diff --git a/Neos.TimeableNodeVisibility/Classes/Service/TimeableNodeVisibilityService.php b/Neos.TimeableNodeVisibility/Classes/Service/TimeableNodeVisibilityService.php index 07bb0429ab9..70056016538 100644 --- a/Neos.TimeableNodeVisibility/Classes/Service/TimeableNodeVisibilityService.php +++ b/Neos.TimeableNodeVisibility/Classes/Service/TimeableNodeVisibilityService.php @@ -95,11 +95,10 @@ private function getNodesWithExceededDates(ContentRepository $contentRepository, foreach ($dimensionSpacePoints as $dimensionSpacePoint) { - $contentGraph = $contentRepository->getContentGraph(); + $contentGraph = $contentRepository->getContentGraph($liveWorkspace->workspaceName); // We fetch without restriction to get also all disabled nodes $subgraph = $contentGraph->getSubgraph( - $liveWorkspace->currentContentStreamId, $dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ); @@ -166,7 +165,7 @@ private function logResult(ChangedVisibility $result): void sprintf('Timed node visibility: %s node [NodeAggregateId: %s, DimensionSpacePoints: %s]: %s', $result->type->value, $result->node->nodeAggregateId->value, - join(',', $result->node->originDimensionSpacePoint->coordinates), + implode(',', $result->node->originDimensionSpacePoint->coordinates), $result->node->getLabel()) ); } diff --git a/Neos.TimeableNodeVisibility/Tests/Behavior/Bootstrap/FeatureContext.php b/Neos.TimeableNodeVisibility/Tests/Behavior/Bootstrap/FeatureContext.php index 66f948b239f..14907e8d62e 100644 --- a/Neos.TimeableNodeVisibility/Tests/Behavior/Bootstrap/FeatureContext.php +++ b/Neos.TimeableNodeVisibility/Tests/Behavior/Bootstrap/FeatureContext.php @@ -60,8 +60,7 @@ public function iHandleExceededNodeDates(): void public function iExpectThisNodeToBeEnabled(): void { Assert::assertNotNull($this->currentNode, 'No current node selected'); - $subgraph = $this->currentContentRepository->getContentGraph()->getSubgraph( - $this->currentContentStreamId, + $subgraph = $this->currentContentRepository->getContentGraph($this->currentWorkspaceName)->getSubgraph( $this->currentDimensionSpacePoint, VisibilityConstraints::withoutRestrictions(), ); @@ -76,8 +75,7 @@ public function iExpectThisNodeToBeEnabled(): void public function iExpectThisNodeToBeDisabled(): void { Assert::assertNotNull($this->currentNode, 'No current node selected'); - $subgraph = $this->currentContentRepository->getContentGraph()->getSubgraph( - $this->currentContentStreamId, + $subgraph = $this->currentContentRepository->getContentGraph($this->currentWorkspaceName)->getSubgraph( $this->currentDimensionSpacePoint, VisibilityConstraints::withoutRestrictions(), );