From aff329135b8abe7f50a52cee2c801038f6947c5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Fri, 5 Apr 2024 09:19:54 +0200 Subject: [PATCH 01/18] FEATURE: ContentGraphAdapter for write side access The write side no longer uses any regular finders for checks but only accesses projection data via the new `ContentGraphAdapterInterface`. Fixes: #4973 --- .../Domain/Repository/ContentGraphAdapter.php | 123 +++++++++++ .../Repository/ContentGraphAdapterFactory.php | 27 +++ .../Classes/ContentRepository.php | 9 + .../Factory/ContentRepositoryFactory.php | 8 +- .../Feature/Common/ConstraintChecks.php | 91 ++++---- .../Common/ContentStreamIdOverride.php | 6 +- .../Feature/Common/NodeVariationInternals.php | 64 +++--- .../Feature/Common/TetheredNodeInternals.php | 16 +- .../ContentGraphAdapterFactoryInterface.php | 15 ++ .../Feature/ContentGraphAdapterInterface.php | 195 ++++++++++++++++++ .../Feature/ContentStreamCommandHandler.php | 82 ++++---- .../DimensionSpaceCommandHandler.php | 47 +++-- .../Feature/NodeAggregateCommandHandler.php | 70 +++---- .../Feature/NodeCreation/NodeCreation.php | 37 ++-- .../Feature/NodeDisabling/NodeDisabling.php | 20 +- .../NodeDuplicationCommandHandler.php | 30 +-- .../NodeModification/NodeModification.php | 24 +-- .../Classes/Feature/NodeMove/NodeMove.php | 177 ++++++++-------- .../NodeReferencing/NodeReferencing.php | 27 +-- .../Feature/NodeRemoval/NodeRemoval.php | 13 +- .../Feature/NodeRenaming/NodeRenaming.php | 14 +- .../Feature/NodeTypeChange/NodeTypeChange.php | 107 +++++----- .../Feature/NodeVariation/NodeVariation.php | 16 +- .../RootNodeCreation/RootNodeHandling.php | 32 ++- .../Feature/SubtreeTagging/SubtreeTagging.php | 13 +- .../Feature/WorkspaceCommandHandler.php | 66 +++--- .../ContentGraph/ContentGraphInterface.php | 60 ------ .../ContentStream/ContentStreamFinder.php | 38 ---- .../Adjustment/TetheredNodeAdjustments.php | 3 +- 29 files changed, 817 insertions(+), 613 deletions(-) create mode 100644 Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraphAdapter.php create mode 100644 Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraphAdapterFactory.php create mode 100644 Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterFactoryInterface.php create mode 100644 Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterInterface.php diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraphAdapter.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraphAdapter.php new file mode 100644 index 00000000000..0f3792ca314 --- /dev/null +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraphAdapter.php @@ -0,0 +1,123 @@ +dbalConnection, $tableNamePrefix); + } +} diff --git a/Neos.ContentRepository.Core/Classes/ContentRepository.php b/Neos.ContentRepository.Core/Classes/ContentRepository.php index 180e9fd0066..8d92d142775 100644 --- a/Neos.ContentRepository.Core/Classes/ContentRepository.php +++ b/Neos.ContentRepository.Core/Classes/ContentRepository.php @@ -29,6 +29,7 @@ use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\Projection\CatchUp; use Neos\ContentRepository\Core\Projection\CatchUpOptions; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentStream\ContentStreamFinder; use Neos\ContentRepository\Core\Projection\ProjectionInterface; @@ -258,4 +259,12 @@ public function getContentDimensionSource(): ContentDimensionSourceInterface { return $this->contentDimensionSource; } + + /** + * @internal + */ + public function getContentGraphAdapter(): ContentGraphAdapterInterface + { + return $this->getContentGraph(); + } } diff --git a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php index b23f23851c0..312c26e4a77 100644 --- a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php +++ b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php @@ -21,6 +21,7 @@ use Neos\ContentRepository\Core\DimensionSpace\InterDimensionalVariationGraph; use Neos\ContentRepository\Core\EventStore\EventNormalizer; use Neos\ContentRepository\Core\EventStore\EventPersister; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterFactoryInterface; use Neos\ContentRepository\Core\Feature\ContentStreamCommandHandler; use Neos\ContentRepository\Core\Feature\DimensionSpaceAdjustment\DimensionSpaceCommandHandler; use Neos\ContentRepository\Core\Feature\NodeAggregateCommandHandler; @@ -56,6 +57,7 @@ public function __construct( private readonly ProjectionCatchUpTriggerInterface $projectionCatchUpTrigger, private readonly UserIdProviderInterface $userIdProvider, private readonly ClockInterface $clock, + private readonly ContentGraphAdapterFactoryInterface $contentGraphAdapterFactory, ) { $contentDimensionZookeeper = new ContentDimensionZookeeper($contentDimensionSource); $interDimensionalVariationGraph = new InterDimensionalVariationGraph( @@ -132,19 +134,23 @@ public function buildService( private function buildCommandBus(): CommandBus { if (!$this->commandBus) { + $contentGraphAdapter = $this->contentGraphAdapterFactory->build($this->projectionFactoryDependencies->contentRepositoryId); $this->commandBus = new CommandBus( new ContentStreamCommandHandler( + $contentGraphAdapter ), new WorkspaceCommandHandler( $this->buildEventPersister(), $this->projectionFactoryDependencies->eventStore, $this->projectionFactoryDependencies->eventNormalizer, + $contentGraphAdapter ), new NodeAggregateCommandHandler( $this->projectionFactoryDependencies->nodeTypeManager, $this->projectionFactoryDependencies->contentDimensionZookeeper, $this->projectionFactoryDependencies->interDimensionalVariationGraph, - $this->projectionFactoryDependencies->propertyConverter + $this->projectionFactoryDependencies->propertyConverter, + $contentGraphAdapter ), new DimensionSpaceCommandHandler( $this->projectionFactoryDependencies->contentDimensionZookeeper, diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php index 6054ce5cc7c..140a34d146a 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php @@ -19,6 +19,7 @@ use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\Exception\DimensionSpacePointNotFound; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterInterface; use Neos\ContentRepository\Core\Feature\NodeModification\Dto\PropertyValuesToWrite; use Neos\ContentRepository\Core\Feature\NodeVariation\Exception\DimensionSpacePointIsAlreadyOccupied; use Neos\ContentRepository\Core\Infrastructure\Property\PropertyType; @@ -47,7 +48,6 @@ use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeNotFound; use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeNotFoundException; use Neos\ContentRepository\Core\SharedModel\Exception\ReferenceCannotBeSet; -use Neos\ContentRepository\Core\SharedModel\Exception\RootNodeAggregateDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Exception\RootNodeAggregateTypeIsAlreadyOccupied; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; @@ -67,21 +67,23 @@ abstract protected function getNodeTypeManager(): NodeTypeManager; abstract protected function getAllowedDimensionSubspace(): DimensionSpacePointSet; + abstract protected function getContentGraphAdapter(): ContentGraphAdapterInterface; + /** * @throws ContentStreamDoesNotExistYet */ protected function requireContentStream( - WorkspaceName $workspaceName, - ContentRepository $contentRepository + WorkspaceName $workspaceName ): ContentStreamId { - $contentStreamId = ContentStreamIdOverride::resolveContentStreamIdForWorkspace($contentRepository, $workspaceName); - if (!$contentRepository->getContentStreamFinder()->hasContentStream($contentStreamId)) { + $contentGraphAdapter = $this->getContentGraphAdapter(); + $contentStreamId = ContentStreamIdOverride::resolveContentStreamIdForWorkspace($contentGraphAdapter, $workspaceName); + if (!$contentGraphAdapter->hasContentStream($contentStreamId)) { throw new ContentStreamDoesNotExistYet( 'Content stream "' . $contentStreamId->value . '" does not exist yet.', 1521386692 ); } - if ($contentRepository->getContentStreamFinder()->findStateForContentStream($contentStreamId) === ContentStreamState::STATE_CLOSED) { + if ($contentGraphAdapter->findStateForContentStream($contentStreamId) === ContentStreamState::STATE_CLOSED) { throw new ContentStreamIsClosed( 'Content stream "' . $contentStreamId->value . '" is closed.', 1710260081 @@ -149,17 +151,15 @@ protected function requireNodeTypeToNotBeOfTypeRoot(NodeType $nodeType): void protected function requireRootNodeTypeToBeUnoccupied( NodeTypeName $nodeTypeName, - ContentStreamId $contentStreamId, - ContentRepository $contentRepository + ContentStreamId $contentStreamId ): void { - try { - $rootNodeAggregate = $contentRepository->getContentGraph()->findRootNodeAggregateByType( - $contentStreamId, - $nodeTypeName - ); + $rootNodeAggregateExists = $this->getContentGraphAdapter()->rootNodeAggregateWithTypeExists( + $contentStreamId, + $nodeTypeName + ); + + if ($rootNodeAggregateExists) { throw RootNodeAggregateTypeIsAlreadyOccupied::butWasExpectedNotTo($nodeTypeName); - } catch (RootNodeAggregateDoesNotExist $exception) { - // all is well } } @@ -247,14 +247,12 @@ protected function requireConstraintsImposedByAncestorsAreMet( ContentStreamId $contentStreamId, NodeType $nodeType, ?NodeName $nodeName, - array $parentNodeAggregateIds, - ContentRepository $contentRepository + array $parentNodeAggregateIds ): void { foreach ($parentNodeAggregateIds as $parentNodeAggregateId) { $parentAggregate = $this->requireProjectedNodeAggregate( $contentStreamId, - $parentNodeAggregateId, - $contentRepository + $parentNodeAggregateId ); if (!$parentAggregate->classification->isTethered()) { try { @@ -266,9 +264,11 @@ protected function requireConstraintsImposedByAncestorsAreMet( } } + $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($contentStreamId); foreach ( - $contentRepository->getContentGraph()->findParentNodeAggregates( + $this->getContentGraphAdapter()->findParentNodeAggregates( $contentStreamId, + $workspace?->workspaceName, $parentNodeAggregateId ) as $grandParentNodeAggregate ) { @@ -383,11 +383,12 @@ protected function areNodeTypeConstraintsImposedByGrandparentValid( */ protected function requireProjectedNodeAggregate( ContentStreamId $contentStreamId, - NodeAggregateId $nodeAggregateId, - ContentRepository $contentRepository + NodeAggregateId $nodeAggregateId ): NodeAggregate { - $nodeAggregate = $contentRepository->getContentGraph()->findNodeAggregateById( + $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($contentStreamId); + $nodeAggregate = $this->getContentGraphAdapter()->findNodeAggregateById( $contentStreamId, + $workspace?->workspaceName, $nodeAggregateId ); @@ -407,11 +408,12 @@ protected function requireProjectedNodeAggregate( */ protected function requireProjectedNodeAggregateToNotExist( ContentStreamId $contentStreamId, - NodeAggregateId $nodeAggregateId, - ContentRepository $contentRepository + NodeAggregateId $nodeAggregateId ): void { - $nodeAggregate = $contentRepository->getContentGraph()->findNodeAggregateById( + $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($contentStreamId); + $nodeAggregate = $this->getContentGraphAdapter()->findNodeAggregateById( $contentStreamId, + $workspace?->workspaceName, $nodeAggregateId ); @@ -429,12 +431,13 @@ protected function requireProjectedNodeAggregateToNotExist( public function requireProjectedParentNodeAggregate( ContentStreamId $contentStreamId, NodeAggregateId $childNodeAggregateId, - OriginDimensionSpacePoint $childOriginDimensionSpacePoint, - ContentRepository $contentRepository + OriginDimensionSpacePoint $childOriginDimensionSpacePoint ): NodeAggregate { - $parentNodeAggregate = $contentRepository->getContentGraph() + $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($contentStreamId); + $parentNodeAggregate = $this->getContentGraphAdapter() ->findParentNodeAggregateByChildOriginDimensionSpacePoint( $contentStreamId, + $workspace?->workspaceName, $childNodeAggregateId, $childOriginDimensionSpacePoint ); @@ -516,8 +519,7 @@ protected function requireNodeAggregateToBeUntethered(NodeAggregate $nodeAggrega protected function requireNodeAggregateToNotBeDescendant( ContentStreamId $contentStreamId, NodeAggregate $nodeAggregate, - NodeAggregate $referenceNodeAggregate, - ContentRepository $contentRepository + NodeAggregate $referenceNodeAggregate ): void { if ($nodeAggregate->nodeAggregateId->equals($referenceNodeAggregate->nodeAggregateId)) { throw new NodeAggregateIsDescendant( @@ -526,17 +528,18 @@ protected function requireNodeAggregateToNotBeDescendant( 1554971124 ); } + $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($contentStreamId); foreach ( - $contentRepository->getContentGraph()->findChildNodeAggregates( + $this->getContentGraphAdapter()->findChildNodeAggregates( $contentStreamId, + $workspace?->workspaceName, $referenceNodeAggregate->nodeAggregateId ) as $childReferenceNodeAggregate ) { $this->requireNodeAggregateToNotBeDescendant( $contentStreamId, $nodeAggregate, - $childReferenceNodeAggregate, - $contentRepository + $childReferenceNodeAggregate ); } } @@ -549,13 +552,12 @@ protected function requireNodeNameToBeUnoccupied( ?NodeName $nodeName, NodeAggregateId $parentNodeAggregateId, OriginDimensionSpacePoint $parentOriginDimensionSpacePoint, - DimensionSpacePointSet $dimensionSpacePoints, - ContentRepository $contentRepository + DimensionSpacePointSet $dimensionSpacePoints ): void { if ($nodeName === null) { return; } - $dimensionSpacePointsOccupiedByChildNodeName = $contentRepository->getContentGraph() + $dimensionSpacePointsOccupiedByChildNodeName = $this->getContentGraphAdapter() ->getDimensionSpacePointsOccupiedByChildNodeName( $contentStreamId, $nodeName, @@ -579,14 +581,16 @@ protected function requireNodeNameToBeUncovered( ContentStreamId $contentStreamId, ?NodeName $nodeName, NodeAggregateId $parentNodeAggregateId, - DimensionSpacePointSet $dimensionSpacePointsToBeCovered, - ContentRepository $contentRepository + DimensionSpacePointSet $dimensionSpacePointsToBeCovered ): void { if ($nodeName === null) { return; } - $childNodeAggregates = $contentRepository->getContentGraph()->findChildNodeAggregatesByName( + $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($contentStreamId); + + $childNodeAggregates = $this->getContentGraphAdapter()->findChildNodeAggregatesByName( $contentStreamId, + $workspace?->workspaceName, $parentNodeAggregateId, $nodeName ); @@ -672,13 +676,10 @@ protected function validateReferenceProperties( } protected function getExpectedVersionOfContentStream( - ContentStreamId $contentStreamId, - ContentRepository $contentRepository + ContentStreamId $contentStreamId ): ExpectedVersion { return ExpectedVersion::fromVersion( - $contentRepository->getContentStreamFinder() - ->findVersionForContentStream($contentStreamId) - ->unwrap() + $this->getContentGraphAdapter()->findVersionForContentStream($contentStreamId)->unwrap() ); } } diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/ContentStreamIdOverride.php b/Neos.ContentRepository.Core/Classes/Feature/Common/ContentStreamIdOverride.php index 96bf09b3a80..c744382c595 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/ContentStreamIdOverride.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/ContentStreamIdOverride.php @@ -14,7 +14,9 @@ namespace Neos\ContentRepository\Core\Feature\Common; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ContentGraphAdapter; use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterInterface; use Neos\ContentRepository\Core\Feature\WorkspaceCommandHandler; use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; @@ -50,10 +52,10 @@ public static function applyContentStreamIdToClosure(ContentStreamId $contentStr /** * @internal */ - public static function resolveContentStreamIdForWorkspace(ContentRepository $contentRepository, WorkspaceName $workspaceName): ContentStreamId + public static function resolveContentStreamIdForWorkspace(ContentGraphAdapterInterface $contentGraphAdapter, WorkspaceName $workspaceName): ContentStreamId { $contentStreamId = self::$contentStreamIdToUse - ?: $contentRepository->getWorkspaceFinder()->findOneByName($workspaceName)?->currentContentStreamId; + ?: $contentGraphAdapter->findWorkspaceByName($workspaceName)?->currentContentStreamId; if (!$contentStreamId) { throw new ContentStreamDoesNotExistYet( diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/NodeVariationInternals.php b/Neos.ContentRepository.Core/Classes/Feature/Common/NodeVariationInternals.php index 54b8d0a60e2..89b2882f002 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/NodeVariationInternals.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/NodeVariationInternals.php @@ -14,12 +14,12 @@ namespace Neos\ContentRepository\Core\Feature\Common; -use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\DimensionSpace; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\EventStore\EventInterface; use Neos\ContentRepository\Core\EventStore\Events; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterInterface; use Neos\ContentRepository\Core\Feature\NodeVariation\Event\NodeGeneralizationVariantWasCreated; use Neos\ContentRepository\Core\Feature\NodeVariation\Event\NodePeerVariantWasCreated; use Neos\ContentRepository\Core\Feature\NodeVariation\Event\NodeSpecializationVariantWasCreated; @@ -37,12 +37,13 @@ trait NodeVariationInternals { abstract protected function getInterDimensionalVariationGraph(): DimensionSpace\InterDimensionalVariationGraph; + abstract protected function getContentGraphAdapter(): ContentGraphAdapterInterface; + protected function createEventsForVariations( ContentStreamId $contentStreamId, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $targetOrigin, - NodeAggregate $nodeAggregate, - ContentRepository $contentRepository + NodeAggregate $nodeAggregate ): Events { return match ( $this->getInterDimensionalVariationGraph()->getVariantType( @@ -54,22 +55,19 @@ protected function createEventsForVariations( $contentStreamId, $sourceOrigin, $targetOrigin, - $nodeAggregate, - $contentRepository + $nodeAggregate ), DimensionSpace\VariantType::TYPE_GENERALIZATION => $this->handleCreateNodeGeneralizationVariant( $contentStreamId, $sourceOrigin, $targetOrigin, - $nodeAggregate, - $contentRepository + $nodeAggregate ), default => $this->handleCreateNodePeerVariant( $contentStreamId, $sourceOrigin, $targetOrigin, - $nodeAggregate, - $contentRepository + $nodeAggregate ), }; } @@ -78,8 +76,7 @@ protected function handleCreateNodeSpecializationVariant( ContentStreamId $contentStreamId, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $targetOrigin, - NodeAggregate $nodeAggregate, - ContentRepository $contentRepository + NodeAggregate $nodeAggregate ): Events { $specializationVisibility = $this->calculateEffectiveVisibility($targetOrigin, $nodeAggregate); $events = $this->collectNodeSpecializationVariantsThatWillHaveBeenCreated( @@ -88,8 +85,7 @@ protected function handleCreateNodeSpecializationVariant( $targetOrigin, $nodeAggregate, $specializationVisibility, - [], - $contentRepository + [] ); return Events::fromArray($events); @@ -105,9 +101,9 @@ protected function collectNodeSpecializationVariantsThatWillHaveBeenCreated( OriginDimensionSpacePoint $targetOrigin, NodeAggregate $nodeAggregate, DimensionSpacePointSet $specializationVisibility, - array $events, - ContentRepository $contentRepository + array $events ): array { + $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($contentStreamId); $events[] = new NodeSpecializationVariantWasCreated( $contentStreamId, $nodeAggregate->nodeAggregateId, @@ -123,8 +119,9 @@ protected function collectNodeSpecializationVariantsThatWillHaveBeenCreated( ); foreach ( - $contentRepository->getContentGraph()->findTetheredChildNodeAggregates( + $this->getContentGraphAdapter()->findTetheredChildNodeAggregates( $contentStreamId, + $workspace?->workspaceName, $nodeAggregate->nodeAggregateId ) as $tetheredChildNodeAggregate ) { @@ -134,8 +131,7 @@ protected function collectNodeSpecializationVariantsThatWillHaveBeenCreated( $targetOrigin, $tetheredChildNodeAggregate, $specializationVisibility, - $events, - $contentRepository + $events ); } @@ -146,8 +142,7 @@ protected function handleCreateNodeGeneralizationVariant( ContentStreamId $contentStreamId, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $targetOrigin, - NodeAggregate $nodeAggregate, - ContentRepository $contentRepository + NodeAggregate $nodeAggregate ): Events { $generalizationVisibility = $this->calculateEffectiveVisibility($targetOrigin, $nodeAggregate); $events = $this->collectNodeGeneralizationVariantsThatWillHaveBeenCreated( @@ -156,8 +151,7 @@ protected function handleCreateNodeGeneralizationVariant( $targetOrigin, $nodeAggregate, $generalizationVisibility, - [], - $contentRepository + [] ); return Events::fromArray($events); @@ -173,9 +167,9 @@ protected function collectNodeGeneralizationVariantsThatWillHaveBeenCreated( OriginDimensionSpacePoint $targetOrigin, NodeAggregate $nodeAggregate, DimensionSpacePointSet $generalizationVisibility, - array $events, - ContentRepository $contentRepository + array $events ): array { + $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($contentStreamId); $events[] = new NodeGeneralizationVariantWasCreated( $contentStreamId, $nodeAggregate->nodeAggregateId, @@ -191,8 +185,9 @@ protected function collectNodeGeneralizationVariantsThatWillHaveBeenCreated( ); foreach ( - $contentRepository->getContentGraph()->findTetheredChildNodeAggregates( + $this->getContentGraphAdapter()->findTetheredChildNodeAggregates( $contentStreamId, + $workspace?->workspaceName, $nodeAggregate->nodeAggregateId ) as $tetheredChildNodeAggregate ) { @@ -202,8 +197,7 @@ protected function collectNodeGeneralizationVariantsThatWillHaveBeenCreated( $targetOrigin, $tetheredChildNodeAggregate, $generalizationVisibility, - $events, - $contentRepository + $events ); } @@ -214,8 +208,7 @@ protected function handleCreateNodePeerVariant( ContentStreamId $contentStreamId, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $targetOrigin, - NodeAggregate $nodeAggregate, - ContentRepository $contentRepository + NodeAggregate $nodeAggregate ): Events { $peerVisibility = $this->calculateEffectiveVisibility($targetOrigin, $nodeAggregate); $events = $this->collectNodePeerVariantsThatWillHaveBeenCreated( @@ -224,8 +217,7 @@ protected function handleCreateNodePeerVariant( $targetOrigin, $nodeAggregate, $peerVisibility, - [], - $contentRepository + [] ); return Events::fromArray($events); @@ -241,9 +233,9 @@ protected function collectNodePeerVariantsThatWillHaveBeenCreated( OriginDimensionSpacePoint $targetOrigin, NodeAggregate $nodeAggregate, DimensionSpacePointSet $peerVisibility, - array $events, - ContentRepository $contentRepository + array $events ): array { + $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($contentStreamId); $events[] = new NodePeerVariantWasCreated( $contentStreamId, $nodeAggregate->nodeAggregateId, @@ -259,8 +251,9 @@ protected function collectNodePeerVariantsThatWillHaveBeenCreated( ); foreach ( - $contentRepository->getContentGraph()->findTetheredChildNodeAggregates( + $this->getContentGraphAdapter()->findTetheredChildNodeAggregates( $contentStreamId, + $workspace?->workspaceName, $nodeAggregate->nodeAggregateId ) as $tetheredChildNodeAggregate ) { @@ -270,8 +263,7 @@ protected function collectNodePeerVariantsThatWillHaveBeenCreated( $targetOrigin, $tetheredChildNodeAggregate, $peerVisibility, - $events, - $contentRepository + $events ); } diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/TetheredNodeInternals.php b/Neos.ContentRepository.Core/Classes/Feature/Common/TetheredNodeInternals.php index 5d2a01fce2d..4c4e7d55d2d 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; @@ -43,8 +42,7 @@ abstract protected function createEventsForVariations( ContentStreamId $contentStreamId, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $targetOrigin, - NodeAggregate $nodeAggregate, - ContentRepository $contentRepository + NodeAggregate $nodeAggregate ): Events; /** @@ -60,11 +58,14 @@ protected function createEventsForMissingTetheredNode( OriginDimensionSpacePoint $originDimensionSpacePoint, NodeName $tetheredNodeName, ?NodeAggregateId $tetheredNodeAggregateId, - NodeType $expectedTetheredNodeType, - ContentRepository $contentRepository + NodeType $expectedTetheredNodeType ): Events { - $childNodeAggregates = $contentRepository->getContentGraph()->findChildNodeAggregatesByName( + // TODO: We should probably just directly use the workspace AND contentStreamId from the $parentNodeAggregate + $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($parentNodeAggregate->contentStreamId); + + $childNodeAggregates = $this->getContentGraphAdapter()->findChildNodeAggregatesByName( $parentNodeAggregate->contentStreamId, + $workspace?->workspaceName, $parentNodeAggregate->nodeAggregateId, $tetheredNodeName ); @@ -152,8 +153,7 @@ protected function createEventsForMissingTetheredNode( $parentNodeAggregate->contentStreamId, $childNodeSource->originDimensionSpacePoint, $originDimensionSpacePoint, - $parentNodeAggregate, - $contentRepository + $parentNodeAggregate ); } else { throw new \RuntimeException( diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterFactoryInterface.php b/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterFactoryInterface.php new file mode 100644 index 00000000000..3dc5897222f --- /dev/null +++ b/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterFactoryInterface.php @@ -0,0 +1,15 @@ + + */ + public function findParentNodeAggregates( + ContentStreamId $contentStreamId, + WorkspaceName $workspaceName, + NodeAggregateId $childNodeAggregateId + ): iterable; + + /** + * @throws NodeAggregatesTypeIsAmbiguous + */ + public function findNodeAggregateById( + ContentStreamId $contentStreamId, + WorkspaceName $workspaceName, + NodeAggregateId $nodeAggregateId + ): ?NodeAggregate; + + public function findParentNodeAggregateByChildOriginDimensionSpacePoint( + ContentStreamId $contentStreamId, + WorkspaceName $workspaceName, + NodeAggregateId $childNodeAggregateId, + OriginDimensionSpacePoint $childOriginDimensionSpacePoint + ): ?NodeAggregate; + + /** + * @return iterable + */ + public function findChildNodeAggregates( + ContentStreamId $contentStreamId, + WorkspaceName $workspaceName, + NodeAggregateId $parentNodeAggregateId + ): iterable; + + /** + * @return iterable + */ + public function findTetheredChildNodeAggregates( + ContentStreamId $contentStreamId, + WorkspaceName $workspaceName, + NodeAggregateId $parentNodeAggregateId + ): iterable; + + /** + */ + public function getDimensionSpacePointsOccupiedByChildNodeName( + ContentStreamId $contentStreamId, + NodeName $nodeName, + NodeAggregateId $parentNodeAggregateId, + OriginDimensionSpacePoint $parentNodeOriginDimensionSpacePoint, + DimensionSpacePointSet $dimensionSpacePointsToCheck + ): DimensionSpacePointSet; + + /** + * A node aggregate may have multiple child node aggregates with the same name + * as long as they do not share dimension space coverage + * + * @return iterable + */ + public function findChildNodeAggregatesByName( + ContentStreamId $contentStreamId, + WorkspaceName $workspaceName, + NodeAggregateId $parentNodeAggregateId, + NodeName $name + ): iterable; + + /* + * NODES, basically anything you would ask a subgraph + */ + + /** + * Does the subgraph with the provided identity contain any nodes + */ + public function subgraphContainsNodes( + ContentStreamId $contentStreamId, + DimensionSpacePoint $dimensionSpacePoint + ): bool; + + /** + * Finds a specified node within a "subgraph" + */ + public function findNodeInSubgraph( + ContentStreamId $contentStreamId, + WorkspaceName $workspaceName, + DimensionSpacePoint $coveredDimensionSpacePoint, + NodeAggregateId $nodeAggregateId + ): ?Node; + + public function findParentNodeInSubgraph( + ContentStreamId $contentStreamId, + WorkspaceName $workspaceName, + DimensionSpacePoint $coveredDimensionSpacePoint, + NodeAggregateId $childNodeAggregateId + ): ?Node; + + public function findChildNodeByNameInSubgraph( + ContentStreamId $contentStreamId, + WorkspaceName $workspaceName, + DimensionSpacePoint $coveredDimensionSpacePoint, + NodeAggregateId $parentNodeAggregateId, + NodeName $nodeNamex + ): ?Node; + + public function findPreceedingSiblingNodesInSubgraph( + ContentStreamId $contentStreamId, + WorkspaceName $workspaceName, + DimensionSpacePoint $coveredDimensionSpacePoint, + NodeAggregateId $startingSiblingNodeAggregateId + ): Nodes; + + public function findSuceedingSiblingNodesInSubgraph( + ContentStreamId $contentStreamId, + WorkspaceName $workspaceName, + DimensionSpacePoint $coveredDimensionSpacePoint, + NodeAggregateId $startingSiblingNodeAggregateId + ): Nodes; + + /* + * CONTENT STREAMS + */ + + public function hasContentStream( + ContentStreamId $contentStreamId + ): bool; + + public function findStateForContentStream( + ContentStreamId $contentStreamId + ): ?ContentStreamState; + + public function findVersionForContentStream( + ContentStreamId $contentStreamId + ): MaybeVersion; + + /* + * WORKSPACES + */ + + public function findWorkspaceByName( + WorkspaceName $workspaceName + ): ?Workspace; + + public function findWorkspaceByCurrentContentStreamId( + ContentStreamId $contentStreamId + ): ?Workspace; +} diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php index 86fe2b2ac22..eac522851a3 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php @@ -44,6 +44,12 @@ */ final class ContentStreamCommandHandler implements CommandHandlerInterface { + + public function __construct( + protected readonly ContentGraphAdapterInterface $contentGraphAdapter + ) { + } + public function canHandle(CommandInterface $command): bool { return method_exists($this, 'handle' . (new \ReflectionClass($command))->getShortName()); @@ -52,11 +58,11 @@ public function canHandle(CommandInterface $command): bool public function handle(CommandInterface $command, ContentRepository $contentRepository): 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), + CloseContentStream::class => $this->handleCloseContentStream($command), + ReopenContentStream::class => $this->handleReopenContentStream($command), + ForkContentStream::class => $this->handleForkContentStream($command), + RemoveContentStream::class => $this->handleRemoveContentStream($command), default => throw new \DomainException('Cannot handle commands of class ' . get_class($command), 1710408206), }; } @@ -65,10 +71,9 @@ public function handle(CommandInterface $command, ContentRepository $contentRepo * @throws ContentStreamAlreadyExists */ private function handleCreateContentStream( - CreateContentStream $command, - ContentRepository $contentRepository + CreateContentStream $command ): EventsToPublish { - $this->requireContentStreamToNotExistYet($command->contentStreamId, $contentRepository); + $this->requireContentStreamToNotExistYet($command->contentStreamId); $streamName = ContentStreamEventStreamName::fromContentStreamId($command->contentStreamId) ->getEventStreamName(); @@ -84,12 +89,11 @@ private function handleCreateContentStream( } private function handleCloseContentStream( - CloseContentStream $command, - ContentRepository $contentRepository + CloseContentStream $command ): EventsToPublish { - $this->requireContentStreamToExist($command->contentStreamId, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($command->contentStreamId, $contentRepository); - $this->requireContentStreamToNotBeClosed($command->contentStreamId, $contentRepository); + $this->requireContentStreamToExist($command->contentStreamId); + $expectedVersion = $this->getExpectedVersionOfContentStream($command->contentStreamId); + $this->requireContentStreamToNotBeClosed($command->contentStreamId); $streamName = ContentStreamEventStreamName::fromContentStreamId($command->contentStreamId)->getEventStreamName(); return new EventsToPublish( @@ -104,12 +108,11 @@ private function handleCloseContentStream( } private function handleReopenContentStream( - ReopenContentStream $command, - ContentRepository $contentRepository + ReopenContentStream $command ): EventsToPublish { - $this->requireContentStreamToExist($command->contentStreamId, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($command->contentStreamId, $contentRepository); - $this->requireContentStreamToBeClosed($command->contentStreamId, $contentRepository); + $this->requireContentStreamToExist($command->contentStreamId); + $expectedVersion = $this->getExpectedVersionOfContentStream($command->contentStreamId); + $this->requireContentStreamToBeClosed($command->contentStreamId); $streamName = ContentStreamEventStreamName::fromContentStreamId($command->contentStreamId)->getEventStreamName(); return new EventsToPublish( @@ -129,14 +132,13 @@ private function handleReopenContentStream( * @throws ContentStreamDoesNotExistYet */ private function handleForkContentStream( - ForkContentStream $command, - ContentRepository $contentRepository + ForkContentStream $command ): EventsToPublish { - $this->requireContentStreamToExist($command->sourceContentStreamId, $contentRepository); - $this->requireContentStreamToNotBeClosed($command->sourceContentStreamId, $contentRepository); - $this->requireContentStreamToNotExistYet($command->newContentStreamId, $contentRepository); + $this->requireContentStreamToExist($command->sourceContentStreamId); + $this->requireContentStreamToNotBeClosed($command->sourceContentStreamId); + $this->requireContentStreamToNotExistYet($command->newContentStreamId); - $sourceContentStreamVersion = $contentRepository->getContentStreamFinder() + $sourceContentStreamVersion = $this->contentGraphAdapter ->findVersionForContentStream($command->sourceContentStreamId); $streamName = ContentStreamEventStreamName::fromContentStreamId($command->newContentStreamId) @@ -157,11 +159,10 @@ private function handleForkContentStream( } private function handleRemoveContentStream( - RemoveContentStream $command, - ContentRepository $contentRepository + RemoveContentStream $command ): EventsToPublish { - $this->requireContentStreamToExist($command->contentStreamId, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($command->contentStreamId, $contentRepository); + $this->requireContentStreamToExist($command->contentStreamId); + $expectedVersion = $this->getExpectedVersionOfContentStream($command->contentStreamId); $streamName = ContentStreamEventStreamName::fromContentStreamId( $command->contentStreamId @@ -183,10 +184,9 @@ private function handleRemoveContentStream( * @throws ContentStreamAlreadyExists */ protected function requireContentStreamToNotExistYet( - ContentStreamId $contentStreamId, - ContentRepository $contentRepository + ContentStreamId $contentStreamId ): void { - if ($contentRepository->getContentStreamFinder()->hasContentStream($contentStreamId)) { + if ($this->contentGraphAdapter->hasContentStream($contentStreamId)) { throw new ContentStreamAlreadyExists( 'Content stream "' . $contentStreamId->value . '" already exists.', 1521386345 @@ -199,10 +199,9 @@ protected function requireContentStreamToNotExistYet( * @throws ContentStreamDoesNotExistYet */ protected function requireContentStreamToExist( - ContentStreamId $contentStreamId, - ContentRepository $contentRepository + ContentStreamId $contentStreamId ): void { - if (!$contentRepository->getContentStreamFinder()->hasContentStream($contentStreamId)) { + if (!$this->contentGraphAdapter->hasContentStream($contentStreamId)) { throw new ContentStreamDoesNotExistYet( 'Content stream "' . $contentStreamId->value . '" does not exist yet.', 1521386692 @@ -211,10 +210,9 @@ protected function requireContentStreamToExist( } protected function requireContentStreamToNotBeClosed( - ContentStreamId $contentStreamId, - ContentRepository $contentRepository + ContentStreamId $contentStreamId ): void { - if ($contentRepository->getContentStreamFinder()->findStateForContentStream($contentStreamId) === ContentStreamState::STATE_CLOSED) { + if ($this->contentGraphAdapter->findStateForContentStream($contentStreamId) === ContentStreamState::STATE_CLOSED) { throw new ContentStreamIsClosed( 'Content stream "' . $contentStreamId->value . '" is closed.', 1710260081 @@ -223,10 +221,9 @@ protected function requireContentStreamToNotBeClosed( } protected function requireContentStreamToBeClosed( - ContentStreamId $contentStreamId, - ContentRepository $contentRepository + ContentStreamId $contentStreamId ): void { - if ($contentRepository->getContentStreamFinder()->findStateForContentStream($contentStreamId) !== ContentStreamState::STATE_CLOSED) { + if ($this->contentGraphAdapter->findStateForContentStream($contentStreamId) !== ContentStreamState::STATE_CLOSED) { throw new ContentStreamIsNotClosed( 'Content stream "' . $contentStreamId->value . '" is not closed.', 1710405911 @@ -235,11 +232,10 @@ protected function requireContentStreamToBeClosed( } protected function getExpectedVersionOfContentStream( - ContentStreamId $contentStreamId, - ContentRepository $contentRepository + ContentStreamId $contentStreamId ): ExpectedVersion { return ExpectedVersion::fromVersion( - $contentRepository->getContentStreamFinder() + $this->contentGraphAdapter ->findVersionForContentStream($contentStreamId) ->unwrap() ); diff --git a/Neos.ContentRepository.Core/Classes/Feature/DimensionSpaceAdjustment/DimensionSpaceCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/DimensionSpaceAdjustment/DimensionSpaceCommandHandler.php index ca0b66861b4..7b6a8030173 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/DimensionSpaceAdjustment/DimensionSpaceCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/DimensionSpaceAdjustment/DimensionSpaceCommandHandler.php @@ -25,6 +25,7 @@ use Neos\ContentRepository\Core\DimensionSpace\VariantType; use Neos\ContentRepository\Core\EventStore\Events; use Neos\ContentRepository\Core\EventStore\EventsToPublish; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterInterface; use Neos\ContentRepository\Core\Feature\ContentStreamEventStreamName; use Neos\ContentRepository\Core\Feature\DimensionSpaceAdjustment\Command\AddDimensionShineThrough; use Neos\ContentRepository\Core\Feature\DimensionSpaceAdjustment\Command\MoveDimensionSpacePoint; @@ -46,6 +47,7 @@ public function __construct( private ContentDimensionZookeeper $contentDimensionZookeeper, private InterDimensionalVariationGraph $interDimensionalVariationGraph, + private ContentGraphAdapterInterface $contentGraphAdapter ) { } @@ -58,14 +60,13 @@ public function handle(CommandInterface $command, ContentRepository $contentRepo { /** @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), + AddDimensionShineThrough::class => $this->handleAddDimensionShineThrough($command), }; } private function handleMoveDimensionSpacePoint( - MoveDimensionSpacePoint $command, - ContentRepository $contentRepository + MoveDimensionSpacePoint $command ): EventsToPublish { $contentStreamId = $this->requireContentStreamForWorkspaceName($command->workspaceName, $contentRepository); $streamName = ContentStreamEventStreamName::fromContentStreamId($contentStreamId) @@ -74,7 +75,7 @@ private function handleMoveDimensionSpacePoint( self::requireDimensionSpacePointToBeEmptyInContentStream( $command->target, $contentStreamId, - $contentRepository->getContentGraph() + $this->contentGraphAdapter ); $this->requireDimensionSpacePointToExist($command->target); @@ -92,8 +93,7 @@ private function handleMoveDimensionSpacePoint( } private function handleAddDimensionShineThrough( - AddDimensionShineThrough $command, - ContentRepository $contentRepository + AddDimensionShineThrough $command ): EventsToPublish { $contentStreamId = $this->requireContentStreamForWorkspaceName($command->workspaceName, $contentRepository); $streamName = ContentStreamEventStreamName::fromContentStreamId($contentStreamId) @@ -102,7 +102,7 @@ private function handleAddDimensionShineThrough( self::requireDimensionSpacePointToBeEmptyInContentStream( $command->target, $contentStreamId, - $contentRepository->getContentGraph() + $this->contentGraphAdapter ); $this->requireDimensionSpacePointToExist($command->target); @@ -135,14 +135,10 @@ protected function requireDimensionSpacePointToExist(DimensionSpacePoint $dimens private static function requireDimensionSpacePointToBeEmptyInContentStream( DimensionSpacePoint $dimensionSpacePoint, ContentStreamId $contentStreamId, - ContentGraphInterface $contentGraph + ContentGraphAdapterInterface $contentGraphAdapter ): void { - $subgraph = $contentGraph->getSubgraph( - $contentStreamId, - $dimensionSpacePoint, - VisibilityConstraints::withoutRestrictions() - ); - if ($subgraph->countNodes() > 0) { + $hasNodes = $contentGraphAdapter->subgraphContainsNodes($contentStreamId, $dimensionSpacePoint); + if ($hasNodes) { throw new DimensionSpacePointAlreadyExists(sprintf( 'the content stream %s already contained nodes in dimension space point %s - this is not allowed.', $contentStreamId->value, @@ -169,12 +165,11 @@ private function requireDimensionSpacePointToBeSpecialization( * @throws ContentStreamDoesNotExistYet */ protected function requireContentStreamForWorkspaceName( - WorkspaceName $workspaceName, - ContentRepository $contentRepository + WorkspaceName $workspaceName ): ContentStreamId { - $contentStreamId = $contentRepository->getWorkspaceFinder()->findOneByName($workspaceName) + $contentStreamId = $this->contentGraphAdapter->findWorkspaceByName($workspaceName) ?->currentContentStreamId; - if (!$contentStreamId || !$contentRepository->getContentStreamFinder()->hasContentStream($contentStreamId)) { + if (!$contentStreamId || !$this->contentGraphAdapter->hasContentStream($contentStreamId)) { throw new ContentStreamDoesNotExistYet( 'Content stream "' . $contentStreamId?->value . '" does not exist yet.', 1521386692 @@ -183,4 +178,18 @@ protected function requireContentStreamForWorkspaceName( return $contentStreamId; } + + /** + * @throws ContentStreamDoesNotExistYet + */ + protected function requireContentStream( + ContentStreamId $contentStreamId + ): void { + if (!$this->contentGraphAdapter->hasContentStream($contentStreamId)) { + throw new ContentStreamDoesNotExistYet( + 'Content stream "' . $contentStreamId->value . '" does not exist yet.', + 1521386692 + ); + } + } } diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php index 27b49905cb6..2bf929c6dda 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php @@ -72,41 +72,20 @@ 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, + protected readonly ContentGraphAdapterInterface $contentGraphAdapter ) { - $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()); @@ -116,29 +95,29 @@ public function handle(CommandInterface $command, ContentRepository $contentRepo { /** @phpstan-ignore-next-line */ return match ($command::class) { - SetNodeProperties::class => $this->handleSetNodeProperties($command, $contentRepository), + SetNodeProperties::class => $this->handleSetNodeProperties($command), SetSerializedNodeProperties::class - => $this->handleSetSerializedNodeProperties($command, $contentRepository), - SetNodeReferences::class => $this->handleSetNodeReferences($command, $contentRepository), + => $this->handleSetSerializedNodeProperties($command), + SetNodeReferences::class => $this->handleSetNodeReferences($command), SetSerializedNodeReferences::class - => $this->handleSetSerializedNodeReferences($command, $contentRepository), - ChangeNodeAggregateType::class => $this->handleChangeNodeAggregateType($command, $contentRepository), - RemoveNodeAggregate::class => $this->handleRemoveNodeAggregate($command, $contentRepository), + => $this->handleSetSerializedNodeReferences($command), + ChangeNodeAggregateType::class => $this->handleChangeNodeAggregateType($command), + RemoveNodeAggregate::class => $this->handleRemoveNodeAggregate($command), CreateNodeAggregateWithNode::class - => $this->handleCreateNodeAggregateWithNode($command, $contentRepository), + => $this->handleCreateNodeAggregateWithNode($command), CreateNodeAggregateWithNodeAndSerializedProperties::class - => $this->handleCreateNodeAggregateWithNodeAndSerializedProperties($command, $contentRepository), - MoveNodeAggregate::class => $this->handleMoveNodeAggregate($command, $contentRepository), - CreateNodeVariant::class => $this->handleCreateNodeVariant($command, $contentRepository), + => $this->handleCreateNodeAggregateWithNodeAndSerializedProperties($command), + MoveNodeAggregate::class => $this->handleMoveNodeAggregate($command), + CreateNodeVariant::class => $this->handleCreateNodeVariant($command), CreateRootNodeAggregateWithNode::class - => $this->handleCreateRootNodeAggregateWithNode($command, $contentRepository), + => $this->handleCreateRootNodeAggregateWithNode($command), 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), + DisableNodeAggregate::class => $this->handleDisableNodeAggregate($command), + EnableNodeAggregate::class => $this->handleEnableNodeAggregate($command), + TagSubtree::class => $this->handleTagSubtree($command), + UntagSubtree::class => $this->handleUntagSubtree($command), + ChangeNodeAggregateName::class => $this->handleChangeNodeAggregateName($command), }; } @@ -167,6 +146,11 @@ public function getPropertyConverter(): PropertyConverter return $this->propertyConverter; } + protected function getContentGraphAdapter(): ContentGraphAdapterInterface + { + return $this->contentGraphAdapter; + } + /** * Use this closure to run code with the Ancestor Node Type Checks disabled; e.g. * during imports. diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php b/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php index c2f1ff8a42a..e2e389d0209 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php @@ -14,7 +14,6 @@ namespace Neos\ContentRepository\Core\Feature\NodeCreation; -use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\DimensionSpace; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\EventStore\Events; @@ -68,8 +67,7 @@ abstract protected function getPropertyConverter(): PropertyConverter; abstract protected function getNodeTypeManager(): NodeTypeManager; private function handleCreateNodeAggregateWithNode( - CreateNodeAggregateWithNode $command, - ContentRepository $contentRepository + CreateNodeAggregateWithNode $command ): EventsToPublish { $this->requireNodeType($command->nodeTypeName); $this->validateProperties($command->initialPropertyValues, $command->nodeTypeName); @@ -91,7 +89,7 @@ private function handleCreateNodeAggregateWithNode( $lowLevelCommand = $lowLevelCommand->withTetheredDescendantNodeAggregateIds($command->tetheredDescendantNodeAggregateIds); } - return $this->handleCreateNodeAggregateWithNodeAndSerializedProperties($lowLevelCommand, $contentRepository); + return $this->handleCreateNodeAggregateWithNodeAndSerializedProperties($lowLevelCommand); } private function validateProperties(?PropertyValuesToWrite $propertyValues, NodeTypeName $nodeTypeName): void @@ -128,11 +126,10 @@ private function validateProperties(?PropertyValuesToWrite $propertyValues, Node * @throws NodeTypeNotFoundException */ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( - CreateNodeAggregateWithNodeAndSerializedProperties $command, - ContentRepository $contentRepository + CreateNodeAggregateWithNodeAndSerializedProperties $command ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $contentStreamId = $this->requireContentStream($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId); $this->requireDimensionSpacePointToExist($command->originDimensionSpacePoint->toDimensionSpacePoint()); $nodeType = $this->requireNodeType($command->nodeTypeName); $this->requireNodeTypeToNotBeAbstract($nodeType); @@ -144,25 +141,21 @@ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( $contentStreamId, $nodeType, $command->nodeName, - [$command->parentNodeAggregateId], - $contentRepository + [$command->parentNodeAggregateId] ); } $this->requireProjectedNodeAggregateToNotExist( $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $command->nodeAggregateId ); $parentNodeAggregate = $this->requireProjectedNodeAggregate( $contentStreamId, - $command->parentNodeAggregateId, - $contentRepository + $command->parentNodeAggregateId ); if ($command->succeedingSiblingNodeAggregateId) { $this->requireProjectedNodeAggregate( $contentStreamId, - $command->succeedingSiblingNodeAggregateId, - $contentRepository + $command->succeedingSiblingNodeAggregateId ); } $this->requireNodeAggregateToCoverDimensionSpacePoint( @@ -181,8 +174,7 @@ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( $command->nodeName, $command->parentNodeAggregateId, $command->originDimensionSpacePoint, - $coveredDimensionSpacePoints, - $contentRepository + $coveredDimensionSpacePoints ); } @@ -199,8 +191,7 @@ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( ) { $this->requireProjectedNodeAggregateToNotExist( $contentStreamId, - $descendantNodeAggregateId, - $contentRepository + $descendantNodeAggregateId ); } @@ -224,7 +215,7 @@ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( $coveredDimensionSpacePoints, $command->nodeAggregateId, $descendantNodeAggregateIds, - null, + null ))); return new EventsToPublish( @@ -274,7 +265,7 @@ private function handleTetheredChildNodes( DimensionSpacePointSet $coveredDimensionSpacePoints, NodeAggregateId $parentNodeAggregateId, NodeAggregateIdsByNodePaths $nodeAggregateIds, - ?NodePath $nodePath, + ?NodePath $nodePath ): Events { $events = []; foreach ($this->getNodeTypeManager()->getTetheredNodesConfigurationForNodeType($nodeType) as $rawNodeName => $childNodeType) { @@ -306,7 +297,7 @@ private function handleTetheredChildNodes( $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..1a35de7c522 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeDisabling/NodeDisabling.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeDisabling/NodeDisabling.php @@ -45,16 +45,14 @@ abstract protected function getInterDimensionalVariationGraph(): DimensionSpace\ * @throws NodeAggregatesTypeIsAmbiguous */ private function handleDisableNodeAggregate( - DisableNodeAggregate $command, - ContentRepository $contentRepository + DisableNodeAggregate $command ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $contentStreamId = $this->requireContentStream($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId); $this->requireDimensionSpacePointToExist($command->coveredDimensionSpacePoint); $nodeAggregate = $this->requireProjectedNodeAggregate( $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $command->nodeAggregateId ); $this->requireNodeAggregateToCoverDimensionSpacePoint( $nodeAggregate, @@ -100,16 +98,14 @@ private function handleDisableNodeAggregate( * @throws NodeAggregatesTypeIsAmbiguous */ public function handleEnableNodeAggregate( - EnableNodeAggregate $command, - ContentRepository $contentRepository + EnableNodeAggregate $command ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $contentStreamId = $this->requireContentStream($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId); $this->requireDimensionSpacePointToExist($command->coveredDimensionSpacePoint); $nodeAggregate = $this->requireProjectedNodeAggregate( $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $command->nodeAggregateId ); $this->requireNodeAggregateToCoverDimensionSpacePoint( $nodeAggregate, diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php index d88df293eab..fbf7176d8f2 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php @@ -71,7 +71,7 @@ public function handle(CommandInterface $command, ContentRepository $contentRepo { /** @phpstan-ignore-next-line */ return match ($command::class) { - CopyNodesRecursively::class => $this->handleCopyNodesRecursively($command, $contentRepository), + CopyNodesRecursively::class => $this->handleCopyNodesRecursively($command), }; } @@ -79,12 +79,11 @@ public function handle(CommandInterface $command, ContentRepository $contentRepo * @throws NodeConstraintException */ private function handleCopyNodesRecursively( - CopyNodesRecursively $command, - ContentRepository $contentRepository + CopyNodesRecursively $command ): 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); + $contentStreamId = $this->requireContentStream($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId); $this->requireDimensionSpacePointToExist( $command->targetDimensionSpacePoint->toDimensionSpacePoint() ); @@ -98,28 +97,24 @@ private function handleCopyNodesRecursively( $contentStreamId, $nodeType, $command->targetNodeName, - [$command->targetParentNodeAggregateId], - $contentRepository + [$command->targetParentNodeAggregateId] ); // Constraint: The new nodeAggregateIds are not allowed to exist yet. $this->requireNewNodeAggregateIdsToNotExist( $contentStreamId, - $command->nodeAggregateIdMapping, - $contentRepository + $command->nodeAggregateIdMapping ); // Constraint: the parent node must exist in the command's DimensionSpacePoint as well $parentNodeAggregate = $this->requireProjectedNodeAggregate( $contentStreamId, - $command->targetParentNodeAggregateId, - $contentRepository + $command->targetParentNodeAggregateId ); if ($command->targetSucceedingSiblingNodeAggregateId) { $this->requireProjectedNodeAggregate( $contentStreamId, - $command->targetSucceedingSiblingNodeAggregateId, - $contentRepository + $command->targetSucceedingSiblingNodeAggregateId ); } $this->requireNodeAggregateToCoverDimensionSpacePoint( @@ -143,8 +138,7 @@ private function handleCopyNodesRecursively( $command->targetNodeName, $command->targetParentNodeAggregateId, $command->targetDimensionSpacePoint, - $coveredDimensionSpacePoints, - $contentRepository + $coveredDimensionSpacePoints ); } @@ -177,14 +171,12 @@ private function handleCopyNodesRecursively( private function requireNewNodeAggregateIdsToNotExist( ContentStreamId $contentStreamId, - Dto\NodeAggregateIdMapping $nodeAggregateIdMapping, - ContentRepository $contentRepository + Dto\NodeAggregateIdMapping $nodeAggregateIdMapping ): void { foreach ($nodeAggregateIdMapping->getAllNewNodeAggregateIds() as $nodeAggregateId) { $this->requireProjectedNodeAggregateToNotExist( $contentStreamId, - $nodeAggregateId, - $contentRepository + $nodeAggregateId ); } } diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeModification/NodeModification.php b/Neos.ContentRepository.Core/Classes/Feature/NodeModification/NodeModification.php index 49d71efa330..cf9fc4d541b 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeModification/NodeModification.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeModification/NodeModification.php @@ -14,7 +14,6 @@ namespace Neos\ContentRepository\Core\Feature\NodeModification; -use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\EventStore\Events; use Neos\ContentRepository\Core\EventStore\EventsToPublish; use Neos\ContentRepository\Core\Feature\Common\NodeAggregateEventPublisher; @@ -40,20 +39,17 @@ abstract protected function requireNodeType(NodeTypeName $nodeTypeName): NodeTyp abstract protected function requireProjectedNodeAggregate( ContentStreamId $contentStreamId, - NodeAggregateId $nodeAggregateId, - ContentRepository $contentRepository + NodeAggregateId $nodeAggregateId ): NodeAggregate; private function handleSetNodeProperties( - SetNodeProperties $command, - ContentRepository $contentRepository + SetNodeProperties $command ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); + $contentStreamId = $this->requireContentStream($command->workspaceName); $this->requireDimensionSpacePointToExist($command->originDimensionSpacePoint->toDimensionSpacePoint()); $nodeAggregate = $this->requireProjectedNodeAggregate( $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $command->nodeAggregateId ); $this->requireNodeAggregateToNotBeRoot($nodeAggregate); $nodeTypeName = $nodeAggregate->nodeTypeName; @@ -71,20 +67,18 @@ private function handleSetNodeProperties( $command->propertyValues->getPropertiesToUnset() ); - return $this->handleSetSerializedNodeProperties($lowLevelCommand, $contentRepository); + return $this->handleSetSerializedNodeProperties($lowLevelCommand); } private function handleSetSerializedNodeProperties( - SetSerializedNodeProperties $command, - ContentRepository $contentRepository + SetSerializedNodeProperties $command ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $contentStreamId = $this->requireContentStream($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId); // Check if node exists $nodeAggregate = $this->requireProjectedNodeAggregate( $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $command->nodeAggregateId ); $nodeType = $this->requireNodeType($nodeAggregate->nodeTypeName); $this->requireNodeAggregateToOccupyDimensionSpacePoint($nodeAggregate, $command->originDimensionSpacePoint); diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php index 9290dfdb872..78504e30814 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php @@ -32,6 +32,7 @@ use Neos\ContentRepository\Core\Feature\NodeMove\Dto\RelationDistributionStrategy; use Neos\ContentRepository\Core\Feature\NodeMove\Dto\SucceedingSiblingNodeMoveDestination; use Neos\ContentRepository\Core\Feature\NodeMove\Event\NodeAggregateWasMoved; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphIdentity; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindPrecedingSiblingNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindSucceedingSiblingNodesFilter; @@ -46,6 +47,7 @@ use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregatesTypeIsAmbiguous; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** * @internal implementation detail of Command Handlers @@ -58,8 +60,7 @@ abstract protected function areAncestorNodeTypeConstraintChecksEnabled(): bool; abstract protected function requireProjectedNodeAggregate( ContentStreamId $contentStreamId, - NodeAggregateId $nodeAggregateId, - ContentRepository $contentRepository + NodeAggregateId $nodeAggregateId ): NodeAggregate; /** @@ -71,16 +72,14 @@ abstract protected function requireProjectedNodeAggregate( * @throws NodeAggregateIsDescendant */ private function handleMoveNodeAggregate( - MoveNodeAggregate $command, - ContentRepository $contentRepository + MoveNodeAggregate $command ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $contentStreamId = $this->requireContentStream($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId); $this->requireDimensionSpacePointToExist($command->dimensionSpacePoint); $nodeAggregate = $this->requireProjectedNodeAggregate( $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $command->nodeAggregateId ); $this->requireNodeAggregateToNotBeRoot($nodeAggregate); $this->requireNodeAggregateToBeUntethered($nodeAggregate); @@ -97,22 +96,19 @@ private function handleMoveNodeAggregate( $contentStreamId, $this->requireNodeType($nodeAggregate->nodeTypeName), $nodeAggregate->nodeName, - [$command->newParentNodeAggregateId], - $contentRepository + [$command->newParentNodeAggregateId] ); $this->requireNodeNameToBeUncovered( $contentStreamId, $nodeAggregate->nodeName, $command->newParentNodeAggregateId, - $affectedDimensionSpacePoints, - $contentRepository + $affectedDimensionSpacePoints ); $newParentNodeAggregate = $this->requireProjectedNodeAggregate( $contentStreamId, - $command->newParentNodeAggregateId, - $contentRepository + $command->newParentNodeAggregateId ); $this->requireNodeAggregateToCoverDimensionSpacePoints( @@ -123,23 +119,20 @@ private function handleMoveNodeAggregate( $this->requireNodeAggregateToNotBeDescendant( $contentStreamId, $newParentNodeAggregate, - $nodeAggregate, - $contentRepository + $nodeAggregate ); } if ($command->newPrecedingSiblingNodeAggregateId) { $this->requireProjectedNodeAggregate( $contentStreamId, - $command->newPrecedingSiblingNodeAggregateId, - $contentRepository + $command->newPrecedingSiblingNodeAggregateId ); } if ($command->newSucceedingSiblingNodeAggregateId) { $this->requireProjectedNodeAggregate( $contentStreamId, - $command->newSucceedingSiblingNodeAggregateId, - $contentRepository + $command->newSucceedingSiblingNodeAggregateId ); } @@ -155,8 +148,7 @@ private function handleMoveNodeAggregate( $command->newPrecedingSiblingNodeAggregateId, $command->newSucceedingSiblingNodeAggregateId, $movedNodeOrigin, - $affectedDimensionSpacePoints, - $contentRepository + $affectedDimensionSpacePoints ) ); } @@ -195,18 +187,18 @@ private function resolveNewParentAssignments( ContentStreamId $contentStreamId, /** The parent node aggregate's id*/ NodeAggregateId $parentId, - DimensionSpace\DimensionSpacePoint $coveredDimensionSpacePoint, - ContentRepository $contentRepository + DimensionSpace\DimensionSpacePoint $coveredDimensionSpacePoint ): CoverageNodeMoveMapping { - $contentSubgraph = $contentRepository->getContentGraph()->getSubgraph( + $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($contentStreamId); + $parentNode = $this->getContentGraphAdapter()->findNodeInSubgraph( $contentStreamId, + $workspace?->workspaceName, $coveredDimensionSpacePoint, - VisibilityConstraints::withoutRestrictions() + $parentId ); - $parentNode = $contentSubgraph->findNodeById($parentId); if ($parentNode === null) { throw new \InvalidArgumentException( - 'Parent ' . $parentId->value . ' not found in subgraph ' . json_encode($contentSubgraph), + sprintf('Parent ' . $parentId->value . ' not found in ontentstream "%s" and dimension space point "%s" ', $contentStreamId->value, json_encode($coveredDimensionSpacePoint)), 1667596931 ); } @@ -236,25 +228,31 @@ private function resolveAffectedDimensionSpacePointSet( }; } - private function findSibling( - ContentSubgraphInterface $contentSubgraph, - ?NodeAggregateId $parentId, - NodeAggregateId $siblingId - ): ?Node { - $siblingCandidate = $contentSubgraph->findNodeById($siblingId); - if ($parentId && $siblingCandidate) { - // If a parent node aggregate is explicitly given, all siblings must have this parent - $parent = $contentSubgraph->findParentNode($siblingId); - if (is_null($parent)) { - throw new \InvalidArgumentException( - 'Parent ' . $parentId->value . ' not found in subgraph ' . json_encode($contentSubgraph), - 1645366837 - ); - } - if ($parent->nodeAggregateId->equals($parentId)) { - return $siblingCandidate; - } - } else { + private function findSiblingWithin( + ContentStreamId $contentStreamId, + WorkspaceName $workspaceName, + DimensionSpace\DimensionSpacePoint $coveredDimensionSpacePoint, + NodeAggregateId $siblingId, + ?NodeAggregateId $parentId + ): ?Node + { + $siblingCandidate = $this->getContentGraphAdapter()->findNodeInSubgraph($contentStreamId, $workspaceName, $coveredDimensionSpacePoint, $siblingId); + if (!$siblingCandidate) { + return null; + } + + if (!$parentId) { + return $siblingCandidate; + } + + $parent = $this->getContentGraphAdapter()->findParentNodeInSubgraph($contentStreamId, $workspaceName, $coveredDimensionSpacePoint, $siblingId); + if (is_null($parent)) { + throw new \InvalidArgumentException( + 'Parent ' . $parentId->value . ' not found in subgraph ' . json_encode($contentSubgraph), + 1645366837 + ); + } + if ($parent->nodeAggregateId->equals($parentId)) { return $siblingCandidate; } @@ -266,21 +264,20 @@ private function resolveSucceedingSiblingFromOriginSiblings( ?NodeAggregateId $parentId, ?NodeAggregateId $precedingSiblingId, ?NodeAggregateId $succeedingSiblingId, - ContentSubgraphInterface $currentContentSubgraph, - ContentSubgraphInterface $originContentSubgraph + ContentStreamId $contentStreamId, + WorkspaceName $workspaceName, + DimensionSpace\DimensionSpacePoint $currentDimensionSpacePoint, + DimensionSpace\DimensionSpacePoint $originDimensionSpacePoint ): ?Node { $succeedingSibling = null; $precedingSiblingCandidates = iterator_to_array( $precedingSiblingId - ? $originContentSubgraph->findPrecedingSiblingNodes($precedingSiblingId, FindPrecedingSiblingNodesFilter::create()) + ? $this->getContentGraphAdapter()->findPreceedingSiblingNodesInSubgraph($contentStreamId, $workspaceName, $originDimensionSpacePoint, $precedingSiblingId) : Nodes::createEmpty() ); $succeedingSiblingCandidates = iterator_to_array( $succeedingSiblingId - ? $originContentSubgraph->findSucceedingSiblingNodes( - $succeedingSiblingId, - FindSucceedingSiblingNodesFilter::create() - ) + ? $this->getContentGraphAdapter()->findSuceedingSiblingNodesInSubgraph($contentStreamId, $workspaceName, $originDimensionSpacePoint, $succeedingSiblingId) : Nodes::createEmpty() ); /* @var $precedingSiblingCandidates Node[] */ @@ -292,10 +289,12 @@ private function resolveSucceedingSiblingFromOriginSiblings( if ($succeedingSiblingCandidates[$i]->nodeAggregateId->equals($nodeAggregateId)) { \array_splice($succeedingSiblingCandidates, $i, 1); } - $succeedingSibling = $this->findSibling( - $currentContentSubgraph, - $parentId, - $succeedingSiblingCandidates[$i]->nodeAggregateId + $succeedingSibling = $this->findSiblingWithin( + $contentStreamId, + $workspaceName, + $currentDimensionSpacePoint, + $succeedingSiblingCandidates[$i]->nodeAggregateId, + $parentId ); if ($succeedingSibling) { break; @@ -306,16 +305,16 @@ private function resolveSucceedingSiblingFromOriginSiblings( if ($precedingSiblingCandidates[$i]->nodeAggregateId->equals($nodeAggregateId)) { \array_splice($precedingSiblingCandidates, $i, 1); } - $precedingSibling = $this->findSibling( - $currentContentSubgraph, - $parentId, - $precedingSiblingCandidates[$i]->nodeAggregateId + $precedingSibling = $this->findSiblingWithin( + $contentStreamId, + $workspaceName, + $currentDimensionSpacePoint, + $precedingSiblingCandidates[$i]->nodeAggregateId, + $parentId ); if ($precedingSibling) { - $alternateSucceedingSiblings = $currentContentSubgraph->findSucceedingSiblingNodes( - $precedingSiblingId, - FindSucceedingSiblingNodesFilter::create(pagination: Pagination::fromLimitAndOffset(1, 0)), - ); + // TODO: I don't think implementing the same filtering as for the contentGraph is sensible here, so we are fetching all siblings while only interested in the next, maybe could become a more specialised method. + $alternateSucceedingSiblings = $this->getContentGraphAdapter()->findSuceedingSiblingNodesInSubgraph($contentStreamId, $workspaceName, $currentDimensionSpacePoint, $precedingSiblingId); if (count($alternateSucceedingSiblings) > 0) { $succeedingSibling = $alternateSucceedingSiblings->first(); break; @@ -341,39 +340,29 @@ private function resolveCoverageNodeMoveMappings( /** A dimension space point occupied by the node aggregate to be moved */ OriginDimensionSpacePoint $originDimensionSpacePoint, /** The dimension space points affected by the move operation */ - DimensionSpacePointSet $affectedDimensionSpacePoints, - ContentRepository $contentRepository + DimensionSpacePointSet $affectedDimensionSpacePoints ): CoverageNodeMoveMappings { /** @var CoverageNodeMoveMapping[] $coverageNodeMoveMappings */ $coverageNodeMoveMappings = []; + $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($contentStreamId); - $visibilityConstraints = VisibilityConstraints::withoutRestrictions(); - $originContentSubgraph = $contentRepository->getContentGraph()->getSubgraph( - $contentStreamId, - $originDimensionSpacePoint->toDimensionSpacePoint(), - $visibilityConstraints - ); foreach ( $nodeAggregate->getCoverageByOccupant($originDimensionSpacePoint) ->getIntersection($affectedDimensionSpacePoints) as $dimensionSpacePoint ) { - $contentSubgraph = $contentRepository->getContentGraph()->getSubgraph( - $contentStreamId, - $dimensionSpacePoint, - $visibilityConstraints - ); - $succeedingSibling = $succeedingSiblingId - ? $this->findSibling($contentSubgraph, $parentId, $succeedingSiblingId) + ? $this->findSiblingWithin($contentStreamId, $workspace?->workspaceName, $originDimensionSpacePoint->toDimensionSpacePoint(), $succeedingSiblingId, $parentId) : null; if (!$succeedingSibling) { $precedingSibling = $precedingSiblingId - ? $this->findSibling($contentSubgraph, $parentId, $precedingSiblingId) + ? $this->findSiblingWithin($contentStreamId, $workspace?->workspaceName, $originDimensionSpacePoint->toDimensionSpacePoint(), $precedingSiblingId, $parentId) : null; if ($precedingSiblingId && $precedingSibling) { - $alternateSucceedingSiblings = $contentSubgraph->findSucceedingSiblingNodes( - $precedingSiblingId, - FindSucceedingSiblingNodesFilter::create(pagination: Pagination::fromLimitAndOffset(1, 0)), + $alternateSucceedingSiblings = $this->getContentGraphAdapter()->findSuceedingSiblingNodesInSubgraph( + $contentStreamId, + $workspace?->workspaceName, + $dimensionSpacePoint, + $precedingSiblingId ); if (count($alternateSucceedingSiblings) > 0) { $succeedingSibling = $alternateSucceedingSiblings->first(); @@ -384,19 +373,20 @@ private function resolveCoverageNodeMoveMappings( $parentId, $precedingSiblingId, $succeedingSiblingId, - $contentSubgraph, - $originContentSubgraph + $contentStreamId, + $workspace?->workspaceName, + $dimensionSpacePoint, + $originDimensionSpacePoint->toDimensionSpacePoint() ); } } if ($succeedingSibling) { // for the event payload, we additionally need the parent of the succeeding sibling - $parentOfSucceedingSibling = $contentSubgraph->findParentNode($succeedingSibling->nodeAggregateId); + $parentOfSucceedingSibling = $this->getContentGraphAdapter()->findParentNodeInSubgraph($contentStreamId, $workspace?->workspaceName, $dimensionSpacePoint, $succeedingSibling->nodeAggregateId); if ($parentOfSucceedingSibling === null) { throw new \InvalidArgumentException( - 'Parent of succeeding sibling ' . $succeedingSibling->nodeAggregateId->value - . ' not found in subgraph ' . json_encode($contentSubgraph), + sprintf('Parent of succeeding sibling ' . $succeedingSibling->nodeAggregateId->value . ' not found in contentstream "%s" and dimension space point "%s" ', $contentStreamId->value, json_encode($dimensionSpacePoint)), 1667817639 ); } @@ -417,10 +407,10 @@ private function resolveCoverageNodeMoveMappings( if ($parentId === null) { // if parent ID is not given, use the parent of the original node, because we want to move // to the end of the sibling list. - $parentId = $contentSubgraph->findParentNode($nodeAggregate->nodeAggregateId)?->nodeAggregateId; + $parentId = $this->getContentGraphAdapter()->findParentNodeInSubgraph($contentStreamId, $workspace?->workspaceName, $dimensionSpacePoint, $nodeAggregate->nodeAggregateId)?->nodeAggregateId; if ($parentId === null) { throw new \InvalidArgumentException( - 'Parent ' . $parentId . ' not found in subgraph ' . json_encode($contentSubgraph), + sprintf('Parent ' . $parentId . ' not found in contentstream "%s" and dimension space point "%s" ', $contentStreamId->value, json_encode($dimensionSpacePoint)), 1667597013 ); } @@ -428,8 +418,7 @@ private function resolveCoverageNodeMoveMappings( $coverageNodeMoveMappings[] = $this->resolveNewParentAssignments( $contentStreamId, $parentId, - $dimensionSpacePoint, - $contentRepository + $dimensionSpacePoint ); } } diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/NodeReferencing.php b/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/NodeReferencing.php index 7e82df8ef55..ae91a4b24a7 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/NodeReferencing.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/NodeReferencing.php @@ -14,7 +14,6 @@ namespace Neos\ContentRepository\Core\Feature\NodeReferencing; -use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\EventStore\Events; use Neos\ContentRepository\Core\EventStore\EventsToPublish; use Neos\ContentRepository\Core\Feature\Common\ConstraintChecks; @@ -39,21 +38,18 @@ trait NodeReferencing abstract protected function requireProjectedNodeAggregate( ContentStreamId $contentStreamId, - NodeAggregateId $nodeAggregateId, - ContentRepository $contentRepository + NodeAggregateId $nodeAggregateId ): NodeAggregate; private function handleSetNodeReferences( - SetNodeReferences $command, - ContentRepository $contentRepository + SetNodeReferences $command ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); + $contentStreamId = $this->requireContentStream($command->workspaceName); $this->requireDimensionSpacePointToExist($command->sourceOriginDimensionSpacePoint->toDimensionSpacePoint()); $sourceNodeAggregate = $this->requireProjectedNodeAggregate( $contentStreamId, - $command->sourceNodeAggregateId, - $contentRepository + $command->sourceNodeAggregateId ); $this->requireNodeAggregateToNotBeRoot($sourceNodeAggregate); $nodeTypeName = $sourceNodeAggregate->nodeTypeName; @@ -88,25 +84,23 @@ private function handleSetNodeReferences( )), ); - return $this->handleSetSerializedNodeReferences($lowLevelCommand, $contentRepository); + return $this->handleSetSerializedNodeReferences($lowLevelCommand); } /** * @throws \Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet */ private function handleSetSerializedNodeReferences( - SetSerializedNodeReferences $command, - ContentRepository $contentRepository + SetSerializedNodeReferences $command ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $contentStreamId = $this->requireContentStream($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId); $this->requireDimensionSpacePointToExist( $command->sourceOriginDimensionSpacePoint->toDimensionSpacePoint() ); $sourceNodeAggregate = $this->requireProjectedNodeAggregate( $contentStreamId, - $command->sourceNodeAggregateId, - $contentRepository + $command->sourceNodeAggregateId ); $this->requireNodeAggregateToNotBeRoot($sourceNodeAggregate); $this->requireNodeAggregateToOccupyDimensionSpacePoint( @@ -119,8 +113,7 @@ private function handleSetSerializedNodeReferences( assert($reference instanceof SerializedNodeReference); $destinationNodeAggregate = $this->requireProjectedNodeAggregate( $contentStreamId, - $reference->targetNodeAggregateId, - $contentRepository + $reference->targetNodeAggregateId ); $this->requireNodeAggregateToNotBeRoot($destinationNodeAggregate); $this->requireNodeAggregateToCoverDimensionSpacePoint( diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeRemoval/NodeRemoval.php b/Neos.ContentRepository.Core/Classes/Feature/NodeRemoval/NodeRemoval.php index 446f7b58e45..dd3ce6db474 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeRemoval/NodeRemoval.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeRemoval/NodeRemoval.php @@ -46,15 +46,13 @@ abstract protected function areAncestorNodeTypeConstraintChecksEnabled(): bool; * @throws DimensionSpacePointNotFound */ private function handleRemoveNodeAggregate( - RemoveNodeAggregate $command, - ContentRepository $contentRepository + RemoveNodeAggregate $command ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $contentStreamId = $this->requireContentStream($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId); $nodeAggregate = $this->requireProjectedNodeAggregate( $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $command->nodeAggregateId ); $this->requireDimensionSpacePointToExist($command->coveredDimensionSpacePoint); $this->requireNodeAggregateNotToBeTethered($nodeAggregate); @@ -65,8 +63,7 @@ private function handleRemoveNodeAggregate( if ($command->removalAttachmentPoint instanceof NodeAggregateId) { $this->requireProjectedNodeAggregate( $contentStreamId, - $command->removalAttachmentPoint, - $contentRepository + $command->removalAttachmentPoint ); } diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php b/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php index 40403f523ba..7f3fb7f3797 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php @@ -30,26 +30,24 @@ trait NodeRenaming { use ConstraintChecks; - private function handleChangeNodeAggregateName(ChangeNodeAggregateName $command, ContentRepository $contentRepository): EventsToPublish + private function handleChangeNodeAggregateName(ChangeNodeAggregateName $command): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $contentStreamId = $this->requireContentStream($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId); $nodeAggregate = $this->requireProjectedNodeAggregate( $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $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 ($this->getContentGraphAdapter()->findParentNodeAggregates($contentStreamId, $command->workspaceName, $command->nodeAggregateId) as $parentNodeAggregate) { foreach ($parentNodeAggregate->occupiedDimensionSpacePoints as $occupiedParentDimensionSpacePoint) { $this->requireNodeNameToBeUnoccupied( $contentStreamId, $command->newNodeName, $parentNodeAggregate->nodeAggregateId, $occupiedParentDimensionSpacePoint, - $parentNodeAggregate->coveredDimensionSpacePoints, - $contentRepository + $parentNodeAggregate->coveredDimensionSpacePoints ); } } diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php b/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php index 25e60f27449..a38d780b880 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php @@ -52,16 +52,14 @@ abstract protected function getNodeTypeManager(): NodeTypeManager; abstract protected function requireProjectedNodeAggregate( ContentStreamId $contentStreamId, - NodeAggregateId $nodeAggregateId, - ContentRepository $contentRepository + NodeAggregateId $nodeAggregateId ): NodeAggregate; abstract protected function requireConstraintsImposedByAncestorsAreMet( ContentStreamId $contentStreamId, NodeType $nodeType, ?NodeName $nodeName, - array $parentNodeAggregateIds, - ContentRepository $contentRepository + array $parentNodeAggregateIds ): void; abstract protected function requireNodeTypeConstraintsImposedByParentToBeMet( @@ -93,8 +91,7 @@ abstract protected function createEventsForMissingTetheredNode( OriginDimensionSpacePoint $originDimensionSpacePoint, NodeName $tetheredNodeName, NodeAggregateId $tetheredNodeAggregateId, - NodeType $expectedTetheredNodeType, - ContentRepository $contentRepository + NodeType $expectedTetheredNodeType ): Events; /** @@ -104,20 +101,18 @@ abstract protected function createEventsForMissingTetheredNode( * @throws NodeAggregatesTypeIsAmbiguous */ private function handleChangeNodeAggregateType( - ChangeNodeAggregateType $command, - ContentRepository $contentRepository + ChangeNodeAggregateType $command ): EventsToPublish { /************** * Constraint checks **************/ // existence of content stream, node type and node aggregate - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $contentStreamId = $this->requireContentStream($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId); $newNodeType = $this->requireNodeType($command->newNodeTypeName); $nodeAggregate = $this->requireProjectedNodeAggregate( $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $command->nodeAggregateId ); // node type detail checks @@ -125,9 +120,11 @@ private function handleChangeNodeAggregateType( $this->requireTetheredDescendantNodeTypesToExist($newNodeType); $this->requireTetheredDescendantNodeTypesToNotBeOfTypeRoot($newNodeType); + $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($nodeAggregate->contentStreamId); // the new node type must be allowed at this position in the tree - $parentNodeAggregates = $contentRepository->getContentGraph()->findParentNodeAggregates( + $parentNodeAggregates = $this->getContentGraphAdapter()->findParentNodeAggregates( $nodeAggregate->contentStreamId, + $workspace?->workspaceName, $nodeAggregate->nodeAggregateId ); foreach ($parentNodeAggregates as $parentNodeAggregate) { @@ -136,15 +133,14 @@ private function handleChangeNodeAggregateType( $contentStreamId, $newNodeType, $nodeAggregate->nodeName, - [$parentNodeAggregate->nodeAggregateId], - $contentRepository + [$parentNodeAggregate->nodeAggregateId] ); } /** @codingStandardsIgnoreStart */ match ($command->strategy) { NodeAggregateTypeChangeChildConstraintConflictResolutionStrategy::STRATEGY_HAPPY_PATH - => $this->requireConstraintsImposedByHappyPathStrategyAreMet($nodeAggregate, $newNodeType, $contentRepository), + => $this->requireConstraintsImposedByHappyPathStrategyAreMet($nodeAggregate, $newNodeType), NodeAggregateTypeChangeChildConstraintConflictResolutionStrategy::STRATEGY_DELETE => null }; /** @codingStandardsIgnoreStop */ @@ -175,13 +171,11 @@ private function handleChangeNodeAggregateType( if ($command->strategy === NodeAggregateTypeChangeChildConstraintConflictResolutionStrategy::STRATEGY_DELETE) { array_push($events, ...iterator_to_array($this->deleteDisallowedNodesWhenChangingNodeType( $nodeAggregate, - $newNodeType, - $contentRepository + $newNodeType ))); array_push($events, ...iterator_to_array($this->deleteObsoleteTetheredNodesWhenChangingNodeType( $nodeAggregate, - $newNodeType, - $contentRepository + $newNodeType ))); } @@ -189,18 +183,19 @@ private function handleChangeNodeAggregateType( $expectedTetheredNodes = $this->getNodeTypeManager()->getTetheredNodesConfigurationForNodeType($newNodeType); foreach ($nodeAggregate->getNodes() as $node) { assert($node instanceof Node); + $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($node->subgraphIdentity->contentStreamId); + foreach ($expectedTetheredNodes as $serializedTetheredNodeName => $expectedTetheredNodeType) { $tetheredNodeName = NodeName::fromString($serializedTetheredNodeName); - $subgraph = $contentRepository->getContentGraph()->getSubgraph( + $tetheredNode = $this->getContentGraphAdapter()->findChildNodeByNameInSubgraph( $node->subgraphIdentity->contentStreamId, + $workspace?->workspaceName, $node->originDimensionSpacePoint->toDimensionSpacePoint(), - VisibilityConstraints::withoutRestrictions() - ); - $tetheredNode = $subgraph->findNodeByPath( - $tetheredNodeName, - $node->nodeAggregateId + $node->nodeAggregateId, + $tetheredNodeName ); + if ($tetheredNode === null) { $tetheredNodeAggregateId = $command->tetheredDescendantNodeAggregateIds ->getNodeAggregateId(NodePath::fromString($tetheredNodeName->value)) @@ -210,8 +205,7 @@ private function handleChangeNodeAggregateType( $node->originDimensionSpacePoint, $tetheredNodeName, $tetheredNodeAggregateId, - $expectedTetheredNodeType, - $contentRepository + $expectedTetheredNodeType ))); } } @@ -235,13 +229,16 @@ private function handleChangeNodeAggregateType( */ private function requireConstraintsImposedByHappyPathStrategyAreMet( NodeAggregate $nodeAggregate, - NodeType $newNodeType, - ContentRepository $contentRepository + NodeType $newNodeType ): void { + // TODO: Use workspaceName from $nodeAggregate + $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($nodeAggregate->contentStreamId); + // 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( + $childNodeAggregates = $this->getContentGraphAdapter()->findChildNodeAggregates( $nodeAggregate->contentStreamId, + $workspace?->workspaceName, $nodeAggregate->nodeAggregateId ); foreach ($childNodeAggregates as $childNodeAggregate) { @@ -257,10 +254,13 @@ 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. + // TODO: use workspaceName from $childNodeAggregate instead + $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($childNodeAggregate->contentStreamId); // 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( + $grandchildNodeAggregates = $this->getContentGraphAdapter()->findChildNodeAggregates( $childNodeAggregate->contentStreamId, + $workspace?->workspaceName, $childNodeAggregate->nodeAggregateId ); foreach ($grandchildNodeAggregates as $grandchildNodeAggregate) { @@ -283,14 +283,18 @@ private function requireConstraintsImposedByHappyPathStrategyAreMet( */ private function deleteDisallowedNodesWhenChangingNodeType( NodeAggregate $nodeAggregate, - NodeType $newNodeType, - ContentRepository $contentRepository + NodeType $newNodeType ): Events { $events = []; + + // TODO: use workspaceName from $childNodeAggregate instead + $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($nodeAggregate->contentStreamId); + // 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( + $childNodeAggregates = $this->getContentGraphAdapter()->findChildNodeAggregates( $nodeAggregate->contentStreamId, + $workspace?->workspaceName, $nodeAggregate->nodeAggregateId ); foreach ($childNodeAggregates as $childNodeAggregate) { @@ -309,8 +313,7 @@ private function deleteDisallowedNodesWhenChangingNodeType( // We now need to find out which edges we need to remove, $dimensionSpacePointsToBeRemoved = $this->findDimensionSpacePointsConnectingParentAndChildAggregate( $nodeAggregate, - $childNodeAggregate, - $contentRepository + $childNodeAggregate ); // AND REMOVE THEM $events[] = $this->removeNodeInDimensionSpacePointSet( @@ -322,10 +325,14 @@ 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. + // TODO: use workspaceName from $childNodeAggregate instead + $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($childNodeAggregate->contentStreamId); + // 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( + $grandchildNodeAggregates = $this->getContentGraphAdapter()->findChildNodeAggregates( $childNodeAggregate->contentStreamId, + $workspace?->workspaceName, $childNodeAggregate->nodeAggregateId ); foreach ($grandchildNodeAggregates as $grandchildNodeAggregate) { @@ -345,8 +352,7 @@ private function deleteDisallowedNodesWhenChangingNodeType( // We now need to find out which edges we need to remove, $dimensionSpacePointsToBeRemoved = $this->findDimensionSpacePointsConnectingParentAndChildAggregate( $childNodeAggregate, - $grandchildNodeAggregate, - $contentRepository + $grandchildNodeAggregate ); // AND REMOVE THEM $events[] = $this->removeNodeInDimensionSpacePointSet( @@ -362,15 +368,16 @@ private function deleteDisallowedNodesWhenChangingNodeType( private function deleteObsoleteTetheredNodesWhenChangingNodeType( NodeAggregate $nodeAggregate, - NodeType $newNodeType, - ContentRepository $contentRepository + NodeType $newNodeType ): Events { $expectedTetheredNodes = $this->getNodeTypeManager()->getTetheredNodesConfigurationForNodeType($newNodeType); $events = []; // find disallowed tethered nodes - $tetheredNodeAggregates = $contentRepository->getContentGraph()->findTetheredChildNodeAggregates( + $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($nodeAggregate->contentStreamId); + $tetheredNodeAggregates = $this->getContentGraphAdapter()->findTetheredChildNodeAggregates( $nodeAggregate->contentStreamId, + $workspace?->workspaceName, $nodeAggregate->nodeAggregateId ); @@ -381,8 +388,7 @@ private function deleteObsoleteTetheredNodesWhenChangingNodeType( // We now need to find out which edges we need to remove, $dimensionSpacePointsToBeRemoved = $this->findDimensionSpacePointsConnectingParentAndChildAggregate( $nodeAggregate, - $tetheredNodeAggregate, - $contentRepository + $tetheredNodeAggregate ); // AND REMOVE THEM $events[] = $this->removeNodeInDimensionSpacePointSet( @@ -420,17 +426,18 @@ private function deleteObsoleteTetheredNodesWhenChangingNodeType( */ private function findDimensionSpacePointsConnectingParentAndChildAggregate( NodeAggregate $parentNodeAggregate, - NodeAggregate $childNodeAggregate, - ContentRepository $contentRepository + NodeAggregate $childNodeAggregate ): DimensionSpacePointSet { $points = []; foreach ($childNodeAggregate->coveredDimensionSpacePoints as $coveredDimensionSpacePoint) { - $subgraph = $contentRepository->getContentGraph()->getSubgraph( + // TODO: Use child node workspaceName here instead + $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($childNodeAggregate->contentStreamId); + $parentNode = $this->getContentGraphAdapter()->findParentNodeInSubgraph( $childNodeAggregate->contentStreamId, + $workspace?->workspaceName, $coveredDimensionSpacePoint, - VisibilityConstraints::withoutRestrictions() + $childNodeAggregate->nodeAggregateId ); - $parentNode = $subgraph->findParentNode($childNodeAggregate->nodeAggregateId); if ( $parentNode && $parentNode->nodeAggregateId->equals($parentNodeAggregate->nodeAggregateId) diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeVariation/NodeVariation.php b/Neos.ContentRepository.Core/Classes/Feature/NodeVariation/NodeVariation.php index 7df4a3acb5b..6c2aca49977 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeVariation/NodeVariation.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeVariation/NodeVariation.php @@ -47,15 +47,13 @@ trait NodeVariation * @throws NodeAggregateDoesCurrentlyNotCoverDimensionSpacePoint */ private function handleCreateNodeVariant( - CreateNodeVariant $command, - ContentRepository $contentRepository + CreateNodeVariant $command ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $contentStreamId = $this->requireContentStream($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId); $nodeAggregate = $this->requireProjectedNodeAggregate( $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $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 :/ @@ -68,8 +66,7 @@ private function handleCreateNodeVariant( $parentNodeAggregate = $this->requireProjectedParentNodeAggregate( $contentStreamId, $command->nodeAggregateId, - $command->sourceOrigin, - $contentRepository + $command->sourceOrigin ); $this->requireNodeAggregateToCoverDimensionSpacePoint( $parentNodeAggregate, @@ -80,8 +77,7 @@ private function handleCreateNodeVariant( $contentStreamId, $command->sourceOrigin, $command->targetOrigin, - $nodeAggregate, - $contentRepository + $nodeAggregate ); return new EventsToPublish( diff --git a/Neos.ContentRepository.Core/Classes/Feature/RootNodeCreation/RootNodeHandling.php b/Neos.ContentRepository.Core/Classes/Feature/RootNodeCreation/RootNodeHandling.php index 0fb102fe6ef..601484ed066 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/RootNodeCreation/RootNodeHandling.php +++ b/Neos.ContentRepository.Core/Classes/Feature/RootNodeCreation/RootNodeHandling.php @@ -67,23 +67,20 @@ abstract protected function requireNodeTypeToBeOfTypeRoot(NodeType $nodeType): v * @throws NodeTypeIsNotOfTypeRoot */ private function handleCreateRootNodeAggregateWithNode( - CreateRootNodeAggregateWithNode $command, - ContentRepository $contentRepository + CreateRootNodeAggregateWithNode $command ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $contentStreamId = $this->requireContentStream($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId); $this->requireProjectedNodeAggregateToNotExist( $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $command->nodeAggregateId ); $nodeType = $this->requireNodeType($command->nodeTypeName); $this->requireNodeTypeToNotBeAbstract($nodeType); $this->requireNodeTypeToBeOfTypeRoot($nodeType); $this->requireRootNodeTypeToBeUnoccupied( $nodeType->name, - $contentStreamId, - $contentRepository + $contentStreamId ); $descendantNodeAggregateIds = $command->tetheredDescendantNodeAggregateIds->completeForNodeOfType( @@ -110,8 +107,7 @@ private function handleCreateRootNodeAggregateWithNode( $this->getInterDimensionalVariationGraph()->getSpecializationSet($rootGeneralization, true), $command->nodeAggregateId, $command->tetheredDescendantNodeAggregateIds, - null, - $contentRepository + null ))); } @@ -145,15 +141,13 @@ private function createRootWithNode( * @return EventsToPublish */ private function handleUpdateRootNodeAggregateDimensions( - UpdateRootNodeAggregateDimensions $command, - ContentRepository $contentRepository + UpdateRootNodeAggregateDimensions $command ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $contentStreamId = $this->requireContentStream($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId); $nodeAggregate = $this->requireProjectedNodeAggregate( $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $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); @@ -191,8 +185,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 +216,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..11007da966e 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/SubtreeTagging/SubtreeTagging.php +++ b/Neos.ContentRepository.Core/Classes/Feature/SubtreeTagging/SubtreeTagging.php @@ -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): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); + $contentStreamId = $this->requireContentStream($command->workspaceName); $this->requireDimensionSpacePointToExist($command->coveredDimensionSpacePoint); - $nodeAggregate = $this->requireProjectedNodeAggregate($contentStreamId, $command->nodeAggregateId, $contentRepository); + $nodeAggregate = $this->requireProjectedNodeAggregate($contentStreamId, $command->nodeAggregateId); $this->requireNodeAggregateToCoverDimensionSpacePoint( $nodeAggregate, $command->coveredDimensionSpacePoint @@ -78,14 +78,13 @@ private function handleTagSubtree(TagSubtree $command, ContentRepository $conten ); } - public function handleUntagSubtree(UntagSubtree $command, ContentRepository $contentRepository): EventsToPublish + public function handleUntagSubtree(UntagSubtree $command): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); + $contentStreamId = $this->requireContentStream($command->workspaceName); $this->requireDimensionSpacePointToExist($command->coveredDimensionSpacePoint); $nodeAggregate = $this->requireProjectedNodeAggregate( $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $command->nodeAggregateId ); $this->requireNodeAggregateToCoverDimensionSpacePoint( $nodeAggregate, diff --git a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php index 34ce2ade5ff..e922dd04c01 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php @@ -88,6 +88,7 @@ public function __construct( private EventPersister $eventPersister, private EventStoreInterface $eventStore, private EventNormalizer $eventNormalizer, + private ContentGraphAdapterInterface $contentGraphAdapter ) { } @@ -101,7 +102,7 @@ public function handle(CommandInterface $command, ContentRepository $contentRepo /** @phpstan-ignore-next-line */ return match ($command::class) { CreateWorkspace::class => $this->handleCreateWorkspace($command, $contentRepository), - RenameWorkspace::class => $this->handleRenameWorkspace($command, $contentRepository), + RenameWorkspace::class => $this->handleRenameWorkspace($command), CreateRootWorkspace::class => $this->handleCreateRootWorkspace($command, $contentRepository), PublishWorkspace::class => $this->handlePublishWorkspace($command, $contentRepository), RebaseWorkspace::class => $this->handleRebaseWorkspace($command, $contentRepository), @@ -109,7 +110,7 @@ public function handle(CommandInterface $command, ContentRepository $contentRepo DiscardIndividualNodesFromWorkspace::class => $this->handleDiscardIndividualNodesFromWorkspace($command, $contentRepository), DiscardWorkspace::class => $this->handleDiscardWorkspace($command, $contentRepository), DeleteWorkspace::class => $this->handleDeleteWorkspace($command, $contentRepository), - ChangeWorkspaceOwner::class => $this->handleChangeWorkspaceOwner($command, $contentRepository), + ChangeWorkspaceOwner::class => $this->handleChangeWorkspaceOwner($command), ChangeBaseWorkspace::class => $this->handleChangeBaseWorkspace($command, $contentRepository), }; } @@ -124,7 +125,7 @@ private function handleCreateWorkspace( CreateWorkspace $command, ContentRepository $contentRepository, ): EventsToPublish { - $existingWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName($command->workspaceName); + $existingWorkspace = $this->contentGraphAdapter->findWorkspaceByName($command->workspaceName); if ($existingWorkspace !== null) { throw new WorkspaceAlreadyExists(sprintf( 'The workspace %s already exists', @@ -132,7 +133,7 @@ private function handleCreateWorkspace( ), 1505830958921); } - $baseWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName($command->baseWorkspaceName); + $baseWorkspace = $this->contentGraphAdapter->findWorkspaceByName($command->baseWorkspaceName); if ($baseWorkspace === null) { throw new BaseWorkspaceDoesNotExist(sprintf( 'The workspace %s (base workspace of %s) does not exist', @@ -170,9 +171,9 @@ private function handleCreateWorkspace( /** * @throws WorkspaceDoesNotExist */ - private function handleRenameWorkspace(RenameWorkspace $command, ContentRepository $contentRepository): EventsToPublish + private function handleRenameWorkspace(RenameWorkspace $command): EventsToPublish { - $this->requireWorkspace($command->workspaceName, $contentRepository); + $this->requireWorkspace($command->workspaceName); $events = Events::with( new WorkspaceWasRenamed( @@ -199,7 +200,7 @@ private function handleCreateRootWorkspace( CreateRootWorkspace $command, ContentRepository $contentRepository, ): EventsToPublish { - $existingWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName($command->workspaceName); + $existingWorkspace = $this->contentGraphAdapter->findWorkspaceByName($command->workspaceName); if ($existingWorkspace !== null) { throw new WorkspaceAlreadyExists(sprintf( 'The workspace %s already exists', @@ -243,8 +244,8 @@ private function handlePublishWorkspace( PublishWorkspace $command, ContentRepository $contentRepository, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository); - $baseWorkspace = $this->requireBaseWorkspace($workspace, $contentRepository); + $workspace = $this->requireWorkspace($command->workspaceName); + $baseWorkspace = $this->requireBaseWorkspace($workspace); $this->publishContentStream( $workspace->currentContentStreamId, @@ -357,8 +358,8 @@ private function handleRebaseWorkspace( RebaseWorkspace $command, ContentRepository $contentRepository, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository); - $baseWorkspace = $this->requireBaseWorkspace($workspace, $contentRepository); + $workspace = $this->requireWorkspace($command->workspaceName); + $baseWorkspace = $this->requireBaseWorkspace($workspace); // 0) close old content stream $contentRepository->handle( @@ -486,13 +487,13 @@ private function handlePublishIndividualNodesFromWorkspace( PublishIndividualNodesFromWorkspace $command, ContentRepository $contentRepository, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository); + $workspace = $this->requireWorkspace($command->workspaceName); $oldWorkspaceContentStreamId = $workspace->currentContentStreamId; - $oldWorkspaceContentStreamIdState = $contentRepository->getContentStreamFinder()->findStateForContentStream($oldWorkspaceContentStreamId); + $oldWorkspaceContentStreamIdState = $this->contentGraphAdapter->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); // 1) close old content stream $contentRepository->handle( @@ -622,13 +623,13 @@ private function handleDiscardIndividualNodesFromWorkspace( DiscardIndividualNodesFromWorkspace $command, ContentRepository $contentRepository, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository); + $workspace = $this->requireWorkspace($command->workspaceName); $oldWorkspaceContentStreamId = $workspace->currentContentStreamId; - $oldWorkspaceContentStreamIdState = $contentRepository->getContentStreamFinder()->findStateForContentStream($oldWorkspaceContentStreamId); + $oldWorkspaceContentStreamIdState = $this->contentGraphAdapter->findStateForContentStream($oldWorkspaceContentStreamId); 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); // 1) close old content stream $contentRepository->handle( @@ -765,8 +766,8 @@ private function handleDiscardWorkspace( DiscardWorkspace $command, ContentRepository $contentRepository, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository); - $baseWorkspace = $this->requireBaseWorkspace($workspace, $contentRepository); + $workspace = $this->requireWorkspace($command->workspaceName); + $baseWorkspace = $this->requireBaseWorkspace($workspace); $newContentStream = $command->newContentStreamId; $contentRepository->handle( @@ -807,13 +808,13 @@ private function handleChangeBaseWorkspace( ChangeBaseWorkspace $command, ContentRepository $contentRepository, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository); + $workspace = $this->requireWorkspace($command->workspaceName); $this->requireEmptyWorkspace($workspace); - $this->requireBaseWorkspace($workspace, $contentRepository); + $this->requireBaseWorkspace($workspace); - $baseWorkspace = $this->requireWorkspace($command->baseWorkspaceName, $contentRepository); + $baseWorkspace = $this->requireWorkspace($command->baseWorkspaceName); - $this->requireNonCircularRelationBetweenWorkspaces($workspace, $baseWorkspace, $contentRepository); + $this->requireNonCircularRelationBetweenWorkspaces($workspace, $baseWorkspace); $contentRepository->handle( ForkContentStream::create( @@ -845,7 +846,7 @@ private function handleDeleteWorkspace( DeleteWorkspace $command, ContentRepository $contentRepository, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository); + $workspace = $this->requireWorkspace($command->workspaceName); $contentRepository->handle( RemoveContentStream::create( @@ -871,10 +872,9 @@ private function handleDeleteWorkspace( * @throws WorkspaceDoesNotExist */ private function handleChangeWorkspaceOwner( - ChangeWorkspaceOwner $command, - ContentRepository $contentRepository, + ChangeWorkspaceOwner $command ): EventsToPublish { - $this->requireWorkspace($command->workspaceName, $contentRepository); + $this->requireWorkspace($command->workspaceName); $events = Events::with( new WorkspaceOwnerWasChanged( @@ -894,9 +894,9 @@ private function handleChangeWorkspaceOwner( /** * @throws WorkspaceDoesNotExist */ - private function requireWorkspace(WorkspaceName $workspaceName, ContentRepository $contentRepository): Workspace + private function requireWorkspace(WorkspaceName $workspaceName): Workspace { - $workspace = $contentRepository->getWorkspaceFinder()->findOneByName($workspaceName); + $workspace = $this->contentGraphAdapter->findWorkspaceByName($workspaceName); if (is_null($workspace)) { throw WorkspaceDoesNotExist::butWasSupposedTo($workspaceName); } @@ -908,13 +908,13 @@ private function requireWorkspace(WorkspaceName $workspaceName, ContentRepositor * @throws WorkspaceHasNoBaseWorkspaceName * @throws BaseWorkspaceDoesNotExist */ - private function requireBaseWorkspace(Workspace $workspace, ContentRepository $contentRepository): Workspace + private function requireBaseWorkspace(Workspace $workspace): Workspace { if (is_null($workspace->baseWorkspaceName)) { throw WorkspaceHasNoBaseWorkspaceName::butWasSupposedTo($workspace->workspaceName); } - $baseWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName($workspace->baseWorkspaceName); + $baseWorkspace = $this->contentGraphAdapter->findWorkspaceByName($workspace->baseWorkspaceName); if ($baseWorkspace === null) { throw BaseWorkspaceDoesNotExist::butWasSupposedTo($workspace->workspaceName); } @@ -926,7 +926,7 @@ 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): 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)); @@ -937,7 +937,7 @@ private function requireNonCircularRelationBetweenWorkspaces(Workspace $workspac 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->contentGraphAdapter->findWorkspaceByName($nextBaseWorkspace->baseWorkspaceName); } } diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php index 4d8d7e4a4a7..bba5c64930e 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php @@ -94,66 +94,6 @@ public function findNodeAggregateById( */ public function findUsedNodeTypeNames(): iterable; - /** - * @internal only for consumption inside the Command Handler - */ - public function findParentNodeAggregateByChildOriginDimensionSpacePoint( - ContentStreamId $contentStreamId, - NodeAggregateId $childNodeAggregateId, - OriginDimensionSpacePoint $childOriginDimensionSpacePoint - ): ?NodeAggregate; - - /** - * @return iterable - * @internal only for consumption inside the Command Handler - */ - public function findParentNodeAggregates( - ContentStreamId $contentStreamId, - NodeAggregateId $childNodeAggregateId - ): iterable; - - /** - * @return iterable - * @internal only for consumption inside the Command Handler - */ - public function findChildNodeAggregates( - ContentStreamId $contentStreamId, - NodeAggregateId $parentNodeAggregateId - ): iterable; - - /** - * A node aggregate may have multiple child node aggregates with the same name - * as long as they do not share dimension space coverage - * - * @return iterable - * @internal only for consumption inside the Command Handler - */ - public function findChildNodeAggregatesByName( - ContentStreamId $contentStreamId, - NodeAggregateId $parentNodeAggregateId, - NodeName $name - ): iterable; - - /** - * @return iterable - * @internal only for consumption inside the Command Handler - */ - public function findTetheredChildNodeAggregates( - ContentStreamId $contentStreamId, - NodeAggregateId $parentNodeAggregateId - ): iterable; - - /** - * @internal only for consumption inside the Command Handler - */ - public function getDimensionSpacePointsOccupiedByChildNodeName( - ContentStreamId $contentStreamId, - NodeName $nodeName, - NodeAggregateId $parentNodeAggregateId, - OriginDimensionSpacePoint $parentNodeOriginDimensionSpacePoint, - DimensionSpacePointSet $dimensionSpacePointsToCheck - ): DimensionSpacePointSet; - /** * @internal only for consumption in testcases */ diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentStream/ContentStreamFinder.php b/Neos.ContentRepository.Core/Classes/Projection/ContentStream/ContentStreamFinder.php index 6bcc6a9b2b0..1933d357817 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentStream/ContentStreamFinder.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentStream/ContentStreamFinder.php @@ -158,42 +158,4 @@ public function findUnusedAndRemovedContentStreams(): iterable )->fetchFirstColumn(); return array_map(ContentStreamId::fromString(...), $contentStreamIds); } - - public function findVersionForContentStream(ContentStreamId $contentStreamId): MaybeVersion - { - $connection = $this->client->getConnection(); - /* @var $state string|false */ - $version = $connection->executeQuery( - ' - SELECT version FROM ' . $this->tableName . ' - WHERE contentStreamId = :contentStreamId - ', - [ - 'contentStreamId' => $contentStreamId->value, - ] - )->fetchOne(); - - if ($version === false) { - return MaybeVersion::fromVersionOrNull(null); - } - - return MaybeVersion::fromVersionOrNull(Version::fromInteger($version)); - } - - public function hasContentStream(ContentStreamId $contentStreamId): bool - { - $connection = $this->client->getConnection(); - /* @var $state string|false */ - $version = $connection->executeQuery( - ' - SELECT version FROM ' . $this->tableName . ' - WHERE contentStreamId = :contentStreamId - ', - [ - 'contentStreamId' => $contentStreamId->value - ] - )->fetchOne(); - - return $version !== false; - } } diff --git a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php index fcdc1104af6..2879e8ba84b 100644 --- a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php +++ b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php @@ -94,8 +94,7 @@ function () use ($nodeAggregate, $originDimensionSpacePoint, $tetheredNodeName, $originDimensionSpacePoint, $tetheredNodeName, null, - $expectedTetheredNodeType, - $this->contentRepository + $expectedTetheredNodeType ); $streamName = ContentStreamEventStreamName::fromContentStreamId($nodeAggregate->contentStreamId); From 4dd0880c91124f49a4465938b367b15de5191813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Fri, 5 Apr 2024 10:49:59 +0200 Subject: [PATCH 02/18] Make ContentGraphAdapterFactory configurable --- .../Repository => }/ContentGraphAdapter.php | 25 +++++++++++++++++-- .../ContentGraphAdapterFactory.php | 5 ++-- .../Factory/ContentRepositoryFactory.php | 14 +++++------ .../Common/ContentStreamIdOverride.php | 2 -- .../ContentGraphAdapterFactoryInterface.php | 7 +++++- .../Configuration/Settings.yaml | 2 ++ .../Classes/ContentRepositoryRegistry.php | 15 +++++++++++ 7 files changed, 55 insertions(+), 15 deletions(-) rename Neos.ContentGraph.DoctrineDbalAdapter/src/{Domain/Repository => }/ContentGraphAdapter.php (88%) rename Neos.ContentGraph.DoctrineDbalAdapter/src/{Domain/Repository => }/ContentGraphAdapterFactory.php (80%) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraphAdapter.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php similarity index 88% rename from Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraphAdapter.php rename to Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php index 0f3792ca314..0fc837768de 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraphAdapter.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php @@ -1,5 +1,5 @@ tableNamePrefix . '_node'; + } + + private function getTablenameForHierachyRelation(): string + { + return $this->tableNamePrefix . '_hierarchyrelation'; + } + + private function getTablenameForDimensionSpacePoints(): string + { + return $this->tableNamePrefix . '_dimensionspacepoints'; + } + + } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraphAdapterFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php similarity index 80% rename from Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraphAdapterFactory.php rename to Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php index 9c5555b4e5f..5441ec97d37 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraphAdapterFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php @@ -1,8 +1,7 @@ commandBus) { - $contentGraphAdapter = $this->contentGraphAdapterFactory->build($this->projectionFactoryDependencies->contentRepositoryId); $this->commandBus = new CommandBus( new ContentStreamCommandHandler( - $contentGraphAdapter + $this->contentGraphAdapter ), new WorkspaceCommandHandler( $this->buildEventPersister(), $this->projectionFactoryDependencies->eventStore, $this->projectionFactoryDependencies->eventNormalizer, - $contentGraphAdapter + $this->contentGraphAdapter ), new NodeAggregateCommandHandler( $this->projectionFactoryDependencies->nodeTypeManager, $this->projectionFactoryDependencies->contentDimensionZookeeper, $this->projectionFactoryDependencies->interDimensionalVariationGraph, $this->projectionFactoryDependencies->propertyConverter, - $contentGraphAdapter + $this->contentGraphAdapter ), new DimensionSpaceCommandHandler( $this->projectionFactoryDependencies->contentDimensionZookeeper, - $this->projectionFactoryDependencies->interDimensionalVariationGraph + $this->projectionFactoryDependencies->interDimensionalVariationGraph, + $this->contentGraphAdapter ), new NodeDuplicationCommandHandler( $this->projectionFactoryDependencies->nodeTypeManager, diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/ContentStreamIdOverride.php b/Neos.ContentRepository.Core/Classes/Feature/Common/ContentStreamIdOverride.php index c744382c595..2f2c83bb2ce 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/ContentStreamIdOverride.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/ContentStreamIdOverride.php @@ -14,8 +14,6 @@ namespace Neos\ContentRepository\Core\Feature\Common; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ContentGraphAdapter; -use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\Feature\ContentGraphAdapterInterface; use Neos\ContentRepository\Core\Feature\WorkspaceCommandHandler; use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet; diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterFactoryInterface.php b/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterFactoryInterface.php index 3dc5897222f..5eff0181360 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterFactoryInterface.php +++ b/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterFactoryInterface.php @@ -11,5 +11,10 @@ */ interface ContentGraphAdapterFactoryInterface { - public function build(ContentRepositoryId $contentRepositoryId): ContentGraphAdapterInterface; + /** + * @param ContentRepositoryId $contentRepositoryId + * @param array $options + * @return ContentGraphAdapterInterface + */ + public function build(ContentRepositoryId $contentRepositoryId, array $options): ContentGraphAdapterInterface; } diff --git a/Neos.ContentRepositoryRegistry.DoctrineDbalClient/Configuration/Settings.yaml b/Neos.ContentRepositoryRegistry.DoctrineDbalClient/Configuration/Settings.yaml index 2c39ca34a96..e727a6e25ea 100644 --- a/Neos.ContentRepositoryRegistry.DoctrineDbalClient/Configuration/Settings.yaml +++ b/Neos.ContentRepositoryRegistry.DoctrineDbalClient/Configuration/Settings.yaml @@ -7,3 +7,5 @@ Neos: # catchUpHooks for content cache flushing 'Neos.ContentRepository:ContentGraph': factoryObjectName: Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphProjectionFactory + contentGraphAdapter: + factoryObjectName: Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphAdapterFactory diff --git a/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php b/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php index 25daca7cf84..99698986c5e 100644 --- a/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php +++ b/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php @@ -9,6 +9,8 @@ use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; use Neos\ContentRepository\Core\Factory\ProjectionsAndCatchUpHooksFactory; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterFactoryInterface; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterInterface; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\Projection\CatchUpHookFactoryInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface; @@ -158,6 +160,7 @@ private function buildFactory(ContentRepositoryId $contentRepositoryId): Content $this->buildProjectionCatchUpTrigger($contentRepositoryId, $contentRepositorySettings), $this->buildUserIdProvider($contentRepositoryId, $contentRepositorySettings), $clock, + $this->buildContentGraphAdapter($contentRepositoryId, $contentRepositorySettings), ); } catch (\Exception $exception) { throw InvalidConfigurationException::fromException($contentRepositoryId, $exception); @@ -281,4 +284,16 @@ private function buildClock(ContentRepositoryId $contentRepositoryIdentifier, ar } return $clockFactory->build($contentRepositoryIdentifier, $contentRepositorySettings['clock']['options'] ?? []); } + + /** @param array $contentRepositorySettings */ + private function buildContentGraphAdapter(ContentRepositoryId $contentRepositoryIdentifier, array $contentRepositorySettings): ContentGraphAdapterInterface + { + isset($contentRepositorySettings['contentGraphAdapter']['factoryObjectName']) || throw InvalidConfigurationException::fromMessage('Content repository "%s" does not have contentGraphAdapter.factoryObjectName configured.', $contentRepositoryIdentifier->value); + $contentGraphAdapterFactory = $this->objectManager->get($contentRepositorySettings['contentGraphAdapter']['factoryObjectName']); + if (!$contentGraphAdapterFactory instanceof ContentGraphAdapterFactoryInterface) { + throw InvalidConfigurationException::fromMessage('contentGraphAdapter.factoryObjectName for content repository "%s" is not an instance of %s but %s.', $contentRepositoryIdentifier->value, ContentGraphAdapterFactoryInterface::class, get_debug_type($contentGraphAdapterFactory)); + } + + return $contentGraphAdapterFactory->build($contentRepositoryIdentifier, $contentRepositorySettings['contentGraphAdapter']['options'] ?? []); + } } From 606a85bce3277ec217fd974ba3747549c3d8fd40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Fri, 5 Apr 2024 19:39:45 +0200 Subject: [PATCH 03/18] Introduce ContentGraphAdapterProvider --- .../src/ContentGraphAdapter.php | 6 +++- .../src/ContentGraphAdapterFactory.php | 26 ----------------- .../src/ContentGraphAdapterProvider.php | 25 +++++++++++++++++ .../ContentGraphAdapterProviderFactory.php | 26 +++++++++++++++++ .../Factory/ContentRepositoryFactory.php | 12 ++++---- .../ContentGraphAdapterFactoryInterface.php | 20 ------------- .../Feature/ContentGraphAdapterInterface.php | 11 +++----- ...ntGraphAdapterProviderFactoryInterface.php | 18 ++++++++++++ .../ContentGraphAdapterProviderInterface.php | 20 +++++++++++++ .../Feature/NodeAggregateCommandHandler.php | 28 +++++++++++++++++-- .../Configuration/Settings.yaml | 2 +- .../Classes/ContentRepositoryRegistry.php | 11 ++++---- 12 files changed, 136 insertions(+), 69 deletions(-) delete mode 100644 Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php create mode 100644 Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterProvider.php create mode 100644 Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterProviderFactory.php delete mode 100644 Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterFactoryInterface.php create mode 100644 Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterProviderFactoryInterface.php create mode 100644 Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterProviderInterface.php diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php index 0fc837768de..db08c023164 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php @@ -23,9 +23,13 @@ */ class ContentGraphAdapter implements ContentGraphAdapterInterface { + private NodeFactory $nodeFactory; + public function __construct( private readonly Connection $dbalConnection, - private readonly string $tableNamePrefix + private readonly string $tableNamePrefix, + public readonly ContentStreamId $contentStreamId, + public readonly WorkspaceName $workspaceName ) { } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php deleted file mode 100644 index 5441ec97d37..00000000000 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php +++ /dev/null @@ -1,26 +0,0 @@ -dbalConnection, $tableNamePrefix); - } -} diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterProvider.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterProvider.php new file mode 100644 index 00000000000..11145db436d --- /dev/null +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterProvider.php @@ -0,0 +1,25 @@ +dbalConnection, $this->tableNamePrefix, $contentStreamId, $workspaceName); + } +} diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterProviderFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterProviderFactory.php new file mode 100644 index 00000000000..ecb9d2e055e --- /dev/null +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterProviderFactory.php @@ -0,0 +1,26 @@ +dbalConnection, $tableNamePrefix); + } +} diff --git a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php index 8fe7e13de11..8a7d32a3cd5 100644 --- a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php +++ b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php @@ -21,7 +21,7 @@ use Neos\ContentRepository\Core\DimensionSpace\InterDimensionalVariationGraph; use Neos\ContentRepository\Core\EventStore\EventNormalizer; use Neos\ContentRepository\Core\EventStore\EventPersister; -use Neos\ContentRepository\Core\Feature\ContentGraphAdapterInterface; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterProviderInterface; use Neos\ContentRepository\Core\Feature\ContentStreamCommandHandler; use Neos\ContentRepository\Core\Feature\DimensionSpaceAdjustment\DimensionSpaceCommandHandler; use Neos\ContentRepository\Core\Feature\NodeAggregateCommandHandler; @@ -57,7 +57,7 @@ public function __construct( private readonly ProjectionCatchUpTriggerInterface $projectionCatchUpTrigger, private readonly UserIdProviderInterface $userIdProvider, private readonly ClockInterface $clock, - private readonly ContentGraphAdapterInterface $contentGraphAdapter, + private readonly ContentGraphAdapterProviderInterface $contentGraphAdapterProvider, ) { $contentDimensionZookeeper = new ContentDimensionZookeeper($contentDimensionSource); $interDimensionalVariationGraph = new InterDimensionalVariationGraph( @@ -136,25 +136,25 @@ private function buildCommandBus(): CommandBus if (!$this->commandBus) { $this->commandBus = new CommandBus( new ContentStreamCommandHandler( - $this->contentGraphAdapter + $this->contentGraphAdapterProvider ), new WorkspaceCommandHandler( $this->buildEventPersister(), $this->projectionFactoryDependencies->eventStore, $this->projectionFactoryDependencies->eventNormalizer, - $this->contentGraphAdapter + $this->contentGraphAdapterProvider ), new NodeAggregateCommandHandler( $this->projectionFactoryDependencies->nodeTypeManager, $this->projectionFactoryDependencies->contentDimensionZookeeper, $this->projectionFactoryDependencies->interDimensionalVariationGraph, $this->projectionFactoryDependencies->propertyConverter, - $this->contentGraphAdapter + $this->contentGraphAdapterProvider ), new DimensionSpaceCommandHandler( $this->projectionFactoryDependencies->contentDimensionZookeeper, $this->projectionFactoryDependencies->interDimensionalVariationGraph, - $this->contentGraphAdapter + $this->contentGraphAdapterProvider ), new NodeDuplicationCommandHandler( $this->projectionFactoryDependencies->nodeTypeManager, diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterFactoryInterface.php b/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterFactoryInterface.php deleted file mode 100644 index 5eff0181360..00000000000 --- a/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterFactoryInterface.php +++ /dev/null @@ -1,20 +0,0 @@ - $options - * @return ContentGraphAdapterInterface - */ - public function build(ContentRepositoryId $contentRepositoryId, array $options): ContentGraphAdapterInterface; -} diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterInterface.php b/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterInterface.php index 36bd600fadb..7712f66e3a8 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterInterface.php +++ b/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterInterface.php @@ -21,7 +21,6 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; use Neos\ContentRepository\Core\Projection\ContentGraph\Nodes; -use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\Projection\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregatesTypeIsAmbiguous; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; @@ -52,8 +51,6 @@ public function rootNodeAggregateWithTypeExists( * @return iterable */ public function findParentNodeAggregates( - ContentStreamId $contentStreamId, - WorkspaceName $workspaceName, NodeAggregateId $childNodeAggregateId ): iterable; @@ -61,14 +58,10 @@ public function findParentNodeAggregates( * @throws NodeAggregatesTypeIsAmbiguous */ public function findNodeAggregateById( - ContentStreamId $contentStreamId, - WorkspaceName $workspaceName, NodeAggregateId $nodeAggregateId ): ?NodeAggregate; public function findParentNodeAggregateByChildOriginDimensionSpacePoint( - ContentStreamId $contentStreamId, - WorkspaceName $workspaceName, NodeAggregateId $childNodeAggregateId, OriginDimensionSpacePoint $childOriginDimensionSpacePoint ): ?NodeAggregate; @@ -192,4 +185,8 @@ public function findWorkspaceByName( public function findWorkspaceByCurrentContentStreamId( ContentStreamId $contentStreamId ): ?Workspace; + + public function getWorkspaceName(): WorkspaceName; + + public function getContentStreamId(): ContentStreamId; } diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterProviderFactoryInterface.php b/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterProviderFactoryInterface.php new file mode 100644 index 00000000000..9ef8e034ee7 --- /dev/null +++ b/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterProviderFactoryInterface.php @@ -0,0 +1,18 @@ + $options + * @return ContentGraphAdapterProviderInterface + */ + public function build(ContentRepositoryId $contentRepositoryId, array $options): ContentGraphAdapterProviderInterface; +} diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterProviderInterface.php b/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterProviderInterface.php new file mode 100644 index 00000000000..4a3b74dd352 --- /dev/null +++ b/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterProviderInterface.php @@ -0,0 +1,20 @@ +propertyConverter; } - protected function getContentGraphAdapter(): ContentGraphAdapterInterface + /** + * WIP Should not have this signature ;) + * @param WorkspaceName|null $workspaceName + * @param ContentStreamId|null $contentStreamId + * @return ContentGraphAdapterInterface + * + */ + protected function getContentGraphAdapter(?WorkspaceName $workspaceName, ?ContentStreamId $contentStreamId = null): ContentGraphAdapterInterface { - return $this->contentGraphAdapter; + if ($contentStreamId && $workspaceName) { + return $this->contentGraphAdapterProvider->get($workspaceName, $contentStreamId); + } + + if ($workspaceName) { + return $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($workspaceName); + } + + if ($contentStreamId) { + return $this->contentGraphAdapterProvider->resolveWorkspaceNameAndGet($contentStreamId); + } + + throw new RuntimeException('To access the ContentGraphAdpater either a WorkspaceName or ContentStreamId is necessary, neither was provided.', 1712331448); } /** diff --git a/Neos.ContentRepositoryRegistry.DoctrineDbalClient/Configuration/Settings.yaml b/Neos.ContentRepositoryRegistry.DoctrineDbalClient/Configuration/Settings.yaml index e727a6e25ea..7441862dd71 100644 --- a/Neos.ContentRepositoryRegistry.DoctrineDbalClient/Configuration/Settings.yaml +++ b/Neos.ContentRepositoryRegistry.DoctrineDbalClient/Configuration/Settings.yaml @@ -8,4 +8,4 @@ Neos: 'Neos.ContentRepository:ContentGraph': factoryObjectName: Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphProjectionFactory contentGraphAdapter: - factoryObjectName: Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphAdapterFactory + factoryObjectName: Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphAdapterProviderFactory diff --git a/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php b/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php index 99698986c5e..862b9d9b2e6 100644 --- a/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php +++ b/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php @@ -9,8 +9,9 @@ use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; use Neos\ContentRepository\Core\Factory\ProjectionsAndCatchUpHooksFactory; -use Neos\ContentRepository\Core\Feature\ContentGraphAdapterFactoryInterface; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterProviderFactoryInterface; use Neos\ContentRepository\Core\Feature\ContentGraphAdapterInterface; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterProviderInterface; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\Projection\CatchUpHookFactoryInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface; @@ -160,7 +161,7 @@ private function buildFactory(ContentRepositoryId $contentRepositoryId): Content $this->buildProjectionCatchUpTrigger($contentRepositoryId, $contentRepositorySettings), $this->buildUserIdProvider($contentRepositoryId, $contentRepositorySettings), $clock, - $this->buildContentGraphAdapter($contentRepositoryId, $contentRepositorySettings), + $this->buildContentGraphAdapterProvider($contentRepositoryId, $contentRepositorySettings), ); } catch (\Exception $exception) { throw InvalidConfigurationException::fromException($contentRepositoryId, $exception); @@ -286,12 +287,12 @@ private function buildClock(ContentRepositoryId $contentRepositoryIdentifier, ar } /** @param array $contentRepositorySettings */ - private function buildContentGraphAdapter(ContentRepositoryId $contentRepositoryIdentifier, array $contentRepositorySettings): ContentGraphAdapterInterface + private function buildContentGraphAdapterProvider(ContentRepositoryId $contentRepositoryIdentifier, array $contentRepositorySettings): ContentGraphAdapterProviderInterface { isset($contentRepositorySettings['contentGraphAdapter']['factoryObjectName']) || throw InvalidConfigurationException::fromMessage('Content repository "%s" does not have contentGraphAdapter.factoryObjectName configured.', $contentRepositoryIdentifier->value); $contentGraphAdapterFactory = $this->objectManager->get($contentRepositorySettings['contentGraphAdapter']['factoryObjectName']); - if (!$contentGraphAdapterFactory instanceof ContentGraphAdapterFactoryInterface) { - throw InvalidConfigurationException::fromMessage('contentGraphAdapter.factoryObjectName for content repository "%s" is not an instance of %s but %s.', $contentRepositoryIdentifier->value, ContentGraphAdapterFactoryInterface::class, get_debug_type($contentGraphAdapterFactory)); + if (!$contentGraphAdapterFactory instanceof ContentGraphAdapterProviderFactoryInterface) { + throw InvalidConfigurationException::fromMessage('contentGraphAdapter.factoryObjectName for content repository "%s" is not an instance of %s but %s.', $contentRepositoryIdentifier->value, ContentGraphAdapterProviderFactoryInterface::class, get_debug_type($contentGraphAdapterFactory)); } return $contentGraphAdapterFactory->build($contentRepositoryIdentifier, $contentRepositorySettings['contentGraphAdapter']['options'] ?? []); From c7480fe7294276a4bc7432529789a74177b7dd1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Sun, 7 Apr 2024 12:41:17 +0200 Subject: [PATCH 04/18] Introduce ContentGraphAdapterProviderInterface This also replaces the ContentStreamIdOverride --- .../src/ContentGraphAdapterProvider.php | 15 ++- .../Factory/ContentRepositoryFactory.php | 4 +- ...ntRepositoryServiceFactoryDependencies.php | 4 + .../Feature/Common/ConstraintChecks.php | 88 ++++++-------- .../Common/ContentStreamIdOverride.php | 67 ----------- .../Feature/Common/NodeCreationInternals.php | 26 +---- .../Feature/Common/NodeVariationInternals.php | 90 +++++---------- .../Feature/Common/TetheredNodeInternals.php | 17 ++- .../Feature/ContentGraphAdapterInterface.php | 44 ++----- .../ContentGraphAdapterProviderInterface.php | 24 +++- .../Feature/ContentStreamCommandHandler.php | 7 +- .../DimensionSpaceCommandHandler.php | 61 +++------- .../Feature/NodeAggregateCommandHandler.php | 22 +--- .../Feature/NodeCreation/NodeCreation.php | 40 +++---- .../Feature/NodeDisabling/NodeDisabling.php | 21 ++-- .../NodeDuplicationCommandHandler.php | 51 +++++--- .../NodeModification/NodeModification.php | 20 ++-- .../Classes/Feature/NodeMove/NodeMove.php | 109 ++++++++---------- .../NodeReferencing/NodeReferencing.php | 20 ++-- .../Feature/NodeRemoval/NodeRemoval.php | 13 +-- .../Feature/NodeRenaming/NodeRenaming.php | 14 +-- .../Feature/NodeTypeChange/NodeTypeChange.php | 90 +++++---------- .../Feature/NodeVariation/NodeVariation.php | 13 +-- .../RootNodeCreation/RootNodeHandling.php | 27 +++-- .../Feature/WorkspaceCommandHandler.php | 101 ++++++++-------- .../Adjustment/TetheredNodeAdjustments.php | 36 +++--- .../src/StructureAdjustmentService.php | 6 +- .../src/StructureAdjustmentServiceFactory.php | 3 +- .../Classes/ContentRepositoryRegistry.php | 1 - 29 files changed, 427 insertions(+), 607 deletions(-) delete mode 100644 Neos.ContentRepository.Core/Classes/Feature/Common/ContentStreamIdOverride.php diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterProvider.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterProvider.php index 11145db436d..67dd66af498 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterProvider.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterProvider.php @@ -18,8 +18,19 @@ public function __construct( ) { } - public function get(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphAdapterInterface + public function resolveWorkspaceNameAndGet(ContentStreamId $contentStreamId): ContentGraphAdapterInterface { - return new ContentGraphAdapter($this->dbalConnection, $this->tableNamePrefix, $contentStreamId, $workspaceName); + // TODO: Implement resolveWorkspaceNameAndGet() method. } + + public function resolveContentStreamIdAndGet(WorkspaceName $workspaceName): ContentGraphAdapterInterface + { + // TODO: Implement resolveContentStreamIdAndGet() method. + } + + public function overrideContentStreamId(WorkspaceName $workspaceName, ContentStreamId $contentStreamId, \Closure $fn): void + { + // TODO: Implement overrideContentStreamId() method. + } + } diff --git a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php index 8a7d32a3cd5..18b980d38d0 100644 --- a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php +++ b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php @@ -127,6 +127,7 @@ public function buildService( $this->getOrBuild(), $this->buildEventPersister(), $this->projectionsAndCatchUpHooks->projections, + $this->contentGraphAdapterProvider ); return $serviceFactory->build($serviceFactoryDependencies); } @@ -159,7 +160,8 @@ private function buildCommandBus(): CommandBus new NodeDuplicationCommandHandler( $this->projectionFactoryDependencies->nodeTypeManager, $this->projectionFactoryDependencies->contentDimensionZookeeper, - $this->projectionFactoryDependencies->interDimensionalVariationGraph + $this->projectionFactoryDependencies->interDimensionalVariationGraph, + $this->contentGraphAdapterProvider ) ); } diff --git a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryServiceFactoryDependencies.php b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryServiceFactoryDependencies.php index ca9df198cee..2b0d2824804 100644 --- a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryServiceFactoryDependencies.php +++ b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryServiceFactoryDependencies.php @@ -20,6 +20,7 @@ use Neos\ContentRepository\Core\DimensionSpace\InterDimensionalVariationGraph; use Neos\ContentRepository\Core\EventStore\EventNormalizer; use Neos\ContentRepository\Core\EventStore\EventPersister; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterProviderInterface; use Neos\ContentRepository\Core\Infrastructure\Property\PropertyConverter; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\Projection\Projections; @@ -47,6 +48,7 @@ private function __construct( // we don't need CommandBus, because this is included in ContentRepository->handle() public EventPersister $eventPersister, public Projections $projections, + public ContentGraphAdapterProviderInterface $contentGraphAdapterProvider ) { } @@ -58,6 +60,7 @@ public static function create( ContentRepository $contentRepository, EventPersister $eventPersister, Projections $projections, + ContentGraphAdapterProviderInterface $contentGraphAdapterProvider ): self { return new self( $projectionFactoryDependencies->contentRepositoryId, @@ -71,6 +74,7 @@ public static function create( $contentRepository, $eventPersister, $projections, + $contentGraphAdapterProvider ); } } diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php index 140a34d146a..592cc2ef9e5 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php @@ -14,7 +14,6 @@ namespace Neos\ContentRepository\Core\Feature\Common; -use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\Exception\DimensionSpacePointNotFound; @@ -67,7 +66,7 @@ abstract protected function getNodeTypeManager(): NodeTypeManager; abstract protected function getAllowedDimensionSubspace(): DimensionSpacePointSet; - abstract protected function getContentGraphAdapter(): ContentGraphAdapterInterface; + abstract protected function getContentGraphAdapter(WorkspaceName $workspaceName): ContentGraphAdapterInterface; /** * @throws ContentStreamDoesNotExistYet @@ -75,22 +74,21 @@ abstract protected function getContentGraphAdapter(): ContentGraphAdapterInterfa protected function requireContentStream( WorkspaceName $workspaceName ): ContentStreamId { - $contentGraphAdapter = $this->getContentGraphAdapter(); - $contentStreamId = ContentStreamIdOverride::resolveContentStreamIdForWorkspace($contentGraphAdapter, $workspaceName); - if (!$contentGraphAdapter->hasContentStream($contentStreamId)) { + $contentGraphAdapter = $this->getContentGraphAdapter($workspaceName); + if (!$contentGraphAdapter->hasContentStream()) { throw new ContentStreamDoesNotExistYet( - 'Content stream "' . $contentStreamId->value . '" does not exist yet.', + 'Content stream "' . $contentGraphAdapter->getContentStreamId()->value . '" does not exist yet.', 1521386692 ); } - if ($contentGraphAdapter->findStateForContentStream($contentStreamId) === ContentStreamState::STATE_CLOSED) { + if ($contentGraphAdapter->findStateForContentStream() === ContentStreamState::STATE_CLOSED) { throw new ContentStreamIsClosed( - 'Content stream "' . $contentStreamId->value . '" is closed.', + 'Content stream "' . $contentGraphAdapter->getContentStreamId()->value . '" is closed.', 1710260081 ); } - return $contentStreamId; + return $contentGraphAdapter->getContentStreamId(); } /** @@ -150,11 +148,10 @@ protected function requireNodeTypeToNotBeOfTypeRoot(NodeType $nodeType): void } protected function requireRootNodeTypeToBeUnoccupied( - NodeTypeName $nodeTypeName, - ContentStreamId $contentStreamId + ContentGraphAdapterInterface $contentGraphAdapter, + NodeTypeName $nodeTypeName ): void { - $rootNodeAggregateExists = $this->getContentGraphAdapter()->rootNodeAggregateWithTypeExists( - $contentStreamId, + $rootNodeAggregateExists = $contentGraphAdapter->rootNodeAggregateWithTypeExists( $nodeTypeName ); @@ -237,21 +234,21 @@ protected function requireNodeTypeToAllowNodesOfTypeInReference( /** * NodeType and NodeName must belong together to the same node, which is the to-be-checked one. * - * @param ContentStreamId $contentStreamId + * @param ContentGraphAdapterInterface $contentGraphAdapter * @param NodeType $nodeType * @param NodeName|null $nodeName * @param array|NodeAggregateId[] $parentNodeAggregateIds * @throws NodeConstraintException */ protected function requireConstraintsImposedByAncestorsAreMet( - ContentStreamId $contentStreamId, + ContentGraphAdapterInterface $contentGraphAdapter, NodeType $nodeType, ?NodeName $nodeName, array $parentNodeAggregateIds ): void { foreach ($parentNodeAggregateIds as $parentNodeAggregateId) { $parentAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraphAdapter, $parentNodeAggregateId ); if (!$parentAggregate->classification->isTethered()) { @@ -264,11 +261,8 @@ protected function requireConstraintsImposedByAncestorsAreMet( } } - $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($contentStreamId); foreach ( - $this->getContentGraphAdapter()->findParentNodeAggregates( - $contentStreamId, - $workspace?->workspaceName, + $contentGraphAdapter->findParentNodeAggregates( $parentNodeAggregateId ) as $grandParentNodeAggregate ) { @@ -382,13 +376,10 @@ protected function areNodeTypeConstraintsImposedByGrandparentValid( * @throws NodeAggregateCurrentlyDoesNotExist */ protected function requireProjectedNodeAggregate( - ContentStreamId $contentStreamId, + ContentGraphAdapterInterface $contentGraphAdapter, NodeAggregateId $nodeAggregateId ): NodeAggregate { - $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($contentStreamId); - $nodeAggregate = $this->getContentGraphAdapter()->findNodeAggregateById( - $contentStreamId, - $workspace?->workspaceName, + $nodeAggregate = $contentGraphAdapter->findNodeAggregateById( $nodeAggregateId ); @@ -407,13 +398,10 @@ protected function requireProjectedNodeAggregate( * @throws NodeAggregateCurrentlyExists */ protected function requireProjectedNodeAggregateToNotExist( - ContentStreamId $contentStreamId, + ContentGraphAdapterInterface $contentGraphAdapter, NodeAggregateId $nodeAggregateId ): void { - $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($contentStreamId); - $nodeAggregate = $this->getContentGraphAdapter()->findNodeAggregateById( - $contentStreamId, - $workspace?->workspaceName, + $nodeAggregate = $contentGraphAdapter->findNodeAggregateById( $nodeAggregateId ); @@ -429,15 +417,12 @@ protected function requireProjectedNodeAggregateToNotExist( * @throws NodeAggregateCurrentlyDoesNotExist */ public function requireProjectedParentNodeAggregate( - ContentStreamId $contentStreamId, + ContentGraphAdapterInterface $contentGraphAdapter, NodeAggregateId $childNodeAggregateId, OriginDimensionSpacePoint $childOriginDimensionSpacePoint ): NodeAggregate { - $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($contentStreamId); - $parentNodeAggregate = $this->getContentGraphAdapter() + $parentNodeAggregate = $contentGraphAdapter ->findParentNodeAggregateByChildOriginDimensionSpacePoint( - $contentStreamId, - $workspace?->workspaceName, $childNodeAggregateId, $childOriginDimensionSpacePoint ); @@ -446,7 +431,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 ' . $contentGraphAdapter->getWorkspaceName()->value, 1645368685 ); } @@ -465,7 +450,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 ); } @@ -517,7 +502,7 @@ protected function requireNodeAggregateToBeUntethered(NodeAggregate $nodeAggrega * @throws NodeAggregateIsDescendant */ protected function requireNodeAggregateToNotBeDescendant( - ContentStreamId $contentStreamId, + ContentGraphAdapterInterface $contentGraphAdapter, NodeAggregate $nodeAggregate, NodeAggregate $referenceNodeAggregate ): void { @@ -528,16 +513,13 @@ protected function requireNodeAggregateToNotBeDescendant( 1554971124 ); } - $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($contentStreamId); foreach ( - $this->getContentGraphAdapter()->findChildNodeAggregates( - $contentStreamId, - $workspace?->workspaceName, + $contentGraphAdapter->findChildNodeAggregates( $referenceNodeAggregate->nodeAggregateId ) as $childReferenceNodeAggregate ) { $this->requireNodeAggregateToNotBeDescendant( - $contentStreamId, + $contentGraphAdapter, $nodeAggregate, $childReferenceNodeAggregate ); @@ -548,7 +530,7 @@ protected function requireNodeAggregateToNotBeDescendant( * @throws NodeNameIsAlreadyOccupied */ protected function requireNodeNameToBeUnoccupied( - ContentStreamId $contentStreamId, + ContentGraphAdapterInterface $contentGraphAdapter, ?NodeName $nodeName, NodeAggregateId $parentNodeAggregateId, OriginDimensionSpacePoint $parentOriginDimensionSpacePoint, @@ -557,9 +539,8 @@ protected function requireNodeNameToBeUnoccupied( if ($nodeName === null) { return; } - $dimensionSpacePointsOccupiedByChildNodeName = $this->getContentGraphAdapter() + $dimensionSpacePointsOccupiedByChildNodeName = $contentGraphAdapter ->getDimensionSpacePointsOccupiedByChildNodeName( - $contentStreamId, $nodeName, $parentNodeAggregateId, $parentOriginDimensionSpacePoint, @@ -578,7 +559,7 @@ protected function requireNodeNameToBeUnoccupied( * @throws NodeNameIsAlreadyCovered */ protected function requireNodeNameToBeUncovered( - ContentStreamId $contentStreamId, + ContentGraphAdapterInterface $contentGraphAdapter, ?NodeName $nodeName, NodeAggregateId $parentNodeAggregateId, DimensionSpacePointSet $dimensionSpacePointsToBeCovered @@ -586,11 +567,8 @@ protected function requireNodeNameToBeUncovered( if ($nodeName === null) { return; } - $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($contentStreamId); - $childNodeAggregates = $this->getContentGraphAdapter()->findChildNodeAggregatesByName( - $contentStreamId, - $workspace?->workspaceName, + $childNodeAggregates = $contentGraphAdapter->findChildNodeAggregatesByName( $parentNodeAggregateId, $nodeName ); @@ -617,7 +595,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_THROW_ON_ERROR) . ' is not yet occupied by node aggregate "' . $nodeAggregate->nodeAggregateId->value . '"', 1552595396 ); @@ -633,7 +611,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_THROW_ON_ERROR) . ' is already occupied by node aggregate "' . $nodeAggregate->nodeAggregateId->value . '"', 1552595441 ); @@ -676,10 +654,10 @@ protected function validateReferenceProperties( } protected function getExpectedVersionOfContentStream( - ContentStreamId $contentStreamId + ContentGraphAdapterInterface $contentGraphAdapter ): ExpectedVersion { return ExpectedVersion::fromVersion( - $this->getContentGraphAdapter()->findVersionForContentStream($contentStreamId)->unwrap() + $contentGraphAdapter->findVersionForContentStream()->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 2f2c83bb2ce..00000000000 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/ContentStreamIdOverride.php +++ /dev/null @@ -1,67 +0,0 @@ -findWorkspaceByName($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..64e6da075e4 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/NodeCreationInternals.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/NodeCreationInternals.php @@ -14,13 +14,10 @@ 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\Filter\FindSucceedingSiblingNodesFilter; -use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterInterface; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; /** * @internal implementation details of command handlers @@ -41,30 +38,19 @@ trait NodeCreationInternals * operates on the explicitly set succeeding sibling instead of the node itself. */ private function resolveInterdimensionalSiblingsForCreation( - ContentRepository $contentRepository, - ContentStreamId $contentStreamId, + ContentGraphAdapterInterface $contentGraphAdapter, NodeAggregateId $requestedSucceedingSiblingNodeAggregateId, OriginDimensionSpacePoint $sourceOrigin, DimensionSpacePointSet $coveredDimensionSpacePoints, ): InterdimensionalSiblings { - $originSubgraph = $contentRepository->getContentGraph()->getSubgraph( - $contentStreamId, + $originAlternativeSucceedingSiblings = $contentGraphAdapter->findSuceedingSiblingNodesInSubgraph( $sourceOrigin->toDimensionSpacePoint(), - VisibilityConstraints::withoutRestrictions() - ); - $originAlternativeSucceedingSiblings = $originSubgraph->findSucceedingSiblingNodes( - $requestedSucceedingSiblingNodeAggregateId, - FindSucceedingSiblingNodesFilter::create() + $requestedSucceedingSiblingNodeAggregateId ); $interdimensionalSiblings = []; foreach ($coveredDimensionSpacePoints as $coveredDimensionSpacePoint) { - $variantSubgraph = $contentRepository->getContentGraph()->getSubgraph( - $contentStreamId, - $coveredDimensionSpacePoint, - VisibilityConstraints::withoutRestrictions() - ); - $variantSucceedingSibling = $variantSubgraph->findNodeById($requestedSucceedingSiblingNodeAggregateId); + $variantSucceedingSibling = $contentGraphAdapter->findNodeInSubgraph($coveredDimensionSpacePoint, $requestedSucceedingSiblingNodeAggregateId); if ($variantSucceedingSibling) { // a) happy path, the explicitly requested succeeding sibling also exists in this dimension space point $interdimensionalSiblings[] = new InterdimensionalSibling( @@ -76,7 +62,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 = $contentGraphAdapter->findNodeInSubgraph($coveredDimensionSpacePoint, $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 89b2882f002..42552132e57 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/NodeVariationInternals.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/NodeVariationInternals.php @@ -23,12 +23,9 @@ 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\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 @@ -37,10 +34,10 @@ trait NodeVariationInternals { abstract protected function getInterDimensionalVariationGraph(): DimensionSpace\InterDimensionalVariationGraph; - abstract protected function getContentGraphAdapter(): ContentGraphAdapterInterface; + abstract protected function getContentGraphAdapter(WorkspaceName $workspaceName): ContentGraphAdapterInterface; protected function createEventsForVariations( - ContentStreamId $contentStreamId, + ContentGraphAdapterInterface $contentGraphAdapter, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $targetOrigin, NodeAggregate $nodeAggregate @@ -52,19 +49,19 @@ protected function createEventsForVariations( ) ) { DimensionSpace\VariantType::TYPE_SPECIALIZATION => $this->handleCreateNodeSpecializationVariant( - $contentStreamId, + $contentGraphAdapter, $sourceOrigin, $targetOrigin, $nodeAggregate ), DimensionSpace\VariantType::TYPE_GENERALIZATION => $this->handleCreateNodeGeneralizationVariant( - $contentStreamId, + $contentGraphAdapter, $sourceOrigin, $targetOrigin, $nodeAggregate ), default => $this->handleCreateNodePeerVariant( - $contentStreamId, + $contentGraphAdapter, $sourceOrigin, $targetOrigin, $nodeAggregate @@ -73,14 +70,14 @@ protected function createEventsForVariations( } protected function handleCreateNodeSpecializationVariant( - ContentStreamId $contentStreamId, + ContentGraphAdapterInterface $contentGraphAdapter, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $targetOrigin, NodeAggregate $nodeAggregate ): Events { $specializationVisibility = $this->calculateEffectiveVisibility($targetOrigin, $nodeAggregate); $events = $this->collectNodeSpecializationVariantsThatWillHaveBeenCreated( - $contentStreamId, + $contentGraphAdapter, $sourceOrigin, $targetOrigin, $nodeAggregate, @@ -96,22 +93,20 @@ protected function handleCreateNodeSpecializationVariant( * @return array */ protected function collectNodeSpecializationVariantsThatWillHaveBeenCreated( - ContentStreamId $contentStreamId, + ContentGraphAdapterInterface $contentGraphAdapter, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $targetOrigin, NodeAggregate $nodeAggregate, DimensionSpacePointSet $specializationVisibility, array $events ): array { - $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($contentStreamId); $events[] = new NodeSpecializationVariantWasCreated( - $contentStreamId, + $contentGraphAdapter->getContentStreamId(), $nodeAggregate->nodeAggregateId, $sourceOrigin, $targetOrigin, $this->resolveInterdimensionalSiblings( - $contentRepository, - $contentStreamId, + $contentGraphAdapter, $nodeAggregate->nodeAggregateId, $sourceOrigin, $specializationVisibility @@ -119,14 +114,12 @@ protected function collectNodeSpecializationVariantsThatWillHaveBeenCreated( ); foreach ( - $this->getContentGraphAdapter()->findTetheredChildNodeAggregates( - $contentStreamId, - $workspace?->workspaceName, + $contentGraphAdapter->findTetheredChildNodeAggregates( $nodeAggregate->nodeAggregateId ) as $tetheredChildNodeAggregate ) { $events = $this->collectNodeSpecializationVariantsThatWillHaveBeenCreated( - $contentStreamId, + $contentGraphAdapter, $sourceOrigin, $targetOrigin, $tetheredChildNodeAggregate, @@ -139,14 +132,14 @@ protected function collectNodeSpecializationVariantsThatWillHaveBeenCreated( } protected function handleCreateNodeGeneralizationVariant( - ContentStreamId $contentStreamId, + ContentGraphAdapterInterface $contentGraphAdapter, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $targetOrigin, NodeAggregate $nodeAggregate ): Events { $generalizationVisibility = $this->calculateEffectiveVisibility($targetOrigin, $nodeAggregate); $events = $this->collectNodeGeneralizationVariantsThatWillHaveBeenCreated( - $contentStreamId, + $contentGraphAdapter, $sourceOrigin, $targetOrigin, $nodeAggregate, @@ -162,22 +155,20 @@ protected function handleCreateNodeGeneralizationVariant( * @return array */ protected function collectNodeGeneralizationVariantsThatWillHaveBeenCreated( - ContentStreamId $contentStreamId, + ContentGraphAdapterInterface $contentGraphAdapter, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $targetOrigin, NodeAggregate $nodeAggregate, DimensionSpacePointSet $generalizationVisibility, array $events ): array { - $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($contentStreamId); $events[] = new NodeGeneralizationVariantWasCreated( - $contentStreamId, + $contentGraphAdapter->getContentStreamId(), $nodeAggregate->nodeAggregateId, $sourceOrigin, $targetOrigin, $this->resolveInterdimensionalSiblings( - $contentRepository, - $contentStreamId, + $contentGraphAdapter, $nodeAggregate->nodeAggregateId, $sourceOrigin, $generalizationVisibility @@ -185,14 +176,12 @@ protected function collectNodeGeneralizationVariantsThatWillHaveBeenCreated( ); foreach ( - $this->getContentGraphAdapter()->findTetheredChildNodeAggregates( - $contentStreamId, - $workspace?->workspaceName, + $contentGraphAdapter->findTetheredChildNodeAggregates( $nodeAggregate->nodeAggregateId ) as $tetheredChildNodeAggregate ) { $events = $this->collectNodeGeneralizationVariantsThatWillHaveBeenCreated( - $contentStreamId, + $contentGraphAdapter, $sourceOrigin, $targetOrigin, $tetheredChildNodeAggregate, @@ -205,14 +194,14 @@ protected function collectNodeGeneralizationVariantsThatWillHaveBeenCreated( } protected function handleCreateNodePeerVariant( - ContentStreamId $contentStreamId, + ContentGraphAdapterInterface $contentGraphAdapter, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $targetOrigin, NodeAggregate $nodeAggregate ): Events { $peerVisibility = $this->calculateEffectiveVisibility($targetOrigin, $nodeAggregate); $events = $this->collectNodePeerVariantsThatWillHaveBeenCreated( - $contentStreamId, + $contentGraphAdapter, $sourceOrigin, $targetOrigin, $nodeAggregate, @@ -228,22 +217,20 @@ protected function handleCreateNodePeerVariant( * @return array */ protected function collectNodePeerVariantsThatWillHaveBeenCreated( - ContentStreamId $contentStreamId, + ContentGraphAdapterInterface $contentGraphAdapter, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $targetOrigin, NodeAggregate $nodeAggregate, DimensionSpacePointSet $peerVisibility, array $events ): array { - $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($contentStreamId); $events[] = new NodePeerVariantWasCreated( - $contentStreamId, + $contentGraphAdapter->getContentStreamId(), $nodeAggregate->nodeAggregateId, $sourceOrigin, $targetOrigin, $this->resolveInterdimensionalSiblings( - $contentRepository, - $contentStreamId, + $contentGraphAdapter, $nodeAggregate->nodeAggregateId, $sourceOrigin, $peerVisibility @@ -251,14 +238,12 @@ protected function collectNodePeerVariantsThatWillHaveBeenCreated( ); foreach ( - $this->getContentGraphAdapter()->findTetheredChildNodeAggregates( - $contentStreamId, - $workspace?->workspaceName, + $contentGraphAdapter->findTetheredChildNodeAggregates( $nodeAggregate->nodeAggregateId ) as $tetheredChildNodeAggregate ) { $events = $this->collectNodePeerVariantsThatWillHaveBeenCreated( - $contentStreamId, + $contentGraphAdapter, $sourceOrigin, $targetOrigin, $tetheredChildNodeAggregate, @@ -283,33 +268,18 @@ 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, + ContentGraphAdapterInterface $contentGraphAdapter, NodeAggregateId $varyingNodeAggregateId, OriginDimensionSpacePoint $sourceOrigin, DimensionSpacePointSet $variantCoverage, ): InterdimensionalSiblings { - $originSubgraph = $contentRepository->getContentGraph()->getSubgraph( - $contentStreamId, - $sourceOrigin->toDimensionSpacePoint(), - VisibilityConstraints::withoutRestrictions() - ); - $originSiblings = $originSubgraph->findSucceedingSiblingNodes( - $varyingNodeAggregateId, - FindSucceedingSiblingNodesFilter::create() - ); + $originSiblings = $contentGraphAdapter->findSuceedingSiblingNodesInSubgraph($sourceOrigin->toDimensionSpacePoint(), $varyingNodeAggregateId); $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 = $contentGraphAdapter->findNodeInSubgraph($variantDimensionSpacePoint, $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 4c4e7d55d2d..7565235dbee 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/TetheredNodeInternals.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/TetheredNodeInternals.php @@ -16,6 +16,7 @@ use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\EventStore\Events; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterInterface; use Neos\ContentRepository\Core\Feature\NodeCreation\Event\NodeAggregateWithNodeWasCreated; use Neos\ContentRepository\Core\Feature\NodeModification\Dto\SerializedPropertyValues; use Neos\ContentRepository\Core\Feature\NodeVariation\Event\NodePeerVariantWasCreated; @@ -39,7 +40,7 @@ trait TetheredNodeInternals abstract protected function getPropertyConverter(): PropertyConverter; abstract protected function createEventsForVariations( - ContentStreamId $contentStreamId, + ContentGraphAdapterInterface $contentGraphAdapter, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $targetOrigin, NodeAggregate $nodeAggregate @@ -54,18 +55,14 @@ abstract protected function createEventsForVariations( * @throws \Exception */ protected function createEventsForMissingTetheredNode( + ContentGraphAdapterInterface $contentGraphAdapter, NodeAggregate $parentNodeAggregate, OriginDimensionSpacePoint $originDimensionSpacePoint, NodeName $tetheredNodeName, ?NodeAggregateId $tetheredNodeAggregateId, NodeType $expectedTetheredNodeType ): Events { - // TODO: We should probably just directly use the workspace AND contentStreamId from the $parentNodeAggregate - $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($parentNodeAggregate->contentStreamId); - - $childNodeAggregates = $this->getContentGraphAdapter()->findChildNodeAggregatesByName( - $parentNodeAggregate->contentStreamId, - $workspace?->workspaceName, + $childNodeAggregates = $contentGraphAdapter->findChildNodeAggregatesByName( $parentNodeAggregate->nodeAggregateId, $tetheredNodeName ); @@ -99,7 +96,7 @@ protected function createEventsForMissingTetheredNode( ); } else { $events[] = new NodeAggregateWithNodeWasCreated( - $parentNodeAggregate->contentStreamId, + $contentGraphAdapter->getContentStreamId(), $tetheredNodeAggregateId, $expectedTetheredNodeType->name, $rootGeneralizationOrigin, @@ -118,7 +115,7 @@ protected function createEventsForMissingTetheredNode( } else { return Events::with( new NodeAggregateWithNodeWasCreated( - $parentNodeAggregate->contentStreamId, + $contentGraphAdapter->getContentStreamId(), $tetheredNodeAggregateId ?: NodeAggregateId::create(), $expectedTetheredNodeType->name, $originDimensionSpacePoint, @@ -150,7 +147,7 @@ protected function createEventsForMissingTetheredNode( } /** @var Node $childNodeSource Node aggregates are never empty */ return $this->createEventsForVariations( - $parentNodeAggregate->contentStreamId, + $contentGraphAdapter, $childNodeSource->originDimensionSpacePoint, $originDimensionSpacePoint, $parentNodeAggregate diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterInterface.php b/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterInterface.php index 7712f66e3a8..1323259414b 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterInterface.php +++ b/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterInterface.php @@ -28,6 +28,7 @@ use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamState; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; +use Neos\EventStore\Model\EventStream\ExpectedVersion; use Neos\EventStore\Model\EventStream\MaybeVersion; /** @@ -43,7 +44,6 @@ interface ContentGraphAdapterInterface */ public function rootNodeAggregateWithTypeExists( - ContentStreamId $contentStreamId, NodeTypeName $nodeTypeName ): bool; @@ -70,8 +70,6 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint( * @return iterable */ public function findChildNodeAggregates( - ContentStreamId $contentStreamId, - WorkspaceName $workspaceName, NodeAggregateId $parentNodeAggregateId ): iterable; @@ -79,15 +77,12 @@ public function findChildNodeAggregates( * @return iterable */ public function findTetheredChildNodeAggregates( - ContentStreamId $contentStreamId, - WorkspaceName $workspaceName, NodeAggregateId $parentNodeAggregateId ): iterable; /** */ public function getDimensionSpacePointsOccupiedByChildNodeName( - ContentStreamId $contentStreamId, NodeName $nodeName, NodeAggregateId $parentNodeAggregateId, OriginDimensionSpacePoint $parentNodeOriginDimensionSpacePoint, @@ -101,8 +96,6 @@ public function getDimensionSpacePointsOccupiedByChildNodeName( * @return iterable */ public function findChildNodeAggregatesByName( - ContentStreamId $contentStreamId, - WorkspaceName $workspaceName, NodeAggregateId $parentNodeAggregateId, NodeName $name ): iterable; @@ -115,7 +108,6 @@ public function findChildNodeAggregatesByName( * Does the subgraph with the provided identity contain any nodes */ public function subgraphContainsNodes( - ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint ): bool; @@ -123,37 +115,32 @@ public function subgraphContainsNodes( * Finds a specified node within a "subgraph" */ public function findNodeInSubgraph( - ContentStreamId $contentStreamId, - WorkspaceName $workspaceName, DimensionSpacePoint $coveredDimensionSpacePoint, NodeAggregateId $nodeAggregateId ): ?Node; public function findParentNodeInSubgraph( - ContentStreamId $contentStreamId, - WorkspaceName $workspaceName, DimensionSpacePoint $coveredDimensionSpacePoint, NodeAggregateId $childNodeAggregateId ): ?Node; + public function findChildNodesInSubgraph( + DimensionSpacePoint $coveredDimensionSpacePoint, + NodeAggregateId $parentNodeAggregateId + ): Nodes; + public function findChildNodeByNameInSubgraph( - ContentStreamId $contentStreamId, - WorkspaceName $workspaceName, DimensionSpacePoint $coveredDimensionSpacePoint, NodeAggregateId $parentNodeAggregateId, NodeName $nodeNamex ): ?Node; public function findPreceedingSiblingNodesInSubgraph( - ContentStreamId $contentStreamId, - WorkspaceName $workspaceName, DimensionSpacePoint $coveredDimensionSpacePoint, NodeAggregateId $startingSiblingNodeAggregateId ): Nodes; public function findSuceedingSiblingNodesInSubgraph( - ContentStreamId $contentStreamId, - WorkspaceName $workspaceName, DimensionSpacePoint $coveredDimensionSpacePoint, NodeAggregateId $startingSiblingNodeAggregateId ): Nodes; @@ -162,30 +149,23 @@ public function findSuceedingSiblingNodesInSubgraph( * CONTENT STREAMS */ - public function hasContentStream( - ContentStreamId $contentStreamId - ): bool; + public function hasContentStream(): bool; - public function findStateForContentStream( - ContentStreamId $contentStreamId - ): ?ContentStreamState; + public function findStateForContentStream(): ?ContentStreamState; - public function findVersionForContentStream( - ContentStreamId $contentStreamId - ): MaybeVersion; + public function findVersionForContentStream(): MaybeVersion; + public function getExpectedVersionOfContentStream(): ExpectedVersion; /* * WORKSPACES */ - public function findWorkspaceByName( - WorkspaceName $workspaceName - ): ?Workspace; - public function findWorkspaceByCurrentContentStreamId( ContentStreamId $contentStreamId ): ?Workspace; + public function withWorkspaceName(WorkspaceName $workspaceName): ContentGraphAdapterInterface; + public function getWorkspaceName(): WorkspaceName; public function getContentStreamId(): ContentStreamId; diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterProviderInterface.php b/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterProviderInterface.php index 4a3b74dd352..f309b9d8ba0 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterProviderInterface.php +++ b/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterProviderInterface.php @@ -1,6 +1,8 @@ requireContentStreamToNotBeClosed($command->sourceContentStreamId); $this->requireContentStreamToNotExistYet($command->newContentStreamId); - $sourceContentStreamVersion = $this->contentGraphAdapter - ->findVersionForContentStream($command->sourceContentStreamId); + // TOOD: THis is not great + $sourceContentStreamVersion = $this->contentGraphAdapterProvider->resolveWorkspaceNameAndGet($command->sourceContentStreamId) + ->findVersionForContentStream(); $streamName = ContentStreamEventStreamName::fromContentStreamId($command->newContentStreamId) ->getEventStreamName(); diff --git a/Neos.ContentRepository.Core/Classes/Feature/DimensionSpaceAdjustment/DimensionSpaceCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/DimensionSpaceAdjustment/DimensionSpaceCommandHandler.php index 7b6a8030173..b786289d651 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/DimensionSpaceAdjustment/DimensionSpaceCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/DimensionSpaceAdjustment/DimensionSpaceCommandHandler.php @@ -26,17 +26,15 @@ use Neos\ContentRepository\Core\EventStore\Events; use Neos\ContentRepository\Core\EventStore\EventsToPublish; use Neos\ContentRepository\Core\Feature\ContentGraphAdapterInterface; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterProviderInterface; use Neos\ContentRepository\Core\Feature\ContentStreamEventStreamName; use Neos\ContentRepository\Core\Feature\DimensionSpaceAdjustment\Command\AddDimensionShineThrough; use Neos\ContentRepository\Core\Feature\DimensionSpaceAdjustment\Command\MoveDimensionSpacePoint; use Neos\ContentRepository\Core\Feature\DimensionSpaceAdjustment\Event\DimensionShineThroughWasAdded; use Neos\ContentRepository\Core\Feature\DimensionSpaceAdjustment\Event\DimensionSpacePointWasMoved; 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; /** @@ -47,7 +45,7 @@ public function __construct( private ContentDimensionZookeeper $contentDimensionZookeeper, private InterDimensionalVariationGraph $interDimensionalVariationGraph, - private ContentGraphAdapterInterface $contentGraphAdapter + private ContentGraphAdapterProviderInterface $contentGraphAdapterProvider ) { } @@ -68,14 +66,13 @@ public function handle(CommandInterface $command, ContentRepository $contentRepo private function handleMoveDimensionSpacePoint( MoveDimensionSpacePoint $command ): EventsToPublish { - $contentStreamId = $this->requireContentStreamForWorkspaceName($command->workspaceName, $contentRepository); - $streamName = ContentStreamEventStreamName::fromContentStreamId($contentStreamId) + $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($command->workspaceName); + $streamName = ContentStreamEventStreamName::fromContentStreamId($contentGraphAdapter->getContentStreamId()) ->getEventStreamName(); self::requireDimensionSpacePointToBeEmptyInContentStream( - $command->target, - $contentStreamId, - $this->contentGraphAdapter + $contentGraphAdapter, + $command->target ); $this->requireDimensionSpacePointToExist($command->target); @@ -83,7 +80,7 @@ private function handleMoveDimensionSpacePoint( $streamName, Events::with( new DimensionSpacePointWasMoved( - $contentStreamId, + $contentGraphAdapter->getContentStreamId(), $command->source, $command->target ), @@ -95,14 +92,13 @@ private function handleMoveDimensionSpacePoint( private function handleAddDimensionShineThrough( AddDimensionShineThrough $command ): EventsToPublish { - $contentStreamId = $this->requireContentStreamForWorkspaceName($command->workspaceName, $contentRepository); - $streamName = ContentStreamEventStreamName::fromContentStreamId($contentStreamId) + $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($command->workspaceName); + $streamName = ContentStreamEventStreamName::fromContentStreamId($contentGraphAdapter->getContentStreamId()) ->getEventStreamName(); self::requireDimensionSpacePointToBeEmptyInContentStream( - $command->target, - $contentStreamId, - $this->contentGraphAdapter + $contentGraphAdapter, + $command->target ); $this->requireDimensionSpacePointToExist($command->target); @@ -112,7 +108,7 @@ private function handleAddDimensionShineThrough( $streamName, Events::with( new DimensionShineThroughWasAdded( - $contentStreamId, + $contentGraphAdapter->getContentStreamId(), $command->source, $command->target ) @@ -133,15 +129,14 @@ protected function requireDimensionSpacePointToExist(DimensionSpacePoint $dimens } private static function requireDimensionSpacePointToBeEmptyInContentStream( - DimensionSpacePoint $dimensionSpacePoint, - ContentStreamId $contentStreamId, - ContentGraphAdapterInterface $contentGraphAdapter + ContentGraphAdapterInterface $contentGraphAdapter, + DimensionSpacePoint $dimensionSpacePoint ): void { - $hasNodes = $contentGraphAdapter->subgraphContainsNodes($contentStreamId, $dimensionSpacePoint); + $hasNodes = $contentGraphAdapter->subgraphContainsNodes($dimensionSpacePoint); if ($hasNodes) { throw new DimensionSpacePointAlreadyExists(sprintf( 'the content stream %s already contained nodes in dimension space point %s - this is not allowed.', - $contentStreamId->value, + $contentGraphAdapter->getContentStreamId()->value, $dimensionSpacePoint->toJson(), ), 1612898126); } @@ -161,33 +156,15 @@ private function requireDimensionSpacePointToBeSpecialization( } } - /** - * @throws ContentStreamDoesNotExistYet - */ - protected function requireContentStreamForWorkspaceName( - WorkspaceName $workspaceName - ): ContentStreamId { - $contentStreamId = $this->contentGraphAdapter->findWorkspaceByName($workspaceName) - ?->currentContentStreamId; - if (!$contentStreamId || !$this->contentGraphAdapter->hasContentStream($contentStreamId)) { - throw new ContentStreamDoesNotExistYet( - 'Content stream "' . $contentStreamId?->value . '" does not exist yet.', - 1521386692 - ); - } - - return $contentStreamId; - } - /** * @throws ContentStreamDoesNotExistYet */ protected function requireContentStream( - ContentStreamId $contentStreamId + ContentGraphAdapterInterface $contentGraphAdapter ): void { - if (!$this->contentGraphAdapter->hasContentStream($contentStreamId)) { + if (!$contentGraphAdapter->hasContentStream()) { throw new ContentStreamDoesNotExistYet( - 'Content stream "' . $contentStreamId->value . '" does not exist yet.', + 'Content stream "' . $contentGraphAdapter->getContentStreamId()->value . '" does not exist yet.', 1521386692 ); } diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php index d1072554838..6d2af5b74a1 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php @@ -14,7 +14,6 @@ namespace Neos\ContentRepository\Core\Feature; -use http\Exception\RuntimeException; use Neos\ContentRepository\Core\CommandHandler\CommandHandlerInterface; use Neos\ContentRepository\Core\CommandHandler\CommandInterface; use Neos\ContentRepository\Core\ContentRepository; @@ -53,7 +52,6 @@ use Neos\ContentRepository\Core\Feature\SubtreeTagging\SubtreeTagging; use Neos\ContentRepository\Core\Infrastructure\Property\PropertyConverter; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** @@ -150,27 +148,13 @@ public function getPropertyConverter(): PropertyConverter } /** - * WIP Should not have this signature ;) - * @param WorkspaceName|null $workspaceName - * @param ContentStreamId|null $contentStreamId + * @param WorkspaceName $workspaceName * @return ContentGraphAdapterInterface * */ - protected function getContentGraphAdapter(?WorkspaceName $workspaceName, ?ContentStreamId $contentStreamId = null): ContentGraphAdapterInterface + protected function getContentGraphAdapter(WorkspaceName $workspaceName): ContentGraphAdapterInterface { - if ($contentStreamId && $workspaceName) { - return $this->contentGraphAdapterProvider->get($workspaceName, $contentStreamId); - } - - if ($workspaceName) { - return $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($workspaceName); - } - - if ($contentStreamId) { - return $this->contentGraphAdapterProvider->resolveWorkspaceNameAndGet($contentStreamId); - } - - throw new RuntimeException('To access the ContentGraphAdpater either a WorkspaceName or ContentStreamId is necessary, neither was provided.', 1712331448); + return $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($workspaceName); } /** diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php b/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php index e2e389d0209..2c5b474f7cb 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php @@ -21,6 +21,7 @@ use Neos\ContentRepository\Core\Feature\Common\InterdimensionalSiblings; use Neos\ContentRepository\Core\Feature\Common\NodeAggregateEventPublisher; use Neos\ContentRepository\Core\Feature\Common\NodeCreationInternals; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterInterface; use Neos\ContentRepository\Core\Feature\ContentStreamEventStreamName; use Neos\ContentRepository\Core\Feature\NodeCreation\Command\CreateNodeAggregateWithNode; use Neos\ContentRepository\Core\Feature\NodeCreation\Command\CreateNodeAggregateWithNodeAndSerializedProperties; @@ -128,8 +129,8 @@ private function validateProperties(?PropertyValuesToWrite $propertyValues, Node private function handleCreateNodeAggregateWithNodeAndSerializedProperties( CreateNodeAggregateWithNodeAndSerializedProperties $command ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId); + $contentGraphAdapter = $this->getContentGraphAdapter($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraphAdapter); $this->requireDimensionSpacePointToExist($command->originDimensionSpacePoint->toDimensionSpacePoint()); $nodeType = $this->requireNodeType($command->nodeTypeName); $this->requireNodeTypeToNotBeAbstract($nodeType); @@ -138,23 +139,23 @@ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( $this->requireTetheredDescendantNodeTypesToNotBeOfTypeRoot($nodeType); if ($this->areAncestorNodeTypeConstraintChecksEnabled()) { $this->requireConstraintsImposedByAncestorsAreMet( - $contentStreamId, + $contentGraphAdapter, $nodeType, $command->nodeName, [$command->parentNodeAggregateId] ); } $this->requireProjectedNodeAggregateToNotExist( - $contentStreamId, + $contentGraphAdapter, $command->nodeAggregateId ); $parentNodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraphAdapter, $command->parentNodeAggregateId ); if ($command->succeedingSiblingNodeAggregateId) { $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraphAdapter, $command->succeedingSiblingNodeAggregateId ); } @@ -170,7 +171,7 @@ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( ); if ($command->nodeName) { $this->requireNodeNameToBeUnoccupied( - $contentStreamId, + $contentGraphAdapter, $command->nodeName, $command->parentNodeAggregateId, $command->originDimensionSpacePoint, @@ -190,7 +191,7 @@ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( $descendantNodeAggregateIds->getNodeAggregateIds() as $descendantNodeAggregateId ) { $this->requireProjectedNodeAggregateToNotExist( - $contentStreamId, + $contentGraphAdapter, $descendantNodeAggregateId ); } @@ -200,17 +201,16 @@ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( $events = [ $this->createRegularWithNode( + $contentGraphAdapter, $command, - $contentStreamId, $coveredDimensionSpacePoints, - $initialPropertyValues, - $contentRepository + $initialPropertyValues ) ]; array_push($events, ...iterator_to_array($this->handleTetheredChildNodes( $command, - $contentStreamId, + $contentGraphAdapter->getContentStreamId(), $nodeType, $coveredDimensionSpacePoints, $command->nodeAggregateId, @@ -219,7 +219,7 @@ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( ))); return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId) + ContentStreamEventStreamName::fromContentStreamId($contentGraphAdapter->getContentStreamId()) ->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand($command, Events::fromArray($events)), $expectedVersion @@ -227,21 +227,19 @@ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( } private function createRegularWithNode( + ContentGraphAdapterInterface $contentGraphAdapter, CreateNodeAggregateWithNodeAndSerializedProperties $command, - ContentStreamId $contentStreamId, DimensionSpacePointSet $coveredDimensionSpacePoints, SerializedPropertyValues $initialPropertyValues, - ContentRepository $contentRepository, ): NodeAggregateWithNodeWasCreated { return new NodeAggregateWithNodeWasCreated( - $contentStreamId, + $contentGraphAdapter->getContentStreamId(), $command->nodeAggregateId, $command->nodeTypeName, $command->originDimensionSpacePoint, $command->succeedingSiblingNodeAggregateId ? $this->resolveInterdimensionalSiblingsForCreation( - $contentRepository, - $contentStreamId, + $contentGraphAdapter, $command->succeedingSiblingNodeAggregateId, $command->originDimensionSpacePoint, $coveredDimensionSpacePoints @@ -260,7 +258,7 @@ private function createRegularWithNode( */ private function handleTetheredChildNodes( CreateNodeAggregateWithNodeAndSerializedProperties $command, - ContentStreamId $contentStreamId, + ContentGraphAdapterInterface $contentGraphAdapter, NodeType $nodeType, DimensionSpacePointSet $coveredDimensionSpacePoints, NodeAggregateId $parentNodeAggregateId, @@ -279,7 +277,7 @@ private function handleTetheredChildNodes( $initialPropertyValues = SerializedPropertyValues::defaultFromNodeType($childNodeType, $this->getPropertyConverter()); $events[] = new NodeAggregateWithNodeWasCreated( - $contentStreamId, + $contentGraphAdapter->getContentStreamId(), $childNodeAggregateId, $childNodeType->name, $command->originDimensionSpacePoint, @@ -292,7 +290,7 @@ private function handleTetheredChildNodes( array_push($events, ...iterator_to_array($this->handleTetheredChildNodes( $command, - $contentStreamId, + $contentGraphAdapter, $childNodeType, $coveredDimensionSpacePoints, $childNodeAggregateId, diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeDisabling/NodeDisabling.php b/Neos.ContentRepository.Core/Classes/Feature/NodeDisabling/NodeDisabling.php index 1a35de7c522..9e06e7f8c62 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeDisabling/NodeDisabling.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeDisabling/NodeDisabling.php @@ -14,7 +14,6 @@ * source code. */ -use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\DimensionSpace; use Neos\ContentRepository\Core\DimensionSpace\Exception\DimensionSpacePointNotFound; use Neos\ContentRepository\Core\EventStore\Events; @@ -47,11 +46,11 @@ abstract protected function getInterDimensionalVariationGraph(): DimensionSpace\ private function handleDisableNodeAggregate( DisableNodeAggregate $command ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId); + $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraphAdapter); $this->requireDimensionSpacePointToExist($command->coveredDimensionSpacePoint); $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraphAdapter, $command->nodeAggregateId ); $this->requireNodeAggregateToCoverDimensionSpacePoint( @@ -72,7 +71,7 @@ private function handleDisableNodeAggregate( $events = Events::with( new SubtreeWasTagged( - $contentStreamId, + $contentGraphAdapter->getContentStreamId(), $command->nodeAggregateId, $affectedDimensionSpacePoints, SubtreeTag::disabled(), @@ -80,7 +79,7 @@ private function handleDisableNodeAggregate( ); return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId) + ContentStreamEventStreamName::fromContentStreamId($contentGraphAdapter->getContentStreamId()) ->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand( $command, @@ -100,11 +99,11 @@ private function handleDisableNodeAggregate( public function handleEnableNodeAggregate( EnableNodeAggregate $command ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId); + $contentGraphAdapter = $this->getContentGraphAdapter($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraphAdapter); $this->requireDimensionSpacePointToExist($command->coveredDimensionSpacePoint); $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraphAdapter, $command->nodeAggregateId ); $this->requireNodeAggregateToCoverDimensionSpacePoint( @@ -125,7 +124,7 @@ public function handleEnableNodeAggregate( $events = Events::with( new SubtreeWasUntagged( - $contentStreamId, + $contentGraphAdapter->getContentStreamId(), $command->nodeAggregateId, $affectedDimensionSpacePoints, SubtreeTag::disabled(), @@ -133,7 +132,7 @@ public function handleEnableNodeAggregate( ); return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId)->getEventStreamName(), + ContentStreamEventStreamName::fromContentStreamId($contentGraphAdapter->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 fbf7176d8f2..f913f8ca3b2 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php @@ -14,6 +14,7 @@ namespace Neos\ContentRepository\Core\Feature\NodeDuplication; +use RuntimeException; use Neos\ContentRepository\Core\CommandHandler\CommandHandlerInterface; use Neos\ContentRepository\Core\CommandHandler\CommandInterface; use Neos\ContentRepository\Core\ContentRepository; @@ -27,6 +28,8 @@ use Neos\ContentRepository\Core\Feature\Common\InterdimensionalSiblings; use Neos\ContentRepository\Core\Feature\Common\NodeAggregateEventPublisher; use Neos\ContentRepository\Core\Feature\Common\NodeCreationInternals; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterInterface; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterProviderInterface; use Neos\ContentRepository\Core\Feature\ContentStreamEventStreamName; use Neos\ContentRepository\Core\Feature\NodeCreation\Event\NodeAggregateWithNodeWasCreated; use Neos\ContentRepository\Core\Feature\NodeDuplication\Command\CopyNodesRecursively; @@ -36,6 +39,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; /** * @internal from userland, you'll use ContentRepository::handle to dispatch commands @@ -49,9 +53,22 @@ public function __construct( private readonly NodeTypeManager $nodeTypeManager, private readonly ContentDimensionZookeeper $contentDimensionZookeeper, private readonly InterDimensionalVariationGraph $interDimensionalVariationGraph, + private readonly ContentGraphAdapterProviderInterface $contentGraphAdapterProvider ) { } + /** + * WIP Should not have this signature ;) + * + * @param WorkspaceName $workspaceName + * @return ContentGraphAdapterInterface + * + */ + protected function getContentGraphAdapter(WorkspaceName $workspaceName): ContentGraphAdapterInterface + { + return $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($workspaceName); + } + protected function getNodeTypeManager(): NodeTypeManager { return $this->nodeTypeManager; @@ -82,8 +99,8 @@ private function handleCopyNodesRecursively( CopyNodesRecursively $command ): EventsToPublish { // Basic constraints (Content Stream / Dimension Space Point / Node Type of to-be-inserted root node) - $contentStreamId = $this->requireContentStream($command->workspaceName); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId); + $contentGraphAdapter = $this->getContentGraphAdapter($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraphAdapter); $this->requireDimensionSpacePointToExist( $command->targetDimensionSpacePoint->toDimensionSpacePoint() ); @@ -94,7 +111,7 @@ 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, + $contentGraphAdapter, $nodeType, $command->targetNodeName, [$command->targetParentNodeAggregateId] @@ -102,18 +119,18 @@ private function handleCopyNodesRecursively( // Constraint: The new nodeAggregateIds are not allowed to exist yet. $this->requireNewNodeAggregateIdsToNotExist( - $contentStreamId, + $contentGraphAdapter, $command->nodeAggregateIdMapping ); // Constraint: the parent node must exist in the command's DimensionSpacePoint as well $parentNodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraphAdapter, $command->targetParentNodeAggregateId ); if ($command->targetSucceedingSiblingNodeAggregateId) { $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraphAdapter, $command->targetSucceedingSiblingNodeAggregateId ); } @@ -134,7 +151,7 @@ private function handleCopyNodesRecursively( // Constraint: The node name must be free in all these dimension space points if ($command->targetNodeName) { $this->requireNodeNameToBeUnoccupied( - $contentStreamId, + $contentGraphAdapter, $command->targetNodeName, $command->targetParentNodeAggregateId, $command->targetDimensionSpacePoint, @@ -145,7 +162,7 @@ private function handleCopyNodesRecursively( // Now, we can start creating the recursive structure. $events = []; $this->createEventsForNodeToInsert( - $contentStreamId, + $contentGraphAdapter, $command->targetDimensionSpacePoint, $coveredDimensionSpacePoints, $command->targetParentNodeAggregateId, @@ -153,13 +170,12 @@ private function handleCopyNodesRecursively( $command->targetNodeName, $command->nodeTreeToInsert, $command->nodeAggregateIdMapping, - $contentRepository, $events ); return new EventsToPublish( ContentStreamEventStreamName::fromContentStreamId( - $contentStreamId + $contentGraphAdapter->getContentStreamId() )->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand( $command, @@ -170,12 +186,12 @@ private function handleCopyNodesRecursively( } private function requireNewNodeAggregateIdsToNotExist( - ContentStreamId $contentStreamId, + ContentGraphAdapterInterface $contentGraphAdapter, Dto\NodeAggregateIdMapping $nodeAggregateIdMapping ): void { foreach ($nodeAggregateIdMapping->getAllNewNodeAggregateIds() as $nodeAggregateId) { $this->requireProjectedNodeAggregateToNotExist( - $contentStreamId, + $contentGraphAdapter, $nodeAggregateId ); } @@ -185,7 +201,7 @@ private function requireNewNodeAggregateIdsToNotExist( * @param array $events */ private function createEventsForNodeToInsert( - ContentStreamId $contentStreamId, + ContentGraphAdapterInterface $contentGraphAdapter, OriginDimensionSpacePoint $originDimensionSpacePoint, DimensionSpacePointSet $coveredDimensionSpacePoints, NodeAggregateId $targetParentNodeAggregateId, @@ -193,11 +209,10 @@ private function createEventsForNodeToInsert( ?NodeName $targetNodeName, NodeSubtreeSnapshot $nodeToInsert, Dto\NodeAggregateIdMapping $nodeAggregateIdMapping, - ContentRepository $contentRepository, array &$events, ): void { $events[] = new NodeAggregateWithNodeWasCreated( - $contentStreamId, + $contentGraphAdapter->getContentStreamId(), $nodeAggregateIdMapping->getNewNodeAggregateId( $nodeToInsert->nodeAggregateId ) ?: NodeAggregateId::create(), @@ -205,8 +220,7 @@ private function createEventsForNodeToInsert( $originDimensionSpacePoint, $targetSucceedingSiblingNodeAggregateId ? $this->resolveInterdimensionalSiblingsForCreation( - $contentRepository, - $contentStreamId, + $contentGraphAdapter, $targetSucceedingSiblingNodeAggregateId, $originDimensionSpacePoint, $coveredDimensionSpacePoints @@ -220,7 +234,7 @@ private function createEventsForNodeToInsert( foreach ($nodeToInsert->childNodes as $childNodeToInsert) { $this->createEventsForNodeToInsert( - $contentStreamId, + $contentGraphAdapter, $originDimensionSpacePoint, $coveredDimensionSpacePoints, // the just-inserted node becomes the new parent node ID @@ -232,7 +246,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 cf9fc4d541b..a92140aab23 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeModification/NodeModification.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeModification/NodeModification.php @@ -17,6 +17,7 @@ use Neos\ContentRepository\Core\EventStore\Events; use Neos\ContentRepository\Core\EventStore\EventsToPublish; use Neos\ContentRepository\Core\Feature\Common\NodeAggregateEventPublisher; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterInterface; use Neos\ContentRepository\Core\Feature\ContentStreamEventStreamName; use Neos\ContentRepository\Core\Feature\NodeModification\Command\SetNodeProperties; use Neos\ContentRepository\Core\Feature\NodeModification\Command\SetSerializedNodeProperties; @@ -28,7 +29,6 @@ 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 @@ -38,17 +38,17 @@ trait NodeModification abstract protected function requireNodeType(NodeTypeName $nodeTypeName): NodeType; abstract protected function requireProjectedNodeAggregate( - ContentStreamId $contentStreamId, + ContentGraphAdapterInterface $contentGraphAdapter, NodeAggregateId $nodeAggregateId ): NodeAggregate; private function handleSetNodeProperties( SetNodeProperties $command ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName); + $contentGraphAdapter = $this->getContentGraphAdapter($command->workspaceName); $this->requireDimensionSpacePointToExist($command->originDimensionSpacePoint->toDimensionSpacePoint()); $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraphAdapter, $command->nodeAggregateId ); $this->requireNodeAggregateToNotBeRoot($nodeAggregate); @@ -73,11 +73,11 @@ private function handleSetNodeProperties( private function handleSetSerializedNodeProperties( SetSerializedNodeProperties $command ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId); + $contentGraphAdapter = $this->getContentGraphAdapter($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraphAdapter); // Check if node exists $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraphAdapter, $command->nodeAggregateId ); $nodeType = $this->requireNodeType($nodeAggregate->nodeTypeName); @@ -92,7 +92,7 @@ private function handleSetSerializedNodeProperties( ); foreach ($affectedOrigins as $affectedOrigin) { $events[] = new NodePropertiesWereSet( - $contentStreamId, + $contentGraphAdapter->getContentStreamId(), $command->nodeAggregateId, $affectedOrigin, $nodeAggregate->getCoverageByOccupant($affectedOrigin), @@ -111,7 +111,7 @@ private function handleSetSerializedNodeProperties( ); foreach ($affectedOrigins as $affectedOrigin) { $events[] = new NodePropertiesWereSet( - $contentStreamId, + $contentGraphAdapter->getContentStreamId(), $command->nodeAggregateId, $affectedOrigin, $nodeAggregate->getCoverageByOccupant($affectedOrigin), @@ -124,7 +124,7 @@ private function handleSetSerializedNodeProperties( $events = $this->mergeSplitEvents($events); return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId) + ContentStreamEventStreamName::fromContentStreamId($contentGraphAdapter->getContentStreamId()) ->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand( $command, diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php index 78504e30814..984e2249fcd 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php @@ -14,7 +14,6 @@ namespace Neos\ContentRepository\Core\Feature\NodeMove; -use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\DimensionSpace; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\Exception\DimensionSpacePointNotFound; @@ -22,6 +21,7 @@ use Neos\ContentRepository\Core\EventStore\Events; use Neos\ContentRepository\Core\EventStore\EventsToPublish; use Neos\ContentRepository\Core\Feature\Common\NodeAggregateEventPublisher; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterInterface; use Neos\ContentRepository\Core\Feature\ContentStreamEventStreamName; use Neos\ContentRepository\Core\Feature\NodeMove\Command\MoveNodeAggregate; use Neos\ContentRepository\Core\Feature\NodeMove\Dto\CoverageNodeMoveMapping; @@ -32,22 +32,14 @@ use Neos\ContentRepository\Core\Feature\NodeMove\Dto\RelationDistributionStrategy; use Neos\ContentRepository\Core\Feature\NodeMove\Dto\SucceedingSiblingNodeMoveDestination; use Neos\ContentRepository\Core\Feature\NodeMove\Event\NodeAggregateWasMoved; -use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphIdentity; -use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface; -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; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; use Neos\ContentRepository\Core\Projection\ContentGraph\Nodes; -use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet; use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregateCurrentlyDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregateIsDescendant; use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregatesTypeIsAmbiguous; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** * @internal implementation detail of Command Handlers @@ -59,7 +51,7 @@ abstract protected function getInterDimensionalVariationGraph(): DimensionSpace\ abstract protected function areAncestorNodeTypeConstraintChecksEnabled(): bool; abstract protected function requireProjectedNodeAggregate( - ContentStreamId $contentStreamId, + ContentGraphAdapterInterface $contentGraphAdapter, NodeAggregateId $nodeAggregateId ): NodeAggregate; @@ -74,11 +66,11 @@ abstract protected function requireProjectedNodeAggregate( private function handleMoveNodeAggregate( MoveNodeAggregate $command ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId); + $contentGraphAdapter = $this->getContentGraphAdapter($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraphAdapter); $this->requireDimensionSpacePointToExist($command->dimensionSpacePoint); $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraphAdapter, $command->nodeAggregateId ); $this->requireNodeAggregateToNotBeRoot($nodeAggregate); @@ -93,21 +85,21 @@ private function handleMoveNodeAggregate( if ($command->newParentNodeAggregateId) { $this->requireConstraintsImposedByAncestorsAreMet( - $contentStreamId, + $contentGraphAdapter, $this->requireNodeType($nodeAggregate->nodeTypeName), $nodeAggregate->nodeName, [$command->newParentNodeAggregateId] ); $this->requireNodeNameToBeUncovered( - $contentStreamId, + $contentGraphAdapter, $nodeAggregate->nodeName, $command->newParentNodeAggregateId, $affectedDimensionSpacePoints ); $newParentNodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraphAdapter, $command->newParentNodeAggregateId ); @@ -117,7 +109,7 @@ private function handleMoveNodeAggregate( ); $this->requireNodeAggregateToNotBeDescendant( - $contentStreamId, + $contentGraphAdapter, $newParentNodeAggregate, $nodeAggregate ); @@ -125,13 +117,13 @@ private function handleMoveNodeAggregate( if ($command->newPrecedingSiblingNodeAggregateId) { $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraphAdapter, $command->newPrecedingSiblingNodeAggregateId ); } if ($command->newSucceedingSiblingNodeAggregateId) { $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraphAdapter, $command->newSucceedingSiblingNodeAggregateId ); } @@ -142,7 +134,7 @@ private function handleMoveNodeAggregate( $originNodeMoveMappings[] = new OriginNodeMoveMapping( $movedNodeOrigin, $this->resolveCoverageNodeMoveMappings( - $contentStreamId, + $contentGraphAdapter, $nodeAggregate, $command->newParentNodeAggregateId, $command->newPrecedingSiblingNodeAggregateId, @@ -155,14 +147,14 @@ private function handleMoveNodeAggregate( $events = Events::with( new NodeAggregateWasMoved( - $contentStreamId, + $contentGraphAdapter->getContentStreamId(), $command->nodeAggregateId, OriginNodeMoveMappings::create(...$originNodeMoveMappings) ) ); $contentStreamEventStreamName = ContentStreamEventStreamName::fromContentStreamId( - $contentStreamId + $contentGraphAdapter->getContentStreamId() ); return new EventsToPublish( @@ -183,22 +175,23 @@ private function handleMoveNodeAggregate( * @todo move to content graph for more efficient calculation, if possible */ private function resolveNewParentAssignments( - /** The content stream the move operation is performed in */ - ContentStreamId $contentStreamId, + /** Adapter with workspace<>contentStream mapping the move operation is performed in */ + ContentGraphAdapterInterface $contentGraphAdapter, /** The parent node aggregate's id*/ NodeAggregateId $parentId, DimensionSpace\DimensionSpacePoint $coveredDimensionSpacePoint ): CoverageNodeMoveMapping { - $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($contentStreamId); - $parentNode = $this->getContentGraphAdapter()->findNodeInSubgraph( - $contentStreamId, - $workspace?->workspaceName, + $parentNode = $contentGraphAdapter->findNodeInSubgraph( $coveredDimensionSpacePoint, $parentId ); if ($parentNode === null) { throw new \InvalidArgumentException( - sprintf('Parent ' . $parentId->value . ' not found in ontentstream "%s" and dimension space point "%s" ', $contentStreamId->value, json_encode($coveredDimensionSpacePoint)), + sprintf( + 'Parent ' . $parentId->value . ' not found in ontentstream "%s" and dimension space point "%s" ', + $contentGraphAdapter->getContentStreamId()->value, + json_encode($coveredDimensionSpacePoint, JSON_PARTIAL_OUTPUT_ON_ERROR) + ), 1667596931 ); } @@ -229,14 +222,13 @@ private function resolveAffectedDimensionSpacePointSet( } private function findSiblingWithin( - ContentStreamId $contentStreamId, - WorkspaceName $workspaceName, + ContentGraphAdapterInterface $contentGraphAdapter, DimensionSpace\DimensionSpacePoint $coveredDimensionSpacePoint, NodeAggregateId $siblingId, ?NodeAggregateId $parentId ): ?Node { - $siblingCandidate = $this->getContentGraphAdapter()->findNodeInSubgraph($contentStreamId, $workspaceName, $coveredDimensionSpacePoint, $siblingId); + $siblingCandidate = $contentGraphAdapter->findNodeInSubgraph($coveredDimensionSpacePoint, $siblingId); if (!$siblingCandidate) { return null; } @@ -245,10 +237,10 @@ private function findSiblingWithin( return $siblingCandidate; } - $parent = $this->getContentGraphAdapter()->findParentNodeInSubgraph($contentStreamId, $workspaceName, $coveredDimensionSpacePoint, $siblingId); + $parent = $contentGraphAdapter->findParentNodeInSubgraph($coveredDimensionSpacePoint, $siblingId); if (is_null($parent)) { throw new \InvalidArgumentException( - 'Parent ' . $parentId->value . ' not found in subgraph ' . json_encode($contentSubgraph), + 'Parent ' . $parentId->value . ' not found in subgraph ' . json_encode($coveredDimensionSpacePoint, JSON_PARTIAL_OUTPUT_ON_ERROR), 1645366837 ); } @@ -260,24 +252,23 @@ private function findSiblingWithin( } private function resolveSucceedingSiblingFromOriginSiblings( + ContentGraphAdapterInterface $contentGraphAdapter, NodeAggregateId $nodeAggregateId, ?NodeAggregateId $parentId, ?NodeAggregateId $precedingSiblingId, ?NodeAggregateId $succeedingSiblingId, - ContentStreamId $contentStreamId, - WorkspaceName $workspaceName, DimensionSpace\DimensionSpacePoint $currentDimensionSpacePoint, DimensionSpace\DimensionSpacePoint $originDimensionSpacePoint ): ?Node { $succeedingSibling = null; $precedingSiblingCandidates = iterator_to_array( $precedingSiblingId - ? $this->getContentGraphAdapter()->findPreceedingSiblingNodesInSubgraph($contentStreamId, $workspaceName, $originDimensionSpacePoint, $precedingSiblingId) + ? $contentGraphAdapter->findPreceedingSiblingNodesInSubgraph($originDimensionSpacePoint, $precedingSiblingId) : Nodes::createEmpty() ); $succeedingSiblingCandidates = iterator_to_array( $succeedingSiblingId - ? $this->getContentGraphAdapter()->findSuceedingSiblingNodesInSubgraph($contentStreamId, $workspaceName, $originDimensionSpacePoint, $succeedingSiblingId) + ? $contentGraphAdapter->findSuceedingSiblingNodesInSubgraph($originDimensionSpacePoint, $succeedingSiblingId) : Nodes::createEmpty() ); /* @var $precedingSiblingCandidates Node[] */ @@ -290,8 +281,7 @@ private function resolveSucceedingSiblingFromOriginSiblings( \array_splice($succeedingSiblingCandidates, $i, 1); } $succeedingSibling = $this->findSiblingWithin( - $contentStreamId, - $workspaceName, + $contentGraphAdapter, $currentDimensionSpacePoint, $succeedingSiblingCandidates[$i]->nodeAggregateId, $parentId @@ -306,15 +296,14 @@ private function resolveSucceedingSiblingFromOriginSiblings( \array_splice($precedingSiblingCandidates, $i, 1); } $precedingSibling = $this->findSiblingWithin( - $contentStreamId, - $workspaceName, + $contentGraphAdapter, $currentDimensionSpacePoint, $precedingSiblingCandidates[$i]->nodeAggregateId, $parentId ); if ($precedingSibling) { // TODO: I don't think implementing the same filtering as for the contentGraph is sensible here, so we are fetching all siblings while only interested in the next, maybe could become a more specialised method. - $alternateSucceedingSiblings = $this->getContentGraphAdapter()->findSuceedingSiblingNodesInSubgraph($contentStreamId, $workspaceName, $currentDimensionSpacePoint, $precedingSiblingId); + $alternateSucceedingSiblings = $contentGraphAdapter->findSuceedingSiblingNodesInSubgraph($currentDimensionSpacePoint, $precedingSiblingId); if (count($alternateSucceedingSiblings) > 0) { $succeedingSibling = $alternateSucceedingSiblings->first(); break; @@ -327,8 +316,8 @@ private function resolveSucceedingSiblingFromOriginSiblings( } private function resolveCoverageNodeMoveMappings( - /** The content stream the move operation is performed in */ - ContentStreamId $contentStreamId, + /** Adapter with workspace<>contentStream mapping the move operation is performed in */ + ContentGraphAdapterInterface $contentGraphAdapter, /** The node aggregate to be moved */ NodeAggregate $nodeAggregate, /** The parent node aggregate id, has precedence over siblings when in doubt */ @@ -344,23 +333,20 @@ private function resolveCoverageNodeMoveMappings( ): CoverageNodeMoveMappings { /** @var CoverageNodeMoveMapping[] $coverageNodeMoveMappings */ $coverageNodeMoveMappings = []; - $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($contentStreamId); foreach ( $nodeAggregate->getCoverageByOccupant($originDimensionSpacePoint) ->getIntersection($affectedDimensionSpacePoints) as $dimensionSpacePoint ) { $succeedingSibling = $succeedingSiblingId - ? $this->findSiblingWithin($contentStreamId, $workspace?->workspaceName, $originDimensionSpacePoint->toDimensionSpacePoint(), $succeedingSiblingId, $parentId) + ? $this->findSiblingWithin($contentGraphAdapter, $originDimensionSpacePoint->toDimensionSpacePoint(), $succeedingSiblingId, $parentId) : null; if (!$succeedingSibling) { $precedingSibling = $precedingSiblingId - ? $this->findSiblingWithin($contentStreamId, $workspace?->workspaceName, $originDimensionSpacePoint->toDimensionSpacePoint(), $precedingSiblingId, $parentId) + ? $this->findSiblingWithin($contentGraphAdapter, $originDimensionSpacePoint->toDimensionSpacePoint(), $precedingSiblingId, $parentId) : null; if ($precedingSiblingId && $precedingSibling) { - $alternateSucceedingSiblings = $this->getContentGraphAdapter()->findSuceedingSiblingNodesInSubgraph( - $contentStreamId, - $workspace?->workspaceName, + $alternateSucceedingSiblings = $contentGraphAdapter->findSuceedingSiblingNodesInSubgraph( $dimensionSpacePoint, $precedingSiblingId ); @@ -369,12 +355,11 @@ private function resolveCoverageNodeMoveMappings( } } else { $succeedingSibling = $this->resolveSucceedingSiblingFromOriginSiblings( + $contentGraphAdapter, $nodeAggregate->nodeAggregateId, $parentId, $precedingSiblingId, $succeedingSiblingId, - $contentStreamId, - $workspace?->workspaceName, $dimensionSpacePoint, $originDimensionSpacePoint->toDimensionSpacePoint() ); @@ -383,10 +368,14 @@ private function resolveCoverageNodeMoveMappings( if ($succeedingSibling) { // for the event payload, we additionally need the parent of the succeeding sibling - $parentOfSucceedingSibling = $this->getContentGraphAdapter()->findParentNodeInSubgraph($contentStreamId, $workspace?->workspaceName, $dimensionSpacePoint, $succeedingSibling->nodeAggregateId); + $parentOfSucceedingSibling = $contentGraphAdapter->findParentNodeInSubgraph($dimensionSpacePoint, $succeedingSibling->nodeAggregateId); if ($parentOfSucceedingSibling === null) { throw new \InvalidArgumentException( - sprintf('Parent of succeeding sibling ' . $succeedingSibling->nodeAggregateId->value . ' not found in contentstream "%s" and dimension space point "%s" ', $contentStreamId->value, json_encode($dimensionSpacePoint)), + sprintf( + 'Parent of succeeding sibling ' . $succeedingSibling->nodeAggregateId->value . ' not found in contentstream "%s" and dimension space point "%s" ', + $contentGraphAdapter->getContentStreamId()->value, + json_encode($dimensionSpacePoint, JSON_PARTIAL_OUTPUT_ON_ERROR) + ), 1667817639 ); } @@ -407,16 +396,20 @@ private function resolveCoverageNodeMoveMappings( if ($parentId === null) { // if parent ID is not given, use the parent of the original node, because we want to move // to the end of the sibling list. - $parentId = $this->getContentGraphAdapter()->findParentNodeInSubgraph($contentStreamId, $workspace?->workspaceName, $dimensionSpacePoint, $nodeAggregate->nodeAggregateId)?->nodeAggregateId; + $parentId = $contentGraphAdapter->findParentNodeInSubgraph($dimensionSpacePoint, $nodeAggregate->nodeAggregateId)?->nodeAggregateId; if ($parentId === null) { throw new \InvalidArgumentException( - sprintf('Parent ' . $parentId . ' not found in contentstream "%s" and dimension space point "%s" ', $contentStreamId->value, json_encode($dimensionSpacePoint)), + sprintf( + 'Parent ' . $parentId . ' not found in contentstream "%s" and dimension space point "%s" ', + $succeedingSibling->nodeAggregateId->value, + json_encode($dimensionSpacePoint, JSON_PARTIAL_OUTPUT_ON_ERROR) + ), 1667597013 ); } } $coverageNodeMoveMappings[] = $this->resolveNewParentAssignments( - $contentStreamId, + $contentGraphAdapter, $parentId, $dimensionSpacePoint ); diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/NodeReferencing.php b/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/NodeReferencing.php index ae91a4b24a7..c76b5dbe8e0 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/NodeReferencing.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/NodeReferencing.php @@ -18,6 +18,7 @@ use Neos\ContentRepository\Core\EventStore\EventsToPublish; use Neos\ContentRepository\Core\Feature\Common\ConstraintChecks; use Neos\ContentRepository\Core\Feature\Common\NodeAggregateEventPublisher; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterInterface; use Neos\ContentRepository\Core\Feature\ContentStreamEventStreamName; use Neos\ContentRepository\Core\Feature\NodeModification\Dto\PropertyScope; use Neos\ContentRepository\Core\Feature\NodeReferencing\Command\SetNodeReferences; @@ -27,7 +28,6 @@ use Neos\ContentRepository\Core\Feature\NodeReferencing\Event\NodeReferencesWereSet; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; /** * @internal implementation detail of Command Handlers @@ -37,7 +37,7 @@ trait NodeReferencing use ConstraintChecks; abstract protected function requireProjectedNodeAggregate( - ContentStreamId $contentStreamId, + ContentGraphAdapterInterface $contentGraphAdapter, NodeAggregateId $nodeAggregateId ): NodeAggregate; @@ -45,10 +45,10 @@ abstract protected function requireProjectedNodeAggregate( private function handleSetNodeReferences( SetNodeReferences $command ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName); + $contentGraphAdapter = $this->getContentGraphAdapter($command->workspaceName); $this->requireDimensionSpacePointToExist($command->sourceOriginDimensionSpacePoint->toDimensionSpacePoint()); $sourceNodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraphAdapter, $command->sourceNodeAggregateId ); $this->requireNodeAggregateToNotBeRoot($sourceNodeAggregate); @@ -93,13 +93,13 @@ private function handleSetNodeReferences( private function handleSetSerializedNodeReferences( SetSerializedNodeReferences $command ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId); + $contentGraphAdapter = $this->getContentGraphAdapter($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraphAdapter); $this->requireDimensionSpacePointToExist( $command->sourceOriginDimensionSpacePoint->toDimensionSpacePoint() ); $sourceNodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraphAdapter, $command->sourceNodeAggregateId ); $this->requireNodeAggregateToNotBeRoot($sourceNodeAggregate); @@ -112,7 +112,7 @@ private function handleSetSerializedNodeReferences( foreach ($command->references as $reference) { assert($reference instanceof SerializedNodeReference); $destinationNodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraphAdapter, $reference->targetNodeAggregateId ); $this->requireNodeAggregateToNotBeRoot($destinationNodeAggregate); @@ -139,7 +139,7 @@ private function handleSetSerializedNodeReferences( $events = Events::with( new NodeReferencesWereSet( - $contentStreamId, + $contentGraphAdapter->getContentStreamId(), $command->sourceNodeAggregateId, $affectedOrigins, $command->referenceName, @@ -148,7 +148,7 @@ private function handleSetSerializedNodeReferences( ); return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId) + ContentStreamEventStreamName::fromContentStreamId($contentGraphAdapter->getContentStreamId()) ->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand( $command, diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeRemoval/NodeRemoval.php b/Neos.ContentRepository.Core/Classes/Feature/NodeRemoval/NodeRemoval.php index dd3ce6db474..177e38bc9d9 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeRemoval/NodeRemoval.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeRemoval/NodeRemoval.php @@ -14,7 +14,6 @@ namespace Neos\ContentRepository\Core\Feature\NodeRemoval; -use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\DimensionSpace; use Neos\ContentRepository\Core\DimensionSpace\Exception\DimensionSpacePointNotFound; use Neos\ContentRepository\Core\EventStore\Events; @@ -48,10 +47,10 @@ abstract protected function areAncestorNodeTypeConstraintChecksEnabled(): bool; private function handleRemoveNodeAggregate( RemoveNodeAggregate $command ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId); + $contentGraphAdapter = $this->getContentGraphAdapter($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraphAdapter); $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraphAdapter, $command->nodeAggregateId ); $this->requireDimensionSpacePointToExist($command->coveredDimensionSpacePoint); @@ -62,14 +61,14 @@ private function handleRemoveNodeAggregate( ); if ($command->removalAttachmentPoint instanceof NodeAggregateId) { $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraphAdapter, $command->removalAttachmentPoint ); } $events = Events::with( new NodeAggregateWasRemoved( - $contentStreamId, + $contentGraphAdapter->getContentStreamId(), $command->nodeAggregateId, $command->nodeVariantSelectionStrategy->resolveAffectedOriginDimensionSpacePoints( $nodeAggregate->getOccupationByCovered($command->coveredDimensionSpacePoint), @@ -86,7 +85,7 @@ private function handleRemoveNodeAggregate( ); return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId) + ContentStreamEventStreamName::fromContentStreamId($contentGraphAdapter->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 7f3fb7f3797..d3f259c26eb 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php @@ -32,18 +32,18 @@ trait NodeRenaming private function handleChangeNodeAggregateName(ChangeNodeAggregateName $command): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId); + $contentGraphAdapter = $this->getContentGraphAdapter($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraphAdapter); $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraphAdapter, $command->nodeAggregateId ); $this->requireNodeAggregateToNotBeRoot($nodeAggregate, 'and Root Node Aggregates cannot be renamed'); $this->requireNodeAggregateToBeUntethered($nodeAggregate); - foreach ($this->getContentGraphAdapter()->findParentNodeAggregates($contentStreamId, $command->workspaceName, $command->nodeAggregateId) as $parentNodeAggregate) { + foreach ($contentGraphAdapter->findParentNodeAggregates($command->nodeAggregateId) as $parentNodeAggregate) { foreach ($parentNodeAggregate->occupiedDimensionSpacePoints as $occupiedParentDimensionSpacePoint) { $this->requireNodeNameToBeUnoccupied( - $contentStreamId, + $contentGraphAdapter, $command->newNodeName, $parentNodeAggregate->nodeAggregateId, $occupiedParentDimensionSpacePoint, @@ -54,14 +54,14 @@ private function handleChangeNodeAggregateName(ChangeNodeAggregateName $command) $events = Events::with( new NodeAggregateNameWasChanged( - $contentStreamId, + $contentGraphAdapter->getContentStreamId(), $command->nodeAggregateId, $command->newNodeName, ), ); return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId)->getEventStreamName(), + ContentStreamEventStreamName::fromContentStreamId($contentGraphAdapter->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 a38d780b880..afdccb7791e 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php @@ -14,13 +14,13 @@ namespace Neos\ContentRepository\Core\Feature\NodeTypeChange; -use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePointSet; use Neos\ContentRepository\Core\EventStore\Events; use Neos\ContentRepository\Core\EventStore\EventsToPublish; use Neos\ContentRepository\Core\Feature\Common\NodeAggregateEventPublisher; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterInterface; use Neos\ContentRepository\Core\Feature\ContentStreamEventStreamName; use Neos\ContentRepository\Core\Feature\NodeRemoval\Event\NodeAggregateWasRemoved; use Neos\ContentRepository\Core\Feature\NodeTypeChange\Command\ChangeNodeAggregateType; @@ -31,14 +31,12 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; use Neos\ContentRepository\Core\Projection\ContentGraph\NodePath; -use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregatesTypeIsAmbiguous; use Neos\ContentRepository\Core\SharedModel\Exception\NodeConstraintException; use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeNotFound; 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,12 +49,12 @@ trait NodeTypeChange abstract protected function getNodeTypeManager(): NodeTypeManager; abstract protected function requireProjectedNodeAggregate( - ContentStreamId $contentStreamId, + ContentGraphAdapterInterface $contentRepositoryAdapter, NodeAggregateId $nodeAggregateId ): NodeAggregate; abstract protected function requireConstraintsImposedByAncestorsAreMet( - ContentStreamId $contentStreamId, + ContentGraphAdapterInterface $contentGraphAdapter, NodeType $nodeType, ?NodeName $nodeName, array $parentNodeAggregateIds @@ -87,6 +85,7 @@ abstract protected function areNodeTypeConstraintsImposedByGrandparentValid( ): bool; abstract protected function createEventsForMissingTetheredNode( + ContentGraphAdapterInterface $contentGraphAdapter, NodeAggregate $parentNodeAggregate, OriginDimensionSpacePoint $originDimensionSpacePoint, NodeName $tetheredNodeName, @@ -99,6 +98,7 @@ abstract protected function createEventsForMissingTetheredNode( * @throws NodeConstraintException * @throws NodeTypeNotFoundException * @throws NodeAggregatesTypeIsAmbiguous + * @throws \Exception */ private function handleChangeNodeAggregateType( ChangeNodeAggregateType $command @@ -107,11 +107,11 @@ private function handleChangeNodeAggregateType( * Constraint checks **************/ // existence of content stream, node type and node aggregate - $contentStreamId = $this->requireContentStream($command->workspaceName); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId); + $contentGraphAdapter = $this->getContentGraphAdapter($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraphAdapter); $newNodeType = $this->requireNodeType($command->newNodeTypeName); $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraphAdapter, $command->nodeAggregateId ); @@ -120,17 +120,14 @@ private function handleChangeNodeAggregateType( $this->requireTetheredDescendantNodeTypesToExist($newNodeType); $this->requireTetheredDescendantNodeTypesToNotBeOfTypeRoot($newNodeType); - $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($nodeAggregate->contentStreamId); // the new node type must be allowed at this position in the tree - $parentNodeAggregates = $this->getContentGraphAdapter()->findParentNodeAggregates( - $nodeAggregate->contentStreamId, - $workspace?->workspaceName, + $parentNodeAggregates = $contentGraphAdapter->findParentNodeAggregates( $nodeAggregate->nodeAggregateId ); foreach ($parentNodeAggregates as $parentNodeAggregate) { assert($parentNodeAggregate instanceof NodeAggregate); $this->requireConstraintsImposedByAncestorsAreMet( - $contentStreamId, + $contentGraphAdapter, $newNodeType, $nodeAggregate->nodeName, [$parentNodeAggregate->nodeAggregateId] @@ -140,7 +137,7 @@ private function handleChangeNodeAggregateType( /** @codingStandardsIgnoreStart */ match ($command->strategy) { NodeAggregateTypeChangeChildConstraintConflictResolutionStrategy::STRATEGY_HAPPY_PATH - => $this->requireConstraintsImposedByHappyPathStrategyAreMet($nodeAggregate, $newNodeType), + => $this->requireConstraintsImposedByHappyPathStrategyAreMet($contentGraphAdapter, $nodeAggregate, $newNodeType), NodeAggregateTypeChangeChildConstraintConflictResolutionStrategy::STRATEGY_DELETE => null }; /** @codingStandardsIgnoreStop */ @@ -161,7 +158,7 @@ private function handleChangeNodeAggregateType( **************/ $events = [ new NodeAggregateTypeWasChanged( - $contentStreamId, + $contentGraphAdapter->getContentStreamId(), $command->nodeAggregateId, $command->newNodeTypeName ), @@ -170,10 +167,12 @@ private function handleChangeNodeAggregateType( // remove disallowed nodes if ($command->strategy === NodeAggregateTypeChangeChildConstraintConflictResolutionStrategy::STRATEGY_DELETE) { array_push($events, ...iterator_to_array($this->deleteDisallowedNodesWhenChangingNodeType( + $contentGraphAdapter, $nodeAggregate, $newNodeType ))); array_push($events, ...iterator_to_array($this->deleteObsoleteTetheredNodesWhenChangingNodeType( + $contentGraphAdapter, $nodeAggregate, $newNodeType ))); @@ -183,14 +182,10 @@ private function handleChangeNodeAggregateType( $expectedTetheredNodes = $this->getNodeTypeManager()->getTetheredNodesConfigurationForNodeType($newNodeType); foreach ($nodeAggregate->getNodes() as $node) { assert($node instanceof Node); - $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($node->subgraphIdentity->contentStreamId); - foreach ($expectedTetheredNodes as $serializedTetheredNodeName => $expectedTetheredNodeType) { $tetheredNodeName = NodeName::fromString($serializedTetheredNodeName); - $tetheredNode = $this->getContentGraphAdapter()->findChildNodeByNameInSubgraph( - $node->subgraphIdentity->contentStreamId, - $workspace?->workspaceName, + $tetheredNode = $contentGraphAdapter->findChildNodeByNameInSubgraph( $node->originDimensionSpacePoint->toDimensionSpacePoint(), $node->nodeAggregateId, $tetheredNodeName @@ -201,6 +196,7 @@ private function handleChangeNodeAggregateType( ->getNodeAggregateId(NodePath::fromString($tetheredNodeName->value)) ?: NodeAggregateId::create(); array_push($events, ...iterator_to_array($this->createEventsForMissingTetheredNode( + $contentGraphAdapter, $nodeAggregate, $node->originDimensionSpacePoint, $tetheredNodeName, @@ -212,7 +208,7 @@ private function handleChangeNodeAggregateType( } return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId)->getEventStreamName(), + ContentStreamEventStreamName::fromContentStreamId($contentGraphAdapter->getContentStreamId())->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand( $command, Events::fromArray($events), @@ -228,17 +224,13 @@ private function handleChangeNodeAggregateType( * @throws NodeConstraintException|NodeTypeNotFoundException */ private function requireConstraintsImposedByHappyPathStrategyAreMet( + ContentGraphAdapterInterface $contentGraphAdapter, NodeAggregate $nodeAggregate, NodeType $newNodeType ): void { - // TODO: Use workspaceName from $nodeAggregate - $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($nodeAggregate->contentStreamId); - // 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 = $this->getContentGraphAdapter()->findChildNodeAggregates( - $nodeAggregate->contentStreamId, - $workspace?->workspaceName, + $childNodeAggregates = $contentGraphAdapter->findChildNodeAggregates( $nodeAggregate->nodeAggregateId ); foreach ($childNodeAggregates as $childNodeAggregate) { @@ -253,14 +245,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. - - // TODO: use workspaceName from $childNodeAggregate instead - $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($childNodeAggregate->contentStreamId); // 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 = $this->getContentGraphAdapter()->findChildNodeAggregates( - $childNodeAggregate->contentStreamId, - $workspace?->workspaceName, + $grandchildNodeAggregates = $contentGraphAdapter->findChildNodeAggregates( $childNodeAggregate->nodeAggregateId ); foreach ($grandchildNodeAggregates as $grandchildNodeAggregate) { @@ -282,19 +269,14 @@ private function requireConstraintsImposedByHappyPathStrategyAreMet( * needs to be modified as well (as they are structurally the same) */ private function deleteDisallowedNodesWhenChangingNodeType( + ContentGraphAdapterInterface $contentGraphAdapter, NodeAggregate $nodeAggregate, NodeType $newNodeType ): Events { $events = []; - - // TODO: use workspaceName from $childNodeAggregate instead - $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($nodeAggregate->contentStreamId); - // 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 = $this->getContentGraphAdapter()->findChildNodeAggregates( - $nodeAggregate->contentStreamId, - $workspace?->workspaceName, + $childNodeAggregates = $contentGraphAdapter->findChildNodeAggregates( $nodeAggregate->nodeAggregateId ); foreach ($childNodeAggregates as $childNodeAggregate) { @@ -312,6 +294,7 @@ 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( + $contentGraphAdapter, $nodeAggregate, $childNodeAggregate ); @@ -324,17 +307,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. - - // TODO: use workspaceName from $childNodeAggregate instead - $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($childNodeAggregate->contentStreamId); - // 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 = $this->getContentGraphAdapter()->findChildNodeAggregates( - $childNodeAggregate->contentStreamId, - $workspace?->workspaceName, - $childNodeAggregate->nodeAggregateId - ); + $grandchildNodeAggregates = $contentGraphAdapter->findChildNodeAggregates($childNodeAggregate->nodeAggregateId); foreach ($grandchildNodeAggregates as $grandchildNodeAggregate) { /* @var $grandchildNodeAggregate NodeAggregate */ // we do not need to test for the parent of grandchild (=child), @@ -351,6 +326,7 @@ 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( + $contentGraphAdapter, $childNodeAggregate, $grandchildNodeAggregate ); @@ -367,6 +343,7 @@ private function deleteDisallowedNodesWhenChangingNodeType( } private function deleteObsoleteTetheredNodesWhenChangingNodeType( + ContentGraphAdapterInterface $contentGraphAdapter, NodeAggregate $nodeAggregate, NodeType $newNodeType ): Events { @@ -374,12 +351,7 @@ private function deleteObsoleteTetheredNodesWhenChangingNodeType( $events = []; // find disallowed tethered nodes - $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($nodeAggregate->contentStreamId); - $tetheredNodeAggregates = $this->getContentGraphAdapter()->findTetheredChildNodeAggregates( - $nodeAggregate->contentStreamId, - $workspace?->workspaceName, - $nodeAggregate->nodeAggregateId - ); + $tetheredNodeAggregates = $contentGraphAdapter->findTetheredChildNodeAggregates($nodeAggregate->nodeAggregateId); foreach ($tetheredNodeAggregates as $tetheredNodeAggregate) { /* @var $tetheredNodeAggregate NodeAggregate */ @@ -387,6 +359,7 @@ 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( + $contentGraphAdapter, $nodeAggregate, $tetheredNodeAggregate ); @@ -425,16 +398,13 @@ private function deleteObsoleteTetheredNodesWhenChangingNodeType( * we originated from) */ private function findDimensionSpacePointsConnectingParentAndChildAggregate( + ContentGraphAdapterInterface $contentGraphAdapter, NodeAggregate $parentNodeAggregate, NodeAggregate $childNodeAggregate ): DimensionSpacePointSet { $points = []; foreach ($childNodeAggregate->coveredDimensionSpacePoints as $coveredDimensionSpacePoint) { - // TODO: Use child node workspaceName here instead - $workspace = $this->getContentGraphAdapter()->findWorkspaceByCurrentContentStreamId($childNodeAggregate->contentStreamId); - $parentNode = $this->getContentGraphAdapter()->findParentNodeInSubgraph( - $childNodeAggregate->contentStreamId, - $workspace?->workspaceName, + $parentNode = $contentGraphAdapter->findParentNodeInSubgraph( $coveredDimensionSpacePoint, $childNodeAggregate->nodeAggregateId ); diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeVariation/NodeVariation.php b/Neos.ContentRepository.Core/Classes/Feature/NodeVariation/NodeVariation.php index 6c2aca49977..c4e1f6545f4 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeVariation/NodeVariation.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeVariation/NodeVariation.php @@ -14,7 +14,6 @@ namespace Neos\ContentRepository\Core\Feature\NodeVariation; -use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\DimensionSpace\Exception\DimensionSpacePointNotFound; use Neos\ContentRepository\Core\EventStore\EventsToPublish; use Neos\ContentRepository\Core\Feature\Common\ConstraintChecks; @@ -49,10 +48,10 @@ trait NodeVariation private function handleCreateNodeVariant( CreateNodeVariant $command ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId); + $contentGraphAdapter = $this->getContentGraphAdapter($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraphAdapter); $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraphAdapter, $command->nodeAggregateId ); // we do this check first, because it gives a more meaningful error message on what you need to do. @@ -64,7 +63,7 @@ private function handleCreateNodeVariant( $this->requireNodeAggregateToOccupyDimensionSpacePoint($nodeAggregate, $command->sourceOrigin); $this->requireNodeAggregateToNotOccupyDimensionSpacePoint($nodeAggregate, $command->targetOrigin); $parentNodeAggregate = $this->requireProjectedParentNodeAggregate( - $contentStreamId, + $contentGraphAdapter, $command->nodeAggregateId, $command->sourceOrigin ); @@ -74,14 +73,14 @@ private function handleCreateNodeVariant( ); $events = $this->createEventsForVariations( - $contentStreamId, + $contentGraphAdapter, $command->sourceOrigin, $command->targetOrigin, $nodeAggregate ); return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId)->getEventStreamName(), + ContentStreamEventStreamName::fromContentStreamId($contentGraphAdapter->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 601484ed066..7818bd70570 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/RootNodeCreation/RootNodeHandling.php +++ b/Neos.ContentRepository.Core/Classes/Feature/RootNodeCreation/RootNodeHandling.php @@ -14,7 +14,6 @@ namespace Neos\ContentRepository\Core\Feature\RootNodeCreation; -use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\EventStore\Events; @@ -69,18 +68,18 @@ abstract protected function requireNodeTypeToBeOfTypeRoot(NodeType $nodeType): v private function handleCreateRootNodeAggregateWithNode( CreateRootNodeAggregateWithNode $command ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId); + $contentGraphAdapter = $this->getContentGraphAdapter($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraphAdapter); $this->requireProjectedNodeAggregateToNotExist( - $contentStreamId, + $contentGraphAdapter, $command->nodeAggregateId ); $nodeType = $this->requireNodeType($command->nodeTypeName); $this->requireNodeTypeToNotBeAbstract($nodeType); $this->requireNodeTypeToBeOfTypeRoot($nodeType); $this->requireRootNodeTypeToBeUnoccupied( - $nodeType->name, - $contentStreamId + $contentGraphAdapter, + $nodeType->name ); $descendantNodeAggregateIds = $command->tetheredDescendantNodeAggregateIds->completeForNodeOfType( @@ -94,14 +93,14 @@ private function handleCreateRootNodeAggregateWithNode( $events = [ $this->createRootWithNode( $command, - $contentStreamId, + $contentGraphAdapter->getContentStreamId(), $this->getAllowedDimensionSubspace() ) ]; foreach ($this->getInterDimensionalVariationGraph()->getRootGeneralizations() as $rootGeneralization) { array_push($events, ...iterator_to_array($this->handleTetheredRootChildNodes( - $contentStreamId, + $contentGraphAdapter->getContentStreamId(), $nodeType, OriginDimensionSpacePoint::fromDimensionSpacePoint($rootGeneralization), $this->getInterDimensionalVariationGraph()->getSpecializationSet($rootGeneralization, true), @@ -111,7 +110,7 @@ private function handleCreateRootNodeAggregateWithNode( ))); } - $contentStreamEventStream = ContentStreamEventStreamName::fromContentStreamId($contentStreamId); + $contentStreamEventStream = ContentStreamEventStreamName::fromContentStreamId($contentGraphAdapter->getContentStreamId()); return new EventsToPublish( $contentStreamEventStream->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand( @@ -143,10 +142,10 @@ private function createRootWithNode( private function handleUpdateRootNodeAggregateDimensions( UpdateRootNodeAggregateDimensions $command ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId); + $contentGraphAdapter = $this->getContentGraphAdapter($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraphAdapter); $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraphAdapter, $command->nodeAggregateId ); if (!$nodeAggregate->classification->isRoot()) { @@ -155,14 +154,14 @@ private function handleUpdateRootNodeAggregateDimensions( $events = Events::with( new RootNodeAggregateDimensionsWereUpdated( - $contentStreamId, + $contentGraphAdapter->getContentStreamId(), $command->nodeAggregateId, $this->getAllowedDimensionSubspace() ) ); $contentStreamEventStream = ContentStreamEventStreamName::fromContentStreamId( - $contentStreamId + $contentGraphAdapter->getContentStreamId() ); return new EventsToPublish( $contentStreamEventStream->getEventStreamName(), diff --git a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php index e922dd04c01..8c3c8a3eb0c 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php @@ -24,7 +24,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; @@ -88,7 +87,7 @@ public function __construct( private EventPersister $eventPersister, private EventStoreInterface $eventStore, private EventNormalizer $eventNormalizer, - private ContentGraphAdapterInterface $contentGraphAdapter + private ContentGraphAdapterProviderInterface $contentGraphAdapterProvider ) { } @@ -125,28 +124,32 @@ private function handleCreateWorkspace( CreateWorkspace $command, ContentRepository $contentRepository, ): EventsToPublish { - $existingWorkspace = $this->contentGraphAdapter->findWorkspaceByName($command->workspaceName); - if ($existingWorkspace !== null) { - throw new WorkspaceAlreadyExists(sprintf( - 'The workspace %s already exists', - $command->workspaceName->value - ), 1505830958921); + try { + $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($command->workspaceName); + } catch (WorkspaceDoesNotExist $e) { + // Desired outcome } - $baseWorkspace = $this->contentGraphAdapter->findWorkspaceByName($command->baseWorkspaceName); - if ($baseWorkspace === null) { - throw new BaseWorkspaceDoesNotExist(sprintf( - 'The workspace %s (base workspace of %s) does not exist', - $command->baseWorkspaceName->value, - $command->workspaceName->value - ), 1513890708); - } + isset($contentGraphAdapter) + && throw new WorkspaceAlreadyExists(sprintf( + 'The workspace %s already exists', + $command->workspaceName->value + ), 1505830958921); + + $baseWorkspaceContentGraphAdapter = $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($command->baseWorkspaceName); +// if ($baseWorkspace === null) { +// throw new BaseWorkspaceDoesNotExist(sprintf( +// 'The workspace %s (base workspace of %s) does not exist', +// $command->baseWorkspaceName->value, +// $command->workspaceName->value +// ), 1513890708); +// } // When the workspace is created, we first have to fork the content stream $contentRepository->handle( ForkContentStream::create( $command->newContentStreamId, - $baseWorkspace->currentContentStreamId, + $baseWorkspaceContentGraphAdapter->getContentStreamId(), ) )->block(); @@ -200,14 +203,18 @@ private function handleCreateRootWorkspace( CreateRootWorkspace $command, ContentRepository $contentRepository, ): EventsToPublish { - $existingWorkspace = $this->contentGraphAdapter->findWorkspaceByName($command->workspaceName); - if ($existingWorkspace !== null) { - throw new WorkspaceAlreadyExists(sprintf( - 'The workspace %s already exists', - $command->workspaceName->value - ), 1505848624450); + try { + $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($command->workspaceName); + } catch (WorkspaceDoesNotExist $e) { + // Desired outcome } + isset($contentGraphAdapter) + && throw new WorkspaceAlreadyExists(sprintf( + 'The workspace %s already exists', + $command->workspaceName->value + ), 1505848624450); + $newContentStreamId = $command->newContentStreamId; $contentRepository->handle( CreateContentStream::create( @@ -385,25 +392,22 @@ private function handleRebaseWorkspace( // - extract the commands from the to-be-rebased content stream; and applies them on the new content stream $originalCommands = $this->extractCommandsFromContentStreamMetadata($workspaceContentStreamName); $rebaseStatistics = new WorkspaceRebaseStatistics(); - ContentStreamIdOverride::applyContentStreamIdToClosure( - $command->rebasedContentStreamId, - function () use ($originalCommands, $contentRepository, $rebaseStatistics): void { - foreach ($originalCommands as $i => $originalCommand) { - // We no longer need to adjust commands as the workspace stays the same - try { - $contentRepository->handle($originalCommand)->block(); - // if we came this far, we know the command was applied successfully. - $rebaseStatistics->commandRebaseSuccess(); - } catch (\Exception $e) { - $rebaseStatistics->commandRebaseError(sprintf( - "Error with command %s in sequence-number %d", - get_class($originalCommand), - $i - ), $e); - } + $this->contentGraphAdapterProvider->overrideContentStreamId($command->workspaceName, $command->rebasedContentStreamId, function () use ($originalCommands, $contentRepository, $rebaseStatistics): void { + foreach ($originalCommands as $i => $originalCommand) { + // We no longer need to adjust commands as the workspace stays the same + try { + $contentRepository->handle($originalCommand)->block(); + // if we came this far, we know the command was applied successfully. + $rebaseStatistics->commandRebaseSuccess(); + } catch (\Exception $e) { + $rebaseStatistics->commandRebaseError(sprintf( + "Error with command %s in sequence-number %d", + get_class($originalCommand), + $i + ), $e); } } - ); + }); // if we got so far without an Exception, we can switch the Workspace's active Content stream. if ($command->rebaseErrorHandlingStrategy === RebaseErrorHandlingStrategy::STRATEGY_FORCE || $rebaseStatistics->hasErrors() === false) { @@ -487,9 +491,10 @@ private function handlePublishIndividualNodesFromWorkspace( PublishIndividualNodesFromWorkspace $command, ContentRepository $contentRepository, ): EventsToPublish { + $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($command->workspaceName); $workspace = $this->requireWorkspace($command->workspaceName); $oldWorkspaceContentStreamId = $workspace->currentContentStreamId; - $oldWorkspaceContentStreamIdState = $this->contentGraphAdapter->findStateForContentStream($oldWorkspaceContentStreamId); + $oldWorkspaceContentStreamIdState = $contentGraphAdapter->findStateForContentStream(); if ($oldWorkspaceContentStreamIdState === null) { throw new \DomainException('Cannot publish nodes on a workspace with a stateless content stream', 1710410114); } @@ -497,7 +502,7 @@ private function handlePublishIndividualNodesFromWorkspace( // 1) close old content stream $contentRepository->handle( - CloseContentStream::create($oldWorkspaceContentStreamId) + CloseContentStream::create($contentGraphAdapter->getContentStreamId()) ); // 2) separate commands in two parts - the ones MATCHING the nodes from the command, and the REST @@ -518,7 +523,8 @@ private function handlePublishIndividualNodesFromWorkspace( try { // 4) using the new content stream, apply the matching commands - ContentStreamIdOverride::applyContentStreamIdToClosure( + $this->contentGraphAdapterProvider->overrideContentStreamId( + $command->workspaceName, $command->contentStreamIdForMatchingPart, function () use ($matchingCommands, $contentRepository, $baseWorkspace): void { foreach ($matchingCommands as $matchingCommand) { @@ -533,8 +539,7 @@ function () use ($matchingCommands, $contentRepository, $baseWorkspace): void { $baseWorkspace->workspaceName, ))->block(); } - } - ); + }); // 5) take EVENTS(MATCHING) and apply them to base WS. $this->publishContentStream( @@ -551,7 +556,8 @@ function () use ($matchingCommands, $contentRepository, $baseWorkspace): void { )->block(); // 7) apply REMAINING commands to the workspace's new content stream - ContentStreamIdOverride::applyContentStreamIdToClosure( + $this->contentGraphAdapterProvider->overrideContentStreamId( + $command->workspaceName, $command->contentStreamIdForRemainingPart, function () use ($contentRepository, $remainingCommands) { foreach ($remainingCommands as $remainingCommand) { @@ -654,7 +660,8 @@ private function handleDiscardIndividualNodesFromWorkspace( // 4) using the new content stream, apply the commands to keep try { - ContentStreamIdOverride::applyContentStreamIdToClosure( + $this->contentGraphAdapterProvider->overrideContentStreamId( + $command->workspaceName, $command->newContentStreamId, function () use ($commandsToKeep, $contentRepository, $baseWorkspace): void { foreach ($commandsToKeep as $matchingCommand) { diff --git a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php index 2879e8ba84b..eb447e8b929 100644 --- a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php +++ b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php @@ -4,12 +4,13 @@ namespace Neos\ContentRepository\StructureAdjustment\Adjustment; -use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\DimensionSpace; use Neos\ContentRepository\Core\EventStore\Events; use Neos\ContentRepository\Core\EventStore\EventsToPublish; use Neos\ContentRepository\Core\Feature\Common\NodeVariationInternals; use Neos\ContentRepository\Core\Feature\Common\TetheredNodeInternals; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterInterface; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterProviderInterface; use Neos\ContentRepository\Core\Feature\ContentStreamEventStreamName; use Neos\ContentRepository\Core\Feature\NodeMove\Dto\CoverageNodeMoveMapping; use Neos\ContentRepository\Core\Feature\NodeMove\Dto\CoverageNodeMoveMappings; @@ -21,12 +22,11 @@ use Neos\ContentRepository\Core\NodeType\NodeType; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\NodeType\NodeTypeName; -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\NodeAggregateId; 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 @@ -36,7 +36,7 @@ class TetheredNodeAdjustments use TetheredNodeInternals; public function __construct( - private readonly ContentRepository $contentRepository, + private readonly ContentGraphAdapterProviderInterface $contentGraphAdapterProvider, private readonly ProjectedNodeIterator $projectedNodeIterator, private readonly NodeTypeManager $nodeTypeManager, private readonly DimensionSpace\InterDimensionalVariationGraph $interDimensionalVariationGraph, @@ -57,6 +57,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 + $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveWorkspaceNameAndGet($nodeAggregate->contentStreamId); // find missing tethered nodes $foundMissingOrDisallowedTetheredNodes = false; $originDimensionSpacePoints = $nodeType->isOfType(NodeTypeName::ROOT_NODE_TYPE_NAME) @@ -69,14 +71,10 @@ public function findAdjustmentsForNodeType(NodeTypeName $nodeTypeName): \Generat foreach ($expectedTetheredNodes as $tetheredNodeName => $expectedTetheredNodeType) { $tetheredNodeName = NodeName::fromString($tetheredNodeName); - $subgraph = $this->contentRepository->getContentGraph()->getSubgraph( - $nodeAggregate->contentStreamId, + $tetheredNode = $contentGraphAdapter->findChildNodeByNameInSubgraph( $originDimensionSpacePoint->toDimensionSpacePoint(), - VisibilityConstraints::withoutRestrictions() - ); - $tetheredNode = $subgraph->findNodeByPath( - $tetheredNodeName, $nodeAggregate->nodeAggregateId, + $tetheredNodeName, ); if ($tetheredNode === null) { $foundMissingOrDisallowedTetheredNodes = true; @@ -88,8 +86,9 @@ 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, $contentGraphAdapter) { $events = $this->createEventsForMissingTetheredNode( + $contentGraphAdapter, $nodeAggregate, $originDimensionSpacePoint, $tetheredNodeName, @@ -113,8 +112,7 @@ function () use ($nodeAggregate, $originDimensionSpacePoint, $tetheredNodeName, } // find disallowed tethered nodes - $tetheredNodeAggregates = $this->contentRepository->getContentGraph()->findTetheredChildNodeAggregates( - $nodeAggregate->contentStreamId, + $tetheredNodeAggregates = $contentGraphAdapter->findTetheredChildNodeAggregates( $nodeAggregate->nodeAggregateId ); foreach ($tetheredNodeAggregates as $tetheredNodeAggregate) { @@ -136,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 = $contentGraphAdapter->findChildNodesInSubgraph($originDimensionSpacePoint->toDimensionSpacePoint(), $nodeAggregate->nodeAggregateId); /** is indexed by node name, and the value is the tethered node itself */ $actualTetheredChildNodes = []; @@ -274,4 +267,9 @@ private function reorderNodes( ExpectedVersion::ANY() ); } + + protected function getContentGraphAdapter(WorkspaceName $workspaceName): ContentGraphAdapterInterface + { + return $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($workspaceName); + } } diff --git a/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentService.php b/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentService.php index 3f50d52afe7..517be4a1cbe 100644 --- a/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentService.php +++ b/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentService.php @@ -9,6 +9,7 @@ use Neos\ContentRepository\Core\EventStore\EventPersister; use Neos\ContentRepository\Core\EventStore\EventsToPublish; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterProviderInterface; use Neos\ContentRepository\Core\Infrastructure\Property\PropertyConverter; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\NodeType\NodeTypeName; @@ -33,7 +34,8 @@ public function __construct( private readonly EventPersister $eventPersister, NodeTypeManager $nodeTypeManager, InterDimensionalVariationGraph $interDimensionalVariationGraph, - PropertyConverter $propertyConverter + PropertyConverter $propertyConverter, + ContentGraphAdapterProviderInterface $contentGraphAdapterProvider ) { $projectedNodeIterator = new ProjectedNodeIterator( $contentRepository->getWorkspaceFinder(), @@ -41,7 +43,7 @@ public function __construct( ); $this->tetheredNodeAdjustments = new TetheredNodeAdjustments( - $contentRepository, + $contentGraphAdapterProvider, $projectedNodeIterator, $nodeTypeManager, $interDimensionalVariationGraph, diff --git a/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentServiceFactory.php b/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentServiceFactory.php index 3363a2ec7fb..6b09142cca7 100644 --- a/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentServiceFactory.php +++ b/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentServiceFactory.php @@ -19,7 +19,8 @@ public function build(ContentRepositoryServiceFactoryDependencies $serviceFactor $serviceFactoryDependencies->eventPersister, $serviceFactoryDependencies->nodeTypeManager, $serviceFactoryDependencies->interDimensionalVariationGraph, - $serviceFactoryDependencies->propertyConverter + $serviceFactoryDependencies->propertyConverter, + $serviceFactoryDependencies->contentGraphAdapterProvider ); } } diff --git a/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php b/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php index 862b9d9b2e6..cc7fc0e1cbe 100644 --- a/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php +++ b/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php @@ -10,7 +10,6 @@ use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; use Neos\ContentRepository\Core\Factory\ProjectionsAndCatchUpHooksFactory; use Neos\ContentRepository\Core\Feature\ContentGraphAdapterProviderFactoryInterface; -use Neos\ContentRepository\Core\Feature\ContentGraphAdapterInterface; use Neos\ContentRepository\Core\Feature\ContentGraphAdapterProviderInterface; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\Projection\CatchUpHookFactoryInterface; From 2f057458527f54ffb68e7d891046a2a8364b97e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Sat, 13 Apr 2024 16:26:06 +0200 Subject: [PATCH 05/18] ContentGraphAdapter working implementation Adds the working DBAL implementation as well as some fixes. Also new Factory/Builder concept --- .../src/ContentGraphAdapter.php | 705 ++++++++++++++++-- .../src/ContentGraphAdapterFactory.php | 56 ++ .../src/ContentGraphAdapterFactoryBuilder.php | 22 + .../src/ContentGraphAdapterProvider.php | 36 - .../ContentGraphAdapterProviderFactory.php | 26 - .../Settings.ContentRepositoryRegistry.yaml | 2 + ...SetNodeReferences_ConstraintChecks.feature | 2 +- .../Factory/ContentRepositoryFactory.php | 10 +- ...ntRepositoryServiceFactoryDependencies.php | 6 +- .../Feature/Common/ConstraintChecks.php | 3 +- .../Feature/Common/NodeCreationInternals.php | 2 +- .../Feature/Common/NodeVariationInternals.php | 2 +- ...entGraphAdapterFactoryBuilderInterface.php | 18 + .../ContentGraphAdapterFactoryInterface.php | 23 + .../Feature/ContentGraphAdapterInterface.php | 26 +- .../Feature/ContentGraphAdapterProvider.php | 77 ++ ...ntGraphAdapterProviderFactoryInterface.php | 18 - .../ContentGraphAdapterProviderInterface.php | 40 - .../Feature/ContentStreamCommandHandler.php | 32 +- .../DimensionSpaceCommandHandler.php | 20 +- .../Feature/NodeAggregateCommandHandler.php | 2 +- .../Feature/NodeCreation/NodeCreation.php | 4 +- .../Feature/NodeDisabling/NodeDisabling.php | 1 + .../NodeDuplicationCommandHandler.php | 4 +- .../NodeModification/NodeModification.php | 1 + .../Classes/Feature/NodeMove/NodeMove.php | 11 +- .../NodeReferencing/NodeReferencing.php | 1 + .../Command/RemoveNodeAggregate.php | 1 - .../Feature/NodeRemoval/NodeRemoval.php | 1 + .../Command/CreateNodeVariant.php | 1 - .../Feature/NodeVariation/NodeVariation.php | 1 + .../RootNodeCreation/RootNodeHandling.php | 2 + .../Feature/SubtreeTagging/SubtreeTagging.php | 17 +- .../Feature/WorkspaceCommandHandler.php | 42 +- .../Adjustment/TetheredNodeAdjustments.php | 4 +- .../src/StructureAdjustmentService.php | 4 +- ...ricCommandExecutionAndEventPublication.php | 2 + .../Configuration/Objects.yaml | 9 + .../Configuration/Settings.yaml | 4 +- .../Classes/ContentRepositoryRegistry.php | 17 +- 40 files changed, 974 insertions(+), 281 deletions(-) create mode 100644 Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php create mode 100644 Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactoryBuilder.php delete mode 100644 Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterProvider.php delete mode 100644 Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterProviderFactory.php create mode 100644 Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterFactoryBuilderInterface.php create mode 100644 Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterFactoryInterface.php create mode 100644 Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterProvider.php delete mode 100644 Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterProviderFactoryInterface.php delete mode 100644 Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterProviderInterface.php diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php index db08c023164..e30b64bf65d 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php @@ -2,20 +2,59 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Driver\Exception as DbalDriverException; +use Doctrine\DBAL\Driver\Exception as DriverException; +use Doctrine\DBAL\Exception as DBALException; +use Doctrine\DBAL\ParameterType; +use Doctrine\DBAL\Query\QueryBuilder; +use Doctrine\DBAL\Result; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRelationAnchorPoint; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\NodeFactory; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\Feature\ContentGraphAdapterInterface; use Neos\ContentRepository\Core\NodeType\NodeTypeName; +use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\CountChildNodesFilter; +use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindChildNodesFilter; +use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindPrecedingSiblingNodesFilter; +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\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\NodeAggregate; +use Neos\ContentRepository\Core\Projection\ContentGraph\NodePath; use Neos\ContentRepository\Core\Projection\ContentGraph\Nodes; +use Neos\ContentRepository\Core\Projection\ContentGraph\SearchTerm; +use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\Projection\Workspace\Workspace; +use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceStatus; +use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet; +use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; +use Neos\ContentRepository\Core\SharedModel\Id\UuidFactory; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateClassification; 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; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamState; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceDescription; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceTitle; +use Neos\EventStore\Model\Event\Version; use Neos\EventStore\Model\EventStream\MaybeVersion; /** @@ -23,110 +62,295 @@ */ class ContentGraphAdapter implements ContentGraphAdapterInterface { - private NodeFactory $nodeFactory; - public function __construct( private readonly Connection $dbalConnection, private readonly string $tableNamePrefix, - public readonly ContentStreamId $contentStreamId, - public readonly WorkspaceName $workspaceName + private readonly NodeFactory $nodeFactory, + public ?WorkspaceName $workspaceName, + public ?ContentStreamId $contentStreamId, ) { + if ($this->workspaceName === null && $this->contentStreamId === null) { + throw new \InvalidArgumentException('Neither ContentStreamId nor WorkspaceName given in creation of ContentGraphAdapter, one is required.', 1712746528); + } } - public function rootNodeAggregateWithTypeExists(ContentStreamId $contentStreamId, NodeTypeName $nodeTypeName): bool + public function rootNodeAggregateWithTypeExists(NodeTypeName $nodeTypeName): bool { - // TODO: Implement rootNodeAggregateWithTypeExists() method. - } + $queryBuilder = $this->dbalConnection->createQueryBuilder() + ->select('COUNT(n.relationanchorpoint)') + ->from($this->getTablenameForNode(), 'n') + ->innerJoin('n', $this->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('h', $this->getTablenameForDimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') + ->where('h.contentstreamid = :contentStreamId') + ->andWhere('h.parentnodeanchor = :rootEdgeParentAnchorId') + ->setParameters([ + 'contentStreamId' => $this->getContentStreamId()->value, + 'rootEdgeParentAnchorId' => NodeRelationAnchorPoint::forRootEdge()->value, + ]); - public function findParentNodeAggregates(ContentStreamId $contentStreamId, WorkspaceName $workspaceName, NodeAggregateId $childNodeAggregateId): iterable - { - // TODO: Implement findParentNodeAggregates() method. + $queryBuilder + ->andWhere('n.nodetypename = :nodeTypeName') + ->setParameter('nodeTypeName', $nodeTypeName->value); + + return $queryBuilder->execute()->fetchOne() > 0; } - public function findNodeAggregateById(ContentStreamId $contentStreamId, WorkspaceName $workspaceName, NodeAggregateId $nodeAggregateId): ?NodeAggregate + public function findParentNodeAggregates(NodeAggregateId $childNodeAggregateId): iterable { - // TODO: Implement findNodeAggregateById() method. + $queryBuilder = $this->dbalConnection->createQueryBuilder() + ->select('pn.*, ph.name, ph.contentstreamid, ph.subtreetags, pdsp.dimensionspacepoint AS covereddimensionspacepoint') + ->from($this->getTablenameForNode(), 'pn') + ->innerJoin('pn', $this->getTablenameForHierachyRelation(), 'ph', 'ph.childnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->getTablenameForHierachyRelation(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('ch', $this->getTablenameForNode(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') + ->innerJoin('ph', $this->getTablenameForDimensionSpacePoints(), 'pdsp', 'pdsp.hash = ph.dimensionspacepointhash') + ->where('cn.nodeaggregateid = :nodeAggregateId') + ->andWhere('ph.contentstreamid = :contentStreamId') + ->andWhere('ch.contentstreamid = :contentStreamId') + ->setParameters([ + 'nodeAggregateId' => $childNodeAggregateId->value, + 'contentStreamId' => $this->getContentStreamId()->value + ]); + + return $this->mapQueryBuilderToNodeAggregates($queryBuilder); } - public function findParentNodeAggregateByChildOriginDimensionSpacePoint(ContentStreamId $contentStreamId, WorkspaceName $workspaceName, NodeAggregateId $childNodeAggregateId, OriginDimensionSpacePoint $childOriginDimensionSpacePoint): ?NodeAggregate + public function findNodeAggregateById(NodeAggregateId $nodeAggregateId): ?NodeAggregate { - // TODO: Implement findParentNodeAggregateByChildOriginDimensionSpacePoint() method. + $queryBuilder = $this->dbalConnection->createQueryBuilder() + ->select('n.*, h.name, h.contentstreamid, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') + ->from($this->getTablenameForHierachyRelation(), 'h') + ->innerJoin('h', $this->getTablenameForNode(), 'n', 'n.relationanchorpoint = h.childnodeanchor') + ->innerJoin('h', $this->getTablenameForDimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') + ->where('n.nodeaggregateid = :nodeAggregateId') + ->andWhere('h.contentstreamid = :contentStreamId') + ->orderBy('n.relationanchorpoint', 'DESC') + ->setParameters([ + 'nodeAggregateId' => $nodeAggregateId->value, + 'contentStreamId' => $this->getContentStreamId()->value + ]); + + return $this->nodeFactory->mapNodeRowsToNodeAggregate( + $this->fetchRows($queryBuilder), + $this->getContentStreamId(), + VisibilityConstraints::withoutRestrictions() + ); } - public function findChildNodeAggregates(ContentStreamId $contentStreamId, WorkspaceName $workspaceName, NodeAggregateId $parentNodeAggregateId): iterable + public function findParentNodeAggregateByChildOriginDimensionSpacePoint(NodeAggregateId $childNodeAggregateId, OriginDimensionSpacePoint $childOriginDimensionSpacePoint): ?NodeAggregate { - // TODO: Implement findChildNodeAggregates() method. + $subQueryBuilder = $this->dbalConnection->createQueryBuilder() + ->select('pn.nodeaggregateid') + ->from($this->getTablenameForNode(), 'pn') + ->innerJoin('pn', $this->getTablenameForHierachyRelation(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('ch', $this->getTablenameForNode(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') + ->where('ch.contentstreamid = :contentStreamId') + ->andWhere('ch.dimensionspacepointhash = :childOriginDimensionSpacePointHash') + ->andWhere('cn.nodeaggregateid = :childNodeAggregateId') + ->andWhere('cn.origindimensionspacepointhash = :childOriginDimensionSpacePointHash'); + + $queryBuilder = $this->dbalConnection->createQueryBuilder() + ->select('n.*, h.name, h.contentstreamid, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') + ->from($this->getTablenameForNode(), 'n') + ->innerJoin('n', $this->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('h', $this->getTablenameForDimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') + ->where('n.nodeaggregateid = (' . $subQueryBuilder->getSQL() . ')') + ->andWhere('h.contentstreamid = :contentStreamId') + ->setParameters([ + 'contentStreamId' => $this->getContentStreamId()->value, + 'childNodeAggregateId' => $childNodeAggregateId->value, + 'childOriginDimensionSpacePointHash' => $childOriginDimensionSpacePoint->hash, + ]); + + return $this->nodeFactory->mapNodeRowsToNodeAggregate( + $this->fetchRows($queryBuilder), + $this->getContentStreamId(), + VisibilityConstraints::withoutRestrictions() + ); } - public function findTetheredChildNodeAggregates(ContentStreamId $contentStreamId, WorkspaceName $workspaceName, NodeAggregateId $parentNodeAggregateId): iterable + public function findChildNodeAggregates(NodeAggregateId $parentNodeAggregateId): iterable { - // TODO: Implement findTetheredChildNodeAggregates() method. + $queryBuilder = $this->buildChildNodeAggregateQuery($parentNodeAggregateId, $this->getContentStreamId()); + + return $this->mapQueryBuilderToNodeAggregates($queryBuilder); } - public function getDimensionSpacePointsOccupiedByChildNodeName(ContentStreamId $contentStreamId, NodeName $nodeName, NodeAggregateId $parentNodeAggregateId, OriginDimensionSpacePoint $parentNodeOriginDimensionSpacePoint, DimensionSpacePointSet $dimensionSpacePointsToCheck): DimensionSpacePointSet + public function findTetheredChildNodeAggregates(NodeAggregateId $parentNodeAggregateId): iterable { - // TODO: Implement getDimensionSpacePointsOccupiedByChildNodeName() method. + $queryBuilder = $this->buildChildNodeAggregateQuery($parentNodeAggregateId, $this->getContentStreamId()) + ->andWhere('cn.classification = :tetheredClassification') + ->setParameter('tetheredClassification', NodeAggregateClassification::CLASSIFICATION_TETHERED->value); + + return $this->mapQueryBuilderToNodeAggregates($queryBuilder); } - public function findChildNodeAggregatesByName(ContentStreamId $contentStreamId, WorkspaceName $workspaceName, NodeAggregateId $parentNodeAggregateId, NodeName $name): iterable + public function getDimensionSpacePointsOccupiedByChildNodeName(NodeName $nodeName, NodeAggregateId $parentNodeAggregateId, OriginDimensionSpacePoint $parentNodeOriginDimensionSpacePoint, DimensionSpacePointSet $dimensionSpacePointsToCheck): DimensionSpacePointSet { - // TODO: Implement findChildNodeAggregatesByName() method. + $queryBuilder = $this->createQueryBuilder() + ->select('dsp.dimensionspacepoint, h.dimensionspacepointhash') + ->from($this->getTablenameForHierachyRelation(), 'h') + ->innerJoin('h', $this->getTablenameForNode(), 'n', 'n.relationanchorpoint = h.parentnodeanchor') + ->innerJoin('h', $this->getTablenameForDimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') + ->innerJoin('n', $this->getTablenameForHierachyRelation(), 'ph', 'ph.childnodeanchor = n.relationanchorpoint') + ->where('n.nodeaggregateid = :parentNodeAggregateId') + ->andWhere('n.origindimensionspacepointhash = :parentNodeOriginDimensionSpacePointHash') + ->andWhere('ph.contentstreamid = :contentStreamId') + ->andWhere('h.contentstreamid = :contentStreamId') + ->andWhere('h.dimensionspacepointhash IN (:dimensionSpacePointHashes)') + ->andWhere('h.name = :nodeName') + ->setParameters([ + 'parentNodeAggregateId' => $parentNodeAggregateId->value, + 'parentNodeOriginDimensionSpacePointHash' => $parentNodeOriginDimensionSpacePoint->hash, + 'contentStreamId' => $this->getContentStreamId()->value, + 'dimensionSpacePointHashes' => $dimensionSpacePointsToCheck->getPointHashes(), + 'nodeName' => $nodeName->value + ], [ + 'dimensionSpacePointHashes' => Connection::PARAM_STR_ARRAY, + ]); + $dimensionSpacePoints = []; + foreach ($this->fetchRows($queryBuilder) as $hierarchyRelationData) { + $dimensionSpacePoints[$hierarchyRelationData['dimensionspacepointhash']] = DimensionSpacePoint::fromJsonString($hierarchyRelationData['dimensionspacepoint']); + } + + return new DimensionSpacePointSet($dimensionSpacePoints); } - public function subgraphContainsNodes(ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint): bool + public function findChildNodeAggregatesByName(NodeAggregateId $parentNodeAggregateId, NodeName $name): iterable { - // TODO: Implement subgraphContainsNodes() method. + $queryBuilder = $this->buildChildNodeAggregateQuery($parentNodeAggregateId, $this->getContentStreamId()) + ->andWhere('ch.name = :relationName') + ->setParameter('relationName', $name->value); + + return $this->mapQueryBuilderToNodeAggregates($queryBuilder); } - public function findNodeInSubgraph(ContentStreamId $contentStreamId, WorkspaceName $workspaceName, DimensionSpacePoint $coveredDimensionSpacePoint, NodeAggregateId $nodeAggregateId): ?Node + public function subgraphContainsNodes(DimensionSpacePoint $dimensionSpacePoint): bool { - // TODO: Implement findNodeInSubgraph() method. + $queryBuilder = $this->createQueryBuilder() + ->select('COUNT(*)') + ->from($this->getTablenameForNode(), 'n') + ->innerJoin('n', $this->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->where('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->getContentStreamId()->value) + ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); + 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 > 0; + } catch (DbalDriverException|DbalException $e) { + throw new \RuntimeException(sprintf('Failed to count all nodes: %s', $e->getMessage()), 1678364741, $e); + } } - public function findParentNodeInSubgraph(ContentStreamId $contentStreamId, WorkspaceName $workspaceName, DimensionSpacePoint $coveredDimensionSpacePoint, NodeAggregateId $childNodeAggregateId): ?Node + public function findNodeInSubgraph(DimensionSpacePoint $coveredDimensionSpacePoint, NodeAggregateId $nodeAggregateId): ?Node { - // TODO: Implement findParentNodeInSubgraph() method. + $queryBuilder = $this->createQueryBuilder() + ->select('n.*, h.name, h.subtreetags') + ->from($this->getTablenameForNode(), 'n') + ->innerJoin('n', $this->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->where('n.nodeaggregateid = :nodeAggregateId')->setParameter('nodeAggregateId', $nodeAggregateId->value) + ->andWhere('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->getContentStreamId()->value) + ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $coveredDimensionSpacePoint->hash); + + // TODO: Do we need subtree tag support here, not for visibility at least + return $this->fetchNode($queryBuilder, $coveredDimensionSpacePoint); } - public function findChildNodeByNameInSubgraph(ContentStreamId $contentStreamId, WorkspaceName $workspaceName, DimensionSpacePoint $coveredDimensionSpacePoint, NodeAggregateId $parentNodeAggregateId, NodeName $nodeNamex): ?Node + public function findChildNodesInSubgraph(DimensionSpacePoint $coveredDimensionSpacePoint, NodeAggregateId $parentNodeAggregateId): Nodes { - // TODO: Implement findChildNodeByNameInSubgraph() method. + $filter = FindChildNodesFilter::create(); + $queryBuilder = $this->buildChildNodesQuery($parentNodeAggregateId, $coveredDimensionSpacePoint, $filter); + $queryBuilder->addOrderBy('h.position'); + + return $this->fetchNodes($queryBuilder, $coveredDimensionSpacePoint); } - public function findPreceedingSiblingNodesInSubgraph(ContentStreamId $contentStreamId, WorkspaceName $workspaceName, DimensionSpacePoint $coveredDimensionSpacePoint, NodeAggregateId $startingSiblingNodeAggregateId): Nodesy + public function findParentNodeInSubgraph(DimensionSpacePoint $coveredDimensionSpacePoint, NodeAggregateId $childNodeAggregateId): ?Node { - // TODO: Implement findPreceedingSiblingNodesInSubgraph() method. + $queryBuilder = $this->createQueryBuilder() + ->select('pn.*, ch.name, ch.subtreetags') + ->from($this->getTablenameForNode(), 'pn') + ->innerJoin('pn', $this->getTablenameForHierachyRelation(), 'ph', 'ph.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->getTablenameForNode(), 'cn', 'cn.relationanchorpoint = ph.childnodeanchor') + ->innerJoin('pn', $this->getTablenameForHierachyRelation(), 'ch', 'ch.childnodeanchor = pn.relationanchorpoint') + ->where('cn.nodeaggregateid = :childNodeAggregateId')->setParameter('childNodeAggregateId', $childNodeAggregateId->value) + ->andWhere('ph.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->getContentStreamId()->value) + ->andWhere('ch.contentstreamid = :contentStreamId') + ->andWhere('ph.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $coveredDimensionSpacePoint->hash) + ->andWhere('ch.dimensionspacepointhash = :dimensionSpacePointHash'); + + return $this->fetchNode($queryBuilder, $coveredDimensionSpacePoint); } - public function findSuceedingSiblingNodesInSubgraph(ContentStreamId $contentStreamId, WorkspaceName $workspaceName, DimensionSpacePoint $coveredDimensionSpacePoint, NodeAggregateId $startingSiblingNodeAggregateId): Nodes + public function findChildNodeByNameInSubgraph(DimensionSpacePoint $coveredDimensionSpacePoint, NodeAggregateId $parentNodeAggregateId, NodeName $nodeName): ?Node { - // TODO: Implement findSuceedingSiblingNodesInSubgraph() method. + $startingNode = $this->findNodeById($parentNodeAggregateId, $coveredDimensionSpacePoint); + + return $startingNode + ? $this->findNodeByPathFromStartingNode(NodePath::fromNodeNames($nodeName), $startingNode, $coveredDimensionSpacePoint) + : null; } - public function hasContentStream(ContentStreamId $contentStreamId): bool + public function findPreceedingSiblingNodesInSubgraph(DimensionSpacePoint $coveredDimensionSpacePoint, NodeAggregateId $startingSiblingNodeAggregateId): Nodes { - // TODO: Implement hasContentStream() method. + $queryBuilder = $this->buildSiblingsQuery(true, $startingSiblingNodeAggregateId, $coveredDimensionSpacePoint, FindPrecedingSiblingNodesFilter::create()); + + return $this->fetchNodes($queryBuilder, $coveredDimensionSpacePoint); } - public function findStateForContentStream(ContentStreamId $contentStreamId): ?ContentStreamState + public function findSucceedingSiblingNodesInSubgraph(DimensionSpacePoint $coveredDimensionSpacePoint, NodeAggregateId $startingSiblingNodeAggregateId): Nodes { - // TODO: Implement findStateForContentStream() method. + $queryBuilder = $this->buildSiblingsQuery(false, $startingSiblingNodeAggregateId, $coveredDimensionSpacePoint, FindSucceedingSiblingNodesFilter::create()); + + return $this->fetchNodes($queryBuilder, $coveredDimensionSpacePoint); } - public function findVersionForContentStream(ContentStreamId $contentStreamId): MaybeVersion + public function hasContentStream(): bool { - // TODO: Implement findVersionForContentStream() method. + try { + $this->getContentStreamId(); + } catch (ContentStreamDoesNotExistYet $_) { + return false; + } + + return true; } - public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace + public function findStateForContentStream(): ?ContentStreamState { - // TODO: Implement findWorkspaceByName() method. + /* @var $state string|false */ + $state = $this->dbalConnection->executeQuery( + ' + SELECT state FROM cr_default_p_contentstream + WHERE contentStreamId = :contentStreamId + ', + [ + 'contentStreamId' => $this->getContentStreamId()->value, + ] + )->fetchOne(); + + return ContentStreamState::tryFrom($state ?: ''); } - public function findWorkspaceByCurrentContentStreamId(ContentStreamId $contentStreamId): ?Workspace + public function findVersionForContentStream(): MaybeVersion { - // TODO: Implement findWorkspaceByCurrentContentStreamId() method. + /* @var $version int|false */ + $version = $this->dbalConnection->executeQuery( + ' + SELECT version FROM cr_default_p_contentstream + WHERE contentStreamId = :contentStreamId + ', + [ + 'contentStreamId' => $this->getContentStreamId()->value, + ] + )->fetchOne(); + + $versionObject = $version !== false ? Version::fromInteger($version) : null; + return MaybeVersion::fromVersionOrNull($versionObject); } private function getTablenameForNode(): string @@ -144,5 +368,398 @@ private function getTablenameForDimensionSpacePoints(): string return $this->tableNamePrefix . '_dimensionspacepoints'; } + /** + * @param QueryBuilder $queryBuilder + * @return iterable + */ + private function mapQueryBuilderToNodeAggregates(QueryBuilder $queryBuilder): iterable + { + return $this->nodeFactory->mapNodeRowsToNodeAggregates( + $this->fetchRows($queryBuilder), + $this->getContentStreamId(), + VisibilityConstraints::withoutRestrictions() + ); + } + + private function createQueryBuilder(): QueryBuilder + { + return $this->dbalConnection->createQueryBuilder(); + } + + private function buildChildNodeAggregateQuery(NodeAggregateId $parentNodeAggregateId, ContentStreamId $contentStreamId): QueryBuilder + { + return $this->dbalConnection->createQueryBuilder() + ->select('cn.*, ch.name, ch.contentstreamid, ch.subtreetags, cdsp.dimensionspacepoint AS covereddimensionspacepoint') + ->from($this->getTablenameForNode(), 'pn') + ->innerJoin('pn', $this->getTablenameForHierachyRelation(), 'ph', 'ph.childnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->getTablenameForHierachyRelation(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('ch', $this->getTablenameForDimensionSpacePoints(), 'cdsp', 'cdsp.hash = ch.dimensionspacepointhash') + ->innerJoin('ch', $this->getTablenameForNode(), '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, + ]); + } + + private function buildChildNodesQuery(NodeAggregateId $parentNodeAggregateId, DimensionSpacePoint $dimensionSpacePoint, FindChildNodesFilter|CountChildNodesFilter $filter): QueryBuilder + { + $queryBuilder = $this->createQueryBuilder() + ->select('n.*, h.name, h.subtreetags') + ->from($this->getTablenameForNode(), 'pn') + ->innerJoin('pn', $this->getTablenameForHierachyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->getTablenameForNode(), 'n', 'h.childnodeanchor = n.relationanchorpoint') + ->where('pn.nodeaggregateid = :parentNodeAggregateId')->setParameter('parentNodeAggregateId', $parentNodeAggregateId->value) + ->andWhere('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->getContentStreamId()->value) + ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); + if ($filter->nodeTypes !== null) { + $this->addNodeTypeCriteria($queryBuilder, $filter->nodeTypes); + } + if ($filter->searchTerm !== null) { + $this->addSearchTermConstraints($queryBuilder, $filter->searchTerm); + } + if ($filter->propertyValue !== null) { + $this->addPropertyValueConstraints($queryBuilder, $filter->propertyValue); + } + + return $queryBuilder; + } + + /** + * @return array> + */ + private function fetchRows(QueryBuilder $queryBuilder): array + { + $result = $queryBuilder->execute(); + if (!$result instanceof Result) { + throw new \RuntimeException(sprintf('Failed to execute query. Expected result to be of type %s, got: %s', Result::class, get_debug_type($result)), 1701443535); + } + try { + return $result->fetchAllAssociative(); + } catch (DriverException|DBALException $e) { + throw new \RuntimeException(sprintf('Failed to fetch rows from database: %s', $e->getMessage()), 1701444358, $e); + } + } + + /** + * @param QueryBuilder $queryBuilder + * @return \Doctrine\DBAL\ForwardCompatibility\Result + * @throws DbalException + */ + private function executeQuery(QueryBuilder $queryBuilder): Result + { + $result = $queryBuilder->execute(); + if (!$result instanceof Result) { + throw new \RuntimeException(sprintf('Expected instance of %s, got %s', Result::class, get_debug_type($result)), 1678370012); + } + + return $result; + } + + public function findNodeById(NodeAggregateId $nodeAggregateId, DimensionSpacePoint $dimensionSpacePoint): ?Node + { + $queryBuilder = $this->createQueryBuilder() + ->select('n.*, h.name, h.subtreetags') + ->from($this->getTablenameForNode(), 'n') + ->innerJoin('n', $this->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->where('n.nodeaggregateid = :nodeAggregateId')->setParameter('nodeAggregateId', $nodeAggregateId->value) + ->andWhere('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->getContentStreamId()->value) + ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); + + return $this->fetchNode($queryBuilder, $dimensionSpacePoint); + } + + private function findNodeByPathFromStartingNode(NodePath $path, Node $startingNode, DimensionSpacePoint $dimensionSpacePoint): ?Node + { + $currentNode = $startingNode; + + foreach ($path->getParts() as $edgeName) { + $currentNode = $this->findChildNodeConnectedThroughEdgeName($currentNode->nodeAggregateId, $edgeName, $dimensionSpacePoint); + if ($currentNode === null) { + return null; + } + } + + return $currentNode; + } + + /** + * Find a single child node by its name + * + * @return Node|null the node that is connected to its parent with the specified $nodeName, or NULL if no matching node exists or the parent node is not accessible + */ + private function findChildNodeConnectedThroughEdgeName(NodeAggregateId $parentNodeAggregateId, NodeName $nodeName, DimensionSpacePoint $dimensionSpacePoint): ?Node + { + $queryBuilder = $this->createQueryBuilder() + ->select('cn.*, h.name, h.subtreetags') + ->from($this->getTablenameForNode(), 'pn') + ->innerJoin('pn', $this->getTablenameForHierachyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->getTablenameForNode(), 'cn', 'cn.relationanchorpoint = h.childnodeanchor') + ->where('pn.nodeaggregateid = :parentNodeAggregateId')->setParameter('parentNodeAggregateId', $parentNodeAggregateId->value) + ->andWhere('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->getContentStreamId()->value) + ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash) + ->andWhere('h.name = :edgeName')->setParameter('edgeName', $nodeName->value); + + return $this->fetchNode($queryBuilder, $dimensionSpacePoint); + } + + private function buildSiblingsQuery(bool $preceding, NodeAggregateId $siblingNodeAggregateId, DimensionSpacePoint $dimensionSpacePoint, FindPrecedingSiblingNodesFilter|FindSucceedingSiblingNodesFilter $filter): QueryBuilder + { + $parentNodeAnchorSubQuery = $this->createQueryBuilder() + ->select('sh.parentnodeanchor') + ->from($this->getTablenameForHierachyRelation(), 'sh') + ->innerJoin('sh', $this->getTablenameForNode(), '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->getTablenameForHierachyRelation(), 'sh') + ->innerJoin('sh', $this->getTablenameForNode(), '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->getTablenameForNode(), 'n') + ->innerJoin('n', $this->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->where('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->getContentStreamId()->value) + ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $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'); + + if ($filter->nodeTypes !== null) { + $this->addNodeTypeCriteria($queryBuilder, $filter->nodeTypes); + } + if ($filter->searchTerm !== null) { + $this->addSearchTermConstraints($queryBuilder, $filter->searchTerm); + } + if ($filter->propertyValue !== null) { + $this->addPropertyValueConstraints($queryBuilder, $filter->propertyValue); + } + if ($filter->pagination !== null) { + $this->applyPagination($queryBuilder, $filter->pagination); + } + + return $queryBuilder; + } + + 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 applyPagination(QueryBuilder $queryBuilder, Pagination $pagination): void + { + $queryBuilder + ->setMaxResults($pagination->limit) + ->setFirstResult($pagination->offset); + } + + private function fetchNode(QueryBuilder $queryBuilder, DimensionSpacePoint $dimensionSpacePoint): ?Node + { + try { + $nodeRow = $this->executeQuery($queryBuilder)->fetchAssociative(); + } catch (DbalDriverException|DbalException $e) { + throw new \RuntimeException(sprintf('Failed to fetch node: %s', $e->getMessage()), 1678286030, $e); + } + if ($nodeRow === false) { + return null; + } + + return $this->nodeFactory->mapNodeRowToNode( + $nodeRow, + $this->getContentStreamId(), + $dimensionSpacePoint, + VisibilityConstraints::withoutRestrictions() + ); + } + + private function fetchNodes(QueryBuilder $queryBuilder, DimensionSpacePoint $dimensionSpacePoint): Nodes + { + try { + $nodeRows = $this->executeQuery($queryBuilder)->fetchAllAssociative(); + } catch (DbalDriverException|DbalException $e) { + throw new \RuntimeException(sprintf('Failed to fetch nodes: %s', $e->getMessage()), 1678292896, $e); + } + + return $this->nodeFactory->mapNodeRowsToNodes($nodeRows, $this->getContentStreamId(), $dimensionSpacePoint, VisibilityConstraints::withoutRestrictions()); + } + + private function createUniqueParameterName(): string + { + return 'param_' . UuidFactory::create(); + } + + public function getWorkspaceName(): WorkspaceName + { + if ($this->workspaceName !== null) { + return $this->workspaceName; + } + + // TODO: This table is not allowed here... + $query = $this->dbalConnection->prepare('SELECT workspacename FROM cr_default_p_workspace WHERE currentcontentstreamid LIKE :contentStreamId'); + $result = $query->executeQuery(['contentStreamId' => $this->getContentStreamId()->value]); + $workspaceNameString = $result->fetchOne(); + if (!$workspaceNameString) { + throw new WorkspaceDoesNotExist(sprintf('A workspace for the ContentStreamId "%s" was not found, cannot proceed.', $this->getContentStreamId()->value), 1712746408); + } + + $this->workspaceName = WorkspaceName::fromString($workspaceNameString); + + return $this->workspaceName; + } + + public function getContentStreamId(): ContentStreamId + { + if ($this->contentStreamId !== null) { + return $this->contentStreamId; + } + + // TODO: This table is not allowed here... + $query = $this->dbalConnection->prepare('SELECT currentcontentstreamid FROM cr_default_p_workspace WHERE workspacename LIKE :workspaceName'); + $result = $query->executeQuery(['workspaceName' => $this->getWorkspaceName()->value]); + $contentStreamIdString = $result->fetchOne(); + if (!$contentStreamIdString) { + throw new ContentStreamDoesNotExistYet(sprintf('A ContentStream for the WorkspaceName "%s" was not found, cannot proceed.', $this->workspaceName?->value), 1712750421); + } + + $this->contentStreamId = ContentStreamId::fromString($contentStreamIdString); + + return $this->contentStreamId; + } + + public function contentStreamExists(): bool + { + /* @var $state string|false */ + $state = $this->dbalConnection->executeQuery( + ' + SELECT state FROM cr_default_p_contentstream + WHERE contentStreamId = :contentStreamId + ', + [ + 'contentStreamId' => $this->getContentStreamId()->value, + ] + )->fetchOne(); + + return $state === false ? false : true; + } + + public function getWorkspace(): Workspace + { + $query = $this->dbalConnection->prepare('SELECT * FROM cr_default_p_workspace WHERE workspacename LIKE :workspaceName'); + $result = $query->executeQuery(['workspaceName' => $this->getWorkspaceName()->value]); + $row = $result->fetchAssociative(); + // We can assume that we get a row otherwise getWorkspaceName would have thrown already + + return new Workspace( + WorkspaceName::fromString($row['workspacename']), + !empty($row['baseworkspacename']) ? WorkspaceName::fromString($row['baseworkspacename']) : null, + WorkspaceTitle::fromString($row['workspacetitle']), + WorkspaceDescription::fromString($row['workspacedescription']), + ContentStreamId::fromString($row['currentcontentstreamid']), + WorkspaceStatus::from($row['status']), + $row['workspaceowner'] + ); + } } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php new file mode 100644 index 00000000000..45cb605d1fa --- /dev/null +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php @@ -0,0 +1,56 @@ +contentRepositoryId + ); + $this->tableNamePrefix = $tableNamePrefix; + + $dimensionSpacePointsRepository = new DimensionSpacePointsRepository($this->dbalConnection, $tableNamePrefix); + $this->nodeFactory = new NodeFactory( + $projectionFactoryDependencies->contentRepositoryId, + $projectionFactoryDependencies->nodeTypeManager, + $projectionFactoryDependencies->propertyConverter, + $dimensionSpacePointsRepository + ); + } + + public function bindAdapter(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphAdapterInterface + { + return new ContentGraphAdapter($this->dbalConnection, $this->tableNamePrefix, $this->nodeFactory, $workspaceName, $contentStreamId); + } + + public function adapterFromContentStreamId(ContentStreamId $contentStreamId): ContentGraphAdapterInterface + { + return new ContentGraphAdapter($this->dbalConnection, $this->tableNamePrefix, $this->nodeFactory, null, $contentStreamId); + } + + public function adapterFromWorkspaceName(WorkspaceName $workspaceName): ContentGraphAdapterInterface + { + return new ContentGraphAdapter($this->dbalConnection, $this->tableNamePrefix, $this->nodeFactory, $workspaceName, null); + } + +} diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactoryBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactoryBuilder.php new file mode 100644 index 00000000000..1104c582196 --- /dev/null +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactoryBuilder.php @@ -0,0 +1,22 @@ +dbalClient->getConnection(), $projectionFactoryDependencies); + } +} diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterProvider.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterProvider.php deleted file mode 100644 index 67dd66af498..00000000000 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterProvider.php +++ /dev/null @@ -1,36 +0,0 @@ -dbalConnection, $tableNamePrefix); - } -} diff --git a/Neos.ContentRepository.BehavioralTests/Configuration/Testing/Behat/Settings.ContentRepositoryRegistry.yaml b/Neos.ContentRepository.BehavioralTests/Configuration/Testing/Behat/Settings.ContentRepositoryRegistry.yaml index 53eafcc4977..d6e103b4c6d 100644 --- a/Neos.ContentRepository.BehavioralTests/Configuration/Testing/Behat/Settings.ContentRepositoryRegistry.yaml +++ b/Neos.ContentRepository.BehavioralTests/Configuration/Testing/Behat/Settings.ContentRepositoryRegistry.yaml @@ -10,3 +10,5 @@ Neos: factoryObjectName: 'Neos\ContentRepository\BehavioralTests\TestSuite\Behavior\GherkinPyStringNodeBasedNodeTypeManagerFactory' contentDimensionSource: factoryObjectName: 'Neos\ContentRepository\BehavioralTests\TestSuite\Behavior\GherkinTableNodeBasedContentDimensionSourceFactory' + contentGraphAdapterFactory: + factoryObjectName: Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphAdapterFactoryBuilder 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 58b46366b4c..c38adfdb27c 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 @@ -73,7 +73,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 "ContentStreamDoesNotExistYet" with code 1521386692 # checks for sourceNodeAggregateId Scenario: Try to reference nodes in a non-existent node aggregate diff --git a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php index 18b980d38d0..beeef9f6b81 100644 --- a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php +++ b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php @@ -21,7 +21,8 @@ use Neos\ContentRepository\Core\DimensionSpace\InterDimensionalVariationGraph; use Neos\ContentRepository\Core\EventStore\EventNormalizer; use Neos\ContentRepository\Core\EventStore\EventPersister; -use Neos\ContentRepository\Core\Feature\ContentGraphAdapterProviderInterface; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterFactoryBuilderInterface; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterProvider; use Neos\ContentRepository\Core\Feature\ContentStreamCommandHandler; use Neos\ContentRepository\Core\Feature\DimensionSpaceAdjustment\DimensionSpaceCommandHandler; use Neos\ContentRepository\Core\Feature\NodeAggregateCommandHandler; @@ -47,6 +48,8 @@ final class ContentRepositoryFactory private ProjectionFactoryDependencies $projectionFactoryDependencies; private ProjectionsAndCatchUpHooks $projectionsAndCatchUpHooks; + private ContentGraphAdapterProvider $contentGraphAdapterProvider; + public function __construct( private readonly ContentRepositoryId $contentRepositoryId, EventStoreInterface $eventStore, @@ -57,7 +60,7 @@ public function __construct( private readonly ProjectionCatchUpTriggerInterface $projectionCatchUpTrigger, private readonly UserIdProviderInterface $userIdProvider, private readonly ClockInterface $clock, - private readonly ContentGraphAdapterProviderInterface $contentGraphAdapterProvider, + ContentGraphAdapterFactoryBuilderInterface $contentGraphAdapterFactoryBuilder, ) { $contentDimensionZookeeper = new ContentDimensionZookeeper($contentDimensionSource); $interDimensionalVariationGraph = new InterDimensionalVariationGraph( @@ -75,6 +78,8 @@ public function __construct( new PropertyConverter($propertySerializer) ); $this->projectionsAndCatchUpHooks = $projectionsAndCatchUpHooksFactory->build($this->projectionFactoryDependencies); + + $this->contentGraphAdapterProvider = new ContentGraphAdapterProvider($contentGraphAdapterFactoryBuilder->build($this->projectionFactoryDependencies)); } // The following properties store "singleton" references of objects for this content repository @@ -122,6 +127,7 @@ public function getOrBuild(): ContentRepository public function buildService( ContentRepositoryServiceFactoryInterface $serviceFactory ): ContentRepositoryServiceInterface { + $serviceFactoryDependencies = ContentRepositoryServiceFactoryDependencies::create( $this->projectionFactoryDependencies, $this->getOrBuild(), diff --git a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryServiceFactoryDependencies.php b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryServiceFactoryDependencies.php index 2b0d2824804..355ce248574 100644 --- a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryServiceFactoryDependencies.php +++ b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryServiceFactoryDependencies.php @@ -20,7 +20,7 @@ use Neos\ContentRepository\Core\DimensionSpace\InterDimensionalVariationGraph; use Neos\ContentRepository\Core\EventStore\EventNormalizer; use Neos\ContentRepository\Core\EventStore\EventPersister; -use Neos\ContentRepository\Core\Feature\ContentGraphAdapterProviderInterface; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterProvider; use Neos\ContentRepository\Core\Infrastructure\Property\PropertyConverter; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\Projection\Projections; @@ -48,7 +48,7 @@ private function __construct( // we don't need CommandBus, because this is included in ContentRepository->handle() public EventPersister $eventPersister, public Projections $projections, - public ContentGraphAdapterProviderInterface $contentGraphAdapterProvider + public ContentGraphAdapterProvider $contentGraphAdapterProvider ) { } @@ -60,7 +60,7 @@ public static function create( ContentRepository $contentRepository, EventPersister $eventPersister, Projections $projections, - ContentGraphAdapterProviderInterface $contentGraphAdapterProvider + ContentGraphAdapterProvider $contentGraphAdapterProvider ): self { return new self( $projectionFactoryDependencies->contentRepositoryId, diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php index 592cc2ef9e5..b4729c22e3a 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php @@ -77,10 +77,11 @@ protected function requireContentStream( $contentGraphAdapter = $this->getContentGraphAdapter($workspaceName); if (!$contentGraphAdapter->hasContentStream()) { throw new ContentStreamDoesNotExistYet( - 'Content stream "' . $contentGraphAdapter->getContentStreamId()->value . '" does not exist yet.', + 'Content stream for "' . $workspaceName->value . '" does not exist yet.', 1521386692 ); } + if ($contentGraphAdapter->findStateForContentStream() === ContentStreamState::STATE_CLOSED) { throw new ContentStreamIsClosed( 'Content stream "' . $contentGraphAdapter->getContentStreamId()->value . '" is closed.', diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/NodeCreationInternals.php b/Neos.ContentRepository.Core/Classes/Feature/Common/NodeCreationInternals.php index 64e6da075e4..6c7089b6648 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/NodeCreationInternals.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/NodeCreationInternals.php @@ -43,7 +43,7 @@ private function resolveInterdimensionalSiblingsForCreation( OriginDimensionSpacePoint $sourceOrigin, DimensionSpacePointSet $coveredDimensionSpacePoints, ): InterdimensionalSiblings { - $originAlternativeSucceedingSiblings = $contentGraphAdapter->findSuceedingSiblingNodesInSubgraph( + $originAlternativeSucceedingSiblings = $contentGraphAdapter->findSucceedingSiblingNodesInSubgraph( $sourceOrigin->toDimensionSpacePoint(), $requestedSucceedingSiblingNodeAggregateId ); diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/NodeVariationInternals.php b/Neos.ContentRepository.Core/Classes/Feature/Common/NodeVariationInternals.php index 42552132e57..43c3b3eadc3 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/NodeVariationInternals.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/NodeVariationInternals.php @@ -273,7 +273,7 @@ private function resolveInterdimensionalSiblings( OriginDimensionSpacePoint $sourceOrigin, DimensionSpacePointSet $variantCoverage, ): InterdimensionalSiblings { - $originSiblings = $contentGraphAdapter->findSuceedingSiblingNodesInSubgraph($sourceOrigin->toDimensionSpacePoint(), $varyingNodeAggregateId); + $originSiblings = $contentGraphAdapter->findSucceedingSiblingNodesInSubgraph($sourceOrigin->toDimensionSpacePoint(), $varyingNodeAggregateId); $interdimensionalSiblings = []; foreach ($variantCoverage as $variantDimensionSpacePoint) { diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterFactoryBuilderInterface.php b/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterFactoryBuilderInterface.php new file mode 100644 index 00000000000..a252e440f41 --- /dev/null +++ b/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterFactoryBuilderInterface.php @@ -0,0 +1,18 @@ +.contentGraphAdapterFactory.factoryObjectName + */ +interface ContentGraphAdapterFactoryBuilderInterface +{ + public function build(ProjectionFactoryDependencies $projectionFactoryDependencies): ContentGraphAdapterFactory; +} diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterFactoryInterface.php b/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterFactoryInterface.php new file mode 100644 index 00000000000..bf7a93108ec --- /dev/null +++ b/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterFactoryInterface.php @@ -0,0 +1,23 @@ + + */ + private array $adapterInstances = []; + + public function __construct( + public readonly ContentGraphAdapterFactoryInterface $contentGraphAdapterFactory + ) + { + } + + /** + * TODO: We should not need this, + * TODO: after introducing the NodeIdentity we can change usages to + * TODO: ContentGraphAdapterProvider::resolveContentStreamIdAndGet() and remove this + * + * @throws ContentStreamDoesNotExistYet if there is no content stream with the provided id + * @deprecated + * + */ + public function resolveWorkspaceNameAndGet(ContentStreamId $contentStreamId): ContentGraphAdapterInterface + { + return $this->contentGraphAdapterFactory->adapterFromContentStreamId($contentStreamId); + } + + /** + * @throws WorkspaceDoesNotExist if there is no workspace with the provided name + * @throws ContentStreamDoesNotExistYet if the provided workspace does not resolve to an existing content stream + */ + public function resolveContentStreamIdAndGet(WorkspaceName $workspaceName): ContentGraphAdapterInterface + { + if (isset($this->adapterInstances[$workspaceName->value])) { + return $this->adapterInstances[$workspaceName->value]; + } + + return $this->contentGraphAdapterFactory->adapterFromWorkspaceName($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. + */ + public function overrideContentStreamId(WorkspaceName $workspaceName, ContentStreamId $contentStreamId, \Closure $fn): void + { + $adapter = $this->contentGraphAdapterFactory->bindAdapter($workspaceName, $contentStreamId); + $replacedAdapter = $this->adapterInstances[$workspaceName->value] ?? null; + $this->adapterInstances[$workspaceName->value] = $adapter; + + try { + $fn(); + } finally { + unset($this->adapterInstances[$workspaceName->value]); + if ($replacedAdapter) { + $this->adapterInstances[$workspaceName->value] = $replacedAdapter; + } + } + } +} diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterProviderFactoryInterface.php b/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterProviderFactoryInterface.php deleted file mode 100644 index 9ef8e034ee7..00000000000 --- a/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterProviderFactoryInterface.php +++ /dev/null @@ -1,18 +0,0 @@ - $options - * @return ContentGraphAdapterProviderInterface - */ - public function build(ContentRepositoryId $contentRepositoryId, array $options): ContentGraphAdapterProviderInterface; -} diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterProviderInterface.php b/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterProviderInterface.php deleted file mode 100644 index f309b9d8ba0..00000000000 --- a/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterProviderInterface.php +++ /dev/null @@ -1,40 +0,0 @@ -requireContentStreamToNotExistYet($command->contentStreamId); $streamName = ContentStreamEventStreamName::fromContentStreamId($command->contentStreamId) ->getEventStreamName(); @@ -136,7 +135,6 @@ private function handleForkContentStream( ): EventsToPublish { $this->requireContentStreamToExist($command->sourceContentStreamId); $this->requireContentStreamToNotBeClosed($command->sourceContentStreamId); - $this->requireContentStreamToNotExistYet($command->newContentStreamId); // TOOD: THis is not great $sourceContentStreamVersion = $this->contentGraphAdapterProvider->resolveWorkspaceNameAndGet($command->sourceContentStreamId) @@ -180,21 +178,6 @@ private function handleRemoveContentStream( ); } - /** - * @param ContentStreamId $contentStreamId - * @throws ContentStreamAlreadyExists - */ - protected function requireContentStreamToNotExistYet( - ContentStreamId $contentStreamId - ): void { - if ($this->contentGraphAdapter->hasContentStream($contentStreamId)) { - throw new ContentStreamAlreadyExists( - 'Content stream "' . $contentStreamId->value . '" already exists.', - 1521386345 - ); - } - } - /** * @param ContentStreamId $contentStreamId * @throws ContentStreamDoesNotExistYet @@ -202,7 +185,8 @@ protected function requireContentStreamToNotExistYet( protected function requireContentStreamToExist( ContentStreamId $contentStreamId ): void { - if (!$this->contentGraphAdapter->hasContentStream($contentStreamId)) { + $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveWorkspaceNameAndGet($contentStreamId); + if (!$contentGraphAdapter->contentStreamExists()) { throw new ContentStreamDoesNotExistYet( 'Content stream "' . $contentStreamId->value . '" does not exist yet.', 1521386692 @@ -213,7 +197,8 @@ protected function requireContentStreamToExist( protected function requireContentStreamToNotBeClosed( ContentStreamId $contentStreamId ): void { - if ($this->contentGraphAdapter->findStateForContentStream($contentStreamId) === ContentStreamState::STATE_CLOSED) { + $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveWorkspaceNameAndGet($contentStreamId); + if ($contentGraphAdapter->findStateForContentStream() === ContentStreamState::STATE_CLOSED) { throw new ContentStreamIsClosed( 'Content stream "' . $contentStreamId->value . '" is closed.', 1710260081 @@ -224,7 +209,8 @@ protected function requireContentStreamToNotBeClosed( protected function requireContentStreamToBeClosed( ContentStreamId $contentStreamId ): void { - if ($this->contentGraphAdapter->findStateForContentStream($contentStreamId) !== ContentStreamState::STATE_CLOSED) { + $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveWorkspaceNameAndGet($contentStreamId); + if ($contentGraphAdapter->findStateForContentStream() !== ContentStreamState::STATE_CLOSED) { throw new ContentStreamIsNotClosed( 'Content stream "' . $contentStreamId->value . '" is not closed.', 1710405911 @@ -235,9 +221,9 @@ protected function requireContentStreamToBeClosed( protected function getExpectedVersionOfContentStream( ContentStreamId $contentStreamId ): ExpectedVersion { + $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveWorkspaceNameAndGet($contentStreamId); return ExpectedVersion::fromVersion( - $this->contentGraphAdapter - ->findVersionForContentStream($contentStreamId) + $contentGraphAdapter->findVersionForContentStream() ->unwrap() ); } diff --git a/Neos.ContentRepository.Core/Classes/Feature/DimensionSpaceAdjustment/DimensionSpaceCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/DimensionSpaceAdjustment/DimensionSpaceCommandHandler.php index b786289d651..64016eb5935 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/DimensionSpaceAdjustment/DimensionSpaceCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/DimensionSpaceAdjustment/DimensionSpaceCommandHandler.php @@ -26,15 +26,13 @@ use Neos\ContentRepository\Core\EventStore\Events; use Neos\ContentRepository\Core\EventStore\EventsToPublish; use Neos\ContentRepository\Core\Feature\ContentGraphAdapterInterface; -use Neos\ContentRepository\Core\Feature\ContentGraphAdapterProviderInterface; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterProvider; use Neos\ContentRepository\Core\Feature\ContentStreamEventStreamName; use Neos\ContentRepository\Core\Feature\DimensionSpaceAdjustment\Command\AddDimensionShineThrough; use Neos\ContentRepository\Core\Feature\DimensionSpaceAdjustment\Command\MoveDimensionSpacePoint; use Neos\ContentRepository\Core\Feature\DimensionSpaceAdjustment\Event\DimensionShineThroughWasAdded; use Neos\ContentRepository\Core\Feature\DimensionSpaceAdjustment\Event\DimensionSpacePointWasMoved; use Neos\ContentRepository\Core\Feature\DimensionSpaceAdjustment\Exception\DimensionSpacePointAlreadyExists; -use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\EventStore\Model\EventStream\ExpectedVersion; /** @@ -45,7 +43,7 @@ public function __construct( private ContentDimensionZookeeper $contentDimensionZookeeper, private InterDimensionalVariationGraph $interDimensionalVariationGraph, - private ContentGraphAdapterProviderInterface $contentGraphAdapterProvider + private ContentGraphAdapterProvider $contentGraphAdapterProvider ) { } @@ -155,18 +153,4 @@ private function requireDimensionSpacePointToBeSpecialization( throw DimensionSpacePointIsNoSpecialization::butWasSupposedToBe($target, $source); } } - - /** - * @throws ContentStreamDoesNotExistYet - */ - protected function requireContentStream( - ContentGraphAdapterInterface $contentGraphAdapter - ): void { - if (!$contentGraphAdapter->hasContentStream()) { - throw new ContentStreamDoesNotExistYet( - 'Content stream "' . $contentGraphAdapter->getContentStreamId()->value . '" does not exist yet.', - 1521386692 - ); - } - } } diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php index 6d2af5b74a1..8c00241b4f5 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php @@ -83,7 +83,7 @@ public function __construct( private readonly DimensionSpace\ContentDimensionZookeeper $contentDimensionZookeeper, private readonly DimensionSpace\InterDimensionalVariationGraph $interDimensionalVariationGraph, private readonly PropertyConverter $propertyConverter, - protected readonly ContentGraphAdapterProviderInterface $contentGraphAdapterProvider + protected readonly ContentGraphAdapterProvider $contentGraphAdapterProvider ) { } diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php b/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php index 2c5b474f7cb..248aa3c8440 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php @@ -42,7 +42,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 @@ -129,6 +128,7 @@ private function validateProperties(?PropertyValuesToWrite $propertyValues, Node private function handleCreateNodeAggregateWithNodeAndSerializedProperties( CreateNodeAggregateWithNodeAndSerializedProperties $command ): EventsToPublish { + $this->requireContentStream($command->workspaceName); $contentGraphAdapter = $this->getContentGraphAdapter($command->workspaceName); $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraphAdapter); $this->requireDimensionSpacePointToExist($command->originDimensionSpacePoint->toDimensionSpacePoint()); @@ -210,7 +210,7 @@ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( array_push($events, ...iterator_to_array($this->handleTetheredChildNodes( $command, - $contentGraphAdapter->getContentStreamId(), + $contentGraphAdapter, $nodeType, $coveredDimensionSpacePoints, $command->nodeAggregateId, diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeDisabling/NodeDisabling.php b/Neos.ContentRepository.Core/Classes/Feature/NodeDisabling/NodeDisabling.php index 9e06e7f8c62..c78564cd4f0 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeDisabling/NodeDisabling.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeDisabling/NodeDisabling.php @@ -46,6 +46,7 @@ abstract protected function getInterDimensionalVariationGraph(): DimensionSpace\ private function handleDisableNodeAggregate( DisableNodeAggregate $command ): EventsToPublish { + $this->requireContentStream($command->workspaceName); $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($command->workspaceName); $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraphAdapter); $this->requireDimensionSpacePointToExist($command->coveredDimensionSpacePoint); diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php index f913f8ca3b2..ee5a179ea62 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php @@ -29,7 +29,7 @@ use Neos\ContentRepository\Core\Feature\Common\NodeAggregateEventPublisher; use Neos\ContentRepository\Core\Feature\Common\NodeCreationInternals; use Neos\ContentRepository\Core\Feature\ContentGraphAdapterInterface; -use Neos\ContentRepository\Core\Feature\ContentGraphAdapterProviderInterface; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterProvider; use Neos\ContentRepository\Core\Feature\ContentStreamEventStreamName; use Neos\ContentRepository\Core\Feature\NodeCreation\Event\NodeAggregateWithNodeWasCreated; use Neos\ContentRepository\Core\Feature\NodeDuplication\Command\CopyNodesRecursively; @@ -53,7 +53,7 @@ public function __construct( private readonly NodeTypeManager $nodeTypeManager, private readonly ContentDimensionZookeeper $contentDimensionZookeeper, private readonly InterDimensionalVariationGraph $interDimensionalVariationGraph, - private readonly ContentGraphAdapterProviderInterface $contentGraphAdapterProvider + private readonly ContentGraphAdapterProvider $contentGraphAdapterProvider ) { } diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeModification/NodeModification.php b/Neos.ContentRepository.Core/Classes/Feature/NodeModification/NodeModification.php index a92140aab23..78e2e89f98a 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeModification/NodeModification.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeModification/NodeModification.php @@ -45,6 +45,7 @@ abstract protected function requireProjectedNodeAggregate( private function handleSetNodeProperties( SetNodeProperties $command ): EventsToPublish { + $this->requireContentStream($command->workspaceName); $contentGraphAdapter = $this->getContentGraphAdapter($command->workspaceName); $this->requireDimensionSpacePointToExist($command->originDimensionSpacePoint->toDimensionSpacePoint()); $nodeAggregate = $this->requireProjectedNodeAggregate( diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php index 984e2249fcd..67010a7f780 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php @@ -66,6 +66,7 @@ abstract protected function requireProjectedNodeAggregate( private function handleMoveNodeAggregate( MoveNodeAggregate $command ): EventsToPublish { + $this->requireContentStream($command->workspaceName); $contentGraphAdapter = $this->getContentGraphAdapter($command->workspaceName); $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraphAdapter); $this->requireDimensionSpacePointToExist($command->dimensionSpacePoint); @@ -268,7 +269,7 @@ private function resolveSucceedingSiblingFromOriginSiblings( ); $succeedingSiblingCandidates = iterator_to_array( $succeedingSiblingId - ? $contentGraphAdapter->findSuceedingSiblingNodesInSubgraph($originDimensionSpacePoint, $succeedingSiblingId) + ? $contentGraphAdapter->findSucceedingSiblingNodesInSubgraph($originDimensionSpacePoint, $succeedingSiblingId) : Nodes::createEmpty() ); /* @var $precedingSiblingCandidates Node[] */ @@ -303,7 +304,7 @@ private function resolveSucceedingSiblingFromOriginSiblings( ); if ($precedingSibling) { // TODO: I don't think implementing the same filtering as for the contentGraph is sensible here, so we are fetching all siblings while only interested in the next, maybe could become a more specialised method. - $alternateSucceedingSiblings = $contentGraphAdapter->findSuceedingSiblingNodesInSubgraph($currentDimensionSpacePoint, $precedingSiblingId); + $alternateSucceedingSiblings = $contentGraphAdapter->findSucceedingSiblingNodesInSubgraph($currentDimensionSpacePoint, $precedingSiblingId); if (count($alternateSucceedingSiblings) > 0) { $succeedingSibling = $alternateSucceedingSiblings->first(); break; @@ -339,14 +340,14 @@ private function resolveCoverageNodeMoveMappings( ->getIntersection($affectedDimensionSpacePoints) as $dimensionSpacePoint ) { $succeedingSibling = $succeedingSiblingId - ? $this->findSiblingWithin($contentGraphAdapter, $originDimensionSpacePoint->toDimensionSpacePoint(), $succeedingSiblingId, $parentId) + ? $this->findSiblingWithin($contentGraphAdapter, $dimensionSpacePoint, $succeedingSiblingId, $parentId) : null; if (!$succeedingSibling) { $precedingSibling = $precedingSiblingId - ? $this->findSiblingWithin($contentGraphAdapter, $originDimensionSpacePoint->toDimensionSpacePoint(), $precedingSiblingId, $parentId) + ? $this->findSiblingWithin($contentGraphAdapter, $dimensionSpacePoint, $precedingSiblingId, $parentId) : null; if ($precedingSiblingId && $precedingSibling) { - $alternateSucceedingSiblings = $contentGraphAdapter->findSuceedingSiblingNodesInSubgraph( + $alternateSucceedingSiblings = $contentGraphAdapter->findSucceedingSiblingNodesInSubgraph( $dimensionSpacePoint, $precedingSiblingId ); diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/NodeReferencing.php b/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/NodeReferencing.php index c76b5dbe8e0..b99ccf7dcee 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/NodeReferencing.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/NodeReferencing.php @@ -45,6 +45,7 @@ abstract protected function requireProjectedNodeAggregate( private function handleSetNodeReferences( SetNodeReferences $command ): EventsToPublish { + $this->requireContentStream($command->workspaceName); $contentGraphAdapter = $this->getContentGraphAdapter($command->workspaceName); $this->requireDimensionSpacePointToExist($command->sourceOriginDimensionSpacePoint->toDimensionSpacePoint()); $sourceNodeAggregate = $this->requireProjectedNodeAggregate( 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 177e38bc9d9..91f4da561a8 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeRemoval/NodeRemoval.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeRemoval/NodeRemoval.php @@ -47,6 +47,7 @@ abstract protected function areAncestorNodeTypeConstraintChecksEnabled(): bool; private function handleRemoveNodeAggregate( RemoveNodeAggregate $command ): EventsToPublish { + $this->requireContentStream($command->workspaceName); $contentGraphAdapter = $this->getContentGraphAdapter($command->workspaceName); $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraphAdapter); $nodeAggregate = $this->requireProjectedNodeAggregate( 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 c4e1f6545f4..19db8561142 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeVariation/NodeVariation.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeVariation/NodeVariation.php @@ -48,6 +48,7 @@ trait NodeVariation private function handleCreateNodeVariant( CreateNodeVariant $command ): EventsToPublish { + $this->requireContentStream($command->workspaceName); $contentGraphAdapter = $this->getContentGraphAdapter($command->workspaceName); $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraphAdapter); $nodeAggregate = $this->requireProjectedNodeAggregate( diff --git a/Neos.ContentRepository.Core/Classes/Feature/RootNodeCreation/RootNodeHandling.php b/Neos.ContentRepository.Core/Classes/Feature/RootNodeCreation/RootNodeHandling.php index 7818bd70570..1b23712a19e 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/RootNodeCreation/RootNodeHandling.php +++ b/Neos.ContentRepository.Core/Classes/Feature/RootNodeCreation/RootNodeHandling.php @@ -42,6 +42,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; /** * @internal implementation detail of Command Handlers @@ -68,6 +69,7 @@ abstract protected function requireNodeTypeToBeOfTypeRoot(NodeType $nodeType): v private function handleCreateRootNodeAggregateWithNode( CreateRootNodeAggregateWithNode $command ): EventsToPublish { + $this->requireContentStream($command->workspaceName); $contentGraphAdapter = $this->getContentGraphAdapter($command->workspaceName); $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraphAdapter); $this->requireProjectedNodeAggregateToNotExist( diff --git a/Neos.ContentRepository.Core/Classes/Feature/SubtreeTagging/SubtreeTagging.php b/Neos.ContentRepository.Core/Classes/Feature/SubtreeTagging/SubtreeTagging.php index 11007da966e..12ed6be6f6d 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/SubtreeTagging/SubtreeTagging.php +++ b/Neos.ContentRepository.Core/Classes/Feature/SubtreeTagging/SubtreeTagging.php @@ -14,7 +14,6 @@ * source code. */ -use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\DimensionSpace; use Neos\ContentRepository\Core\EventStore\Events; use Neos\ContentRepository\Core\EventStore\EventsToPublish; @@ -38,9 +37,9 @@ abstract protected function getInterDimensionalVariationGraph(): DimensionSpace\ private function handleTagSubtree(TagSubtree $command): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName); + $contentGraphAdapter = $this->getContentGraphAdapter($command->workspaceName); $this->requireDimensionSpacePointToExist($command->coveredDimensionSpacePoint); - $nodeAggregate = $this->requireProjectedNodeAggregate($contentStreamId, $command->nodeAggregateId); + $nodeAggregate = $this->requireProjectedNodeAggregate($contentGraphAdapter, $command->nodeAggregateId); $this->requireNodeAggregateToCoverDimensionSpacePoint( $nodeAggregate, $command->coveredDimensionSpacePoint @@ -60,7 +59,7 @@ private function handleTagSubtree(TagSubtree $command): EventsToPublish $events = Events::with( new SubtreeWasTagged( - $contentStreamId, + $contentGraphAdapter->getContentStreamId(), $command->nodeAggregateId, $affectedDimensionSpacePoints, $command->tag, @@ -68,7 +67,7 @@ private function handleTagSubtree(TagSubtree $command): EventsToPublish ); return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId) + ContentStreamEventStreamName::fromContentStreamId($contentGraphAdapter->getContentStreamId()) ->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand( $command, @@ -80,10 +79,10 @@ private function handleTagSubtree(TagSubtree $command): EventsToPublish public function handleUntagSubtree(UntagSubtree $command): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName); + $contentGraphAdapter = $this->getContentGraphAdapter($command->workspaceName); $this->requireDimensionSpacePointToExist($command->coveredDimensionSpacePoint); $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraphAdapter, $command->nodeAggregateId ); $this->requireNodeAggregateToCoverDimensionSpacePoint( @@ -105,7 +104,7 @@ public function handleUntagSubtree(UntagSubtree $command): EventsToPublish $events = Events::with( new SubtreeWasUntagged( - $contentStreamId, + $contentGraphAdapter->getContentStreamId(), $command->nodeAggregateId, $affectedDimensionSpacePoints, $command->tag, @@ -113,7 +112,7 @@ public function handleUntagSubtree(UntagSubtree $command): EventsToPublish ); return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId)->getEventStreamName(), + ContentStreamEventStreamName::fromContentStreamId($contentGraphAdapter->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 8c3c8a3eb0c..44dc7835c1a 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php @@ -87,7 +87,7 @@ public function __construct( private EventPersister $eventPersister, private EventStoreInterface $eventStore, private EventNormalizer $eventNormalizer, - private ContentGraphAdapterProviderInterface $contentGraphAdapterProvider + private ContentGraphAdapterProvider $contentGraphAdapterProvider ) { } @@ -126,11 +126,12 @@ private function handleCreateWorkspace( ): EventsToPublish { try { $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($command->workspaceName); - } catch (WorkspaceDoesNotExist $e) { + $contentStreamId = $contentGraphAdapter->getContentStreamId(); + } catch (ContentStreamDoesNotExistYet $e) { // Desired outcome } - isset($contentGraphAdapter) + isset($contentStreamId) && throw new WorkspaceAlreadyExists(sprintf( 'The workspace %s already exists', $command->workspaceName->value @@ -205,11 +206,12 @@ private function handleCreateRootWorkspace( ): EventsToPublish { try { $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($command->workspaceName); - } catch (WorkspaceDoesNotExist $e) { + $contentStreamId = $contentGraphAdapter->getContentStreamId(); + } catch (ContentStreamDoesNotExistYet $e) { // Desired outcome } - isset($contentGraphAdapter) + isset($contentStreamId) && throw new WorkspaceAlreadyExists(sprintf( 'The workspace %s already exists', $command->workspaceName->value @@ -524,7 +526,7 @@ private function handlePublishIndividualNodesFromWorkspace( try { // 4) using the new content stream, apply the matching commands $this->contentGraphAdapterProvider->overrideContentStreamId( - $command->workspaceName, + $baseWorkspace->workspaceName, $command->contentStreamIdForMatchingPart, function () use ($matchingCommands, $contentRepository, $baseWorkspace): void { foreach ($matchingCommands as $matchingCommand) { @@ -629,9 +631,10 @@ private function handleDiscardIndividualNodesFromWorkspace( DiscardIndividualNodesFromWorkspace $command, ContentRepository $contentRepository, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName); - $oldWorkspaceContentStreamId = $workspace->currentContentStreamId; - $oldWorkspaceContentStreamIdState = $this->contentGraphAdapter->findStateForContentStream($oldWorkspaceContentStreamId); + $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($command->workspaceName); + $workspace = $contentGraphAdapter->getWorkspace(); + $oldWorkspaceContentStreamId = $contentGraphAdapter->getContentStreamId(); + $oldWorkspaceContentStreamIdState = $contentGraphAdapter->findStateForContentStream(); if ($oldWorkspaceContentStreamIdState === null) { throw new \DomainException('Cannot discard nodes on a workspace with a stateless content stream', 1710408112); } @@ -661,7 +664,7 @@ private function handleDiscardIndividualNodesFromWorkspace( // 4) using the new content stream, apply the commands to keep try { $this->contentGraphAdapterProvider->overrideContentStreamId( - $command->workspaceName, + $baseWorkspace->workspaceName, $command->newContentStreamId, function () use ($commandsToKeep, $contentRepository, $baseWorkspace): void { foreach ($commandsToKeep as $matchingCommand) { @@ -903,12 +906,8 @@ private function handleChangeWorkspaceOwner( */ private function requireWorkspace(WorkspaceName $workspaceName): Workspace { - $workspace = $this->contentGraphAdapter->findWorkspaceByName($workspaceName); - if (is_null($workspace)) { - throw WorkspaceDoesNotExist::butWasSupposedTo($workspaceName); - } - - return $workspace; + $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($workspaceName); + return $contentGraphAdapter->getWorkspace(); } /** @@ -921,8 +920,10 @@ private function requireBaseWorkspace(Workspace $workspace): Workspace throw WorkspaceHasNoBaseWorkspaceName::butWasSupposedTo($workspace->workspaceName); } - $baseWorkspace = $this->contentGraphAdapter->findWorkspaceByName($workspace->baseWorkspaceName); - if ($baseWorkspace === null) { + try { + $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($workspace->baseWorkspaceName); + $baseWorkspace = $contentGraphAdapter->getWorkspace(); + } catch(WorkspaceDoesNotExist $_) { throw BaseWorkspaceDoesNotExist::butWasSupposedTo($workspace->workspaceName); } @@ -940,11 +941,12 @@ private function requireNonCircularRelationBetweenWorkspaces(Workspace $workspac } $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 = $this->contentGraphAdapter->findWorkspaceByName($nextBaseWorkspace->baseWorkspaceName); + $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($nextBaseWorkspace->baseWorkspaceName); + $nextBaseWorkspace = $contentGraphAdapter->getWorkspace(); } } diff --git a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php index eb447e8b929..3e4cd0450e0 100644 --- a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php +++ b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php @@ -10,7 +10,7 @@ use Neos\ContentRepository\Core\Feature\Common\NodeVariationInternals; use Neos\ContentRepository\Core\Feature\Common\TetheredNodeInternals; use Neos\ContentRepository\Core\Feature\ContentGraphAdapterInterface; -use Neos\ContentRepository\Core\Feature\ContentGraphAdapterProviderInterface; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterProvider; use Neos\ContentRepository\Core\Feature\ContentStreamEventStreamName; use Neos\ContentRepository\Core\Feature\NodeMove\Dto\CoverageNodeMoveMapping; use Neos\ContentRepository\Core\Feature\NodeMove\Dto\CoverageNodeMoveMappings; @@ -36,7 +36,7 @@ class TetheredNodeAdjustments use TetheredNodeInternals; public function __construct( - private readonly ContentGraphAdapterProviderInterface $contentGraphAdapterProvider, + private readonly ContentGraphAdapterProvider $contentGraphAdapterProvider, private readonly ProjectedNodeIterator $projectedNodeIterator, private readonly NodeTypeManager $nodeTypeManager, private readonly DimensionSpace\InterDimensionalVariationGraph $interDimensionalVariationGraph, diff --git a/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentService.php b/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentService.php index 517be4a1cbe..4208a3a4092 100644 --- a/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentService.php +++ b/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentService.php @@ -9,7 +9,7 @@ use Neos\ContentRepository\Core\EventStore\EventPersister; use Neos\ContentRepository\Core\EventStore\EventsToPublish; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; -use Neos\ContentRepository\Core\Feature\ContentGraphAdapterProviderInterface; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterProvider; use Neos\ContentRepository\Core\Infrastructure\Property\PropertyConverter; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\NodeType\NodeTypeName; @@ -35,7 +35,7 @@ public function __construct( NodeTypeManager $nodeTypeManager, InterDimensionalVariationGraph $interDimensionalVariationGraph, PropertyConverter $propertyConverter, - ContentGraphAdapterProviderInterface $contentGraphAdapterProvider + ContentGraphAdapterProvider $contentGraphAdapterProvider ) { $projectedNodeIterator = new ProjectedNodeIterator( $contentRepository->getWorkspaceFinder(), diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/GenericCommandExecutionAndEventPublication.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/GenericCommandExecutionAndEventPublication.php index 5082190b726..f9ce37a5f79 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/GenericCommandExecutionAndEventPublication.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/GenericCommandExecutionAndEventPublication.php @@ -223,6 +223,8 @@ public function eventNumberIs(int $eventNumber, string $eventType, TableNode $pa $actual = DimensionSpacePointSet::fromArray($actualValue); Assert::assertTrue($expected->equals($actual), 'Actual Dimension Space Point set "' . json_encode($actualValue) . '" does not match expected Dimension Space Point set "' . $assertionTableRow['Expected'] . '"'); } else { +// die(); +// exit; Assert::assertJsonStringEqualsJsonString($assertionTableRow['Expected'], json_encode($actualValue)); } } diff --git a/Neos.ContentRepositoryRegistry.DoctrineDbalClient/Configuration/Objects.yaml b/Neos.ContentRepositoryRegistry.DoctrineDbalClient/Configuration/Objects.yaml index 1dfd0f4d332..e300debe9cc 100644 --- a/Neos.ContentRepositoryRegistry.DoctrineDbalClient/Configuration/Objects.yaml +++ b/Neos.ContentRepositoryRegistry.DoctrineDbalClient/Configuration/Objects.yaml @@ -9,3 +9,12 @@ Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphProjectionFactory: value: 'Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphProjectionFactory' 2: object: 'Neos\ContentRepository\Core\Infrastructure\DbalClientInterface' + +Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphAdapterFactoryBuilder: + scope: singleton + factoryObjectName: 'Neos\ContentRepositoryRegistry\Infrastructure\GenericObjectFactory' + arguments: + 1: + value: 'Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphAdapterFactoryBuilder' + 2: + object: 'Neos\ContentRepository\Core\Infrastructure\DbalClientInterface' diff --git a/Neos.ContentRepositoryRegistry.DoctrineDbalClient/Configuration/Settings.yaml b/Neos.ContentRepositoryRegistry.DoctrineDbalClient/Configuration/Settings.yaml index 7441862dd71..b13fcf70e46 100644 --- a/Neos.ContentRepositoryRegistry.DoctrineDbalClient/Configuration/Settings.yaml +++ b/Neos.ContentRepositoryRegistry.DoctrineDbalClient/Configuration/Settings.yaml @@ -7,5 +7,5 @@ Neos: # catchUpHooks for content cache flushing 'Neos.ContentRepository:ContentGraph': factoryObjectName: Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphProjectionFactory - contentGraphAdapter: - factoryObjectName: Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphAdapterProviderFactory + contentGraphAdapterFactory: + factoryObjectName: Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphAdapterFactoryBuilder diff --git a/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php b/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php index cc7fc0e1cbe..6c5c63cd8d3 100644 --- a/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php +++ b/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php @@ -9,8 +9,9 @@ use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; use Neos\ContentRepository\Core\Factory\ProjectionsAndCatchUpHooksFactory; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterFactoryBuilderInterface; use Neos\ContentRepository\Core\Feature\ContentGraphAdapterProviderFactoryInterface; -use Neos\ContentRepository\Core\Feature\ContentGraphAdapterProviderInterface; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterProvider; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\Projection\CatchUpHookFactoryInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface; @@ -286,14 +287,14 @@ private function buildClock(ContentRepositoryId $contentRepositoryIdentifier, ar } /** @param array $contentRepositorySettings */ - private function buildContentGraphAdapterProvider(ContentRepositoryId $contentRepositoryIdentifier, array $contentRepositorySettings): ContentGraphAdapterProviderInterface + private function buildContentGraphAdapterProvider(ContentRepositoryId $contentRepositoryIdentifier, array $contentRepositorySettings): ContentGraphAdapterFactoryBuilderInterface { - isset($contentRepositorySettings['contentGraphAdapter']['factoryObjectName']) || throw InvalidConfigurationException::fromMessage('Content repository "%s" does not have contentGraphAdapter.factoryObjectName configured.', $contentRepositoryIdentifier->value); - $contentGraphAdapterFactory = $this->objectManager->get($contentRepositorySettings['contentGraphAdapter']['factoryObjectName']); - if (!$contentGraphAdapterFactory instanceof ContentGraphAdapterProviderFactoryInterface) { - throw InvalidConfigurationException::fromMessage('contentGraphAdapter.factoryObjectName for content repository "%s" is not an instance of %s but %s.', $contentRepositoryIdentifier->value, ContentGraphAdapterProviderFactoryInterface::class, get_debug_type($contentGraphAdapterFactory)); + isset($contentRepositorySettings['contentGraphAdapterFactory']['factoryObjectName']) || throw InvalidConfigurationException::fromMessage('Content repository "%s" does not have contentGraphAdapterFactory.factoryObjectName configured.', $contentRepositoryIdentifier->value); + $contentGraphAdapterFactoryBuilder = $this->objectManager->get($contentRepositorySettings['contentGraphAdapterFactory']['factoryObjectName']); + if (!$contentGraphAdapterFactoryBuilder instanceof ContentGraphAdapterFactoryBuilderInterface) { + throw InvalidConfigurationException::fromMessage('contentGraphAdapterFactory.factoryObjectName for content repository "%s" is not an instance of %s but %s.', $contentRepositoryIdentifier->value, ContentGraphAdapterFactoryBuilderInterface::class, get_debug_type($contentGraphAdapterFactoryBuilder)); } - - return $contentGraphAdapterFactory->build($contentRepositoryIdentifier, $contentRepositorySettings['contentGraphAdapter']['options'] ?? []); + // TODO: Do we want to add options here? Would then have to be a setter I guess.... + return $contentGraphAdapterFactoryBuilder; } } From 590d143157b7124f443218095c05f1cf090b03ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Sat, 13 Apr 2024 17:47:15 +0200 Subject: [PATCH 06/18] Linting and Naming ContentGraphAdapter --- .../src/ContentGraphAdapter.php | 12 ++++++------ .../src/ContentGraphAdapterFactory.php | 11 +++++------ .../src/ContentGraphAdapterFactoryBuilder.php | 1 + .../ContentGraphAdapterFactoryBuilderInterface.php | 1 + .../Feature/ContentGraphAdapterFactoryInterface.php | 7 ++++--- .../Classes/Feature/ContentGraphAdapterProvider.php | 10 +++++----- .../Classes/Feature/ContentStreamCommandHandler.php | 1 - .../Classes/Feature/NodeMove/NodeMove.php | 3 +-- .../Classes/Feature/WorkspaceCommandHandler.php | 5 +++-- .../AssetUsage/Dto/AssetIdAndOriginalAssetId.php | 1 + 10 files changed, 27 insertions(+), 25 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php index e30b64bf65d..6135b19e828 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php @@ -1,4 +1,5 @@ workspaceName === null && $this->contentStreamId === null) { throw new \InvalidArgumentException('Neither ContentStreamId nor WorkspaceName given in creation of ContentGraphAdapter, one is required.', 1712746528); } @@ -241,7 +241,7 @@ public function subgraphContainsNodes(DimensionSpacePoint $dimensionSpacePoint): } return $result > 0; - } catch (DbalDriverException|DbalException $e) { + } catch (DbalDriverException | DbalException $e) { throw new \RuntimeException(sprintf('Failed to count all nodes: %s', $e->getMessage()), 1678364741, $e); } } @@ -439,7 +439,7 @@ private function fetchRows(QueryBuilder $queryBuilder): array } try { return $result->fetchAllAssociative(); - } catch (DriverException|DBALException $e) { + } catch (DriverException | DBALException $e) { throw new \RuntimeException(sprintf('Failed to fetch rows from database: %s', $e->getMessage()), 1701444358, $e); } } @@ -660,7 +660,7 @@ private function fetchNode(QueryBuilder $queryBuilder, DimensionSpacePoint $dime { try { $nodeRow = $this->executeQuery($queryBuilder)->fetchAssociative(); - } catch (DbalDriverException|DbalException $e) { + } catch (DbalDriverException | DbalException $e) { throw new \RuntimeException(sprintf('Failed to fetch node: %s', $e->getMessage()), 1678286030, $e); } if ($nodeRow === false) { @@ -679,7 +679,7 @@ private function fetchNodes(QueryBuilder $queryBuilder, DimensionSpacePoint $dim { try { $nodeRows = $this->executeQuery($queryBuilder)->fetchAllAssociative(); - } catch (DbalDriverException|DbalException $e) { + } catch (DbalDriverException | DbalException $e) { throw new \RuntimeException(sprintf('Failed to fetch nodes: %s', $e->getMessage()), 1678292896, $e); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php index 45cb605d1fa..26665c55980 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php @@ -1,4 +1,5 @@ contentRepositoryId ); @@ -38,19 +38,18 @@ public function __construct( ); } - public function bindAdapter(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphAdapterInterface + public function create(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphAdapterInterface { return new ContentGraphAdapter($this->dbalConnection, $this->tableNamePrefix, $this->nodeFactory, $workspaceName, $contentStreamId); } - public function adapterFromContentStreamId(ContentStreamId $contentStreamId): ContentGraphAdapterInterface + public function createFromContentStreamId(ContentStreamId $contentStreamId): ContentGraphAdapterInterface { return new ContentGraphAdapter($this->dbalConnection, $this->tableNamePrefix, $this->nodeFactory, null, $contentStreamId); } - public function adapterFromWorkspaceName(WorkspaceName $workspaceName): ContentGraphAdapterInterface + public function createFromWorkspaceName(WorkspaceName $workspaceName): ContentGraphAdapterInterface { return new ContentGraphAdapter($this->dbalConnection, $this->tableNamePrefix, $this->nodeFactory, $workspaceName, null); } - } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactoryBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactoryBuilder.php index 1104c582196..fe3ccdfb48c 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactoryBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactoryBuilder.php @@ -1,4 +1,5 @@ contentGraphAdapterFactory->adapterFromContentStreamId($contentStreamId); + return $this->contentGraphAdapterFactory->createFromContentStreamId($contentStreamId); } /** @@ -50,7 +50,7 @@ public function resolveContentStreamIdAndGet(WorkspaceName $workspaceName): Cont return $this->adapterInstances[$workspaceName->value]; } - return $this->contentGraphAdapterFactory->adapterFromWorkspaceName($workspaceName); + return $this->contentGraphAdapterFactory->createFromWorkspaceName($workspaceName); } /** @@ -61,7 +61,7 @@ public function resolveContentStreamIdAndGet(WorkspaceName $workspaceName): Cont */ public function overrideContentStreamId(WorkspaceName $workspaceName, ContentStreamId $contentStreamId, \Closure $fn): void { - $adapter = $this->contentGraphAdapterFactory->bindAdapter($workspaceName, $contentStreamId); + $adapter = $this->contentGraphAdapterFactory->create($workspaceName, $contentStreamId); $replacedAdapter = $this->adapterInstances[$workspaceName->value] ?? null; $this->adapterInstances[$workspaceName->value] = $adapter; diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php index c2e1424e5d2..3d533f2835d 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php @@ -44,7 +44,6 @@ */ final class ContentStreamCommandHandler implements CommandHandlerInterface { - public function __construct( protected readonly ContentGraphAdapterProvider $contentGraphAdapterProvider ) { diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php index 67010a7f780..21eb6b1534d 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php @@ -227,8 +227,7 @@ private function findSiblingWithin( DimensionSpace\DimensionSpacePoint $coveredDimensionSpacePoint, NodeAggregateId $siblingId, ?NodeAggregateId $parentId - ): ?Node - { + ): ?Node { $siblingCandidate = $contentGraphAdapter->findNodeInSubgraph($coveredDimensionSpacePoint, $siblingId); if (!$siblingCandidate) { return null; diff --git a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php index 268efb2cb8d..723f6c60214 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php @@ -546,7 +546,8 @@ function () use ($matchingCommands, $contentRepository, $baseWorkspace): void { $baseWorkspace->workspaceName, ))->block(); } - }); + } + ); // 5) take EVENTS(MATCHING) and apply them to base WS. $this->publishContentStream( @@ -928,7 +929,7 @@ private function requireBaseWorkspace(Workspace $workspace): Workspace try { $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($workspace->baseWorkspaceName); $baseWorkspace = $contentGraphAdapter->getWorkspace(); - } catch(WorkspaceDoesNotExist $_) { + } catch (WorkspaceDoesNotExist $_) { throw BaseWorkspaceDoesNotExist::butWasSupposedTo($workspace->workspaceName); } diff --git a/Neos.Neos/Classes/AssetUsage/Dto/AssetIdAndOriginalAssetId.php b/Neos.Neos/Classes/AssetUsage/Dto/AssetIdAndOriginalAssetId.php index d373d57f9cc..cfb347b0a6d 100644 --- a/Neos.Neos/Classes/AssetUsage/Dto/AssetIdAndOriginalAssetId.php +++ b/Neos.Neos/Classes/AssetUsage/Dto/AssetIdAndOriginalAssetId.php @@ -9,6 +9,7 @@ /** * @internal */ + #[Flow\Proxy(false)] readonly class AssetIdAndOriginalAssetId { From 1b1f3861659fb145329244cb4d911eac218b5413 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Sat, 13 Apr 2024 18:58:43 +0200 Subject: [PATCH 07/18] More code cleanup --- .../src/ContentGraphAdapter.php | 13 +++++++++++-- .../src/ContentGraphAdapterFactory.php | 10 +++++++--- .../Classes/ContentRepository.php | 8 -------- .../Classes/Feature/NodeMove/NodeMove.php | 2 +- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php index 6135b19e828..2c1e2bb3841 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php @@ -15,6 +15,7 @@ use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\Feature\ContentGraphAdapterInterface; +use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\NodeType\NodeTypeName; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\CountChildNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindChildNodesFilter; @@ -59,7 +60,9 @@ use Neos\EventStore\Model\EventStream\MaybeVersion; /** + * DBAL implementation of low level read query operations for the content graph * + * @įnternal */ class ContentGraphAdapter implements ContentGraphAdapterInterface { @@ -67,6 +70,7 @@ public function __construct( private readonly Connection $dbalConnection, private readonly string $tableNamePrefix, private readonly NodeFactory $nodeFactory, + private readonly NodeTypeManager $nodeTypeManager, public ?WorkspaceName $workspaceName, public ?ContentStreamId $contentStreamId, ) { @@ -93,7 +97,12 @@ public function rootNodeAggregateWithTypeExists(NodeTypeName $nodeTypeName): boo ->andWhere('n.nodetypename = :nodeTypeName') ->setParameter('nodeTypeName', $nodeTypeName->value); - return $queryBuilder->execute()->fetchOne() > 0; + $result = $queryBuilder->execute(); + if (!$result instanceof Result) { + return false; + } + + return $result->fetchOne() > 0; } public function findParentNodeAggregates(NodeAggregateId $childNodeAggregateId): iterable @@ -750,8 +759,8 @@ public function getWorkspace(): Workspace $query = $this->dbalConnection->prepare('SELECT * FROM cr_default_p_workspace WHERE workspacename LIKE :workspaceName'); $result = $query->executeQuery(['workspaceName' => $this->getWorkspaceName()->value]); $row = $result->fetchAssociative(); - // We can assume that we get a row otherwise getWorkspaceName would have thrown already + // We can assume that we get a row otherwise getWorkspaceName would have thrown already return new Workspace( WorkspaceName::fromString($row['workspacename']), !empty($row['baseworkspacename']) ? WorkspaceName::fromString($row['baseworkspacename']) : null, diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php index 26665c55980..4b01172ab0e 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php @@ -8,6 +8,7 @@ use Neos\ContentRepository\Core\Factory\ProjectionFactoryDependencies; use Neos\ContentRepository\Core\Feature\ContentGraphAdapterFactoryInterface; use Neos\ContentRepository\Core\Feature\ContentGraphAdapterInterface; +use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; @@ -18,6 +19,8 @@ class ContentGraphAdapterFactory implements ContentGraphAdapterFactoryInterface { private NodeFactory $nodeFactory; + private NodeTypeManager $nodeTypeManager; + private string $tableNamePrefix; public function __construct( @@ -30,6 +33,7 @@ public function __construct( $this->tableNamePrefix = $tableNamePrefix; $dimensionSpacePointsRepository = new DimensionSpacePointsRepository($this->dbalConnection, $tableNamePrefix); + $this->nodeTypeManager = $projectionFactoryDependencies->nodeTypeManager; $this->nodeFactory = new NodeFactory( $projectionFactoryDependencies->contentRepositoryId, $projectionFactoryDependencies->nodeTypeManager, @@ -40,16 +44,16 @@ public function __construct( public function create(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphAdapterInterface { - return new ContentGraphAdapter($this->dbalConnection, $this->tableNamePrefix, $this->nodeFactory, $workspaceName, $contentStreamId); + return new ContentGraphAdapter($this->dbalConnection, $this->tableNamePrefix, $this->nodeFactory, $this->nodeTypeManager, $workspaceName, $contentStreamId); } public function createFromContentStreamId(ContentStreamId $contentStreamId): ContentGraphAdapterInterface { - return new ContentGraphAdapter($this->dbalConnection, $this->tableNamePrefix, $this->nodeFactory, null, $contentStreamId); + return new ContentGraphAdapter($this->dbalConnection, $this->tableNamePrefix, $this->nodeFactory, $this->nodeTypeManager, null, $contentStreamId); } public function createFromWorkspaceName(WorkspaceName $workspaceName): ContentGraphAdapterInterface { - return new ContentGraphAdapter($this->dbalConnection, $this->tableNamePrefix, $this->nodeFactory, $workspaceName, null); + return new ContentGraphAdapter($this->dbalConnection, $this->tableNamePrefix, $this->nodeFactory, $this->nodeTypeManager, $workspaceName, null); } } diff --git a/Neos.ContentRepository.Core/Classes/ContentRepository.php b/Neos.ContentRepository.Core/Classes/ContentRepository.php index 8d92d142775..f60e4f0f696 100644 --- a/Neos.ContentRepository.Core/Classes/ContentRepository.php +++ b/Neos.ContentRepository.Core/Classes/ContentRepository.php @@ -259,12 +259,4 @@ public function getContentDimensionSource(): ContentDimensionSourceInterface { return $this->contentDimensionSource; } - - /** - * @internal - */ - public function getContentGraphAdapter(): ContentGraphAdapterInterface - { - return $this->getContentGraph(); - } } diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php index 21eb6b1534d..7c278857240 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php @@ -401,7 +401,7 @@ private function resolveCoverageNodeMoveMappings( throw new \InvalidArgumentException( sprintf( 'Parent ' . $parentId . ' not found in contentstream "%s" and dimension space point "%s" ', - $succeedingSibling->nodeAggregateId->value, + $contentGraphAdapter->getContentStreamId()->value, json_encode($dimensionSpacePoint, JSON_PARTIAL_OUTPUT_ON_ERROR) ), 1667597013 From 6d501b21ef266abf4fb475bd4bccea5c92b49e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Sat, 13 Apr 2024 21:17:20 +0200 Subject: [PATCH 08/18] Code renaming and refinement --- .../src/ContentGraphAdapter.php | 4 +++- .../src/ContentGraphAdapterFactory.php | 14 ++++++++---- .../Feature/ContentGraphAdapterProvider.php | 8 +++---- .../Feature/ContentStreamCommandHandler.php | 10 ++++----- .../DimensionSpaceCommandHandler.php | 4 ++-- .../Feature/NodeAggregateCommandHandler.php | 2 +- .../Feature/NodeDisabling/NodeDisabling.php | 2 +- .../NodeDuplicationCommandHandler.php | 2 +- .../Feature/WorkspaceCommandHandler.php | 16 +++++++------- .../ContentGraph/ContentGraphInterface.php | 22 +++++++++++++++++++ .../Adjustment/TetheredNodeAdjustments.php | 4 ++-- 11 files changed, 59 insertions(+), 29 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php index 2c1e2bb3841..7702f76e83d 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php @@ -44,6 +44,7 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\Projection\Workspace\Workspace; use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceStatus; +use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet; use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Id\UuidFactory; @@ -62,13 +63,14 @@ /** * DBAL implementation of low level read query operations for the content graph * - * @įnternal + * @internal */ class ContentGraphAdapter implements ContentGraphAdapterInterface { public function __construct( private readonly Connection $dbalConnection, private readonly string $tableNamePrefix, + private ContentRepositoryId $contentRepositoryId, private readonly NodeFactory $nodeFactory, private readonly NodeTypeManager $nodeTypeManager, public ?WorkspaceName $workspaceName, diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php index 4b01172ab0e..9f087a45175 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php @@ -9,11 +9,14 @@ use Neos\ContentRepository\Core\Feature\ContentGraphAdapterFactoryInterface; use Neos\ContentRepository\Core\Feature\ContentGraphAdapterInterface; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; +use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** - * + * Factory for the ContentGraphAdapter implementation for doctrine DBAL + * + * @internal */ class ContentGraphAdapterFactory implements ContentGraphAdapterFactoryInterface { @@ -23,6 +26,8 @@ class ContentGraphAdapterFactory implements ContentGraphAdapterFactoryInterface private string $tableNamePrefix; + private ContentRepositoryId $contentRepositoryId; + public function __construct( private readonly Connection $dbalConnection, ProjectionFactoryDependencies $projectionFactoryDependencies @@ -31,6 +36,7 @@ public function __construct( $projectionFactoryDependencies->contentRepositoryId ); $this->tableNamePrefix = $tableNamePrefix; + $this->contentRepositoryId = $projectionFactoryDependencies->contentRepositoryId; $dimensionSpacePointsRepository = new DimensionSpacePointsRepository($this->dbalConnection, $tableNamePrefix); $this->nodeTypeManager = $projectionFactoryDependencies->nodeTypeManager; @@ -44,16 +50,16 @@ public function __construct( public function create(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphAdapterInterface { - return new ContentGraphAdapter($this->dbalConnection, $this->tableNamePrefix, $this->nodeFactory, $this->nodeTypeManager, $workspaceName, $contentStreamId); + return new ContentGraphAdapter($this->dbalConnection, $this->tableNamePrefix, $this->contentRepositoryId, $this->nodeFactory, $this->nodeTypeManager, $workspaceName, $contentStreamId); } public function createFromContentStreamId(ContentStreamId $contentStreamId): ContentGraphAdapterInterface { - return new ContentGraphAdapter($this->dbalConnection, $this->tableNamePrefix, $this->nodeFactory, $this->nodeTypeManager, null, $contentStreamId); + return new ContentGraphAdapter($this->dbalConnection, $this->tableNamePrefix, $this->contentRepositoryId, $this->nodeFactory, $this->nodeTypeManager, null, $contentStreamId); } public function createFromWorkspaceName(WorkspaceName $workspaceName): ContentGraphAdapterInterface { - return new ContentGraphAdapter($this->dbalConnection, $this->tableNamePrefix, $this->nodeFactory, $this->nodeTypeManager, $workspaceName, null); + return new ContentGraphAdapter($this->dbalConnection, $this->tableNamePrefix, $this->contentRepositoryId, $this->nodeFactory, $this->nodeTypeManager, $workspaceName, null); } } diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterProvider.php b/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterProvider.php index 5101a7eaa6c..1de0f2abb8c 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterProvider.php +++ b/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterProvider.php @@ -12,7 +12,7 @@ * This is available on the write side of the ContentRepository. * * @see ContentGraphAdapterInterface - * @internal + * @api only for read access during write operations and in services */ class ContentGraphAdapterProvider { @@ -29,13 +29,13 @@ public function __construct( /** * TODO: We should not need this, * TODO: after introducing the NodeIdentity we can change usages to - * TODO: ContentGraphAdapterProvider::resolveContentStreamIdAndGet() and remove this + * TODO: ContentGraphAdapterProvider::fromWorkspaceName() and remove this * * @throws ContentStreamDoesNotExistYet if there is no content stream with the provided id * @deprecated * */ - public function resolveWorkspaceNameAndGet(ContentStreamId $contentStreamId): ContentGraphAdapterInterface + public function fromContentStreamId(ContentStreamId $contentStreamId): ContentGraphAdapterInterface { return $this->contentGraphAdapterFactory->createFromContentStreamId($contentStreamId); } @@ -44,7 +44,7 @@ public function resolveWorkspaceNameAndGet(ContentStreamId $contentStreamId): Co * @throws WorkspaceDoesNotExist if there is no workspace with the provided name * @throws ContentStreamDoesNotExistYet if the provided workspace does not resolve to an existing content stream */ - public function resolveContentStreamIdAndGet(WorkspaceName $workspaceName): ContentGraphAdapterInterface + public function fromWorkspaceName(WorkspaceName $workspaceName): ContentGraphAdapterInterface { if (isset($this->adapterInstances[$workspaceName->value])) { return $this->adapterInstances[$workspaceName->value]; diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php index 3d533f2835d..109eeffaa10 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php @@ -136,7 +136,7 @@ private function handleForkContentStream( $this->requireContentStreamToNotBeClosed($command->sourceContentStreamId); // TOOD: THis is not great - $sourceContentStreamVersion = $this->contentGraphAdapterProvider->resolveWorkspaceNameAndGet($command->sourceContentStreamId) + $sourceContentStreamVersion = $this->contentGraphAdapterProvider->fromContentStreamId($command->sourceContentStreamId) ->findVersionForContentStream(); $streamName = ContentStreamEventStreamName::fromContentStreamId($command->newContentStreamId) @@ -184,7 +184,7 @@ private function handleRemoveContentStream( protected function requireContentStreamToExist( ContentStreamId $contentStreamId ): void { - $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveWorkspaceNameAndGet($contentStreamId); + $contentGraphAdapter = $this->contentGraphAdapterProvider->fromContentStreamId($contentStreamId); if (!$contentGraphAdapter->contentStreamExists()) { throw new ContentStreamDoesNotExistYet( 'Content stream "' . $contentStreamId->value . '" does not exist yet.', @@ -196,7 +196,7 @@ protected function requireContentStreamToExist( protected function requireContentStreamToNotBeClosed( ContentStreamId $contentStreamId ): void { - $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveWorkspaceNameAndGet($contentStreamId); + $contentGraphAdapter = $this->contentGraphAdapterProvider->fromContentStreamId($contentStreamId); if ($contentGraphAdapter->findStateForContentStream() === ContentStreamState::STATE_CLOSED) { throw new ContentStreamIsClosed( 'Content stream "' . $contentStreamId->value . '" is closed.', @@ -208,7 +208,7 @@ protected function requireContentStreamToNotBeClosed( protected function requireContentStreamToBeClosed( ContentStreamId $contentStreamId ): void { - $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveWorkspaceNameAndGet($contentStreamId); + $contentGraphAdapter = $this->contentGraphAdapterProvider->fromContentStreamId($contentStreamId); if ($contentGraphAdapter->findStateForContentStream() !== ContentStreamState::STATE_CLOSED) { throw new ContentStreamIsNotClosed( 'Content stream "' . $contentStreamId->value . '" is not closed.', @@ -220,7 +220,7 @@ protected function requireContentStreamToBeClosed( protected function getExpectedVersionOfContentStream( ContentStreamId $contentStreamId ): ExpectedVersion { - $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveWorkspaceNameAndGet($contentStreamId); + $contentGraphAdapter = $this->contentGraphAdapterProvider->fromContentStreamId($contentStreamId); return ExpectedVersion::fromVersion( $contentGraphAdapter->findVersionForContentStream() ->unwrap() diff --git a/Neos.ContentRepository.Core/Classes/Feature/DimensionSpaceAdjustment/DimensionSpaceCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/DimensionSpaceAdjustment/DimensionSpaceCommandHandler.php index 64016eb5935..fb207e36b8e 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/DimensionSpaceAdjustment/DimensionSpaceCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/DimensionSpaceAdjustment/DimensionSpaceCommandHandler.php @@ -64,7 +64,7 @@ public function handle(CommandInterface $command, ContentRepository $contentRepo private function handleMoveDimensionSpacePoint( MoveDimensionSpacePoint $command ): EventsToPublish { - $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($command->workspaceName); + $contentGraphAdapter = $this->contentGraphAdapterProvider->fromWorkspaceName($command->workspaceName); $streamName = ContentStreamEventStreamName::fromContentStreamId($contentGraphAdapter->getContentStreamId()) ->getEventStreamName(); @@ -90,7 +90,7 @@ private function handleMoveDimensionSpacePoint( private function handleAddDimensionShineThrough( AddDimensionShineThrough $command ): EventsToPublish { - $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($command->workspaceName); + $contentGraphAdapter = $this->contentGraphAdapterProvider->fromWorkspaceName($command->workspaceName); $streamName = ContentStreamEventStreamName::fromContentStreamId($contentGraphAdapter->getContentStreamId()) ->getEventStreamName(); diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php index 8c00241b4f5..a2c35c4bb4d 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php @@ -154,7 +154,7 @@ public function getPropertyConverter(): PropertyConverter */ protected function getContentGraphAdapter(WorkspaceName $workspaceName): ContentGraphAdapterInterface { - return $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($workspaceName); + return $this->contentGraphAdapterProvider->fromWorkspaceName($workspaceName); } /** diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeDisabling/NodeDisabling.php b/Neos.ContentRepository.Core/Classes/Feature/NodeDisabling/NodeDisabling.php index c78564cd4f0..b970b2157c2 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeDisabling/NodeDisabling.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeDisabling/NodeDisabling.php @@ -47,7 +47,7 @@ private function handleDisableNodeAggregate( DisableNodeAggregate $command ): EventsToPublish { $this->requireContentStream($command->workspaceName); - $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($command->workspaceName); + $contentGraphAdapter = $this->contentGraphAdapterProvider->fromWorkspaceName($command->workspaceName); $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraphAdapter); $this->requireDimensionSpacePointToExist($command->coveredDimensionSpacePoint); $nodeAggregate = $this->requireProjectedNodeAggregate( diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php index ee5a179ea62..daf97445bf8 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php @@ -66,7 +66,7 @@ public function __construct( */ protected function getContentGraphAdapter(WorkspaceName $workspaceName): ContentGraphAdapterInterface { - return $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($workspaceName); + return $this->contentGraphAdapterProvider->fromWorkspaceName($workspaceName); } protected function getNodeTypeManager(): NodeTypeManager diff --git a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php index 723f6c60214..303d0c21aa1 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php @@ -126,7 +126,7 @@ private function handleCreateWorkspace( ContentRepository $contentRepository, ): EventsToPublish { try { - $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($command->workspaceName); + $contentGraphAdapter = $this->contentGraphAdapterProvider->fromWorkspaceName($command->workspaceName); $contentStreamId = $contentGraphAdapter->getContentStreamId(); } catch (ContentStreamDoesNotExistYet $e) { // Desired outcome @@ -138,7 +138,7 @@ private function handleCreateWorkspace( $command->workspaceName->value ), 1505830958921); - $baseWorkspaceContentGraphAdapter = $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($command->baseWorkspaceName); + $baseWorkspaceContentGraphAdapter = $this->contentGraphAdapterProvider->fromWorkspaceName($command->baseWorkspaceName); // if ($baseWorkspace === null) { // throw new BaseWorkspaceDoesNotExist(sprintf( // 'The workspace %s (base workspace of %s) does not exist', @@ -206,7 +206,7 @@ private function handleCreateRootWorkspace( ContentRepository $contentRepository, ): EventsToPublish { try { - $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($command->workspaceName); + $contentGraphAdapter = $this->contentGraphAdapterProvider->fromWorkspaceName($command->workspaceName); $contentStreamId = $contentGraphAdapter->getContentStreamId(); } catch (ContentStreamDoesNotExistYet $e) { // Desired outcome @@ -498,7 +498,7 @@ private function handlePublishIndividualNodesFromWorkspace( PublishIndividualNodesFromWorkspace $command, ContentRepository $contentRepository, ): EventsToPublish { - $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($command->workspaceName); + $contentGraphAdapter = $this->contentGraphAdapterProvider->fromWorkspaceName($command->workspaceName); $workspace = $this->requireWorkspace($command->workspaceName); $oldWorkspaceContentStreamId = $workspace->currentContentStreamId; $oldWorkspaceContentStreamIdState = $contentGraphAdapter->findStateForContentStream(); @@ -637,7 +637,7 @@ private function handleDiscardIndividualNodesFromWorkspace( DiscardIndividualNodesFromWorkspace $command, ContentRepository $contentRepository, ): EventsToPublish { - $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($command->workspaceName); + $contentGraphAdapter = $this->contentGraphAdapterProvider->fromWorkspaceName($command->workspaceName); $workspace = $contentGraphAdapter->getWorkspace(); $oldWorkspaceContentStreamId = $contentGraphAdapter->getContentStreamId(); $oldWorkspaceContentStreamIdState = $contentGraphAdapter->findStateForContentStream(); @@ -912,7 +912,7 @@ private function handleChangeWorkspaceOwner( */ private function requireWorkspace(WorkspaceName $workspaceName): Workspace { - $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($workspaceName); + $contentGraphAdapter = $this->contentGraphAdapterProvider->fromWorkspaceName($workspaceName); return $contentGraphAdapter->getWorkspace(); } @@ -927,7 +927,7 @@ private function requireBaseWorkspace(Workspace $workspace): Workspace } try { - $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($workspace->baseWorkspaceName); + $contentGraphAdapter = $this->contentGraphAdapterProvider->fromWorkspaceName($workspace->baseWorkspaceName); $baseWorkspace = $contentGraphAdapter->getWorkspace(); } catch (WorkspaceDoesNotExist $_) { throw BaseWorkspaceDoesNotExist::butWasSupposedTo($workspace->workspaceName); @@ -951,7 +951,7 @@ private function requireNonCircularRelationBetweenWorkspaces(Workspace $workspac 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)); } - $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($nextBaseWorkspace->baseWorkspaceName); + $contentGraphAdapter = $this->contentGraphAdapterProvider->fromWorkspaceName($nextBaseWorkspace->baseWorkspaceName); $nextBaseWorkspace = $contentGraphAdapter->getWorkspace(); } } diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php index bba5c64930e..6ef12e1e5b6 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php @@ -86,6 +86,28 @@ public function findNodeAggregateById( NodeAggregateId $nodeAggregateId ): ?NodeAggregate; + /** + * @param ContentStreamId $contentStreamId + * @param NodeAggregateId $childNodeAggregateId + * @return iterable + */ + public function findParentNodeAggregates( + ContentStreamId $contentStreamId, + NodeAggregateId $childNodeAggregateId + ): iterable; + + /** + * @param ContentStreamId $contentStreamId + * @param NodeAggregateId $parentNodeAggregateId + * @param NodeName $name + * @return iterable + */ + public function findChildNodeAggregatesByName( + ContentStreamId $contentStreamId, + NodeAggregateId $parentNodeAggregateId, + NodeName $name + ): iterable; + /** * Returns all node types in use, from the graph projection * diff --git a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php index 3e4cd0450e0..ee3c35464c6 100644 --- a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php +++ b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php @@ -58,7 +58,7 @@ public function findAdjustmentsForNodeType(NodeTypeName $nodeTypeName): \Generat foreach ($this->projectedNodeIterator->nodeAggregatesOfType($nodeTypeName) as $nodeAggregate) { // TODO: We should use $nodeAggregate->workspaceName as soon as it's available - $contentGraphAdapter = $this->contentGraphAdapterProvider->resolveWorkspaceNameAndGet($nodeAggregate->contentStreamId); + $contentGraphAdapter = $this->contentGraphAdapterProvider->fromContentStreamId($nodeAggregate->contentStreamId); // find missing tethered nodes $foundMissingOrDisallowedTetheredNodes = false; $originDimensionSpacePoints = $nodeType->isOfType(NodeTypeName::ROOT_NODE_TYPE_NAME) @@ -270,6 +270,6 @@ private function reorderNodes( protected function getContentGraphAdapter(WorkspaceName $workspaceName): ContentGraphAdapterInterface { - return $this->contentGraphAdapterProvider->resolveContentStreamIdAndGet($workspaceName); + return $this->contentGraphAdapterProvider->fromWorkspaceName($workspaceName); } } From a55e6c8595b85666488ebf55b3bfa206a4097d43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Sat, 13 Apr 2024 21:45:54 +0200 Subject: [PATCH 09/18] linter fix --- .../src/ContentGraphAdapterFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php index 9f087a45175..8cd27e16ed7 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php @@ -15,7 +15,7 @@ /** * Factory for the ContentGraphAdapter implementation for doctrine DBAL - * + * * @internal */ class ContentGraphAdapterFactory implements ContentGraphAdapterFactoryInterface From 4af5126def66340316c868e4c6329ea72ee5741d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Sat, 13 Apr 2024 22:45:03 +0200 Subject: [PATCH 10/18] Use ContentGraphAdapter in SiteServiceInternals --- .../src/ContentGraphAdapter.php | 28 +++++++++++++------ .../Feature/ContentGraphAdapterInterface.php | 7 +++-- .../Domain/Service/SiteServiceInternals.php | 21 ++++++++------ .../Service/SiteServiceInternalsFactory.php | 3 +- 4 files changed, 40 insertions(+), 19 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php index 7702f76e83d..91c5e418ddd 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php @@ -70,7 +70,7 @@ class ContentGraphAdapter implements ContentGraphAdapterInterface public function __construct( private readonly Connection $dbalConnection, private readonly string $tableNamePrefix, - private ContentRepositoryId $contentRepositoryId, + public readonly ContentRepositoryId $contentRepositoryId, private readonly NodeFactory $nodeFactory, private readonly NodeTypeManager $nodeTypeManager, public ?WorkspaceName $workspaceName, @@ -82,9 +82,15 @@ public function __construct( } public function rootNodeAggregateWithTypeExists(NodeTypeName $nodeTypeName): bool + { + $rootNodeAggregate = $this->findRootNodeAggregateByType($nodeTypeName); + return (bool)$rootNodeAggregate; + } + + public function findRootNodeAggregateByType(NodeTypeName $nodeTypeName): ?NodeAggregate { $queryBuilder = $this->dbalConnection->createQueryBuilder() - ->select('COUNT(n.relationanchorpoint)') + ->select('n.*, h.name, h.contentstreamid, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') ->from($this->getTablenameForNode(), 'n') ->innerJoin('n', $this->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') ->innerJoin('h', $this->getTablenameForDimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') @@ -99,12 +105,11 @@ public function rootNodeAggregateWithTypeExists(NodeTypeName $nodeTypeName): boo ->andWhere('n.nodetypename = :nodeTypeName') ->setParameter('nodeTypeName', $nodeTypeName->value); - $result = $queryBuilder->execute(); - if (!$result instanceof Result) { - return false; - } - - return $result->fetchOne() > 0; + return $this->nodeFactory->mapNodeRowsToNodeAggregate( + $this->fetchRows($queryBuilder), + $this->getContentStreamId(), + VisibilityConstraints::withoutRestrictions() + ); } public function findParentNodeAggregates(NodeAggregateId $childNodeAggregateId): iterable @@ -763,13 +768,20 @@ public function getWorkspace(): Workspace $row = $result->fetchAssociative(); // We can assume that we get a row otherwise getWorkspaceName would have thrown already + return new Workspace( + /** @phpstan-ignore-next-line */ WorkspaceName::fromString($row['workspacename']), !empty($row['baseworkspacename']) ? WorkspaceName::fromString($row['baseworkspacename']) : null, + /** @phpstan-ignore-next-line */ WorkspaceTitle::fromString($row['workspacetitle']), + /** @phpstan-ignore-next-line */ WorkspaceDescription::fromString($row['workspacedescription']), + /** @phpstan-ignore-next-line */ ContentStreamId::fromString($row['currentcontentstreamid']), + /** @phpstan-ignore-next-line */ WorkspaceStatus::from($row['status']), + /** @phpstan-ignore-next-line */ $row['workspaceowner'] ); } diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterInterface.php b/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterInterface.php index d37fed5d27f..c6bff192a9b 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterInterface.php +++ b/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterInterface.php @@ -28,14 +28,13 @@ use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamState; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; -use Neos\EventStore\Model\EventStream\ExpectedVersion; use Neos\EventStore\Model\EventStream\MaybeVersion; /** * This is a read API provided for constraint checks within the write side. * It must be bound to a contentStreamId and workspaceName on creation. * - * @internal only for consumption in command handlers + * @api only for consumption in command handlers and content graph services */ interface ContentGraphAdapterInterface { @@ -56,6 +55,10 @@ public function rootNodeAggregateWithTypeExists( NodeTypeName $nodeTypeName ): bool; + public function findRootNodeAggregateByType( + NodeTypeName $nodeTypeName + ): ?NodeAggregate; + /** * @return iterable */ diff --git a/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php b/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php index 7dbe858a5c5..acb7b5f1b08 100644 --- a/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php +++ b/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php @@ -19,6 +19,7 @@ use Neos\ContentRepository\Core\DimensionSpace\InterDimensionalVariationGraph; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterProvider; use Neos\ContentRepository\Core\Feature\NodeCreation\Command\CreateNodeAggregateWithNode; use Neos\ContentRepository\Core\Feature\NodeModification\Dto\PropertyValuesToWrite; use Neos\ContentRepository\Core\Feature\NodeRemoval\Command\RemoveNodeAggregate; @@ -39,7 +40,8 @@ public function __construct( private ContentRepository $contentRepository, private InterDimensionalVariationGraph $interDimensionalVariationGraph, - private NodeTypeManager $nodeTypeManager + private NodeTypeManager $nodeTypeManager, + private ContentGraphAdapterProvider $contentGraphAdapterProvider ) { } @@ -54,15 +56,18 @@ public function removeSiteNode(SiteNodeName $siteNodeName): void 1651921482 ); } - $contentGraph = $this->contentRepository->getContentGraph(); foreach ($this->contentRepository->getWorkspaceFinder()->findAll() as $workspace) { - $sitesNodeAggregate = $contentGraph->findRootNodeAggregateByType( - $workspace->currentContentStreamId, + $contentGraphAdapter = $this->contentGraphAdapterProvider->fromWorkspaceName($workspace->workspaceName); + $sitesNodeAggregate = $contentGraphAdapter->findRootNodeAggregateByType( NodeTypeNameFactory::forSites() ); - $siteNodeAggregates = $contentGraph->findChildNodeAggregatesByName( - $workspace->currentContentStreamId, + + if (!$sitesNodeAggregate) { + continue; + } + + $siteNodeAggregates = $contentGraphAdapter->findChildNodeAggregatesByName( $sitesNodeAggregate->nodeAggregateId, $siteNodeName->toNodeName() ); @@ -99,8 +104,8 @@ public function createSiteNodeIfNotExists(Site $site, string $nodeTypeName): voi throw SiteNodeTypeIsInvalid::becauseItIsNotOfTypeSite(NodeTypeName::fromString($nodeTypeName)); } - $siteNodeAggregate = $this->contentRepository->getContentGraph()->findChildNodeAggregatesByName( - $liveWorkspace->currentContentStreamId, + $contentGraphAdapter = $this->contentGraphAdapterProvider->fromWorkspaceName($liveWorkspace->workspaceName); + $siteNodeAggregate = $contentGraphAdapter->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..e16177d77a6 100644 --- a/Neos.Neos/Classes/Domain/Service/SiteServiceInternalsFactory.php +++ b/Neos.Neos/Classes/Domain/Service/SiteServiceInternalsFactory.php @@ -27,7 +27,8 @@ public function build(ContentRepositoryServiceFactoryDependencies $serviceFactor return new SiteServiceInternals( $serviceFactoryDependencies->contentRepository, $serviceFactoryDependencies->interDimensionalVariationGraph, - $serviceFactoryDependencies->nodeTypeManager + $serviceFactoryDependencies->nodeTypeManager, + $serviceFactoryDependencies->contentGraphAdapterProvider ); } } From dd30d55fddf3e52299d42ac953a11826420203aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Sun, 14 Apr 2024 15:06:29 +0200 Subject: [PATCH 11/18] Query deduplication part 1 --- .../src/ContentGraphAdapter.php | 293 +++-------------- .../src/Domain/Repository/ContentGraph.php | 187 +---------- .../src/Domain/Repository/ContentSubgraph.php | 287 +++------------- .../src/NodeQueryBuilder.php | 307 ++++++++++++++++++ 4 files changed, 419 insertions(+), 655 deletions(-) create mode 100644 Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php index 91c5e418ddd..38b14c60c32 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php @@ -6,7 +6,6 @@ use Doctrine\DBAL\Driver\Exception as DbalDriverException; use Doctrine\DBAL\Driver\Exception as DriverException; use Doctrine\DBAL\Exception as DBALException; -use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Query\QueryBuilder; use Doctrine\DBAL\Result; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRelationAnchorPoint; @@ -24,23 +23,10 @@ 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\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\NodeAggregate; use Neos\ContentRepository\Core\Projection\ContentGraph\NodePath; use Neos\ContentRepository\Core\Projection\ContentGraph\Nodes; -use Neos\ContentRepository\Core\Projection\ContentGraph\SearchTerm; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\Projection\Workspace\Workspace; use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceStatus; @@ -51,7 +37,6 @@ use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateClassification; 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; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamState; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceDescription; @@ -67,9 +52,11 @@ */ class ContentGraphAdapter implements ContentGraphAdapterInterface { + private readonly NodeQueryBuilder $nodeQueryBuilder; + public function __construct( private readonly Connection $dbalConnection, - private readonly string $tableNamePrefix, + string $tableNamePrefix, public readonly ContentRepositoryId $contentRepositoryId, private readonly NodeFactory $nodeFactory, private readonly NodeTypeManager $nodeTypeManager, @@ -79,6 +66,8 @@ public function __construct( if ($this->workspaceName === null && $this->contentStreamId === null) { throw new \InvalidArgumentException('Neither ContentStreamId nor WorkspaceName given in creation of ContentGraphAdapter, one is required.', 1712746528); } + + $this->nodeQueryBuilder = new NodeQueryBuilder($dbalConnection, $tableNamePrefix); } public function rootNodeAggregateWithTypeExists(NodeTypeName $nodeTypeName): bool @@ -89,11 +78,7 @@ public function rootNodeAggregateWithTypeExists(NodeTypeName $nodeTypeName): boo public function findRootNodeAggregateByType(NodeTypeName $nodeTypeName): ?NodeAggregate { - $queryBuilder = $this->dbalConnection->createQueryBuilder() - ->select('n.*, h.name, h.contentstreamid, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') - ->from($this->getTablenameForNode(), 'n') - ->innerJoin('n', $this->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->innerJoin('h', $this->getTablenameForDimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeAggregateQuery() ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.parentnodeanchor = :rootEdgeParentAnchorId') ->setParameters([ @@ -114,16 +99,11 @@ public function findRootNodeAggregateByType(NodeTypeName $nodeTypeName): ?NodeAg public function findParentNodeAggregates(NodeAggregateId $childNodeAggregateId): iterable { - $queryBuilder = $this->dbalConnection->createQueryBuilder() - ->select('pn.*, ph.name, ph.contentstreamid, ph.subtreetags, pdsp.dimensionspacepoint AS covereddimensionspacepoint') - ->from($this->getTablenameForNode(), 'pn') - ->innerJoin('pn', $this->getTablenameForHierachyRelation(), 'ph', 'ph.childnodeanchor = pn.relationanchorpoint') - ->innerJoin('pn', $this->getTablenameForHierachyRelation(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('ch', $this->getTablenameForNode(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') - ->innerJoin('ph', $this->getTablenameForDimensionSpacePoints(), 'pdsp', 'pdsp.hash = ph.dimensionspacepointhash') - ->where('cn.nodeaggregateid = :nodeAggregateId') - ->andWhere('ph.contentstreamid = :contentStreamId') + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeAggregateQuery() + ->innerJoin('n', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), 'ch', 'ch.parentnodeanchor = n.relationanchorpoint') + ->innerJoin('ch', $this->nodeQueryBuilder->getTablenameForNode(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') ->andWhere('ch.contentstreamid = :contentStreamId') + ->andWhere('cn.nodeaggregateid = :nodeAggregateId') ->setParameters([ 'nodeAggregateId' => $childNodeAggregateId->value, 'contentStreamId' => $this->getContentStreamId()->value @@ -134,13 +114,8 @@ public function findParentNodeAggregates(NodeAggregateId $childNodeAggregateId): public function findNodeAggregateById(NodeAggregateId $nodeAggregateId): ?NodeAggregate { - $queryBuilder = $this->dbalConnection->createQueryBuilder() - ->select('n.*, h.name, h.contentstreamid, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') - ->from($this->getTablenameForHierachyRelation(), 'h') - ->innerJoin('h', $this->getTablenameForNode(), 'n', 'n.relationanchorpoint = h.childnodeanchor') - ->innerJoin('h', $this->getTablenameForDimensionSpacePoints(), '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, @@ -158,9 +133,9 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint(NodeAggr { $subQueryBuilder = $this->dbalConnection->createQueryBuilder() ->select('pn.nodeaggregateid') - ->from($this->getTablenameForNode(), 'pn') - ->innerJoin('pn', $this->getTablenameForHierachyRelation(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('ch', $this->getTablenameForNode(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') + ->from($this->nodeQueryBuilder->getTablenameForNode(), 'pn') + ->innerJoin('pn', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('ch', $this->nodeQueryBuilder->getTablenameForNode(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') ->where('ch.contentstreamid = :contentStreamId') ->andWhere('ch.dimensionspacepointhash = :childOriginDimensionSpacePointHash') ->andWhere('cn.nodeaggregateid = :childNodeAggregateId') @@ -168,9 +143,9 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint(NodeAggr $queryBuilder = $this->dbalConnection->createQueryBuilder() ->select('n.*, h.name, h.contentstreamid, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') - ->from($this->getTablenameForNode(), 'n') - ->innerJoin('n', $this->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->innerJoin('h', $this->getTablenameForDimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') + ->from($this->nodeQueryBuilder->getTablenameForNode(), 'n') + ->innerJoin('n', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('h', $this->nodeQueryBuilder->getTablenameForDimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') ->where('n.nodeaggregateid = (' . $subQueryBuilder->getSQL() . ')') ->andWhere('h.contentstreamid = :contentStreamId') ->setParameters([ @@ -188,14 +163,14 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint(NodeAggr public function findChildNodeAggregates(NodeAggregateId $parentNodeAggregateId): iterable { - $queryBuilder = $this->buildChildNodeAggregateQuery($parentNodeAggregateId, $this->getContentStreamId()); + $queryBuilder = $this->nodeQueryBuilder->buildChildNodeAggregateQuery($parentNodeAggregateId, $this->getContentStreamId()); return $this->mapQueryBuilderToNodeAggregates($queryBuilder); } public function findTetheredChildNodeAggregates(NodeAggregateId $parentNodeAggregateId): iterable { - $queryBuilder = $this->buildChildNodeAggregateQuery($parentNodeAggregateId, $this->getContentStreamId()) + $queryBuilder = $this->nodeQueryBuilder->buildChildNodeAggregateQuery($parentNodeAggregateId, $this->getContentStreamId()) ->andWhere('cn.classification = :tetheredClassification') ->setParameter('tetheredClassification', NodeAggregateClassification::CLASSIFICATION_TETHERED->value); @@ -206,10 +181,10 @@ public function getDimensionSpacePointsOccupiedByChildNodeName(NodeName $nodeNam { $queryBuilder = $this->createQueryBuilder() ->select('dsp.dimensionspacepoint, h.dimensionspacepointhash') - ->from($this->getTablenameForHierachyRelation(), 'h') - ->innerJoin('h', $this->getTablenameForNode(), 'n', 'n.relationanchorpoint = h.parentnodeanchor') - ->innerJoin('h', $this->getTablenameForDimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') - ->innerJoin('n', $this->getTablenameForHierachyRelation(), 'ph', 'ph.childnodeanchor = n.relationanchorpoint') + ->from($this->nodeQueryBuilder->getTablenameForHierachyRelation(), 'h') + ->innerJoin('h', $this->nodeQueryBuilder->getTablenameForNode(), 'n', 'n.relationanchorpoint = h.parentnodeanchor') + ->innerJoin('h', $this->nodeQueryBuilder->getTablenameForDimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') + ->innerJoin('n', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), 'ph', 'ph.childnodeanchor = n.relationanchorpoint') ->where('n.nodeaggregateid = :parentNodeAggregateId') ->andWhere('n.origindimensionspacepointhash = :parentNodeOriginDimensionSpacePointHash') ->andWhere('ph.contentstreamid = :contentStreamId') @@ -235,7 +210,7 @@ public function getDimensionSpacePointsOccupiedByChildNodeName(NodeName $nodeNam public function findChildNodeAggregatesByName(NodeAggregateId $parentNodeAggregateId, NodeName $name): iterable { - $queryBuilder = $this->buildChildNodeAggregateQuery($parentNodeAggregateId, $this->getContentStreamId()) + $queryBuilder = $this->nodeQueryBuilder->buildChildNodeAggregateQuery($parentNodeAggregateId, $this->getContentStreamId()) ->andWhere('ch.name = :relationName') ->setParameter('relationName', $name->value); @@ -246,8 +221,8 @@ public function subgraphContainsNodes(DimensionSpacePoint $dimensionSpacePoint): { $queryBuilder = $this->createQueryBuilder() ->select('COUNT(*)') - ->from($this->getTablenameForNode(), 'n') - ->innerJoin('n', $this->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->from($this->nodeQueryBuilder->getTablenameForNode(), 'n') + ->innerJoin('n', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->getContentStreamId()->value) ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); try { @@ -264,13 +239,7 @@ public function subgraphContainsNodes(DimensionSpacePoint $dimensionSpacePoint): public function findNodeInSubgraph(DimensionSpacePoint $coveredDimensionSpacePoint, NodeAggregateId $nodeAggregateId): ?Node { - $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.name, h.subtreetags') - ->from($this->getTablenameForNode(), 'n') - ->innerJoin('n', $this->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->where('n.nodeaggregateid = :nodeAggregateId')->setParameter('nodeAggregateId', $nodeAggregateId->value) - ->andWhere('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->getContentStreamId()->value) - ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $coveredDimensionSpacePoint->hash); + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeByIdQuery($nodeAggregateId, $this->getContentStreamId(), $coveredDimensionSpacePoint); // TODO: Do we need subtree tag support here, not for visibility at least return $this->fetchNode($queryBuilder, $coveredDimensionSpacePoint); @@ -287,24 +256,13 @@ public function findChildNodesInSubgraph(DimensionSpacePoint $coveredDimensionSp public function findParentNodeInSubgraph(DimensionSpacePoint $coveredDimensionSpacePoint, NodeAggregateId $childNodeAggregateId): ?Node { - $queryBuilder = $this->createQueryBuilder() - ->select('pn.*, ch.name, ch.subtreetags') - ->from($this->getTablenameForNode(), 'pn') - ->innerJoin('pn', $this->getTablenameForHierachyRelation(), 'ph', 'ph.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('pn', $this->getTablenameForNode(), 'cn', 'cn.relationanchorpoint = ph.childnodeanchor') - ->innerJoin('pn', $this->getTablenameForHierachyRelation(), 'ch', 'ch.childnodeanchor = pn.relationanchorpoint') - ->where('cn.nodeaggregateid = :childNodeAggregateId')->setParameter('childNodeAggregateId', $childNodeAggregateId->value) - ->andWhere('ph.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->getContentStreamId()->value) - ->andWhere('ch.contentstreamid = :contentStreamId') - ->andWhere('ph.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $coveredDimensionSpacePoint->hash) - ->andWhere('ch.dimensionspacepointhash = :dimensionSpacePointHash'); - + $queryBuilder = $this->nodeQueryBuilder->buildBasicParentNodeQuery($childNodeAggregateId, $this->getContentStreamId(), $coveredDimensionSpacePoint); return $this->fetchNode($queryBuilder, $coveredDimensionSpacePoint); } public function findChildNodeByNameInSubgraph(DimensionSpacePoint $coveredDimensionSpacePoint, NodeAggregateId $parentNodeAggregateId, NodeName $nodeName): ?Node { - $startingNode = $this->findNodeById($parentNodeAggregateId, $coveredDimensionSpacePoint); + $startingNode = $this->findNodeInSubgraph($coveredDimensionSpacePoint, $parentNodeAggregateId); return $startingNode ? $this->findNodeByPathFromStartingNode(NodePath::fromNodeNames($nodeName), $startingNode, $coveredDimensionSpacePoint) @@ -369,21 +327,6 @@ public function findVersionForContentStream(): MaybeVersion return MaybeVersion::fromVersionOrNull($versionObject); } - private function getTablenameForNode(): string - { - return $this->tableNamePrefix . '_node'; - } - - private function getTablenameForHierachyRelation(): string - { - return $this->tableNamePrefix . '_hierarchyrelation'; - } - - private function getTablenameForDimensionSpacePoints(): string - { - return $this->tableNamePrefix . '_dimensionspacepoints'; - } - /** * @param QueryBuilder $queryBuilder * @return iterable @@ -402,43 +345,17 @@ private function createQueryBuilder(): QueryBuilder return $this->dbalConnection->createQueryBuilder(); } - private function buildChildNodeAggregateQuery(NodeAggregateId $parentNodeAggregateId, ContentStreamId $contentStreamId): QueryBuilder - { - return $this->dbalConnection->createQueryBuilder() - ->select('cn.*, ch.name, ch.contentstreamid, ch.subtreetags, cdsp.dimensionspacepoint AS covereddimensionspacepoint') - ->from($this->getTablenameForNode(), 'pn') - ->innerJoin('pn', $this->getTablenameForHierachyRelation(), 'ph', 'ph.childnodeanchor = pn.relationanchorpoint') - ->innerJoin('pn', $this->getTablenameForHierachyRelation(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('ch', $this->getTablenameForDimensionSpacePoints(), 'cdsp', 'cdsp.hash = ch.dimensionspacepointhash') - ->innerJoin('ch', $this->getTablenameForNode(), '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, - ]); - } - private function buildChildNodesQuery(NodeAggregateId $parentNodeAggregateId, DimensionSpacePoint $dimensionSpacePoint, FindChildNodesFilter|CountChildNodesFilter $filter): QueryBuilder { - $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.name, h.subtreetags') - ->from($this->getTablenameForNode(), 'pn') - ->innerJoin('pn', $this->getTablenameForHierachyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('pn', $this->getTablenameForNode(), 'n', 'h.childnodeanchor = n.relationanchorpoint') - ->where('pn.nodeaggregateid = :parentNodeAggregateId')->setParameter('parentNodeAggregateId', $parentNodeAggregateId->value) - ->andWhere('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->getContentStreamId()->value) - ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); + $queryBuilder = $this->nodeQueryBuilder->buildBasicChildNodesQuery($parentNodeAggregateId, $this->getContentStreamId(), $dimensionSpacePoint); if ($filter->nodeTypes !== null) { $this->addNodeTypeCriteria($queryBuilder, $filter->nodeTypes); } 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); } return $queryBuilder; @@ -475,19 +392,6 @@ private function executeQuery(QueryBuilder $queryBuilder): Result return $result; } - public function findNodeById(NodeAggregateId $nodeAggregateId, DimensionSpacePoint $dimensionSpacePoint): ?Node - { - $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.name, h.subtreetags') - ->from($this->getTablenameForNode(), 'n') - ->innerJoin('n', $this->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->where('n.nodeaggregateid = :nodeAggregateId')->setParameter('nodeAggregateId', $nodeAggregateId->value) - ->andWhere('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->getContentStreamId()->value) - ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); - - return $this->fetchNode($queryBuilder, $dimensionSpacePoint); - } - private function findNodeByPathFromStartingNode(NodePath $path, Node $startingNode, DimensionSpacePoint $dimensionSpacePoint): ?Node { $currentNode = $startingNode; @@ -511,9 +415,9 @@ private function findChildNodeConnectedThroughEdgeName(NodeAggregateId $parentNo { $queryBuilder = $this->createQueryBuilder() ->select('cn.*, h.name, h.subtreetags') - ->from($this->getTablenameForNode(), 'pn') - ->innerJoin('pn', $this->getTablenameForHierachyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('pn', $this->getTablenameForNode(), 'cn', 'cn.relationanchorpoint = h.childnodeanchor') + ->from($this->nodeQueryBuilder->getTablenameForNode(), 'pn') + ->innerJoin('pn', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->nodeQueryBuilder->getTablenameForNode(), 'cn', 'cn.relationanchorpoint = h.childnodeanchor') ->where('pn.nodeaggregateid = :parentNodeAggregateId')->setParameter('parentNodeAggregateId', $parentNodeAggregateId->value) ->andWhere('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->getContentStreamId()->value) ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash) @@ -524,41 +428,16 @@ private function findChildNodeConnectedThroughEdgeName(NodeAggregateId $parentNo private function buildSiblingsQuery(bool $preceding, NodeAggregateId $siblingNodeAggregateId, DimensionSpacePoint $dimensionSpacePoint, FindPrecedingSiblingNodesFilter|FindSucceedingSiblingNodesFilter $filter): QueryBuilder { - $parentNodeAnchorSubQuery = $this->createQueryBuilder() - ->select('sh.parentnodeanchor') - ->from($this->getTablenameForHierachyRelation(), 'sh') - ->innerJoin('sh', $this->getTablenameForNode(), '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->getTablenameForHierachyRelation(), 'sh') - ->innerJoin('sh', $this->getTablenameForNode(), '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->getTablenameForNode(), 'n') - ->innerJoin('n', $this->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->where('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->getContentStreamId()->value) - ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $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->getContentStreamId(), $dimensionSpacePoint); if ($filter->nodeTypes !== null) { $this->addNodeTypeCriteria($queryBuilder, $filter->nodeTypes); } 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); @@ -569,100 +448,8 @@ private function buildSiblingsQuery(bool $preceding, NodeAggregateId $siblingNod 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'; + $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilder, $constraintsWithSubNodeTypes, $nodeTableAlias); } private function applyPagination(QueryBuilder $queryBuilder, Pagination $pagination): void diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index 1df35d06fa3..ec3b2be547c 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -14,16 +14,15 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository; -use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver\Exception as DriverException; use Doctrine\DBAL\Exception as DBALException; use Doctrine\DBAL\Query\QueryBuilder; use Doctrine\DBAL\Result; +use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphAdapter; use Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphProjection; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRelationAnchorPoint; +use Neos\ContentGraph\DoctrineDbalAdapter\NodeQueryBuilder; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; -use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; -use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\Infrastructure\DbalClientInterface; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\NodeType\NodeTypeName; @@ -36,7 +35,6 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Exception\RootNodeAggregateDoesNotExist; -use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateClassification; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; @@ -64,6 +62,8 @@ */ final class ContentGraph implements ContentGraphInterface { + private readonly NodeQueryBuilder $nodeQueryBuilder; + /** * @var array */ @@ -76,6 +76,7 @@ public function __construct( private readonly NodeTypeManager $nodeTypeManager, private readonly string $tableNamePrefix ) { + $this->nodeQueryBuilder = new NodeQueryBuilder($this->client->getConnection(), $this->tableNamePrefix); } final public function getSubgraph( @@ -139,12 +140,8 @@ 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') + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeAggregateQuery(); + $queryBuilder ->andWhere('h.parentnodeanchor = :rootEdgeParentAnchorId') ->setParameters([ 'contentStreamId' => $contentStreamId->value, @@ -163,12 +160,8 @@ 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, @@ -181,23 +174,8 @@ 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') - ->setParameters([ - 'nodeAggregateId' => $nodeAggregateId->value, - 'contentStreamId' => $contentStreamId->value - ]); - - return $this->nodeFactory->mapNodeRowsToNodeAggregate( - $this->fetchRows($queryBuilder), - $contentStreamId, - VisibilityConstraints::withoutRestrictions() - ); + $contentGraphAdapter = new ContentGraphAdapter($this->client->getConnection(), $this->tableNamePrefix, $this->contentRepositoryId, $this->nodeFactory, $this->nodeTypeManager, null, $contentStreamId); + return $contentGraphAdapter->findNodeAggregateById($nodeAggregateId); } /** @@ -207,57 +185,8 @@ 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') - ->andWhere('ch.contentstreamid = :contentStreamId') - ->setParameters([ - 'nodeAggregateId' => $childNodeAggregateId->value, - 'contentStreamId' => $contentStreamId->value - ]); - - return $this->mapQueryBuilderToNodeAggregates($queryBuilder, $contentStreamId); - } - - public function findParentNodeAggregateByChildOriginDimensionSpacePoint( - ContentStreamId $contentStreamId, - 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') - ->where('ch.contentstreamid = :contentStreamId') - ->andWhere('ch.dimensionspacepointhash = :childOriginDimensionSpacePointHash') - ->andWhere('cn.nodeaggregateid = :childNodeAggregateId') - ->andWhere('cn.origindimensionspacepointhash = :childOriginDimensionSpacePointHash'); - - $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') - ->where('n.nodeaggregateid = (' . $subQueryBuilder->getSQL() . ')') - ->andWhere('h.contentstreamid = :contentStreamId') - ->setParameters([ - 'contentStreamId' => $contentStreamId->value, - 'childNodeAggregateId' => $childNodeAggregateId->value, - 'childOriginDimensionSpacePointHash' => $childOriginDimensionSpacePoint->hash, - ]); - - return $this->nodeFactory->mapNodeRowsToNodeAggregate( - $this->fetchRows($queryBuilder), - $contentStreamId, - VisibilityConstraints::withoutRestrictions() - ); + $contentGraphAdapter = new ContentGraphAdapter($this->client->getConnection(), $this->tableNamePrefix, $this->contentRepositoryId, $this->nodeFactory, $this->nodeTypeManager, null, $contentStreamId); + return $contentGraphAdapter->findParentNodeAggregates($childNodeAggregateId); } /** @@ -267,8 +196,8 @@ public function findChildNodeAggregates( ContentStreamId $contentStreamId, NodeAggregateId $parentNodeAggregateId ): iterable { - $queryBuilder = $this->buildChildNodeAggregateQuery($parentNodeAggregateId, $contentStreamId); - return $this->mapQueryBuilderToNodeAggregates($queryBuilder, $contentStreamId); + $contentGraphAdapter = new ContentGraphAdapter($this->client->getConnection(), $this->tableNamePrefix, $this->contentRepositoryId, $this->nodeFactory, $this->nodeTypeManager, null, $contentStreamId); + return $contentGraphAdapter->findChildNodeAggregates($parentNodeAggregateId); } /** @@ -279,66 +208,8 @@ public function findChildNodeAggregatesByName( 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) - ->andWhere('cn.classification = :tetheredClassification') - ->setParameter('tetheredClassification', NodeAggregateClassification::CLASSIFICATION_TETHERED->value); - return $this->mapQueryBuilderToNodeAggregates($queryBuilder, $contentStreamId); - } - - /** - * @param ContentStreamId $contentStreamId - * @param NodeName $nodeName - * @param NodeAggregateId $parentNodeAggregateId - * @param OriginDimensionSpacePoint $parentNodeOriginDimensionSpacePoint - * @param DimensionSpacePointSet $dimensionSpacePointsToCheck - * @return DimensionSpacePointSet - */ - public function getDimensionSpacePointsOccupiedByChildNodeName( - ContentStreamId $contentStreamId, - 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') - ->where('n.nodeaggregateid = :parentNodeAggregateId') - ->andWhere('n.origindimensionspacepointhash = :parentNodeOriginDimensionSpacePointHash') - ->andWhere('ph.contentstreamid = :contentStreamId') - ->andWhere('h.contentstreamid = :contentStreamId') - ->andWhere('h.dimensionspacepointhash IN (:dimensionSpacePointHashes)') - ->andWhere('h.name = :nodeName') - ->setParameters([ - 'parentNodeAggregateId' => $parentNodeAggregateId->value, - 'parentNodeOriginDimensionSpacePointHash' => $parentNodeOriginDimensionSpacePoint->hash, - 'contentStreamId' => $contentStreamId->value, - 'dimensionSpacePointHashes' => $dimensionSpacePointsToCheck->getPointHashes(), - 'nodeName' => $nodeName->value - ], [ - 'dimensionSpacePointHashes' => Connection::PARAM_STR_ARRAY, - ]); - $dimensionSpacePoints = []; - foreach ($this->fetchRows($queryBuilder) as $hierarchyRelationData) { - $dimensionSpacePoints[$hierarchyRelationData['dimensionspacepointhash']] = DimensionSpacePoint::fromJsonString($hierarchyRelationData['dimensionspacepoint']); - } - return new DimensionSpacePointSet($dimensionSpacePoints); + $contentGraphAdapter = new ContentGraphAdapter($this->client->getConnection(), $this->tableNamePrefix, $this->contentRepositoryId, $this->nodeFactory, $this->nodeTypeManager, null, $contentStreamId); + return $contentGraphAdapter->findChildNodeAggregatesByName($parentNodeAggregateId, $name); } public function countNodes(): int @@ -359,10 +230,7 @@ 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 array_map(static fn (array $row) => NodeTypeName::fromString($row['nodetypename']), $this->nodeQueryBuilder->findUsedNodeTypeNames()); } /** @@ -374,25 +242,6 @@ 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, - ]); - } - private function createQueryBuilder(): QueryBuilder { return $this->client->getConnection()->createQueryBuilder(); diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php index c1a278822d7..da76523c92a 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php @@ -14,12 +14,11 @@ 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\NodeQueryBuilder; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\Infrastructure\DbalClientInterface; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; @@ -47,23 +46,10 @@ 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; @@ -101,7 +87,7 @@ */ final class ContentSubgraph implements ContentSubgraphInterface { - private int $dynamicParameterCount = 0; + private readonly NodeQueryBuilder $nodeQueryBuilder; public function __construct( private readonly ContentRepositoryId $contentRepositoryId, @@ -111,8 +97,9 @@ public function __construct( private readonly DbalClientInterface $client, private readonly NodeFactory $nodeFactory, private readonly NodeTypeManager $nodeTypeManager, - private readonly string $tableNamePrefix + string $tableNamePrefix ) { + $this->nodeQueryBuilder = new NodeQueryBuilder($this->client->getConnection(), $tableNamePrefix); } public function getIdentity(): ContentSubgraphIdentity @@ -168,13 +155,7 @@ 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->buildBasicNodeByIdQuery($nodeAggregateId, $this->contentStreamId, $this->dimensionSpacePoint); $this->addSubtreeTagConstraints($queryBuilder); return $this->fetchNode($queryBuilder); } @@ -183,8 +164,8 @@ 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') + ->from($this->nodeQueryBuilder->getTablenameForNode(), 'n') + ->innerJoin('n', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), '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) @@ -196,17 +177,7 @@ public function findRootNodeByType(NodeTypeName $nodeTypeName): ?Node 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 +209,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 +255,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->getTablenameForNode(), 'n') + ->innerJoin('n', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('n.nodeaggregateid = :entryNodeAggregateId'); @@ -301,8 +265,8 @@ 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->getTablenameForHierachyRelation(), 'h', 'h.parentnodeanchor = p.relationanchorpoint') + ->innerJoin('p', $this->nodeQueryBuilder->getTablenameForNode(), 'c', 'c.relationanchorpoint = h.childnodeanchor') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); if ($filter->maximumLevels !== null) { @@ -383,9 +347,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->getTablenameForNode(), '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->getTablenameForHierachyRelation(), 'ph', 'n.relationanchorpoint = ph.childnodeanchor') ->andWhere('ph.contentstreamid = :contentStreamId') ->andWhere('ph.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('n.nodeaggregateid = :entryNodeAggregateId'); @@ -394,19 +358,13 @@ 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->getTablenameForNode(), 'pn', 'pn.relationanchorpoint = cn.parentnodeanchor') + ->innerJoin('pn', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), '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'); } @@ -448,8 +406,8 @@ public function countNodes(): int { $queryBuilder = $this->createQueryBuilder() ->select('COUNT(*)') - ->from($this->tableNamePrefix . '_node', 'n') - ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->from($this->nodeQueryBuilder->getTablenameForNode(), 'n') + ->innerJoin('n', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->contentStreamId->value) ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash); try { @@ -488,12 +446,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 . '.'; @@ -506,117 +458,21 @@ 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'; + $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilder, $constraintsWithSubNodeTypes, $nodeTableAlias); } 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); } 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 +484,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->getTablenameForHierachyRelation(), 'sh') + ->innerJoin('sh', $this->nodeQueryBuilder->getTablenameForNode(), 'sn', 'sn.relationanchorpoint = sh.childnodeanchor') + ->innerJoin('sh', $this->nodeQueryBuilder->getTablenameForReferenceRelation(), 'r', 'r.nodeanchorpoint = sn.relationanchorpoint') + ->innerJoin('sh', $this->nodeQueryBuilder->getTablenameForNode(), 'dn', 'dn.nodeaggregateid = r.destinationnodeaggregateid') + ->innerJoin('sh', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), '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') @@ -644,19 +500,19 @@ private function buildReferencesQuery(bool $backReferences, NodeAggregateId $nod $this->addNodeTypeCriteria($queryBuilder, $filter->nodeTypes, "{$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'); + $this->nodeQueryBuilder->addPropertyValueConstraints($queryBuilder, $filter->referencePropertyValue, 'r'); } if ($filter->nodePropertyValue !== null) { - $this->addPropertyValueConstraints($queryBuilder, $filter->nodePropertyValue, "{$destinationTablePrefix}n"); + $this->nodeQueryBuilder->addPropertyValueConstraints($queryBuilder, $filter->nodePropertyValue, "{$destinationTablePrefix}n"); } if ($filter->referenceName !== null) { $queryBuilder->andWhere('r.name = :referenceName')->setParameter('referenceName', $filter->referenceName->value); @@ -678,42 +534,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); } 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 +559,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->getTablenameForNode(), '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->getTablenameForHierachyRelation(), 'ch', 'ch.parentnodeanchor = n.relationanchorpoint') + ->innerJoin('ch', $this->nodeQueryBuilder->getTablenameForNode(), 'c', 'c.relationanchorpoint = ch.childnodeanchor') + ->innerJoin('n', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), 'ph', 'n.relationanchorpoint = ph.childnodeanchor') ->where('ch.contentstreamid = :contentStreamId') ->andWhere('ch.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('ph.contentstreamid = :contentStreamId') @@ -744,18 +575,13 @@ 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->getTablenameForNode(), 'pn', 'pn.relationanchorpoint = cn.parentnodeanchor') + ->innerJoin('pn', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), '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'); } @@ -770,11 +596,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->getTablenameForNode(), '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->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('n', $this->nodeQueryBuilder->getTablenameForNode(), 'p', 'p.relationanchorpoint = h.parentnodeanchor') + ->innerJoin('n', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), 'ph', 'ph.childnodeanchor = p.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('ph.contentstreamid = :contentStreamId') @@ -785,26 +611,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->getTablenameForHierachyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->nodeQueryBuilder->getTablenameForNode(), '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); } 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 +638,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', diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php new file mode 100644 index 00000000000..248334ef862 --- /dev/null +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php @@ -0,0 +1,307 @@ +createQueryBuilder() + ->select('n.*, h.contentstreamid, h.name, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') + ->from($this->getTablenameForNode(), 'n') + ->innerJoin('n', $this->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('h', $this->getTablenameForDimensionSpacePoints(), '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->getTablenameForNode(), 'pn') + ->innerJoin('pn', $this->getTablenameForHierachyRelation(), 'ph', 'ph.childnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->getTablenameForHierachyRelation(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('ch', $this->getTablenameForDimensionSpacePoints(), 'cdsp', 'cdsp.hash = ch.dimensionspacepointhash') + ->innerJoin('ch', $this->getTablenameForNode(), '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 buildBasicNodeByIdQuery(NodeAggregateId $nodeAggregateId, ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder + { + return $this->createQueryBuilder() + ->select('n.*, h.name, h.subtreetags') + ->from($this->getTablenameForNode(), 'n') + ->innerJoin('n', $this->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->where('n.nodeaggregateid = :nodeAggregateId')->setParameter('nodeAggregateId', $nodeAggregateId->value) + ->andWhere('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->getTablenameForNode(), 'pn') + ->innerJoin('pn', $this->getTablenameForHierachyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->getTablenameForNode(), '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->getTablenameForNode(), 'pn') + ->innerJoin('pn', $this->getTablenameForHierachyRelation(), 'ph', 'ph.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->getTablenameForNode(), 'cn', 'cn.relationanchorpoint = ph.childnodeanchor') + ->innerJoin('pn', $this->getTablenameForHierachyRelation(), '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 + { + $parentNodeAnchorSubQuery = $this->createQueryBuilder() + ->select('sh.parentnodeanchor') + ->from($this->getTablenameForHierachyRelation(), 'sh') + ->innerJoin('sh', $this->getTablenameForNode(), '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->getTablenameForHierachyRelation(), 'sh') + ->innerJoin('sh', $this->getTablenameForNode(), 'sn', 'sn.relationanchorpoint = sh.childnodeanchor') + ->where('sn.nodeaggregateid = :siblingNodeAggregateId') + ->andWhere('sh.contentstreamid = :contentStreamId') + ->andWhere('sh.dimensionspacepointhash = :dimensionSpacePointHash'); + + return $this->createQueryBuilder() + ->select('n.*, h.name, h.subtreetags') + ->from($this->getTablenameForNode(), 'n') + ->innerJoin('n', $this->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->where('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $contentStreamId->value) + ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $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'); + } + + 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'; + } + + public function getTablenameForNode(): string + { + return $this->tableNamePrefix . '_node'; + } + + public function getTablenameForHierachyRelation(): string + { + return $this->tableNamePrefix . '_hierarchyrelation'; + } + + public function getTablenameForDimensionSpacePoints(): string + { + return $this->tableNamePrefix . '_dimensionspacepoints'; + } + + public function getTablenameForReferenceRelation(): string + { + return $this->tableNamePrefix . '_referencerelation'; + } + + /** + * @return array> + * @throws DBALException + */ + public function findUsedNodeTypeNames(): array + { + $rows = $this->fetchRows($this->createQueryBuilder() + ->select('DISTINCT nodetypename') + ->from($this->getTablenameForNode())); + + return $rows; + } + + /** + * @return array> + * @throws DBALException + */ + public function fetchRows(QueryBuilder $queryBuilder): array + { + $result = $queryBuilder->execute(); + if (!$result instanceof Result) { + throw new \RuntimeException(sprintf('Failed to execute query. Expected result to be of type %s, got: %s', Result::class, get_debug_type($result)), 1701443535); + } + try { + return $result->fetchAllAssociative(); + } catch (DriverException|DBALException $e) { + throw new \RuntimeException(sprintf('Failed to fetch rows from database: %s', $e->getMessage()), 1701444358, $e); + } + } + + private function createQueryBuilder(): QueryBuilder + { + return $this->connection->createQueryBuilder(); + } + + private function createUniqueParameterName(): string + { + return 'param_' . str_replace('-', '', UuidFactory::create()); + } +} From 027cb025f72fb9acfa01207aac6dba495e4c7280 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Sun, 14 Apr 2024 21:47:36 +0200 Subject: [PATCH 12/18] Query deduplication part 2 --- .../src/ContentGraphAdapter.php | 61 +++++++++--------- .../src/Domain/Repository/ContentGraph.php | 30 +++------ .../src/Domain/Repository/ContentSubgraph.php | 52 +++++---------- .../src/NodeQueryBuilder.php | 63 ++++++++++++------- 4 files changed, 91 insertions(+), 115 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php index 38b14c60c32..2c8f54d62b1 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php @@ -19,12 +19,13 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\CountChildNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindChildNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindPrecedingSiblingNodesFilter; +use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindRootNodeAggregatesFilter; 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\Pagination\Pagination; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; +use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregates; use Neos\ContentRepository\Core\Projection\ContentGraph\NodePath; use Neos\ContentRepository\Core\Projection\ContentGraph\Nodes; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; @@ -32,8 +33,8 @@ use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceStatus; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet; +use Neos\ContentRepository\Core\SharedModel\Exception\RootNodeAggregateDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; -use Neos\ContentRepository\Core\SharedModel\Id\UuidFactory; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateClassification; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; @@ -72,29 +73,34 @@ public function __construct( public function rootNodeAggregateWithTypeExists(NodeTypeName $nodeTypeName): bool { - $rootNodeAggregate = $this->findRootNodeAggregateByType($nodeTypeName); - return (bool)$rootNodeAggregate; + try { + return (bool)$this->findRootNodeAggregateByType($nodeTypeName); + } catch(\Exception $_) { + } + + return false; } public function findRootNodeAggregateByType(NodeTypeName $nodeTypeName): ?NodeAggregate { - $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeAggregateQuery() - ->where('h.contentstreamid = :contentStreamId') - ->andWhere('h.parentnodeanchor = :rootEdgeParentAnchorId') - ->setParameters([ - 'contentStreamId' => $this->getContentStreamId()->value, - 'rootEdgeParentAnchorId' => NodeRelationAnchorPoint::forRootEdge()->value, - ]); - - $queryBuilder - ->andWhere('n.nodetypename = :nodeTypeName') - ->setParameter('nodeTypeName', $nodeTypeName->value); + $rootNodeAggregateQueryBuilder = $this->nodeQueryBuilder->buildFindRootNodeAggregatesQuery($this->getContentStreamId(), FindRootNodeAggregatesFilter::create(nodeTypeName: $nodeTypeName)); + $rootNodeAggregates = NodeAggregates::fromArray(iterator_to_array($this->mapQueryBuilderToNodeAggregates($rootNodeAggregateQueryBuilder))); + if ($rootNodeAggregates->count() < 1) { + throw RootNodeAggregateDoesNotExist::butWasExpectedTo($nodeTypeName); + } + if ($rootNodeAggregates->count() > 1) { + $ids = []; + foreach ($rootNodeAggregates as $rootNodeAggregate) { + $ids[] = $rootNodeAggregate->nodeAggregateId->value; + } + throw new \RuntimeException(sprintf( + 'More than one root node aggregate of type "%s" found (IDs: %s).', + $nodeTypeName->value, + implode(', ', $ids) + )); + } - return $this->nodeFactory->mapNodeRowsToNodeAggregate( - $this->fetchRows($queryBuilder), - $this->getContentStreamId(), - VisibilityConstraints::withoutRestrictions() - ); + return $rootNodeAggregates->first(); } public function findParentNodeAggregates(NodeAggregateId $childNodeAggregateId): iterable @@ -349,7 +355,7 @@ private function buildChildNodesQuery(NodeAggregateId $parentNodeAggregateId, Di { $queryBuilder = $this->nodeQueryBuilder->buildBasicChildNodesQuery($parentNodeAggregateId, $this->getContentStreamId(), $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->nodeQueryBuilder->addSearchTermConstraints($queryBuilder, $filter->searchTerm); @@ -431,7 +437,7 @@ private function buildSiblingsQuery(bool $preceding, NodeAggregateId $siblingNod $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeSiblingsQuery($preceding, $siblingNodeAggregateId, $this->getContentStreamId(), $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->nodeQueryBuilder->addSearchTermConstraints($queryBuilder, $filter->searchTerm); @@ -446,12 +452,6 @@ private function buildSiblingsQuery(bool $preceding, NodeAggregateId $siblingNod return $queryBuilder; } - private function addNodeTypeCriteria(QueryBuilder $queryBuilder, NodeTypeCriteria $nodeTypeCriteria, string $nodeTableAlias = 'n'): void - { - $constraintsWithSubNodeTypes = ExpandedNodeTypeCriteria::create($nodeTypeCriteria, $this->nodeTypeManager); - $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilder, $constraintsWithSubNodeTypes, $nodeTableAlias); - } - private function applyPagination(QueryBuilder $queryBuilder, Pagination $pagination): void { $queryBuilder @@ -489,11 +489,6 @@ private function fetchNodes(QueryBuilder $queryBuilder, DimensionSpacePoint $dim return $this->nodeFactory->mapNodeRowsToNodes($nodeRows, $this->getContentStreamId(), $dimensionSpacePoint, VisibilityConstraints::withoutRestrictions()); } - private function createUniqueParameterName(): string - { - return 'param_' . UuidFactory::create(); - } - public function getWorkspaceName(): WorkspaceName { if ($this->workspaceName !== null) { diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index ec3b2be547c..cf164fb92df 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -20,7 +20,6 @@ use Doctrine\DBAL\Result; use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphAdapter; use Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphProjection; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRelationAnchorPoint; use Neos\ContentGraph\DoctrineDbalAdapter\NodeQueryBuilder; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\Infrastructure\DbalClientInterface; @@ -79,7 +78,7 @@ public function __construct( $this->nodeQueryBuilder = new NodeQueryBuilder($this->client->getConnection(), $this->tableNamePrefix); } - final public function getSubgraph( + public function getSubgraph( ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint, VisibilityConstraints $visibilityConstraints @@ -115,6 +114,9 @@ public function findRootNodeAggregateByType( FindRootNodeAggregatesFilter::create(nodeTypeName: $nodeTypeName) ); + if ($rootNodeAggregates->count() < 1) { + throw RootNodeAggregateDoesNotExist::butWasExpectedTo($nodeTypeName); + } if ($rootNodeAggregates->count() > 1) { $ids = []; foreach ($rootNodeAggregates as $rootNodeAggregate) { @@ -127,33 +129,15 @@ public function findRootNodeAggregateByType( )); } - $rootNodeAggregate = $rootNodeAggregates->first(); - - if ($rootNodeAggregate === null) { - throw RootNodeAggregateDoesNotExist::butWasExpectedTo($nodeTypeName); - } - - return $rootNodeAggregate; + return $rootNodeAggregates->first(); } public function findRootNodeAggregates( ContentStreamId $contentStreamId, FindRootNodeAggregatesFilter $filter, ): NodeAggregates { - $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeAggregateQuery(); - $queryBuilder - ->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($contentStreamId, $filter); + return NodeAggregates::fromArray(iterator_to_array($this->mapQueryBuilderToNodeAggregates($rootNodeAggregateQueryBuilder, $contentStreamId))); } public function findNodeAggregatesByType( diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php index da76523c92a..e07211ce0dd 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php @@ -162,15 +162,9 @@ public function findNodeById(NodeAggregateId $nodeAggregateId): ?Node public function findRootNodeByType(NodeTypeName $nodeTypeName): ?Node { - $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.name, h.subtreetags') - ->from($this->nodeQueryBuilder->getTablenameForNode(), 'n') - ->innerJoin('n', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), '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); } @@ -273,7 +267,7 @@ public function findSubtree(NodeAggregateId $entryNodeAggregateId, FindSubtreeFi $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); @@ -366,7 +360,7 @@ public function findClosestNode(NodeAggregateId $entryNodeAggregateId, FindClose $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, @@ -404,21 +398,18 @@ public function countDescendantNodes(NodeAggregateId $entryNodeAggregateId, Coun public function countNodes(): int { - $queryBuilder = $this->createQueryBuilder() - ->select('COUNT(*)') - ->from($this->nodeQueryBuilder->getTablenameForNode(), 'n') - ->innerJoin('n', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), '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 @@ -456,17 +447,11 @@ private function addSubtreeTagConstraints(QueryBuilder $queryBuilder, string $hi } } - private function addNodeTypeCriteria(QueryBuilder $queryBuilder, NodeTypeCriteria $nodeTypeCriteria, string $nodeTableAlias = 'n'): void - { - $constraintsWithSubNodeTypes = ExpandedNodeTypeCriteria::create($nodeTypeCriteria, $this->nodeTypeManager); - $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilder, $constraintsWithSubNodeTypes, $nodeTableAlias); - } - private function buildChildNodesQuery(NodeAggregateId $parentNodeAggregateId, FindChildNodesFilter|CountChildNodesFilter $filter): QueryBuilder { $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->nodeQueryBuilder->addSearchTermConstraints($queryBuilder, $filter->searchTerm); @@ -497,7 +482,7 @@ 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->nodeQueryBuilder->addSearchTermConstraints($queryBuilder, $filter->nodeSearchTerm, "{$destinationTablePrefix}n"); @@ -511,9 +496,6 @@ private function buildReferencesQuery(bool $backReferences, NodeAggregateId $nod if ($filter->referencePropertyValue !== null) { $this->nodeQueryBuilder->addPropertyValueConstraints($queryBuilder, $filter->referencePropertyValue, 'r'); } - if ($filter->nodePropertyValue !== null) { - $this->nodeQueryBuilder->addPropertyValueConstraints($queryBuilder, $filter->nodePropertyValue, "{$destinationTablePrefix}n"); - } if ($filter->referenceName !== null) { $queryBuilder->andWhere('r.name = :referenceName')->setParameter('referenceName', $filter->referenceName->value); } @@ -538,7 +520,7 @@ private function buildSiblingsQuery(bool $preceding, NodeAggregateId $siblingNod $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->nodeQueryBuilder->addSearchTermConstraints($queryBuilder, $filter->searchTerm); @@ -583,7 +565,7 @@ private function buildAncestorNodesQueries(NodeAggregateId $entryNodeAggregateId $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'); } @@ -619,7 +601,7 @@ private function buildDescendantNodesQueries(NodeAggregateId $entryNodeAggregate $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->nodeQueryBuilder->addSearchTermConstraints($queryBuilderCte, $filter->searchTerm); diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php index 248334ef862..7a01781eacf 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php @@ -1,4 +1,5 @@ buildBasicNodeAggregateQuery(); + $queryBuilder + ->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('n.*, h.name, h.subtreetags') - ->from($this->getTablenameForNode(), 'n') - ->innerJoin('n', $this->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->where('n.nodeaggregateid = :nodeAggregateId')->setParameter('nodeAggregateId', $nodeAggregateId->value) - ->andWhere('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $contentStreamId->value) + ->select($select) + ->from($this->getTablenameForNode(), $nodeTableAlias) + ->innerJoin($nodeTableAlias, $this->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = ' . $nodeTableAlias . '.relationanchorpoint') + ->where('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $contentStreamId->value) ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); } + public function buildBasicNodeByIdQuery(NodeAggregateId $nodeAggregateId, ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder + { + return $this->buildBasicNodeQuery($contentStreamId, $dimensionSpacePoint) + ->andWhere('n.nodeaggregateid = :nodeAggregateId')->setParameter('nodeAggregateId', $nodeAggregateId->value); + } + public function buildBasicChildNodesQuery(NodeAggregateId $parentNodeAggregateId, ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder { return $this->createQueryBuilder() @@ -110,28 +136,17 @@ public function buildBasicParentNodeQuery(NodeAggregateId $childNodeAggregateId, public function buildBasicNodeSiblingsQuery(bool $preceding, NodeAggregateId $siblingNodeAggregateId, ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder { - $parentNodeAnchorSubQuery = $this->createQueryBuilder() - ->select('sh.parentnodeanchor') + $sharedSubQuery = $this->createQueryBuilder() ->from($this->getTablenameForHierachyRelation(), 'sh') ->innerJoin('sh', $this->getTablenameForNode(), '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->getTablenameForHierachyRelation(), 'sh') - ->innerJoin('sh', $this->getTablenameForNode(), '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->createQueryBuilder() - ->select('n.*, h.name, h.subtreetags') - ->from($this->getTablenameForNode(), 'n') - ->innerJoin('n', $this->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->where('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $contentStreamId->value) - ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash) + return $this->buildBasicNodeQuery($contentStreamId, $dimensionSpacePoint) ->andWhere('h.parentnodeanchor = (' . $parentNodeAnchorSubQuery->getSQL() . ')') ->andWhere('n.nodeaggregateid != :siblingNodeAggregateId')->setParameter('siblingNodeAggregateId', $siblingNodeAggregateId->value) ->andWhere('h.position ' . ($preceding ? '<' : '>') . ' (' . $siblingPositionSubQuery->getSQL() . ')') @@ -290,7 +305,7 @@ public function fetchRows(QueryBuilder $queryBuilder): array } try { return $result->fetchAllAssociative(); - } catch (DriverException|DBALException $e) { + } catch (DriverException $e) { throw new \RuntimeException(sprintf('Failed to fetch rows from database: %s', $e->getMessage()), 1701444358, $e); } } From dad8756d0004f3e4533ad7dcadfb42e82370c8d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Sun, 14 Apr 2024 23:49:50 +0200 Subject: [PATCH 13/18] More cleanup --- .../src/ContentGraphAdapter.php | 40 ++++++------------- .../src/Domain/Repository/ContentSubgraph.php | 4 +- .../src/NodeQueryBuilder.php | 7 +--- .../Feature/ContentGraphAdapterInterface.php | 2 - .../Feature/ContentStreamCommandHandler.php | 2 +- 5 files changed, 16 insertions(+), 39 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php index 2c8f54d62b1..0d4faf29e11 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php @@ -75,7 +75,7 @@ public function rootNodeAggregateWithTypeExists(NodeTypeName $nodeTypeName): boo { try { return (bool)$this->findRootNodeAggregateByType($nodeTypeName); - } catch(\Exception $_) { + } catch (\Exception $_) { } return false; @@ -225,12 +225,7 @@ public function findChildNodeAggregatesByName(NodeAggregateId $parentNodeAggrega public function subgraphContainsNodes(DimensionSpacePoint $dimensionSpacePoint): bool { - $queryBuilder = $this->createQueryBuilder() - ->select('COUNT(*)') - ->from($this->nodeQueryBuilder->getTablenameForNode(), 'n') - ->innerJoin('n', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->where('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->getContentStreamId()->value) - ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeQuery($this->getContentStreamId(), $dimensionSpacePoint, 'n', 'COUNT(*)'); try { $result = $this->executeQuery($queryBuilder)->fetchOne(); if (!is_int($result)) { @@ -253,8 +248,7 @@ public function findNodeInSubgraph(DimensionSpacePoint $coveredDimensionSpacePoi public function findChildNodesInSubgraph(DimensionSpacePoint $coveredDimensionSpacePoint, NodeAggregateId $parentNodeAggregateId): Nodes { - $filter = FindChildNodesFilter::create(); - $queryBuilder = $this->buildChildNodesQuery($parentNodeAggregateId, $coveredDimensionSpacePoint, $filter); + $queryBuilder = $this->buildChildNodesQuery($parentNodeAggregateId, $coveredDimensionSpacePoint, FindChildNodesFilter::create()); $queryBuilder->addOrderBy('h.position'); return $this->fetchNodes($queryBuilder, $coveredDimensionSpacePoint); @@ -292,12 +286,18 @@ public function findSucceedingSiblingNodesInSubgraph(DimensionSpacePoint $covere public function hasContentStream(): bool { try { - $this->getContentStreamId(); + /* @var $state string|false */ + $state = $this->dbalConnection->executeQuery( + 'SELECT state FROM cr_default_p_contentstream WHERE contentStreamId = :contentStreamId', + [ + 'contentStreamId' => $this->getContentStreamId()->value, + ] + )->fetchOne(); + + return $state !== false; } catch (ContentStreamDoesNotExistYet $_) { return false; } - - return true; } public function findStateForContentStream(): ?ContentStreamState @@ -527,22 +527,6 @@ public function getContentStreamId(): ContentStreamId return $this->contentStreamId; } - public function contentStreamExists(): bool - { - /* @var $state string|false */ - $state = $this->dbalConnection->executeQuery( - ' - SELECT state FROM cr_default_p_contentstream - WHERE contentStreamId = :contentStreamId - ', - [ - 'contentStreamId' => $this->getContentStreamId()->value, - ] - )->fetchOne(); - - return $state === false ? false : true; - } - public function getWorkspace(): Workspace { $query = $this->dbalConnection->prepare('SELECT * FROM cr_default_p_workspace WHERE workspacename LIKE :workspaceName'); diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php index e07211ce0dd..29dc3994ff5 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php @@ -635,9 +635,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/NodeQueryBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php index 7a01781eacf..9f472fb8909 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php @@ -74,8 +74,7 @@ public function buildChildNodeAggregateQuery(NodeAggregateId $parentNodeAggregat public function buildFindRootNodeAggregatesQuery(ContentStreamId $contentStreamId, FindRootNodeAggregatesFilter $filter): QueryBuilder { - $queryBuilder = $this->buildBasicNodeAggregateQuery(); - $queryBuilder + $queryBuilder = $this->buildBasicNodeAggregateQuery() ->andWhere('h.parentnodeanchor = :rootEdgeParentAnchorId') ->setParameters([ 'contentStreamId' => $contentStreamId->value, @@ -83,9 +82,7 @@ public function buildFindRootNodeAggregatesQuery(ContentStreamId $contentStreamI ]); if ($filter->nodeTypeName !== null) { - $queryBuilder - ->andWhere('n.nodetypename = :nodeTypeName') - ->setParameter('nodeTypeName', $filter->nodeTypeName->value); + $queryBuilder->andWhere('n.nodetypename = :nodeTypeName')->setParameter('nodeTypeName', $filter->nodeTypeName->value); } return $queryBuilder; diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterInterface.php b/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterInterface.php index c6bff192a9b..227d7729d47 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterInterface.php +++ b/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterInterface.php @@ -167,8 +167,6 @@ public function findStateForContentStream(): ?ContentStreamState; public function findVersionForContentStream(): MaybeVersion; - public function contentStreamExists(): bool; - /* * WORKSPACES */ diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php index 109eeffaa10..9d7875bb61d 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php @@ -185,7 +185,7 @@ protected function requireContentStreamToExist( ContentStreamId $contentStreamId ): void { $contentGraphAdapter = $this->contentGraphAdapterProvider->fromContentStreamId($contentStreamId); - if (!$contentGraphAdapter->contentStreamExists()) { + if (!$contentGraphAdapter->hasContentStream()) { throw new ContentStreamDoesNotExistYet( 'Content stream "' . $contentStreamId->value . '" does not exist yet.', 1521386692 From 87d6336a1cb61b19c5f2f3f6cf3fc10ab763873c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Sun, 14 Apr 2024 23:59:42 +0200 Subject: [PATCH 14/18] Fix that phpstan doesn't understand condition --- .../src/Domain/Repository/ContentGraph.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index cf164fb92df..16a3a8fd29a 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -114,9 +114,6 @@ public function findRootNodeAggregateByType( FindRootNodeAggregatesFilter::create(nodeTypeName: $nodeTypeName) ); - if ($rootNodeAggregates->count() < 1) { - throw RootNodeAggregateDoesNotExist::butWasExpectedTo($nodeTypeName); - } if ($rootNodeAggregates->count() > 1) { $ids = []; foreach ($rootNodeAggregates as $rootNodeAggregate) { @@ -129,6 +126,10 @@ public function findRootNodeAggregateByType( )); } + if (!$rootNodeAggregates->first()) { + throw RootNodeAggregateDoesNotExist::butWasExpectedTo($nodeTypeName); + } + return $rootNodeAggregates->first(); } From 8d580b8696d9846f51558a5dffc9111ef7427f75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Mon, 15 Apr 2024 11:21:21 +0200 Subject: [PATCH 15/18] Cleanup --- .../src/ContentGraphAdapter.php | 10 ++-------- .../GenericCommandExecutionAndEventPublication.php | 2 -- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php index 0d4faf29e11..1a008c4bbb7 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php @@ -304,10 +304,7 @@ public function findStateForContentStream(): ?ContentStreamState { /* @var $state string|false */ $state = $this->dbalConnection->executeQuery( - ' - SELECT state FROM cr_default_p_contentstream - WHERE contentStreamId = :contentStreamId - ', + 'SELECT state FROM cr_default_p_contentstream WHERE contentStreamId = :contentStreamId', [ 'contentStreamId' => $this->getContentStreamId()->value, ] @@ -320,10 +317,7 @@ public function findVersionForContentStream(): MaybeVersion { /* @var $version int|false */ $version = $this->dbalConnection->executeQuery( - ' - SELECT version FROM cr_default_p_contentstream - WHERE contentStreamId = :contentStreamId - ', + 'SELECT version FROM cr_default_p_contentstream WHERE contentStreamId = :contentStreamId', [ 'contentStreamId' => $this->getContentStreamId()->value, ] diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/GenericCommandExecutionAndEventPublication.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/GenericCommandExecutionAndEventPublication.php index 63a5e25e82a..114a73ce42f 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/GenericCommandExecutionAndEventPublication.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/GenericCommandExecutionAndEventPublication.php @@ -224,8 +224,6 @@ public function eventNumberIs(int $eventNumber, string $eventType, TableNode $pa $actual = DimensionSpacePointSet::fromArray($actualValue); Assert::assertTrue($expected->equals($actual), 'Actual Dimension Space Point set "' . json_encode($actualValue) . '" does not match expected Dimension Space Point set "' . $assertionTableRow['Expected'] . '"'); } else { -// die(); -// exit; Assert::assertJsonStringEqualsJsonString($assertionTableRow['Expected'], json_encode($actualValue)); } } From 2f8bfe51d52fa4329995873d55bad9c7274f589b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Tue, 16 Apr 2024 09:25:46 +0200 Subject: [PATCH 16/18] Make factory signature clearer --- .../src/ContentGraphAdapterFactory.php | 25 ++++++++----------- .../src/ContentGraphAdapterFactoryBuilder.php | 9 +++++-- .../Factory/ContentRepositoryFactory.php | 7 +++++- ...entGraphAdapterFactoryBuilderInterface.php | 11 +++++--- 4 files changed, 31 insertions(+), 21 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php index 8cd27e16ed7..094d5f603f1 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php @@ -5,9 +5,9 @@ use Doctrine\DBAL\Connection; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\DimensionSpacePointsRepository; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\NodeFactory; -use Neos\ContentRepository\Core\Factory\ProjectionFactoryDependencies; use Neos\ContentRepository\Core\Feature\ContentGraphAdapterFactoryInterface; use Neos\ContentRepository\Core\Feature\ContentGraphAdapterInterface; +use Neos\ContentRepository\Core\Infrastructure\Property\PropertyConverter; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; @@ -22,28 +22,23 @@ class ContentGraphAdapterFactory implements ContentGraphAdapterFactoryInterface { private NodeFactory $nodeFactory; - private NodeTypeManager $nodeTypeManager; - private string $tableNamePrefix; - private ContentRepositoryId $contentRepositoryId; - public function __construct( private readonly Connection $dbalConnection, - ProjectionFactoryDependencies $projectionFactoryDependencies + private readonly ContentRepositoryId $contentRepositoryId, + private readonly NodeTypeManager $nodeTypeManager, + PropertyConverter $propertyConverter ) { - $tableNamePrefix = DoctrineDbalContentGraphProjectionFactory::graphProjectionTableNamePrefix( - $projectionFactoryDependencies->contentRepositoryId + $this->tableNamePrefix = DoctrineDbalContentGraphProjectionFactory::graphProjectionTableNamePrefix( + $contentRepositoryId ); - $this->tableNamePrefix = $tableNamePrefix; - $this->contentRepositoryId = $projectionFactoryDependencies->contentRepositoryId; - $dimensionSpacePointsRepository = new DimensionSpacePointsRepository($this->dbalConnection, $tableNamePrefix); - $this->nodeTypeManager = $projectionFactoryDependencies->nodeTypeManager; + $dimensionSpacePointsRepository = new DimensionSpacePointsRepository($this->dbalConnection, $this->tableNamePrefix); $this->nodeFactory = new NodeFactory( - $projectionFactoryDependencies->contentRepositoryId, - $projectionFactoryDependencies->nodeTypeManager, - $projectionFactoryDependencies->propertyConverter, + $contentRepositoryId, + $nodeTypeManager, + $propertyConverter, $dimensionSpacePointsRepository ); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactoryBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactoryBuilder.php index fe3ccdfb48c..4e90d3c6043 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactoryBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactoryBuilder.php @@ -4,7 +4,11 @@ use Neos\ContentRepository\Core\Factory\ProjectionFactoryDependencies; use Neos\ContentRepository\Core\Feature\ContentGraphAdapterFactoryBuilderInterface; +use Neos\ContentRepository\Core\Feature\ContentGraphAdapterFactoryInterface; use Neos\ContentRepository\Core\Infrastructure\DbalClientInterface; +use Neos\ContentRepository\Core\Infrastructure\Property\PropertyConverter; +use Neos\ContentRepository\Core\NodeType\NodeTypeManager; +use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; /** * Builder to combine injected dependencies and ProjectionFActoryDependencies into a ContentGraphAdapterFactory @@ -16,8 +20,9 @@ public function __construct(private readonly DbalClientInterface $dbalClient) { } - public function build(ProjectionFactoryDependencies $projectionFactoryDependencies): ContentGraphAdapterFactory + public function build(ContentRepositoryId $contentRepositoryId, NodeTypeManager $nodeTypeManager, PropertyConverter $propertyConverter): ContentGraphAdapterFactoryInterface { - return new ContentGraphAdapterFactory($this->dbalClient->getConnection(), $projectionFactoryDependencies); + return new ContentGraphAdapterFactory($this->dbalClient->getConnection(), $contentRepositoryId, $nodeTypeManager, $propertyConverter); } + } diff --git a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php index beeef9f6b81..920bd06efc0 100644 --- a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php +++ b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php @@ -67,6 +67,7 @@ public function __construct( $contentDimensionSource, $contentDimensionZookeeper ); + $propertyConverter = new PropertyConverter($propertySerializer); $this->projectionFactoryDependencies = new ProjectionFactoryDependencies( $contentRepositoryId, $eventStore, @@ -79,7 +80,11 @@ public function __construct( ); $this->projectionsAndCatchUpHooks = $projectionsAndCatchUpHooksFactory->build($this->projectionFactoryDependencies); - $this->contentGraphAdapterProvider = new ContentGraphAdapterProvider($contentGraphAdapterFactoryBuilder->build($this->projectionFactoryDependencies)); + $this->contentGraphAdapterProvider = new ContentGraphAdapterProvider($contentGraphAdapterFactoryBuilder->build( + $this->contentRepositoryId, + $nodeTypeManager, + $propertyConverter + )); } // The following properties store "singleton" references of objects for this content repository diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterFactoryBuilderInterface.php b/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterFactoryBuilderInterface.php index 078d4e7cb36..ae88cc3f0a6 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterFactoryBuilderInterface.php +++ b/Neos.ContentRepository.Core/Classes/Feature/ContentGraphAdapterFactoryBuilderInterface.php @@ -2,8 +2,9 @@ namespace Neos\ContentRepository\Core\Feature; -use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphAdapterFactory; -use Neos\ContentRepository\Core\Factory\ProjectionFactoryDependencies; +use Neos\ContentRepository\Core\Infrastructure\Property\PropertyConverter; +use Neos\ContentRepository\Core\NodeType\NodeTypeManager; +use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; /** * This allows you to combine constructor injection with dependencies from @@ -15,5 +16,9 @@ */ interface ContentGraphAdapterFactoryBuilderInterface { - public function build(ProjectionFactoryDependencies $projectionFactoryDependencies): ContentGraphAdapterFactory; + public function build( + ContentRepositoryId $contentRepositoryId, + NodeTypeManager $nodeTypeManager, + PropertyConverter $propertyConverter + ): ContentGraphAdapterFactoryInterface; } From 9826f994debc4151a0933feb119a725148455590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Thu, 18 Apr 2024 09:24:22 +0200 Subject: [PATCH 17/18] Linter fixes --- .../src/ContentGraphAdapterFactoryBuilder.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactoryBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactoryBuilder.php index 4e90d3c6043..a5e983cbda3 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactoryBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactoryBuilder.php @@ -2,7 +2,6 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter; -use Neos\ContentRepository\Core\Factory\ProjectionFactoryDependencies; use Neos\ContentRepository\Core\Feature\ContentGraphAdapterFactoryBuilderInterface; use Neos\ContentRepository\Core\Feature\ContentGraphAdapterFactoryInterface; use Neos\ContentRepository\Core\Infrastructure\DbalClientInterface; @@ -24,5 +23,4 @@ public function build(ContentRepositoryId $contentRepositoryId, NodeTypeManager { return new ContentGraphAdapterFactory($this->dbalClient->getConnection(), $contentRepositoryId, $nodeTypeManager, $propertyConverter); } - } From bcf77d72c84289a2bef23fae7b5bd3e1d3b69dcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Sat, 20 Apr 2024 12:32:37 +0200 Subject: [PATCH 18/18] Add ContentGraphTableNames I think the idea is good, just where we create it and how we pass it along needs refinement, given that this PR will not stay as it is anyways, just putting it in to keep the idea intact. --- .../src/ContentGraphAdapter.php | 79 +++++-------------- .../src/ContentGraphAdapterFactory.php | 8 +- .../src/ContentGraphTableNames.php | 43 ++++++++++ .../DoctrineDbalContentGraphProjection.php | 61 +++++++------- .../DoctrineDbalContentGraphSchemaBuilder.php | 13 +-- .../src/Domain/Projection/NodeRecord.php | 2 +- .../src/Domain/Repository/ContentGraph.php | 10 +-- .../src/Domain/Repository/ContentSubgraph.php | 51 ++++++------ .../DimensionSpacePointsRepository.php | 10 ++- .../src/NodeQueryBuilder.php | 43 +++++----- 10 files changed, 167 insertions(+), 153 deletions(-) create mode 100644 Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php index 1a008c4bbb7..f8095ebf72d 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapter.php @@ -8,7 +8,6 @@ use Doctrine\DBAL\Exception as DBALException; use Doctrine\DBAL\Query\QueryBuilder; use Doctrine\DBAL\Result; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRelationAnchorPoint; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\NodeFactory; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; @@ -18,11 +17,8 @@ use Neos\ContentRepository\Core\NodeType\NodeTypeName; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\CountChildNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindChildNodesFilter; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindPrecedingSiblingNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindRootNodeAggregatesFilter; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindSucceedingSiblingNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\NodeType\ExpandedNodeTypeCriteria; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\Pagination\Pagination; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregates; @@ -60,7 +56,6 @@ public function __construct( string $tableNamePrefix, public readonly ContentRepositoryId $contentRepositoryId, private readonly NodeFactory $nodeFactory, - private readonly NodeTypeManager $nodeTypeManager, public ?WorkspaceName $workspaceName, public ?ContentStreamId $contentStreamId, ) { @@ -106,8 +101,8 @@ public function findRootNodeAggregateByType(NodeTypeName $nodeTypeName): ?NodeAg public function findParentNodeAggregates(NodeAggregateId $childNodeAggregateId): iterable { $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeAggregateQuery() - ->innerJoin('n', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), 'ch', 'ch.parentnodeanchor = n.relationanchorpoint') - ->innerJoin('ch', $this->nodeQueryBuilder->getTablenameForNode(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') + ->innerJoin('n', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'ch', 'ch.parentnodeanchor = n.relationanchorpoint') + ->innerJoin('ch', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') ->andWhere('ch.contentstreamid = :contentStreamId') ->andWhere('cn.nodeaggregateid = :nodeAggregateId') ->setParameters([ @@ -139,9 +134,9 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint(NodeAggr { $subQueryBuilder = $this->dbalConnection->createQueryBuilder() ->select('pn.nodeaggregateid') - ->from($this->nodeQueryBuilder->getTablenameForNode(), 'pn') - ->innerJoin('pn', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('ch', $this->nodeQueryBuilder->getTablenameForNode(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') + ->from($this->nodeQueryBuilder->contentGraphTableNames->node(), 'pn') + ->innerJoin('pn', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('ch', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') ->where('ch.contentstreamid = :contentStreamId') ->andWhere('ch.dimensionspacepointhash = :childOriginDimensionSpacePointHash') ->andWhere('cn.nodeaggregateid = :childNodeAggregateId') @@ -149,9 +144,9 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint(NodeAggr $queryBuilder = $this->dbalConnection->createQueryBuilder() ->select('n.*, h.name, h.contentstreamid, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') - ->from($this->nodeQueryBuilder->getTablenameForNode(), 'n') - ->innerJoin('n', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->innerJoin('h', $this->nodeQueryBuilder->getTablenameForDimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') + ->from($this->nodeQueryBuilder->contentGraphTableNames->node(), 'n') + ->innerJoin('n', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('h', $this->nodeQueryBuilder->contentGraphTableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') ->where('n.nodeaggregateid = (' . $subQueryBuilder->getSQL() . ')') ->andWhere('h.contentstreamid = :contentStreamId') ->setParameters([ @@ -187,10 +182,10 @@ public function getDimensionSpacePointsOccupiedByChildNodeName(NodeName $nodeNam { $queryBuilder = $this->createQueryBuilder() ->select('dsp.dimensionspacepoint, h.dimensionspacepointhash') - ->from($this->nodeQueryBuilder->getTablenameForHierachyRelation(), 'h') - ->innerJoin('h', $this->nodeQueryBuilder->getTablenameForNode(), 'n', 'n.relationanchorpoint = h.parentnodeanchor') - ->innerJoin('h', $this->nodeQueryBuilder->getTablenameForDimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') - ->innerJoin('n', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), 'ph', 'ph.childnodeanchor = n.relationanchorpoint') + ->from($this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'h') + ->innerJoin('h', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'n', 'n.relationanchorpoint = h.parentnodeanchor') + ->innerJoin('h', $this->nodeQueryBuilder->contentGraphTableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') + ->innerJoin('n', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'ph', 'ph.childnodeanchor = n.relationanchorpoint') ->where('n.nodeaggregateid = :parentNodeAggregateId') ->andWhere('n.origindimensionspacepointhash = :parentNodeOriginDimensionSpacePointHash') ->andWhere('ph.contentstreamid = :contentStreamId') @@ -248,7 +243,7 @@ public function findNodeInSubgraph(DimensionSpacePoint $coveredDimensionSpacePoi public function findChildNodesInSubgraph(DimensionSpacePoint $coveredDimensionSpacePoint, NodeAggregateId $parentNodeAggregateId): Nodes { - $queryBuilder = $this->buildChildNodesQuery($parentNodeAggregateId, $coveredDimensionSpacePoint, FindChildNodesFilter::create()); + $queryBuilder = $this->buildChildNodesQuery($parentNodeAggregateId, $coveredDimensionSpacePoint); $queryBuilder->addOrderBy('h.position'); return $this->fetchNodes($queryBuilder, $coveredDimensionSpacePoint); @@ -271,14 +266,14 @@ public function findChildNodeByNameInSubgraph(DimensionSpacePoint $coveredDimens public function findPreceedingSiblingNodesInSubgraph(DimensionSpacePoint $coveredDimensionSpacePoint, NodeAggregateId $startingSiblingNodeAggregateId): Nodes { - $queryBuilder = $this->buildSiblingsQuery(true, $startingSiblingNodeAggregateId, $coveredDimensionSpacePoint, FindPrecedingSiblingNodesFilter::create()); + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeSiblingsQuery(true, $startingSiblingNodeAggregateId, $this->getContentStreamId(), $coveredDimensionSpacePoint); return $this->fetchNodes($queryBuilder, $coveredDimensionSpacePoint); } public function findSucceedingSiblingNodesInSubgraph(DimensionSpacePoint $coveredDimensionSpacePoint, NodeAggregateId $startingSiblingNodeAggregateId): Nodes { - $queryBuilder = $this->buildSiblingsQuery(false, $startingSiblingNodeAggregateId, $coveredDimensionSpacePoint, FindSucceedingSiblingNodesFilter::create()); + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeSiblingsQuery(false, $startingSiblingNodeAggregateId, $this->getContentStreamId(), $coveredDimensionSpacePoint); return $this->fetchNodes($queryBuilder, $coveredDimensionSpacePoint); } @@ -345,18 +340,9 @@ private function createQueryBuilder(): QueryBuilder return $this->dbalConnection->createQueryBuilder(); } - private function buildChildNodesQuery(NodeAggregateId $parentNodeAggregateId, DimensionSpacePoint $dimensionSpacePoint, FindChildNodesFilter|CountChildNodesFilter $filter): QueryBuilder + private function buildChildNodesQuery(NodeAggregateId $parentNodeAggregateId, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder { $queryBuilder = $this->nodeQueryBuilder->buildBasicChildNodesQuery($parentNodeAggregateId, $this->getContentStreamId(), $dimensionSpacePoint); - if ($filter->nodeTypes !== null) { - $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilder, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager)); - } - if ($filter->searchTerm !== null) { - $this->nodeQueryBuilder->addSearchTermConstraints($queryBuilder, $filter->searchTerm); - } - if ($filter->propertyValue !== null) { - $this->nodeQueryBuilder->addPropertyValueConstraints($queryBuilder, $filter->propertyValue); - } return $queryBuilder; } @@ -415,9 +401,9 @@ private function findChildNodeConnectedThroughEdgeName(NodeAggregateId $parentNo { $queryBuilder = $this->createQueryBuilder() ->select('cn.*, h.name, h.subtreetags') - ->from($this->nodeQueryBuilder->getTablenameForNode(), 'pn') - ->innerJoin('pn', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('pn', $this->nodeQueryBuilder->getTablenameForNode(), 'cn', 'cn.relationanchorpoint = h.childnodeanchor') + ->from($this->nodeQueryBuilder->contentGraphTableNames->node(), 'pn') + ->innerJoin('pn', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'cn', 'cn.relationanchorpoint = h.childnodeanchor') ->where('pn.nodeaggregateid = :parentNodeAggregateId')->setParameter('parentNodeAggregateId', $parentNodeAggregateId->value) ->andWhere('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->getContentStreamId()->value) ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash) @@ -426,33 +412,6 @@ private function findChildNodeConnectedThroughEdgeName(NodeAggregateId $parentNo return $this->fetchNode($queryBuilder, $dimensionSpacePoint); } - private function buildSiblingsQuery(bool $preceding, NodeAggregateId $siblingNodeAggregateId, DimensionSpacePoint $dimensionSpacePoint, FindPrecedingSiblingNodesFilter|FindSucceedingSiblingNodesFilter $filter): QueryBuilder - { - $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeSiblingsQuery($preceding, $siblingNodeAggregateId, $this->getContentStreamId(), $dimensionSpacePoint); - - if ($filter->nodeTypes !== null) { - $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilder, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager)); - } - if ($filter->searchTerm !== null) { - $this->nodeQueryBuilder->addSearchTermConstraints($queryBuilder, $filter->searchTerm); - } - if ($filter->propertyValue !== null) { - $this->nodeQueryBuilder->addPropertyValueConstraints($queryBuilder, $filter->propertyValue); - } - if ($filter->pagination !== null) { - $this->applyPagination($queryBuilder, $filter->pagination); - } - - return $queryBuilder; - } - - private function applyPagination(QueryBuilder $queryBuilder, Pagination $pagination): void - { - $queryBuilder - ->setMaxResults($pagination->limit) - ->setFirstResult($pagination->offset); - } - private function fetchNode(QueryBuilder $queryBuilder, DimensionSpacePoint $dimensionSpacePoint): ?Node { try { diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php index 094d5f603f1..4cdc7575013 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphAdapterFactory.php @@ -27,7 +27,7 @@ class ContentGraphAdapterFactory implements ContentGraphAdapterFactoryInterface public function __construct( private readonly Connection $dbalConnection, private readonly ContentRepositoryId $contentRepositoryId, - private readonly NodeTypeManager $nodeTypeManager, + NodeTypeManager $nodeTypeManager, PropertyConverter $propertyConverter ) { $this->tableNamePrefix = DoctrineDbalContentGraphProjectionFactory::graphProjectionTableNamePrefix( @@ -45,16 +45,16 @@ public function __construct( public function create(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphAdapterInterface { - return new ContentGraphAdapter($this->dbalConnection, $this->tableNamePrefix, $this->contentRepositoryId, $this->nodeFactory, $this->nodeTypeManager, $workspaceName, $contentStreamId); + return new ContentGraphAdapter($this->dbalConnection, $this->tableNamePrefix, $this->contentRepositoryId, $this->nodeFactory, $workspaceName, $contentStreamId); } public function createFromContentStreamId(ContentStreamId $contentStreamId): ContentGraphAdapterInterface { - return new ContentGraphAdapter($this->dbalConnection, $this->tableNamePrefix, $this->contentRepositoryId, $this->nodeFactory, $this->nodeTypeManager, null, $contentStreamId); + return new ContentGraphAdapter($this->dbalConnection, $this->tableNamePrefix, $this->contentRepositoryId, $this->nodeFactory, null, $contentStreamId); } public function createFromWorkspaceName(WorkspaceName $workspaceName): ContentGraphAdapterInterface { - return new ContentGraphAdapter($this->dbalConnection, $this->tableNamePrefix, $this->contentRepositoryId, $this->nodeFactory, $this->nodeTypeManager, $workspaceName, null); + return new ContentGraphAdapter($this->dbalConnection, $this->tableNamePrefix, $this->contentRepositoryId, $this->nodeFactory, $workspaceName, null); } } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php new file mode 100644 index 00000000000..20a9cc3d062 --- /dev/null +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php @@ -0,0 +1,43 @@ +tableNamePrefix . '_node'; + } + + public function hierachyRelation(): 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..cf1e223012e 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -85,6 +85,8 @@ final class DoctrineDbalContentGraphProjection implements ProjectionInterface, W private DbalCheckpointStorage $checkpointStorage; + private ContentGraphTableNames $contentGraphTableNames; + public function __construct( private readonly DbalClientInterface $dbalClient, private readonly NodeFactory $nodeFactory, @@ -94,9 +96,10 @@ public function __construct( private readonly string $tableNamePrefix, private readonly DimensionSpacePointsRepository $dimensionSpacePointsRepository ) { + $this->contentGraphTableNames = ContentGraphTableNames::withPrefix($tableNamePrefix); $this->checkpointStorage = new DbalCheckpointStorage( $this->dbalClient->getConnection(), - $this->tableNamePrefix . '_checkpoint', + $this->contentGraphTableNames->checkpoint(), self::class ); } @@ -174,10 +177,10 @@ public function reset(): void 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->contentGraphTableNames->node()); + $connection->executeQuery('TRUNCATE table ' . $this->contentGraphTableNames->hierachyRelation()); + $connection->executeQuery('TRUNCATE table ' . $this->contentGraphTableNames->referenceRelation()); + $connection->executeQuery('TRUNCATE table ' . $this->contentGraphTableNames->dimensionSpacePoints()); } public function canHandle(EventInterface $event): bool @@ -311,7 +314,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->contentGraphTableNames->hierachyRelation() . ' WHERE parentnodeanchor = :parentNodeAnchor AND childnodeanchor = :childNodeAnchor @@ -360,8 +363,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->contentGraphTableNames->hierachyRelation() . ' h + INNER JOIN ' . $this->contentGraphTableNames->node() . ' n on h.childnodeanchor = n.relationanchorpoint SET h.name = :newName, @@ -597,7 +600,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->contentGraphTableNames->hierachyRelation() . ' ( parentnodeanchor, childnodeanchor, `name`, @@ -615,7 +618,7 @@ private function whenContentStreamWasForked(ContentStreamWasForked $event): void h.subtreetags, "' . $event->newContentStreamId->value . '" AS contentstreamid FROM - ' . $this->tableNamePrefix . '_hierarchyrelation h + ' . $this->contentGraphTableNames->hierachyRelation() . ' h WHERE h.contentstreamid = :sourceContentStreamId ', [ 'sourceContentStreamId' => $event->sourceContentStreamId->value @@ -632,7 +635,7 @@ private function whenContentStreamWasRemoved(ContentStreamWasRemoved $event): vo // Drop hierarchy relations $this->getDatabaseConnection()->executeUpdate(' - DELETE FROM ' . $this->tableNamePrefix . '_hierarchyrelation + DELETE FROM ' . $this->contentGraphTableNames->hierachyRelation() . ' WHERE contentstreamid = :contentStreamId ', [ @@ -641,23 +644,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->contentGraphTableNames->node() . ' WHERE NOT EXISTS ( - SELECT 1 FROM ' . $this->tableNamePrefix . '_hierarchyrelation - WHERE ' . $this->tableNamePrefix . '_hierarchyrelation.childnodeanchor - = ' . $this->tableNamePrefix . '_node.relationanchorpoint + SELECT 1 FROM ' . $this->contentGraphTableNames->hierachyRelation() . ' + WHERE ' . $this->contentGraphTableNames->hierachyRelation() . '.childnodeanchor + = ' . $this->contentGraphTableNames->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->contentGraphTableNames->referenceRelation() . ' WHERE NOT EXISTS ( - SELECT 1 FROM ' . $this->tableNamePrefix . '_node - WHERE ' . $this->tableNamePrefix . '_node.relationanchorpoint - = ' . $this->tableNamePrefix . '_referencerelation.nodeanchorpoint + SELECT 1 FROM ' . $this->contentGraphTableNames->node() . ' + WHERE ' . $this->contentGraphTableNames->node() . '.relationanchorpoint + = ' . $this->contentGraphTableNames->referenceRelation() . '.nodeanchorpoint ) '); }); @@ -742,7 +745,7 @@ function (NodeRecord $node) use ($eventEnvelope) { ); // remove old - $this->getDatabaseConnection()->delete($this->tableNamePrefix . '_referencerelation', [ + $this->getDatabaseConnection()->delete($this->contentGraphTableNames->referenceRelation(), [ 'nodeanchorpoint' => $nodeAnchorPoint?->value, 'name' => $event->referenceName->value ]); @@ -751,7 +754,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->contentGraphTableNames->referenceRelation(), [ 'name' => $event->referenceName->value, 'position' => $position, 'nodeanchorpoint' => $nodeAnchorPoint?->value, @@ -870,7 +873,7 @@ private function updateNodeRecordWithCopyOnWrite( // IMPORTANT: We need to reconnect BOTH the incoming and outgoing edges. $this->getDatabaseConnection()->executeStatement( ' - UPDATE ' . $this->tableNamePrefix . '_hierarchyrelation h + UPDATE ' . $this->contentGraphTableNames->hierachyRelation() . ' h SET -- if our (copied) node is the child, we update h.childNodeAnchor h.childnodeanchor @@ -914,7 +917,7 @@ private function copyReferenceRelations( NodeRelationAnchorPoint $destinationRelationAnchorPoint ): void { $this->getDatabaseConnection()->executeStatement(' - INSERT INTO ' . $this->tableNamePrefix . '_referencerelation ( + INSERT INTO ' . $this->contentGraphTableNames->referenceRelation() . ' ( nodeanchorpoint, name, position, @@ -926,7 +929,7 @@ private function copyReferenceRelations( ref.position, ref.destinationnodeaggregateid FROM - ' . $this->tableNamePrefix . '_referencerelation ref + ' . $this->contentGraphTableNames->referenceRelation() . ' ref WHERE ref.nodeanchorpoint = :sourceNodeAnchorPoint ', [ 'sourceNodeAnchorPoint' => $sourceRelationAnchorPoint->value, @@ -945,8 +948,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->contentGraphTableNames->node() . ' n + INNER JOIN ' . $this->contentGraphTableNames->hierachyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint AND h.contentstreamid = :contentStreamId @@ -975,7 +978,7 @@ function (NodeRecord $nodeRecord) use ($event) { // 2) hierarchy relations $this->getDatabaseConnection()->executeStatement( ' - UPDATE ' . $this->tableNamePrefix . '_hierarchyrelation h + UPDATE ' . $this->contentGraphTableNames->hierachyRelation() . ' h SET h.dimensionspacepointhash = :newDimensionSpacePointHash WHERE @@ -999,7 +1002,7 @@ private function whenDimensionShineThroughWasAdded(DimensionShineThroughWasAdded // 1) hierarchy relations $this->getDatabaseConnection()->executeStatement( ' - INSERT INTO ' . $this->tableNamePrefix . '_hierarchyrelation ( + INSERT INTO ' . $this->contentGraphTableNames->hierachyRelation() . ' ( parentnodeanchor, childnodeanchor, `name`, @@ -1017,7 +1020,7 @@ private function whenDimensionShineThroughWasAdded(DimensionShineThroughWasAdded :newDimensionSpacePointHash AS dimensionspacepointhash, h.contentstreamid FROM - ' . $this->tableNamePrefix . '_hierarchyrelation h + ' . $this->contentGraphTableNames->hierachyRelation() . ' h WHERE h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :sourceDimensionSpacePointHash', [ diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php index 06cc35ff431..69fe81ac8f3 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php @@ -17,9 +17,12 @@ class DoctrineDbalContentGraphSchemaBuilder { private const DEFAULT_TEXT_COLLATION = 'utf8mb4_unicode_520_ci'; + private readonly ContentGraphTableNames $contentGraphTableNames; + public function __construct( - private readonly string $tableNamePrefix + string $tableNamePrefix ) { + $this->contentGraphTableNames = ContentGraphTableNames::withPrefix($tableNamePrefix); } public function buildSchema(AbstractSchemaManager $schemaManager): Schema @@ -34,7 +37,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 +58,7 @@ private function createNodeTable(): Table private function createHierarchyRelationTable(): Table { - $table = new Table($this->tableNamePrefix . '_hierarchyrelation', [ + $table = new Table($this->contentGraphTableNames->hierachyRelation(), [ (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), @@ -72,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) ]); @@ -83,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/Domain/Projection/NodeRecord.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php index fbc75314807..698064110de 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php @@ -15,7 +15,6 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection; use Doctrine\DBAL\Connection; -use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Doctrine\DBAL\Types\Types; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\DimensionSpacePointsRepository; use Neos\ContentRepository\Core\Feature\NodeModification\Dto\SerializedPropertyValues; @@ -53,6 +52,7 @@ public function __construct( */ public function updateToDatabase(Connection $databaseConnection, string $tableNamePrefix): void { + $databaseConnection->update( $tableNamePrefix . '_node', [ diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index 16a3a8fd29a..6c6ec9531cd 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -159,7 +159,7 @@ public function findNodeAggregateById( ContentStreamId $contentStreamId, NodeAggregateId $nodeAggregateId ): ?NodeAggregate { - $contentGraphAdapter = new ContentGraphAdapter($this->client->getConnection(), $this->tableNamePrefix, $this->contentRepositoryId, $this->nodeFactory, $this->nodeTypeManager, null, $contentStreamId); + $contentGraphAdapter = new ContentGraphAdapter($this->client->getConnection(), $this->tableNamePrefix, $this->contentRepositoryId, $this->nodeFactory, null, $contentStreamId); return $contentGraphAdapter->findNodeAggregateById($nodeAggregateId); } @@ -170,7 +170,7 @@ public function findParentNodeAggregates( ContentStreamId $contentStreamId, NodeAggregateId $childNodeAggregateId ): iterable { - $contentGraphAdapter = new ContentGraphAdapter($this->client->getConnection(), $this->tableNamePrefix, $this->contentRepositoryId, $this->nodeFactory, $this->nodeTypeManager, null, $contentStreamId); + $contentGraphAdapter = new ContentGraphAdapter($this->client->getConnection(), $this->tableNamePrefix, $this->contentRepositoryId, $this->nodeFactory, null, $contentStreamId); return $contentGraphAdapter->findParentNodeAggregates($childNodeAggregateId); } @@ -181,7 +181,7 @@ public function findChildNodeAggregates( ContentStreamId $contentStreamId, NodeAggregateId $parentNodeAggregateId ): iterable { - $contentGraphAdapter = new ContentGraphAdapter($this->client->getConnection(), $this->tableNamePrefix, $this->contentRepositoryId, $this->nodeFactory, $this->nodeTypeManager, null, $contentStreamId); + $contentGraphAdapter = new ContentGraphAdapter($this->client->getConnection(), $this->tableNamePrefix, $this->contentRepositoryId, $this->nodeFactory, null, $contentStreamId); return $contentGraphAdapter->findChildNodeAggregates($parentNodeAggregateId); } @@ -193,7 +193,7 @@ public function findChildNodeAggregatesByName( NodeAggregateId $parentNodeAggregateId, NodeName $name ): iterable { - $contentGraphAdapter = new ContentGraphAdapter($this->client->getConnection(), $this->tableNamePrefix, $this->contentRepositoryId, $this->nodeFactory, $this->nodeTypeManager, null, $contentStreamId); + $contentGraphAdapter = new ContentGraphAdapter($this->client->getConnection(), $this->tableNamePrefix, $this->contentRepositoryId, $this->nodeFactory, null, $contentStreamId); return $contentGraphAdapter->findChildNodeAggregatesByName($parentNodeAggregateId, $name); } @@ -201,7 +201,7 @@ public function countNodes(): int { $queryBuilder = $this->createQueryBuilder() ->select('COUNT(*)') - ->from($this->tableNamePrefix . '_node'); + ->from($this->nodeQueryBuilder->contentGraphTableNames->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); diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php index 29dc3994ff5..d2ad6a3008a 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php @@ -41,7 +41,6 @@ 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; @@ -249,8 +248,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->nodeQueryBuilder->getTablenameForNode(), 'n') - ->innerJoin('n', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->from($this->nodeQueryBuilder->contentGraphTableNames->node(), 'n') + ->innerJoin('n', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('n.nodeaggregateid = :entryNodeAggregateId'); @@ -259,8 +258,8 @@ 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->nodeQueryBuilder->getTablenameForHierachyRelation(), 'h', 'h.parentnodeanchor = p.relationanchorpoint') - ->innerJoin('p', $this->nodeQueryBuilder->getTablenameForNode(), 'c', 'c.relationanchorpoint = h.childnodeanchor') + ->innerJoin('p', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'h', 'h.parentnodeanchor = p.relationanchorpoint') + ->innerJoin('p', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'c', 'c.relationanchorpoint = h.childnodeanchor') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); if ($filter->maximumLevels !== null) { @@ -341,9 +340,9 @@ public function findClosestNode(NodeAggregateId $entryNodeAggregateId, FindClose { $queryBuilderInitial = $this->createQueryBuilder() ->select('n.*, ph.name, ph.subtreetags, ph.parentnodeanchor') - ->from($this->nodeQueryBuilder->getTablenameForNode(), 'n') + ->from($this->nodeQueryBuilder->contentGraphTableNames->node(), 'n') // we need to join with the hierarchy relation, because we need the node name. - ->innerJoin('n', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), 'ph', 'n.relationanchorpoint = ph.childnodeanchor') + ->innerJoin('n', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'ph', 'n.relationanchorpoint = ph.childnodeanchor') ->andWhere('ph.contentstreamid = :contentStreamId') ->andWhere('ph.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('n.nodeaggregateid = :entryNodeAggregateId'); @@ -352,8 +351,8 @@ public function findClosestNode(NodeAggregateId $entryNodeAggregateId, FindClose $queryBuilderRecursive = $this->createQueryBuilder() ->select('pn.*, h.name, h.subtreetags, h.parentnodeanchor') ->from('ancestry', 'cn') - ->innerJoin('cn', $this->nodeQueryBuilder->getTablenameForNode(), 'pn', 'pn.relationanchorpoint = cn.parentnodeanchor') - ->innerJoin('pn', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = pn.relationanchorpoint') + ->innerJoin('cn', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'pn', 'pn.relationanchorpoint = cn.parentnodeanchor') + ->innerJoin('pn', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'h', 'h.childnodeanchor = pn.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); $this->addSubtreeTagConstraints($queryBuilderRecursive); @@ -469,11 +468,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->nodeQueryBuilder->getTablenameForHierachyRelation(), 'sh') - ->innerJoin('sh', $this->nodeQueryBuilder->getTablenameForNode(), 'sn', 'sn.relationanchorpoint = sh.childnodeanchor') - ->innerJoin('sh', $this->nodeQueryBuilder->getTablenameForReferenceRelation(), 'r', 'r.nodeanchorpoint = sn.relationanchorpoint') - ->innerJoin('sh', $this->nodeQueryBuilder->getTablenameForNode(), 'dn', 'dn.nodeaggregateid = r.destinationnodeaggregateid') - ->innerJoin('sh', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), 'dh', 'dh.childnodeanchor = dn.relationanchorpoint') + ->from($this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'sh') + ->innerJoin('sh', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'sn', 'sn.relationanchorpoint = sh.childnodeanchor') + ->innerJoin('sh', $this->nodeQueryBuilder->contentGraphTableNames->referenceRelation(), 'r', 'r.nodeanchorpoint = sn.relationanchorpoint') + ->innerJoin('sh', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'dn', 'dn.nodeaggregateid = r.destinationnodeaggregateid') + ->innerJoin('sh', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), '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') @@ -541,11 +540,11 @@ private function buildAncestorNodesQueries(NodeAggregateId $entryNodeAggregateId { $queryBuilderInitial = $this->createQueryBuilder() ->select('n.*, ph.name, ph.subtreetags, ph.parentnodeanchor') - ->from($this->nodeQueryBuilder->getTablenameForNode(), 'n') + ->from($this->nodeQueryBuilder->contentGraphTableNames->node(), 'n') // we need to join with the hierarchy relation, because we need the node name. - ->innerJoin('n', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), 'ch', 'ch.parentnodeanchor = n.relationanchorpoint') - ->innerJoin('ch', $this->nodeQueryBuilder->getTablenameForNode(), 'c', 'c.relationanchorpoint = ch.childnodeanchor') - ->innerJoin('n', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), 'ph', 'n.relationanchorpoint = ph.childnodeanchor') + ->innerJoin('n', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'ch', 'ch.parentnodeanchor = n.relationanchorpoint') + ->innerJoin('ch', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'c', 'c.relationanchorpoint = ch.childnodeanchor') + ->innerJoin('n', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'ph', 'n.relationanchorpoint = ph.childnodeanchor') ->where('ch.contentstreamid = :contentStreamId') ->andWhere('ch.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('ph.contentstreamid = :contentStreamId') @@ -557,8 +556,8 @@ private function buildAncestorNodesQueries(NodeAggregateId $entryNodeAggregateId $queryBuilderRecursive = $this->createQueryBuilder() ->select('pn.*, h.name, h.subtreetags, h.parentnodeanchor') ->from('ancestry', 'cn') - ->innerJoin('cn', $this->nodeQueryBuilder->getTablenameForNode(), 'pn', 'pn.relationanchorpoint = cn.parentnodeanchor') - ->innerJoin('pn', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = pn.relationanchorpoint') + ->innerJoin('cn', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'pn', 'pn.relationanchorpoint = cn.parentnodeanchor') + ->innerJoin('pn', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'h', 'h.childnodeanchor = pn.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); $this->addSubtreeTagConstraints($queryBuilderRecursive); @@ -578,11 +577,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->nodeQueryBuilder->getTablenameForNode(), 'n') + ->from($this->nodeQueryBuilder->contentGraphTableNames->node(), 'n') // we need to join with the hierarchy relation, because we need the node name. - ->innerJoin('n', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->innerJoin('n', $this->nodeQueryBuilder->getTablenameForNode(), 'p', 'p.relationanchorpoint = h.parentnodeanchor') - ->innerJoin('n', $this->nodeQueryBuilder->getTablenameForHierachyRelation(), 'ph', 'ph.childnodeanchor = p.relationanchorpoint') + ->innerJoin('n', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('n', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'p', 'p.relationanchorpoint = h.parentnodeanchor') + ->innerJoin('n', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'ph', 'ph.childnodeanchor = p.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('ph.contentstreamid = :contentStreamId') @@ -593,8 +592,8 @@ 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->nodeQueryBuilder->getTablenameForHierachyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('pn', $this->nodeQueryBuilder->getTablenameForNode(), 'cn', 'cn.relationanchorpoint = h.childnodeanchor') + ->innerJoin('pn', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'cn', 'cn.relationanchorpoint = h.childnodeanchor') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); $this->addSubtreeTagConstraints($queryBuilderRecursive); diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/DimensionSpacePointsRepository.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/DimensionSpacePointsRepository.php index 1a409b5abc6..374d750c3ad 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; @@ -30,10 +31,13 @@ final class DimensionSpacePointsRepository */ private array $dimensionspacePointsRuntimeCache = []; + private readonly ContentGraphTableNames $contentGraphTableNames; + public function __construct( private readonly Connection $databaseConnection, - private readonly string $tableNamePrefix + string $tableNamePrefix ) { + $this->contentGraphTableNames = ContentGraphTableNames::withPrefix($tableNamePrefix); } public function insertDimensionSpacePoint(AbstractDimensionSpacePoint $dimensionSpacePoint): void @@ -81,7 +85,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->contentGraphTableNames->dimensionSpacePoints() . ' (hash, dimensionspacepoint) VALUES (:dimensionspacepointhash, :dimensionspacepoint)', [ 'dimensionspacepointhash' => $hash, 'dimensionspacepoint' => $dimensionSpacePointCoordinatesJson @@ -96,7 +100,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->contentGraphTableNames->dimensionSpacePoints()); foreach ($allDimensionSpacePoints as $dimensionSpacePointRow) { $this->dimensionspacePointsRuntimeCache[(string)$dimensionSpacePointRow['hash']] = (string)$dimensionSpacePointRow['dimensionspacepoint']; } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php index 9f472fb8909..c04bb1c72f3 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php @@ -35,19 +35,22 @@ */ class NodeQueryBuilder { + public readonly ContentGraphTableNames $contentGraphTableNames; + public function __construct( private readonly Connection $connection, private readonly string $tableNamePrefix ) { + $this->contentGraphTableNames = ContentGraphTableNames::withPrefix($tableNamePrefix); } public function buildBasicNodeAggregateQuery(): QueryBuilder { $queryBuilder = $this->createQueryBuilder() ->select('n.*, h.contentstreamid, h.name, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') - ->from($this->getTablenameForNode(), 'n') - ->innerJoin('n', $this->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->innerJoin('h', $this->getTablenameForDimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') + ->from($this->contentGraphTableNames->node(), 'n') + ->innerJoin('n', $this->contentGraphTableNames->hierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('h', $this->contentGraphTableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') ->where('h.contentstreamid = :contentStreamId'); return $queryBuilder; @@ -57,11 +60,11 @@ public function buildChildNodeAggregateQuery(NodeAggregateId $parentNodeAggregat { return $this->createQueryBuilder() ->select('cn.*, ch.name, ch.contentstreamid, ch.subtreetags, cdsp.dimensionspacepoint AS covereddimensionspacepoint') - ->from($this->getTablenameForNode(), 'pn') - ->innerJoin('pn', $this->getTablenameForHierachyRelation(), 'ph', 'ph.childnodeanchor = pn.relationanchorpoint') - ->innerJoin('pn', $this->getTablenameForHierachyRelation(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('ch', $this->getTablenameForDimensionSpacePoints(), 'cdsp', 'cdsp.hash = ch.dimensionspacepointhash') - ->innerJoin('ch', $this->getTablenameForNode(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') + ->from($this->contentGraphTableNames->node(), 'pn') + ->innerJoin('pn', $this->contentGraphTableNames->hierachyRelation(), 'ph', 'ph.childnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->contentGraphTableNames->hierachyRelation(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('ch', $this->contentGraphTableNames->dimensionSpacePoints(), 'cdsp', 'cdsp.hash = ch.dimensionspacepointhash') + ->innerJoin('ch', $this->contentGraphTableNames->node(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') ->where('pn.nodeaggregateid = :parentNodeAggregateId') ->andWhere('ph.contentstreamid = :contentStreamId') ->andWhere('ch.contentstreamid = :contentStreamId') @@ -92,8 +95,8 @@ public function buildBasicNodeQuery(ContentStreamId $contentStreamId, DimensionS { return $this->createQueryBuilder() ->select($select) - ->from($this->getTablenameForNode(), $nodeTableAlias) - ->innerJoin($nodeTableAlias, $this->getTablenameForHierachyRelation(), 'h', 'h.childnodeanchor = ' . $nodeTableAlias . '.relationanchorpoint') + ->from($this->contentGraphTableNames->node(), $nodeTableAlias) + ->innerJoin($nodeTableAlias, $this->contentGraphTableNames->hierachyRelation(), 'h', 'h.childnodeanchor = ' . $nodeTableAlias . '.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $contentStreamId->value) ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); } @@ -108,9 +111,9 @@ public function buildBasicChildNodesQuery(NodeAggregateId $parentNodeAggregateId { return $this->createQueryBuilder() ->select('n.*, h.name, h.subtreetags') - ->from($this->getTablenameForNode(), 'pn') - ->innerJoin('pn', $this->getTablenameForHierachyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('pn', $this->getTablenameForNode(), 'n', 'h.childnodeanchor = n.relationanchorpoint') + ->from($this->contentGraphTableNames->node(), 'pn') + ->innerJoin('pn', $this->contentGraphTableNames->hierachyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->contentGraphTableNames->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); @@ -120,10 +123,10 @@ public function buildBasicParentNodeQuery(NodeAggregateId $childNodeAggregateId, { return $this->createQueryBuilder() ->select('pn.*, ch.name, ch.subtreetags') - ->from($this->getTablenameForNode(), 'pn') - ->innerJoin('pn', $this->getTablenameForHierachyRelation(), 'ph', 'ph.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('pn', $this->getTablenameForNode(), 'cn', 'cn.relationanchorpoint = ph.childnodeanchor') - ->innerJoin('pn', $this->getTablenameForHierachyRelation(), 'ch', 'ch.childnodeanchor = pn.relationanchorpoint') + ->from($this->contentGraphTableNames->node(), 'pn') + ->innerJoin('pn', $this->contentGraphTableNames->hierachyRelation(), 'ph', 'ph.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->contentGraphTableNames->node(), 'cn', 'cn.relationanchorpoint = ph.childnodeanchor') + ->innerJoin('pn', $this->contentGraphTableNames->hierachyRelation(), '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') @@ -134,8 +137,8 @@ public function buildBasicParentNodeQuery(NodeAggregateId $childNodeAggregateId, public function buildBasicNodeSiblingsQuery(bool $preceding, NodeAggregateId $siblingNodeAggregateId, ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder { $sharedSubQuery = $this->createQueryBuilder() - ->from($this->getTablenameForHierachyRelation(), 'sh') - ->innerJoin('sh', $this->getTablenameForNode(), 'sn', 'sn.relationanchorpoint = sh.childnodeanchor') + ->from($this->contentGraphTableNames->hierachyRelation(), 'sh') + ->innerJoin('sh', $this->contentGraphTableNames->node(), 'sn', 'sn.relationanchorpoint = sh.childnodeanchor') ->where('sn.nodeaggregateid = :siblingNodeAggregateId') ->andWhere('sh.contentstreamid = :contentStreamId') ->andWhere('sh.dimensionspacepointhash = :dimensionSpacePointHash'); @@ -285,7 +288,7 @@ public function findUsedNodeTypeNames(): array { $rows = $this->fetchRows($this->createQueryBuilder() ->select('DISTINCT nodetypename') - ->from($this->getTablenameForNode())); + ->from($this->contentGraphTableNames->node())); return $rows; }