diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2ab48800296..83f1baadc62 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -182,11 +182,10 @@ jobs: # We enable the race condition tracker presets: 'default': - projections: - 'Neos.ContentRepository:ContentGraph': - catchUpHooks: - 'Neos.ContentRepository.BehavioralTests:RaceConditionTracker': - factoryObjectName: Neos\ContentRepository\BehavioralTests\ProjectionRaceConditionTester\RaceTrackerCatchUpHookFactory + contentGraphProjection: + catchUpHooks: + 'Neos.ContentRepository.BehavioralTests:RaceConditionTracker': + factoryObjectName: Neos\ContentRepository\BehavioralTests\ProjectionRaceConditionTester\RaceTrackerCatchUpHookFactory ContentRepository: BehavioralTests: raceConditionTracker: diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentRepositoryReadModelAdapter.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php similarity index 90% rename from Neos.ContentGraph.DoctrineDbalAdapter/src/ContentRepositoryReadModelAdapter.php rename to Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php index 56ad85a9d63..891a07e441d 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentRepositoryReadModelAdapter.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphReadModelAdapter.php @@ -18,7 +18,7 @@ use Doctrine\DBAL\Exception; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ContentGraph; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\NodeFactory; -use Neos\ContentRepository\Core\ContentRepositoryReadModelAdapterInterface; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphReadModelInterface; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStream; @@ -32,10 +32,9 @@ use Neos\EventStore\Model\Event\Version; /** - * @internal only used inside the - * @see ContentRepositoryReadModel + * @internal */ -final readonly class ContentRepositoryReadModelAdapter implements ContentRepositoryReadModelAdapterInterface +final readonly class ContentGraphReadModelAdapter implements ContentGraphReadModelInterface { public function __construct( private Connection $dbal, @@ -131,6 +130,21 @@ public function findContentStreams(): ContentStreams return ContentStreams::fromArray(array_map(self::contentStreamFromDatabaseRow(...), $rows)); } + public function countNodes(): int + { + $countNodesStatement = <<tableNames->node()} + SQL; + try { + return (int)$this->dbal->fetchOne($countNodesStatement); + } catch (Exception $e) { + throw new \RuntimeException(sprintf('Failed to count rows in database: %s', $e->getMessage()), 1701444590, $e); + } + } + /** * @param array $row */ diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index ecd410b0a07..2b6ef815f5d 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -17,7 +17,7 @@ use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRelationAnchorPoint; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\DimensionSpacePointsRepository; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ProjectionContentGraph; -use Neos\ContentRepository\Core\ContentRepositoryReadModel; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphReadModelInterface; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; @@ -65,8 +65,8 @@ use Neos\ContentRepository\Core\Projection\CheckpointStorageStatusType; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeTags; use Neos\ContentRepository\Core\Projection\ContentGraph\Timestamps; -use Neos\ContentRepository\Core\Projection\ProjectionInterface; use Neos\ContentRepository\Core\Projection\ProjectionStatus; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphProjectionInterface; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateClassification; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; @@ -76,10 +76,9 @@ use Neos\EventStore\Model\EventEnvelope; /** - * @implements ProjectionInterface * @internal but the graph projection is api */ -final class DoctrineDbalContentGraphProjection implements ProjectionInterface +final class DoctrineDbalContentGraphProjection implements ContentGraphProjectionInterface { use ContentStream; use NodeMove; @@ -98,7 +97,7 @@ public function __construct( private readonly ProjectionContentGraph $projectionContentGraph, private readonly ContentGraphTableNames $tableNames, private readonly DimensionSpacePointsRepository $dimensionSpacePointsRepository, - private readonly ContentRepositoryReadModel $contentRepositoryReadModel + private readonly ContentGraphReadModelInterface $contentGraphReadModel ) { $this->checkpointStorage = new DbalCheckpointStorage( $this->dbal, @@ -177,9 +176,9 @@ public function getCheckpointStorage(): DbalCheckpointStorage return $this->checkpointStorage; } - public function getState(): ContentRepositoryReadModel + public function getState(): ContentGraphReadModelInterface { - return $this->contentRepositoryReadModel; + return $this->contentGraphReadModel; } public function canHandle(EventInterface $event): bool diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php index 1b750c114a4..a5c4d6ae25e 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php @@ -8,18 +8,15 @@ use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\DimensionSpacePointsRepository; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\NodeFactory; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ProjectionContentGraph; -use Neos\ContentRepository\Core\ContentRepositoryReadModel; use Neos\ContentRepository\Core\Factory\ProjectionFactoryDependencies; -use Neos\ContentRepository\Core\Projection\ProjectionFactoryInterface; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphProjectionFactoryInterface; /** * Use this class as ProjectionFactory in your configuration to construct a content graph * - * @implements ProjectionFactoryInterface - * * @api */ -final class DoctrineDbalContentGraphProjectionFactory implements ProjectionFactoryInterface +final class DoctrineDbalContentGraphProjectionFactory implements ContentGraphProjectionFactoryInterface { public function __construct( private readonly Connection $dbal, @@ -42,7 +39,7 @@ public function build( $dimensionSpacePointsRepository ); - $contentRepositoryReadModelAdapter = new ContentRepositoryReadModelAdapter( + $contentGraphReadModel = new ContentGraphReadModelAdapter( $this->dbal, $nodeFactory, $projectionFactoryDependencies->contentRepositoryId, @@ -58,7 +55,7 @@ public function build( ), $tableNames, $dimensionSpacePointsRepository, - new ContentRepositoryReadModel($contentRepositoryReadModelAdapter) + $contentGraphReadModel ); } } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index 68b0fd85fc0..d88070f7351 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -277,19 +277,6 @@ public function getDimensionSpacePointsOccupiedByChildNodeName(NodeName $nodeNam return new DimensionSpacePointSet($dimensionSpacePoints); } - public function countNodes(): int - { - $queryBuilder = $this->createQueryBuilder() - ->select('COUNT(*)') - ->from($this->nodeQueryBuilder->tableNames->node()); - try { - $result = $queryBuilder->executeQuery(); - return (int)$result->fetchOne(); - } catch (DBALException $e) { - throw new \RuntimeException(sprintf('Failed to count rows in database: %s', $e->getMessage()), 1701444590, $e); - } - } - public function findUsedNodeTypeNames(): NodeTypeNames { return NodeTypeNames::fromArray(array_map( diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperRepositoryReadModelAdapter.php b/Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperGraphReadModelAdapter.php similarity index 87% rename from Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperRepositoryReadModelAdapter.php rename to Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperGraphReadModelAdapter.php index 245ddd82d1f..d4d68465df2 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperRepositoryReadModelAdapter.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperGraphReadModelAdapter.php @@ -7,8 +7,7 @@ use Doctrine\DBAL\Connection; use Neos\ContentGraph\PostgreSQLAdapter\Domain\Repository\ContentHypergraph; use Neos\ContentGraph\PostgreSQLAdapter\Domain\Repository\NodeFactory; -use Neos\ContentRepository\Core\ContentRepositoryReadModel; -use Neos\ContentRepository\Core\ContentRepositoryReadModelAdapterInterface; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphReadModelInterface; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; @@ -20,10 +19,9 @@ use Neos\ContentRepository\Core\SharedModel\Workspace\Workspaces; /** - * @internal only used within - * @see ContentRepositoryReadModel + * @internal */ -final readonly class ContentHyperRepositoryReadModelAdapter implements ContentRepositoryReadModelAdapterInterface +final readonly class ContentHyperGraphReadModelAdapter implements ContentGraphReadModelInterface { public function __construct( private Connection $dbal, @@ -62,4 +60,10 @@ public function findContentStreams(): ContentStreams // TODO: Implement getContentStreams() method. return ContentStreams::createEmpty(); } + + public function countNodes(): int + { + // TODO: Implement countNodes method. + return 0; + } } diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Projection/HypergraphProjection.php b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Projection/HypergraphProjection.php index d5e9fb999bf..75b373c9c22 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Projection/HypergraphProjection.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Projection/HypergraphProjection.php @@ -26,7 +26,7 @@ use Neos\ContentGraph\PostgreSQLAdapter\Domain\Projection\Feature\NodeVariation; use Neos\ContentGraph\PostgreSQLAdapter\Domain\Projection\Feature\SubtreeTagging; use Neos\ContentGraph\PostgreSQLAdapter\Domain\Projection\SchemaBuilder\HypergraphSchemaBuilder; -use Neos\ContentRepository\Core\ContentRepositoryReadModel; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphReadModelInterface; use Neos\ContentRepository\Core\EventStore\EventInterface; use Neos\ContentRepository\Core\Feature\ContentStreamForking\Event\ContentStreamWasForked; use Neos\ContentRepository\Core\Feature\NodeCreation\Event\NodeAggregateWithNodeWasCreated; @@ -44,18 +44,17 @@ use Neos\ContentRepository\Core\Infrastructure\DbalCheckpointStorage; use Neos\ContentRepository\Core\Infrastructure\DbalSchemaDiff; use Neos\ContentRepository\Core\Projection\CheckpointStorageStatusType; -use Neos\ContentRepository\Core\Projection\ProjectionInterface; use Neos\ContentRepository\Core\Projection\ProjectionStatus; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphProjectionInterface; use Neos\EventStore\Model\Event\SequenceNumber; use Neos\EventStore\Model\EventEnvelope; /** * The alternate reality-aware hypergraph projector for the PostgreSQL backend via Doctrine DBAL * - * @implements ProjectionInterface * @internal the parent Content Graph is public */ -final class HypergraphProjection implements ProjectionInterface +final class HypergraphProjection implements ContentGraphProjectionInterface { use ContentStreamForking; use NodeCreation; @@ -73,7 +72,7 @@ final class HypergraphProjection implements ProjectionInterface public function __construct( private readonly Connection $dbal, private readonly string $tableNamePrefix, - private readonly ContentRepositoryReadModel $contentRepositoryReadModel + private readonly ContentGraphReadModelInterface $contentGraphReadModel ) { $this->projectionHypergraph = new ProjectionHypergraph($this->dbal, $this->tableNamePrefix); $this->checkpointStorage = new DbalCheckpointStorage( @@ -219,9 +218,9 @@ public function getCheckpointStorage(): DbalCheckpointStorage return $this->checkpointStorage; } - public function getState(): ContentRepositoryReadModel + public function getState(): ContentGraphReadModelInterface { - return $this->contentRepositoryReadModel; + return $this->contentGraphReadModel; } protected function getProjectionHypergraph(): ProjectionHypergraph diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php index cfab1b47fb8..29cc36eb21b 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php @@ -259,17 +259,6 @@ public function getDimensionSpacePointsOccupiedByChildNodeName( return new DimensionSpacePointSet($occupiedDimensionSpacePoints); } - /** - * @throws \Doctrine\DBAL\Driver\Exception - * @throws \Doctrine\DBAL\Exception - */ - public function countNodes(): int - { - $query = 'SELECT COUNT(*) FROM ' . $this->tableNamePrefix . '_node'; - - return $this->dbal->executeQuery($query)->fetchOne(); - } - public function findUsedNodeTypeNames(): NodeTypeNames { return NodeTypeNames::createEmpty(); diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/HypergraphProjectionFactory.php b/Neos.ContentGraph.PostgreSQLAdapter/src/HypergraphProjectionFactory.php index 1e3fbdb872a..3d3c002c094 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/HypergraphProjectionFactory.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/HypergraphProjectionFactory.php @@ -7,16 +7,14 @@ use Doctrine\DBAL\Connection; use Neos\ContentGraph\PostgreSQLAdapter\Domain\Projection\HypergraphProjection; use Neos\ContentGraph\PostgreSQLAdapter\Domain\Repository\NodeFactory; -use Neos\ContentRepository\Core\ContentRepositoryReadModel; use Neos\ContentRepository\Core\Factory\ProjectionFactoryDependencies; -use Neos\ContentRepository\Core\Projection\ProjectionFactoryInterface; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphProjectionFactoryInterface; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; /** - * @implements ProjectionFactoryInterface * @api */ -final class HypergraphProjectionFactory implements ProjectionFactoryInterface +final class HypergraphProjectionFactory implements ContentGraphProjectionFactoryInterface { public function __construct( private readonly Connection $dbal, @@ -45,7 +43,7 @@ public function build( return new HypergraphProjection( $this->dbal, $tableNamePrefix, - new ContentRepositoryReadModel(new ContentHyperRepositoryReadModelAdapter($this->dbal, $nodeFactory, $projectionFactoryDependencies->contentRepositoryId, $projectionFactoryDependencies->nodeTypeManager, $tableNamePrefix)) + new ContentHyperGraphReadModelAdapter($this->dbal, $nodeFactory, $projectionFactoryDependencies->contentRepositoryId, $projectionFactoryDependencies->nodeTypeManager, $tableNamePrefix) ); } } diff --git a/Neos.ContentRepository.BehavioralTests/Configuration/Settings.yaml b/Neos.ContentRepository.BehavioralTests/Configuration/Settings.yaml index f5877126acc..d684f0d8326 100644 --- a/Neos.ContentRepository.BehavioralTests/Configuration/Settings.yaml +++ b/Neos.ContentRepository.BehavioralTests/Configuration/Settings.yaml @@ -5,8 +5,7 @@ Neos: #ContentRepositoryRegistry: # presets: # 'default': - # projections: - # 'Neos.ContentRepository:ContentGraph': + # contentGraphProjection: # catchUpHooks: # 'Neos.ContentRepository.BehavioralTests:RaceConditionTracker': # factoryObjectName: Neos\ContentRepository\BehavioralTests\ProjectionRaceConditionTester\RaceTrackerCatchUpHookFactory diff --git a/Neos.ContentRepository.BehavioralTests/Configuration/Testing/Behat/Postgres/Settings.yaml b/Neos.ContentRepository.BehavioralTests/Configuration/Testing/Behat/Postgres/Settings.yaml index c8c79383b53..5a7694823b5 100644 --- a/Neos.ContentRepository.BehavioralTests/Configuration/Testing/Behat/Postgres/Settings.yaml +++ b/Neos.ContentRepository.BehavioralTests/Configuration/Testing/Behat/Postgres/Settings.yaml @@ -2,7 +2,6 @@ Neos: ContentRepositoryRegistry: presets: 'default': - projections: - 'Neos.ContentRepository:ContentGraph': - factoryObjectName: Neos\ContentGraph\PostgreSQLAdapter\HypergraphProjectionFactory + contentGraphProjection: + factoryObjectName: Neos\ContentGraph\PostgreSQLAdapter\HypergraphProjectionFactory diff --git a/Neos.ContentRepository.BehavioralTests/deployment/neos-in-docker/Configuration/Testing/Behat/Settings.yaml b/Neos.ContentRepository.BehavioralTests/deployment/neos-in-docker/Configuration/Testing/Behat/Settings.yaml index 32051f54db4..b132f2c6b39 100644 --- a/Neos.ContentRepository.BehavioralTests/deployment/neos-in-docker/Configuration/Testing/Behat/Settings.yaml +++ b/Neos.ContentRepository.BehavioralTests/deployment/neos-in-docker/Configuration/Testing/Behat/Settings.yaml @@ -28,11 +28,10 @@ Neos: # We enable the race condition tracker. For details on how this works, see RaceTrackerCatchUpHook.php presets: 'default': - projections: - 'Neos.ContentRepository:ContentGraph': - catchUpHooks: - 'Neos.ContentRepository.BehavioralTests:RaceConditionTracker': - factoryObjectName: Neos\ContentRepository\BehavioralTests\ProjectionRaceConditionTester\RaceTrackerCatchUpHookFactory + contentGraphProjection: + catchUpHooks: + 'Neos.ContentRepository.BehavioralTests:RaceConditionTracker': + factoryObjectName: Neos\ContentRepository\BehavioralTests\ProjectionRaceConditionTester\RaceTrackerCatchUpHookFactory ContentRepository: BehavioralTests: raceConditionTracker: diff --git a/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php b/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php index 725022f02e7..622af9c9a15 100644 --- a/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php +++ b/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php @@ -16,6 +16,7 @@ use Neos\ContentRepository\Core\CommandHandler\CommandInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphReadModelInterface; use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; @@ -38,7 +39,8 @@ final class CommandHandlingDependencies private array $overriddenContentGraphInstances = []; public function __construct( - private readonly ContentRepository $contentRepository + private readonly ContentRepository $contentRepository, + private readonly ContentGraphReadModelInterface $contentGraphReadModel ) { } @@ -49,7 +51,7 @@ public function handle(CommandInterface $command): void public function getContentStreamVersion(ContentStreamId $contentStreamId): Version { - $contentStream = $this->contentRepository->findContentStreamById($contentStreamId); + $contentStream = $this->contentGraphReadModel->findContentStreamById($contentStreamId); if ($contentStream === null) { throw new \InvalidArgumentException(sprintf('Failed to find content stream with id "%s"', $contentStreamId->value), 1716902051); } @@ -58,12 +60,12 @@ public function getContentStreamVersion(ContentStreamId $contentStreamId): Versi public function contentStreamExists(ContentStreamId $contentStreamId): bool { - return $this->contentRepository->findContentStreamById($contentStreamId) !== null; + return $this->contentGraphReadModel->findContentStreamById($contentStreamId) !== null; } public function getContentStreamStatus(ContentStreamId $contentStreamId): ContentStreamStatus { - $contentStream = $this->contentRepository->findContentStreamById($contentStreamId); + $contentStream = $this->contentGraphReadModel->findContentStreamById($contentStreamId); if ($contentStream === null) { throw new \InvalidArgumentException(sprintf('Failed to find content stream with id "%s"', $contentStreamId->value), 1716902219); } @@ -72,7 +74,7 @@ public function getContentStreamStatus(ContentStreamId $contentStreamId): Conten public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace { - return $this->contentRepository->findWorkspaceByName($workspaceName); + return $this->contentGraphReadModel->findWorkspaceByName($workspaceName); } /** @@ -83,8 +85,11 @@ public function getContentGraph(WorkspaceName $workspaceName): ContentGraphInter if (isset($this->overriddenContentGraphInstances[$workspaceName->value])) { return $this->overriddenContentGraphInstances[$workspaceName->value]; } - - return $this->contentRepository->getContentGraph($workspaceName); + $workspace = $this->contentGraphReadModel->findWorkspaceByName($workspaceName); + if ($workspace === null) { + throw WorkspaceDoesNotExist::butWasSupposedTo($workspaceName); + } + return $this->contentGraphReadModel->buildContentGraph($workspace->workspaceName, $workspace->currentContentStreamId); } /** @@ -102,7 +107,7 @@ public function overrideContentStreamId(WorkspaceName $workspaceName, ContentStr throw new \RuntimeException('Contentstream override for this workspace already in effect, nesting not allowed.', 1715170938); } - $contentGraph = $this->contentRepository->projectionState(ContentRepositoryReadModel::class)->getContentGraphByWorkspaceNameAndContentStreamId($workspaceName, $contentStreamId); + $contentGraph = $this->contentGraphReadModel->buildContentGraph($workspaceName, $contentStreamId); $this->overriddenContentGraphInstances[$workspaceName->value] = $contentGraph; try { diff --git a/Neos.ContentRepository.Core/Classes/ContentRepository.php b/Neos.ContentRepository.Core/Classes/ContentRepository.php index 0f4fa64fcf1..696b2de305d 100644 --- a/Neos.ContentRepository.Core/Classes/ContentRepository.php +++ b/Neos.ContentRepository.Core/Classes/ContentRepository.php @@ -29,6 +29,8 @@ use Neos\ContentRepository\Core\Projection\CatchUp; use Neos\ContentRepository\Core\Projection\CatchUpOptions; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphReadModelInterface; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphProjectionInterface; use Neos\ContentRepository\Core\Projection\ProjectionInterface; use Neos\ContentRepository\Core\Projection\ProjectionsAndCatchUpHooks; use Neos\ContentRepository\Core\Projection\ProjectionStateInterface; @@ -70,7 +72,6 @@ final class ContentRepository private CommandHandlingDependencies $commandHandlingDependencies; - /** * @internal use the {@see ContentRepositoryFactory::getOrBuild()} to instantiate */ @@ -86,8 +87,9 @@ public function __construct( private readonly ContentDimensionSourceInterface $contentDimensionSource, private readonly UserIdProviderInterface $userIdProvider, private readonly ClockInterface $clock, + private readonly ContentGraphReadModelInterface $contentGraphReadModel ) { - $this->commandHandlingDependencies = new CommandHandlingDependencies($this); + $this->commandHandlingDependencies = new CommandHandlingDependencies($this, $this->contentGraphReadModel); } /** @@ -141,18 +143,24 @@ public function handle(CommandInterface $command): void */ public function projectionState(string $projectionStateClassName): ProjectionStateInterface { + if (!isset($this->projectionStateCache)) { + foreach ($this->projectionsAndCatchUpHooks->projections as $projection) { + if ($projection instanceof ContentGraphProjectionInterface) { + continue; + } + $projectionState = $projection->getState(); + $this->projectionStateCache[$projectionState::class] = $projectionState; + } + } if (isset($this->projectionStateCache[$projectionStateClassName])) { /** @var T $projectionState */ $projectionState = $this->projectionStateCache[$projectionStateClassName]; return $projectionState; } - foreach ($this->projectionsAndCatchUpHooks->projections as $projection) { - $projectionState = $projection->getState(); - if ($projectionState instanceof $projectionStateClassName) { - $this->projectionStateCache[$projectionStateClassName] = $projectionState; - return $projectionState; - } + if (in_array(ContentGraphReadModelInterface::class, class_implements($projectionStateClassName), true)) { + throw new \InvalidArgumentException(sprintf('Accessing the internal content repository projection state via %s(%s) is not allowed. Please use the API on the content repository instead.', __FUNCTION__, $projectionStateClassName), 1729338679); } + throw new \InvalidArgumentException(sprintf('A projection state of type "%s" is not registered in this content repository instance.', $projectionStateClassName), 1662033650); } @@ -218,7 +226,7 @@ public function setUp(): void public function status(): ContentRepositoryStatus { - $projectionStatuses = ProjectionStatuses::create(); + $projectionStatuses = ProjectionStatuses::createEmpty(); foreach ($this->projectionsAndCatchUpHooks->projections as $projectionClassName => $projection) { $projectionStatuses = $projectionStatuses->with($projectionClassName, $projection->status()); } @@ -249,7 +257,11 @@ public function resetProjectionState(string $projectionClassName): void */ public function getContentGraph(WorkspaceName $workspaceName): ContentGraphInterface { - return $this->getContentRepositoryReadModel()->getContentGraphByWorkspaceName($workspaceName); + $workspace = $this->contentGraphReadModel->findWorkspaceByName($workspaceName); + if ($workspace === null) { + throw WorkspaceDoesNotExist::butWasSupposedTo($workspaceName); + } + return $this->contentGraphReadModel->buildContentGraph($workspaceName, $workspace->currentContentStreamId); } /** @@ -257,7 +269,7 @@ public function getContentGraph(WorkspaceName $workspaceName): ContentGraphInter */ public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace { - return $this->getContentRepositoryReadModel()->findWorkspaceByName($workspaceName); + return $this->contentGraphReadModel->findWorkspaceByName($workspaceName); } /** @@ -266,17 +278,17 @@ public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace */ public function findWorkspaces(): Workspaces { - return $this->getContentRepositoryReadModel()->findWorkspaces(); + return $this->contentGraphReadModel->findWorkspaces(); } public function findContentStreamById(ContentStreamId $contentStreamId): ?ContentStream { - return $this->getContentRepositoryReadModel()->findContentStreamById($contentStreamId); + return $this->contentGraphReadModel->findContentStreamById($contentStreamId); } public function findContentStreams(): ContentStreams { - return $this->getContentRepositoryReadModel()->findContentStreams(); + return $this->contentGraphReadModel->findContentStreams(); } public function getNodeTypeManager(): NodeTypeManager @@ -293,9 +305,4 @@ public function getContentDimensionSource(): ContentDimensionSourceInterface { return $this->contentDimensionSource; } - - private function getContentRepositoryReadModel(): ContentRepositoryReadModel - { - return $this->projectionState(ContentRepositoryReadModel::class); - } } diff --git a/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModel.php b/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModel.php deleted file mode 100644 index 38733eaf7a7..00000000000 --- a/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModel.php +++ /dev/null @@ -1,89 +0,0 @@ -adapter->findWorkspaceByName($workspaceName); - } - - public function findWorkspaces(): Workspaces - { - return $this->adapter->findWorkspaces(); - } - - public function findContentStreamById(ContentStreamId $contentStreamId): ?ContentStream - { - return $this->adapter->findContentStreamById($contentStreamId); - } - - public function findContentStreams(): ContentStreams - { - return $this->adapter->findContentStreams(); - } - - /** - * The default way to get a content graph to operate on. - * The currently assigned ContentStreamId for the given Workspace is resolved internally. - * - * @throws WorkspaceDoesNotExist if the provided workspace does not resolve to an existing content stream - * @see ContentRepository::getContentGraph() - */ - public function getContentGraphByWorkspaceName(WorkspaceName $workspaceName): ContentGraphInterface - { - $workspace = $this->findWorkspaceByName($workspaceName); - if ($workspace === null) { - throw WorkspaceDoesNotExist::butWasSupposedTo($workspaceName); - } - return $this->adapter->buildContentGraph($workspace->workspaceName, $workspace->currentContentStreamId); - } - - /** - * For testing we allow getting an instance set by both parameters, effectively overriding the relationship at will - * - * @param WorkspaceName $workspaceName - * @param ContentStreamId $contentStreamId - * @internal Only for testing - */ - public function getContentGraphByWorkspaceNameAndContentStreamId(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphInterface - { - return $this->adapter->buildContentGraph($workspaceName, $contentStreamId); - } -} diff --git a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php index 94f734c3911..c5ba2c24772 100644 --- a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php +++ b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php @@ -99,6 +99,7 @@ public function getOrBuild(): ContentRepository $this->projectionFactoryDependencies->contentDimensionSource, $this->userIdProvider, $this->clock, + $this->projectionsAndCatchUpHooks->contentGraphProjection->getState() ); } return $this->contentRepository; @@ -123,7 +124,7 @@ public function buildService( $this->projectionFactoryDependencies, $this->getOrBuild(), $this->buildEventPersister(), - $this->projectionsAndCatchUpHooks->projections, + $this->projectionsAndCatchUpHooks, ); return $serviceFactory->build($serviceFactoryDependencies); } diff --git a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryServiceFactoryDependencies.php b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryServiceFactoryDependencies.php index dadb00e4762..08ea272181d 100644 --- a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryServiceFactoryDependencies.php +++ b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryServiceFactoryDependencies.php @@ -22,7 +22,7 @@ use Neos\ContentRepository\Core\EventStore\EventPersister; use Neos\ContentRepository\Core\Infrastructure\Property\PropertyConverter; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; -use Neos\ContentRepository\Core\Projection\Projections; +use Neos\ContentRepository\Core\Projection\ProjectionsAndCatchUpHooks; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\EventStore\EventStoreInterface; @@ -46,7 +46,7 @@ private function __construct( public ContentRepository $contentRepository, // we don't need CommandBus, because this is included in ContentRepository->handle() public EventPersister $eventPersister, - public Projections $projections, + public ProjectionsAndCatchUpHooks $projectionsAndCatchUpHooks, ) { } @@ -57,7 +57,7 @@ public static function create( ProjectionFactoryDependencies $projectionFactoryDependencies, ContentRepository $contentRepository, EventPersister $eventPersister, - Projections $projections, + ProjectionsAndCatchUpHooks $projectionsAndCatchUpHooks, ): self { return new self( $projectionFactoryDependencies->contentRepositoryId, @@ -70,7 +70,7 @@ public static function create( $projectionFactoryDependencies->propertyConverter, $contentRepository, $eventPersister, - $projections, + $projectionsAndCatchUpHooks, ); } } diff --git a/Neos.ContentRepository.Core/Classes/Factory/ProjectionsAndCatchUpHooksFactory.php b/Neos.ContentRepository.Core/Classes/Factory/ProjectionsAndCatchUpHooksFactory.php index 609afd289a8..cd369a87a6e 100644 --- a/Neos.ContentRepository.Core/Classes/Factory/ProjectionsAndCatchUpHooksFactory.php +++ b/Neos.ContentRepository.Core/Classes/Factory/ProjectionsAndCatchUpHooksFactory.php @@ -11,6 +11,7 @@ use Neos\ContentRepository\Core\Projection\Projections; use Neos\ContentRepository\Core\Projection\ProjectionsAndCatchUpHooks; use Neos\ContentRepository\Core\Projection\ProjectionStateInterface; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphProjectionInterface; /** * @api for custom framework integrations, not for users of the CR @@ -53,6 +54,7 @@ public function registerCatchUpHookFactory(ProjectionFactoryInterface $factory, */ public function build(ProjectionFactoryDependencies $projectionFactoryDependencies): ProjectionsAndCatchUpHooks { + $contentGraphProjection = null; $projectionsArray = []; $catchUpHookFactoriesByProjectionClassName = []; foreach ($this->factories as $factoryDefinition) { @@ -71,9 +73,20 @@ public function build(ProjectionFactoryDependencies $projectionFactoryDependenci $options, ); $catchUpHookFactoriesByProjectionClassName[$projection::class] = $catchUpHookFactories; - $projectionsArray[] = $projection; + if ($projection instanceof ContentGraphProjectionInterface) { + if ($contentGraphProjection !== null) { + throw new \RuntimeException(sprintf('Content repository requires exactly one %s to be registered.', ContentGraphProjectionInterface::class)); + } + $contentGraphProjection = $projection; + } else { + $projectionsArray[] = $projection; + } + } + + if ($contentGraphProjection === null) { + throw new \RuntimeException(sprintf('Content repository requires the %s to be registered.', ContentGraphProjectionInterface::class)); } - return new ProjectionsAndCatchUpHooks(Projections::fromArray($projectionsArray), $catchUpHookFactoriesByProjectionClassName); + return new ProjectionsAndCatchUpHooks($contentGraphProjection, Projections::fromArray($projectionsArray), $catchUpHookFactoriesByProjectionClassName); } } diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php index 67af3f6d5ff..e8cac17eb67 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php @@ -142,13 +142,6 @@ public function getDimensionSpacePointsOccupiedByChildNodeName( DimensionSpacePointSet $dimensionSpacePointsToCheck ): DimensionSpacePointSet; - /** - * Provides the total number of projected nodes regardless of workspace or content stream. - * - * @internal only for consumption in testcases - */ - public function countNodes(): int; - /** @internal The content stream id where the workspace name points to for this instance */ public function getContentStreamId(): ContentStreamId; } diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphProjectionFactoryInterface.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphProjectionFactoryInterface.php new file mode 100644 index 00000000000..a385d454e76 --- /dev/null +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphProjectionFactoryInterface.php @@ -0,0 +1,23 @@ + + * @api for creating a custom content repository graph projection implementation, **not for users of the CR** + */ +interface ContentGraphProjectionFactoryInterface extends ProjectionFactoryInterface +{ + /** + * @param array $options + */ + public function build( + ProjectionFactoryDependencies $projectionFactoryDependencies, + array $options, + ): ContentGraphProjectionInterface; +} diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphProjectionInterface.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphProjectionInterface.php new file mode 100644 index 00000000000..fca250fd797 --- /dev/null +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphProjectionInterface.php @@ -0,0 +1,16 @@ + + * @api for creating a custom content repository graph projection implementation, **not for users of the CR** + */ +interface ContentGraphProjectionInterface extends ProjectionInterface +{ + public function getState(): ContentGraphReadModelInterface; +} diff --git a/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModelAdapterInterface.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphReadModelInterface.php similarity index 68% rename from Neos.ContentRepository.Core/Classes/ContentRepositoryReadModelAdapterInterface.php rename to Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphReadModelInterface.php index 7d266fdec9d..5902b8aba00 100644 --- a/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModelAdapterInterface.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphReadModelInterface.php @@ -12,10 +12,9 @@ declare(strict_types=1); -namespace Neos\ContentRepository\Core; +namespace Neos\ContentRepository\Core\Projection\ContentGraph; -use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; -use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; +use Neos\ContentRepository\Core\Projection\ProjectionStateInterface; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStream; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreams; @@ -24,11 +23,9 @@ use Neos\ContentRepository\Core\SharedModel\Workspace\Workspaces; /** - * Create implementations of ContentGraphs bound to a specific Workspace and/or ContentStream - * - * @internal This is just an implementation detail to delegate creating the specific implementations of a ContentGraph. + * @api for creating a custom content repository graph projection implementation, **not for users of the CR** */ -interface ContentRepositoryReadModelAdapterInterface +interface ContentGraphReadModelInterface extends ProjectionStateInterface { public function buildContentGraph(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphInterface; @@ -39,4 +36,11 @@ public function findWorkspaces(): Workspaces; public function findContentStreamById(ContentStreamId $contentStreamId): ?ContentStream; public function findContentStreams(): ContentStreams; + + /** + * Provides the total number of projected nodes regardless of workspace or content stream. + * + * @internal only for consumption in testcases + */ + public function countNodes(): int; } diff --git a/Neos.ContentRepository.Core/Classes/Projection/ProjectionStatuses.php b/Neos.ContentRepository.Core/Classes/Projection/ProjectionStatuses.php index 79a59b0a550..6acaaadc83c 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ProjectionStatuses.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ProjectionStatuses.php @@ -18,7 +18,7 @@ private function __construct( ) { } - public static function create(): self + public static function createEmpty(): self { return new self([]); } diff --git a/Neos.ContentRepository.Core/Classes/Projection/Projections.php b/Neos.ContentRepository.Core/Classes/Projection/Projections.php index 0bc7dffec20..4a6f6a3bb3a 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/Projections.php +++ b/Neos.ContentRepository.Core/Classes/Projection/Projections.php @@ -78,6 +78,15 @@ public function has(string $projectionClassName): bool return array_key_exists($projectionClassName, $this->projections); } + /** + * @param ProjectionInterface $projection + * @return self + */ + public function with(ProjectionInterface $projection): self + { + return self::fromArray([...$this->projections, $projection]); + } + /** * @return list>> */ diff --git a/Neos.ContentRepository.Core/Classes/Projection/ProjectionsAndCatchUpHooks.php b/Neos.ContentRepository.Core/Classes/Projection/ProjectionsAndCatchUpHooks.php index 86eebadae4a..255e59b7600 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ProjectionsAndCatchUpHooks.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ProjectionsAndCatchUpHooks.php @@ -4,18 +4,24 @@ namespace Neos\ContentRepository\Core\Projection; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphProjectionInterface; + /** * @internal */ final readonly class ProjectionsAndCatchUpHooks { + public Projections $projections; + /** * @param array>, CatchUpHookFactories> $catchUpHookFactoriesByProjectionClassName */ public function __construct( - public Projections $projections, + public ContentGraphProjectionInterface $contentGraphProjection, + Projections $additionalProjections, private array $catchUpHookFactoriesByProjectionClassName, ) { + $this->projections = $additionalProjections->with($this->contentGraphProjection); } /** diff --git a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Bootstrap/FeatureContext.php b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Bootstrap/FeatureContext.php index 00421a6bea4..775919a753a 100644 --- a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Bootstrap/FeatureContext.php +++ b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Bootstrap/FeatureContext.php @@ -12,7 +12,6 @@ use Neos\ContentRepository\BehavioralTests\TestSuite\Behavior\GherkinPyStringNodeBasedNodeTypeManagerFactory; use Neos\ContentRepository\BehavioralTests\TestSuite\Behavior\GherkinTableNodeBasedContentDimensionSourceFactory; use Neos\ContentRepository\Core\ContentRepository; -use Neos\ContentRepository\Core\ContentRepositoryReadModel; use Neos\ContentRepository\Core\EventStore\EventNormalizer; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryDependencies; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php index 12ebd6b0a5f..e4505319463 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php @@ -14,7 +14,6 @@ namespace Neos\ContentRepository\TestSuite\Behavior\Features\Bootstrap; -use Neos\ContentRepository\Core\ContentRepositoryReadModel; use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface; @@ -125,9 +124,7 @@ public function visibilityConstraintsAreSetTo(string $restrictionType): void public function getCurrentSubgraph(): ContentSubgraphInterface { - $contentRepositoryReadModel = $this->currentContentRepository->projectionState(ContentRepositoryReadModel::class); - - return $contentRepositoryReadModel->getContentGraphByWorkspaceName($this->currentWorkspaceName)->getSubgraph( + return $this->currentContentRepository->getContentGraph($this->currentWorkspaceName)->getSubgraph( $this->currentDimensionSpacePoint, $this->currentVisibilityConstraints ); diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php index ab550c6bd35..0384232e778 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php @@ -16,6 +16,8 @@ use Behat\Behat\Hook\Scope\BeforeScenarioScope; use Behat\Gherkin\Node\TableNode; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphReadModelInterface; +use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryDependencies; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; use Neos\ContentRepository\Core\Feature\NodeModification\Dto\PropertyValuesToWrite; @@ -155,7 +157,20 @@ public function workspaceHasStatus(string $rawWorkspaceName, string $status): vo */ public function iExpectTheGraphProjectionToConsistOfExactlyNodes(int $expectedNumberOfNodes): void { - $actualNumberOfNodes = $this->currentContentRepository->getContentGraph($this->currentWorkspaceName)->countNodes(); + // HACK to access + $contentGraphReadModelAccess = new class implements ContentRepositoryServiceFactoryInterface { + public ContentGraphReadModelInterface|null $instance; + public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): ContentRepositoryServiceInterface + { + $this->instance = $serviceFactoryDependencies->projectionsAndCatchUpHooks->contentGraphProjection->getState(); + return new class implements ContentRepositoryServiceInterface + { + }; + } + }; + $this->getContentRepositoryService($contentGraphReadModelAccess); + + $actualNumberOfNodes = $contentGraphReadModelAccess->instance->countNodes(); Assert::assertSame($expectedNumberOfNodes, $actualNumberOfNodes, 'Content graph consists of ' . $actualNumberOfNodes . ' nodes, expected were ' . $expectedNumberOfNodes . '.'); } diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php index d5fde970ec4..40c02ccde08 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php @@ -16,7 +16,6 @@ use Behat\Gherkin\Node\TableNode; use GuzzleHttp\Psr7\Uri; -use Neos\ContentRepository\Core\ContentRepositoryReadModel; use Neos\ContentRepository\Core\Feature\SubtreeTagging\Dto\SubtreeTag; use Neos\ContentRepository\Core\NodeType\NodeTypeName; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface; diff --git a/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php b/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php index a97a126b08c..4b78f99f13d 100644 --- a/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php +++ b/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php @@ -14,7 +14,10 @@ use Neos\ContentRepository\Core\Projection\CatchUpHookFactoryInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphProjectionFactoryInterface; use Neos\ContentRepository\Core\Projection\ProjectionFactoryInterface; +use Neos\ContentRepository\Core\Projection\ProjectionInterface; +use Neos\ContentRepository\Core\Projection\ProjectionStateInterface; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryIds; use Neos\ContentRepository\Core\SharedModel\User\UserIdProviderInterface; @@ -239,29 +242,54 @@ private function buildPropertySerializer(ContentRepositoryId $contentRepositoryI /** @param array $contentRepositorySettings */ private function buildProjectionsFactory(ContentRepositoryId $contentRepositoryId, array $contentRepositorySettings): ProjectionsAndCatchUpHooksFactory { - (isset($contentRepositorySettings['projections']) && is_array($contentRepositorySettings['projections'])) || throw InvalidConfigurationException::fromMessage('Content repository "%s" does not have projections configured, or the value is no array.', $contentRepositoryId->value); - $projectionsFactory = new ProjectionsAndCatchUpHooksFactory(); - foreach ($contentRepositorySettings['projections'] as $projectionName => $projectionOptions) { + $projectionsAndCatchUpHooksFactory = new ProjectionsAndCatchUpHooksFactory(); + + // content graph projection: + if (!isset($contentRepositorySettings['contentGraphProjection']['factoryObjectName'])) { + throw InvalidConfigurationException::fromMessage('Content repository "%s" does not have the contentGraphProjection.factoryObjectName configured.', $contentRepositoryId->value); + } + + $projectionFactory = $this->objectManager->get($contentRepositorySettings['contentGraphProjection']['factoryObjectName']); + if (!$projectionFactory instanceof ContentGraphProjectionFactoryInterface) { + throw InvalidConfigurationException::fromMessage('Projection factory object name of contentGraphProjection (content repository "%s") is not an instance of %s but %s.', $contentRepositoryId->value, ContentGraphProjectionFactoryInterface::class, get_debug_type($projectionFactory)); + } + $projectionsAndCatchUpHooksFactory->registerFactory($projectionFactory, $contentRepositorySettings['contentGraphProjection']['options'] ?? []); + + $this->registerCatchupHookForProjection($contentRepositorySettings['contentGraphProjection'], $projectionsAndCatchUpHooksFactory, $projectionFactory, 'contentGraphProjection', $contentRepositoryId); + + // additional projections: + (is_array($contentRepositorySettings['projections'] ?? [])) || throw InvalidConfigurationException::fromMessage('Content repository "%s" expects projections configured as array.', $contentRepositoryId->value); + foreach ($contentRepositorySettings['projections'] ?? [] as $projectionName => $projectionOptions) { if ($projectionOptions === null) { continue; } - $projectionFactory = $this->objectManager->get($projectionOptions['factoryObjectName']); + (is_array($projectionOptions)) || throw InvalidConfigurationException::fromMessage('Projection "%s" (content repository "%s") must be configured as array got %s', $projectionName, $contentRepositoryId->value, get_debug_type($projectionOptions)); + $projectionFactory = isset($projectionOptions['factoryObjectName']) ? $this->objectManager->get($projectionOptions['factoryObjectName']) : null; if (!$projectionFactory instanceof ProjectionFactoryInterface) { throw InvalidConfigurationException::fromMessage('Projection factory object name for projection "%s" (content repository "%s") is not an instance of %s but %s.', $projectionName, $contentRepositoryId->value, ProjectionFactoryInterface::class, get_debug_type($projectionFactory)); } - $projectionsFactory->registerFactory($projectionFactory, $projectionOptions['options'] ?? []); - foreach (($projectionOptions['catchUpHooks'] ?? []) as $catchUpHookOptions) { - if ($catchUpHookOptions === null) { - continue; - } - $catchUpHookFactory = $this->objectManager->get($catchUpHookOptions['factoryObjectName']); - if (!$catchUpHookFactory instanceof CatchUpHookFactoryInterface) { - throw InvalidConfigurationException::fromMessage('CatchUpHook factory object name for projection "%s" (content repository "%s") is not an instance of %s but %s', $projectionName, $contentRepositoryId->value, CatchUpHookFactoryInterface::class, get_debug_type($catchUpHookFactory)); - } - $projectionsFactory->registerCatchUpHookFactory($projectionFactory, $catchUpHookFactory); + $projectionsAndCatchUpHooksFactory->registerFactory($projectionFactory, $projectionOptions['options'] ?? []); + + $this->registerCatchupHookForProjection($projectionOptions, $projectionsAndCatchUpHooksFactory, $projectionFactory, $projectionName, $contentRepositoryId); + } + return $projectionsAndCatchUpHooksFactory; + } + + /** + * @param ProjectionFactoryInterface> $projectionFactory + */ + private function registerCatchupHookForProjection(mixed $projectionOptions, ProjectionsAndCatchUpHooksFactory $projectionsAndCatchUpHooksFactory, ProjectionFactoryInterface $projectionFactory, string $projectionName, ContentRepositoryId $contentRepositoryId): void + { + foreach (($projectionOptions['catchUpHooks'] ?? []) as $catchUpHookOptions) { + if ($catchUpHookOptions === null) { + continue; + } + $catchUpHookFactory = $this->objectManager->get($catchUpHookOptions['factoryObjectName']); + if (!$catchUpHookFactory instanceof CatchUpHookFactoryInterface) { + throw InvalidConfigurationException::fromMessage('CatchUpHook factory object name for projection "%s" (content repository "%s") is not an instance of %s but %s', $projectionName, $contentRepositoryId->value, CatchUpHookFactoryInterface::class, get_debug_type($catchUpHookFactory)); } + $projectionsAndCatchUpHooksFactory->registerCatchUpHookFactory($projectionFactory, $catchUpHookFactory); } - return $projectionsFactory; } /** @param array $contentRepositorySettings */ diff --git a/Neos.ContentRepositoryRegistry/Classes/Service/ProjectionReplayServiceFactory.php b/Neos.ContentRepositoryRegistry/Classes/Service/ProjectionReplayServiceFactory.php index 337297d9bb6..0f4bc5f7a05 100644 --- a/Neos.ContentRepositoryRegistry/Classes/Service/ProjectionReplayServiceFactory.php +++ b/Neos.ContentRepositoryRegistry/Classes/Service/ProjectionReplayServiceFactory.php @@ -22,7 +22,7 @@ final class ProjectionReplayServiceFactory implements ContentRepositoryServiceFa public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): ContentRepositoryServiceInterface { return new ProjectionReplayService( - $serviceFactoryDependencies->projections, + $serviceFactoryDependencies->projectionsAndCatchUpHooks->projections, $serviceFactoryDependencies->contentRepository, $serviceFactoryDependencies->eventStore, ); diff --git a/Neos.ContentRepositoryRegistry/Configuration/Settings.yaml b/Neos.ContentRepositoryRegistry/Configuration/Settings.yaml index 87f26a8c811..83b207597eb 100644 --- a/Neos.ContentRepositoryRegistry/Configuration/Settings.yaml +++ b/Neos.ContentRepositoryRegistry/Configuration/Settings.yaml @@ -65,12 +65,18 @@ Neos: ProxyAwareObjectNormalizer: className: Neos\ContentRepositoryRegistry\Infrastructure\Property\Normalizer\ProxyAwareObjectNormalizer - projections: - # NOTE: the following name must be stable, because we use it f.e. in Neos UI to register catchUpHooks for content cache flushing - 'Neos.ContentRepository:ContentGraph': - # NOTE: This introduces a soft-dependency to the neos/contentgraph-doctrinedbaladapter package, but it can be overridden when a different adapter is used - factoryObjectName: Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphProjectionFactory + contentGraphProjection: + # NOTE: This introduces a soft-dependency to the neos/contentgraph-doctrinedbaladapter package, but it can be overridden when a different adapter is used + factoryObjectName: Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphProjectionFactory - catchUpHooks: - 'Neos.ContentRepositoryRegistry:FlushSubgraphCachePool': - factoryObjectName: Neos\ContentRepositoryRegistry\SubgraphCachingInMemory\FlushSubgraphCachePoolCatchUpHookFactory + catchUpHooks: + 'Neos.ContentRepositoryRegistry:FlushSubgraphCachePool': + factoryObjectName: Neos\ContentRepositoryRegistry\SubgraphCachingInMemory\FlushSubgraphCachePoolCatchUpHookFactory + + # additional projections: + # + # projections: + # 'My.Package:SomeProjection': # just a name + # factoryObjectName: My\Package\Projection\SomeProjectionFactory + # options: {} + # catchUpHooks: {} diff --git a/Neos.Neos/Configuration/Settings.ContentRepositoryRegistry.yaml b/Neos.Neos/Configuration/Settings.ContentRepositoryRegistry.yaml index a3ae6ab53c0..d7d0bc7c720 100644 --- a/Neos.Neos/Configuration/Settings.ContentRepositoryRegistry.yaml +++ b/Neos.Neos/Configuration/Settings.ContentRepositoryRegistry.yaml @@ -6,6 +6,13 @@ Neos: userIdProvider: factoryObjectName: Neos\Neos\UserIdProvider\UserIdProviderFactory + contentGraphProjection: + catchUpHooks: + 'Neos.Neos:FlushContentCache': + factoryObjectName: Neos\Neos\Fusion\Cache\GraphProjectorCatchUpHookForCacheFlushingFactory + 'Neos.Neos:AssetUsage': + factoryObjectName: Neos\Neos\AssetUsage\CatchUpHook\AssetUsageCatchUpHookFactory + projections: 'Neos.Neos:DocumentUriPathProjection': factoryObjectName: Neos\Neos\FrontendRouting\Projection\DocumentUriPathProjectionFactory @@ -15,9 +22,3 @@ Neos: 'Neos.Neos:PendingChangesProjection': factoryObjectName: Neos\Neos\PendingChangesProjection\ChangeProjectionFactory - 'Neos.ContentRepository:ContentGraph': - catchUpHooks: - 'Neos.Neos:FlushContentCache': - factoryObjectName: Neos\Neos\Fusion\Cache\GraphProjectorCatchUpHookForCacheFlushingFactory - 'Neos.Neos:AssetUsage': - factoryObjectName: Neos\Neos\AssetUsage\CatchUpHook\AssetUsageCatchUpHookFactory