From 2db672e1c70fc5ab6e632df8decf4a9deda128d4 Mon Sep 17 00:00:00 2001 From: Bastian Waidelich Date: Sat, 25 May 2024 11:37:25 +0200 Subject: [PATCH 01/22] TASK: Add workspace content stream mapping to content graph projection Previously the `DoctrineDbalContentGraphProjection` accessed the `workspace` table of a different projection in order to resolve the workspace<->content stream mapping. This change adds the `workspace` table to the content graph projection and uses that instead for the resolution. **Note:** This is not a breaking change because it comes with a migration and does not require a replay, but a ./flow cr:setup is needed in order to apply that! Related: #5038 --- .../src/ContentGraphFactory.php | 9 +- .../src/ContentGraphTableNames.php | 5 + .../DoctrineDbalContentGraphProjection.php | 123 ++++++++++++++++-- .../DoctrineDbalContentGraphSchemaBuilder.php | 25 +++- .../Domain/Projection/Feature/Workspace.php | 112 ++++++++++++++++ .../Infrastructure/DbalSchemaFactory.php | 7 + 6 files changed, 257 insertions(+), 24 deletions(-) create mode 100644 Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/Workspace.php diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php index 5f1b45b09a5..4aee9e1b41c 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php @@ -42,18 +42,11 @@ public function __construct( public function buildForWorkspace(WorkspaceName $workspaceName): ContentGraph { - // FIXME: Should be part of this projection, this is forbidden - $tableName = strtolower(sprintf( - 'cr_%s_p_%s', - $this->contentRepositoryId->value, - 'workspace' - )); - $currentContentStreamIdStatement = <<tableNames->workspace()} WHERE workspaceName = :workspaceName LIMIT 1 diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php index 82ec0d0b389..e591fdec386 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php @@ -42,6 +42,11 @@ public function referenceRelation(): string return $this->tableNamePrefix . '_referencerelation'; } + public function workspace(): string + { + return $this->tableNamePrefix . '_workspace'; + } + 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 2cd030ee3bc..ed89f7adcf4 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -10,6 +10,7 @@ use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature\NodeRemoval; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature\NodeVariation; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature\SubtreeTagging; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature\Workspace; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\HierarchyRelation; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRecord; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRelationAnchorPoint; @@ -42,8 +43,15 @@ use Neos\ContentRepository\Core\Feature\SubtreeTagging\Dto\SubtreeTags; use Neos\ContentRepository\Core\Feature\SubtreeTagging\Event\SubtreeWasTagged; use Neos\ContentRepository\Core\Feature\SubtreeTagging\Event\SubtreeWasUntagged; +use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Event\RootWorkspaceWasCreated; +use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Event\WorkspaceWasCreated; +use Neos\ContentRepository\Core\Feature\WorkspaceModification\Event\WorkspaceBaseWorkspaceWasChanged; +use Neos\ContentRepository\Core\Feature\WorkspaceModification\Event\WorkspaceWasRemoved; use Neos\ContentRepository\Core\Feature\WorkspacePublication\Event\WorkspaceWasDiscarded; use Neos\ContentRepository\Core\Feature\WorkspacePublication\Event\WorkspaceWasPartiallyDiscarded; +use Neos\ContentRepository\Core\Feature\WorkspacePublication\Event\WorkspaceWasPartiallyPublished; +use Neos\ContentRepository\Core\Feature\WorkspacePublication\Event\WorkspaceWasPublished; +use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Event\WorkspaceRebaseFailed; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Event\WorkspaceWasRebased; use Neos\ContentRepository\Core\Infrastructure\DbalCheckpointStorage; use Neos\ContentRepository\Core\Infrastructure\DbalSchemaDiff; @@ -66,10 +74,11 @@ */ final class DoctrineDbalContentGraphProjection implements ProjectionInterface { + use NodeMove; + use NodeRemoval; use NodeVariation; use SubtreeTagging; - use NodeRemoval; - use NodeMove; + use Workspace; public const RELATION_DEFAULT_OFFSET = 128; @@ -92,7 +101,19 @@ public function __construct( public function setUp(): void { - foreach ($this->determineRequiredSqlStatements() as $statement) { + $statements = $this->determineRequiredSqlStatements(); + + // MIGRATION from 2024-05-23: copy data from "cr__p_workspace" to "cr__p_graph_workspace" table + $legacyWorkspaceTableName = str_replace('_p_graph_workspace', '_p_workspace', $this->tableNames->workspace()); + if ( + $this->dbal->getSchemaManager()->tablesExist([$legacyWorkspaceTableName]) + && !$this->dbal->getSchemaManager()->tablesExist([$this->tableNames->workspace()]) + ) { + $statements[] = 'INSERT INTO ' . $this->tableNames->workspace() . ' (workspacename, baseworkspacename, currentcontentstreamid, status) SELECT workspacename, baseworkspacename, currentcontentstreamid, status FROM ' . $legacyWorkspaceTableName; + } + // /MIGRATION + + foreach ($statements as $statement) { try { $this->dbal->executeStatement($statement); } catch (DBALException $e) { @@ -164,11 +185,18 @@ public function canHandle(EventInterface $event): bool NodeSpecializationVariantWasCreated::class, RootNodeAggregateDimensionsWereUpdated::class, RootNodeAggregateWithNodeWasCreated::class, + RootWorkspaceWasCreated::class, SubtreeWasTagged::class, SubtreeWasUntagged::class, + WorkspaceBaseWorkspaceWasChanged::class, + WorkspaceRebaseFailed::class, + WorkspaceWasCreated::class, WorkspaceWasDiscarded::class, WorkspaceWasPartiallyDiscarded::class, - WorkspaceWasRebased::class + WorkspaceWasPartiallyPublished::class, + WorkspaceWasPublished::class, + WorkspaceWasRebased::class, + WorkspaceWasRemoved::class, ]); } @@ -191,14 +219,18 @@ public function apply(EventInterface $event, EventEnvelope $eventEnvelope): void NodeSpecializationVariantWasCreated::class => $this->whenNodeSpecializationVariantWasCreated($event, $eventEnvelope), RootNodeAggregateDimensionsWereUpdated::class => $this->whenRootNodeAggregateDimensionsWereUpdated($event), RootNodeAggregateWithNodeWasCreated::class => $this->whenRootNodeAggregateWithNodeWasCreated($event, $eventEnvelope), + RootWorkspaceWasCreated::class => $this->whenRootWorkspaceWasCreated($event), SubtreeWasTagged::class => $this->whenSubtreeWasTagged($event), SubtreeWasUntagged::class => $this->whenSubtreeWasUntagged($event), - // the following three events are not actually handled, but we need to include them in {@see canHandle()} in order - // to trigger the catchup hooks for those (i.e. {@see GraphProjectorCatchUpHookForCacheFlushing}). This can - // be removed with https://github.com/neos/neos-development-collection/issues/4992 - WorkspaceWasDiscarded::class, - WorkspaceWasPartiallyDiscarded::class, - WorkspaceWasRebased::class => null, + WorkspaceBaseWorkspaceWasChanged::class => $this->whenWorkspaceBaseWorkspaceWasChanged($event), + WorkspaceRebaseFailed::class => $this->whenWorkspaceRebaseFailed($event), + WorkspaceWasCreated::class => $this->whenWorkspaceWasCreated($event), + WorkspaceWasDiscarded::class => $this->whenWorkspaceWasDiscarded($event), + WorkspaceWasPartiallyDiscarded::class => $this->whenWorkspaceWasPartiallyDiscarded($event), + WorkspaceWasPartiallyPublished::class => $this->whenWorkspaceWasPartiallyPublished($event), + WorkspaceWasPublished::class => $this->whenWorkspaceWasPublished($event), + WorkspaceWasRebased::class => $this->whenWorkspaceWasRebased($event), + WorkspaceWasRemoved::class => $this->whenWorkspaceWasRemoved($event), default => throw new \InvalidArgumentException(sprintf('Unsupported event %s', get_debug_type($event))), }; } @@ -632,6 +664,11 @@ private function whenRootNodeAggregateWithNodeWasCreated(RootNodeAggregateWithNo ); } + private function whenRootWorkspaceWasCreated(RootWorkspaceWasCreated $event): void + { + $this->createWorkspace($event->workspaceName, null, $event->newContentStreamId); + } + private function whenSubtreeWasTagged(SubtreeWasTagged $event): void { $this->addSubtreeTag($event->contentStreamId, $event->nodeAggregateId, $event->affectedDimensionSpacePoints, $event->tag); @@ -642,6 +679,71 @@ private function whenSubtreeWasUntagged(SubtreeWasUntagged $event): void $this->removeSubtreeTag($event->contentStreamId, $event->nodeAggregateId, $event->affectedDimensionSpacePoints, $event->tag); } + private function whenWorkspaceBaseWorkspaceWasChanged(WorkspaceBaseWorkspaceWasChanged $event): void + { + $this->updateBaseWorkspace($event->workspaceName, $event->baseWorkspaceName, $event->newContentStreamId); + } + + private function whenWorkspaceRebaseFailed(WorkspaceRebaseFailed $event): void + { + $this->markWorkspaceAsOutdatedConflict($event->workspaceName); + } + + private function whenWorkspaceWasCreated(WorkspaceWasCreated $event): void + { + $this->createWorkspace($event->workspaceName, $event->baseWorkspaceName, $event->newContentStreamId); + } + + private function whenWorkspaceWasDiscarded(WorkspaceWasDiscarded $event): void + { + $this->updateWorkspaceContentStreamId($event->workspaceName, $event->newContentStreamId); + $this->markWorkspaceAsOutdated($event->workspaceName); + $this->markDependentWorkspacesAsOutdated($event->workspaceName); + } + + private function whenWorkspaceWasPartiallyDiscarded(WorkspaceWasPartiallyDiscarded $event): void + { + $this->updateWorkspaceContentStreamId($event->workspaceName, $event->newContentStreamId); + $this->markDependentWorkspacesAsOutdated($event->workspaceName); + } + + private function whenWorkspaceWasPartiallyPublished(WorkspaceWasPartiallyPublished $event): void + { + // TODO: How do we test this method? – It's hard to design a BDD testcase that fails if this method is commented out... + $this->updateWorkspaceContentStreamId($event->sourceWorkspaceName, $event->newSourceContentStreamId); + $this->markDependentWorkspacesAsOutdated($event->targetWorkspaceName); + + // NASTY: we need to set the source workspace name as non-outdated; as it has been made up-to-date again. + $this->markWorkspaceAsUpToDate($event->sourceWorkspaceName); + + $this->markDependentWorkspacesAsOutdated($event->sourceWorkspaceName); + } + + private function whenWorkspaceWasPublished(WorkspaceWasPublished $event): void + { + // TODO: How do we test this method? – It's hard to design a BDD testcase that fails if this method is commented out... + $this->updateWorkspaceContentStreamId($event->sourceWorkspaceName, $event->newSourceContentStreamId); + $this->markDependentWorkspacesAsOutdated($event->targetWorkspaceName); + + // NASTY: we need to set the source workspace name as non-outdated; as it has been made up-to-date again. + $this->markWorkspaceAsUpToDate($event->sourceWorkspaceName); + + $this->markDependentWorkspacesAsOutdated($event->sourceWorkspaceName); + } + + private function whenWorkspaceWasRebased(WorkspaceWasRebased $event): void + { + $this->updateWorkspaceContentStreamId($event->workspaceName, $event->newContentStreamId); + $this->markDependentWorkspacesAsOutdated($event->workspaceName); + + // When the rebase is successful, we can set the status of the workspace back to UP_TO_DATE. + $this->markWorkspaceAsUpToDate($event->workspaceName); + } + + private function whenWorkspaceWasRemoved(WorkspaceWasRemoved $event): void + { + $this->removeWorkspace($event->workspaceName); + } /** --------------------------------- */ @@ -662,6 +764,7 @@ private function truncateDatabaseTables(): void $this->dbal->executeQuery('TRUNCATE table ' . $this->tableNames->hierarchyRelation()); $this->dbal->executeQuery('TRUNCATE table ' . $this->tableNames->referenceRelation()); $this->dbal->executeQuery('TRUNCATE table ' . $this->tableNames->dimensionSpacePoints()); + $this->dbal->executeQuery('TRUNCATE table ' . $this->tableNames->workspace()); } catch (DBALException $e) { throw new \RuntimeException(sprintf('Failed to truncate database tables for projection %s: %s', self::class, $e->getMessage()), 1716478318, $e); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php index adbf2add9b8..13b561e12f9 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php @@ -22,7 +22,7 @@ class DoctrineDbalContentGraphSchemaBuilder private const DEFAULT_TEXT_COLLATION = 'utf8mb4_unicode_520_ci'; public function __construct( - private readonly ContentGraphTableNames $contentGraphTableNames + private readonly ContentGraphTableNames $tableNames ) { } @@ -36,13 +36,14 @@ public function buildSchema(AbstractSchemaManager $schemaManager): Schema $this->createNodeTable(), $this->createHierarchyRelationTable(), $this->createReferenceRelationTable(), - $this->createDimensionSpacePointsTable() + $this->createDimensionSpacePointsTable(), + $this->createWorkspaceTable(), ]); } private function createNodeTable(): Table { - $table = self::createTable($this->contentGraphTableNames->node(), [ + $table = self::createTable($this->tableNames->node(), [ DbalSchemaFactory::columnForNodeAnchorPoint('relationanchorpoint')->setAutoincrement(true), DbalSchemaFactory::columnForNodeAggregateId('nodeaggregateid')->setNotnull(false), DbalSchemaFactory::columnForDimensionSpacePointHash('origindimensionspacepointhash')->setNotnull(false), @@ -64,7 +65,7 @@ private function createNodeTable(): Table private function createHierarchyRelationTable(): Table { - $table = self::createTable($this->contentGraphTableNames->hierarchyRelation(), [ + $table = self::createTable($this->tableNames->hierarchyRelation(), [ (new Column('position', self::type(Types::INTEGER)))->setNotnull(true), DbalSchemaFactory::columnForContentStreamId('contentstreamid')->setNotnull(true), DbalSchemaFactory::columnForDimensionSpacePointHash('dimensionspacepointhash')->setNotnull(true), @@ -84,7 +85,7 @@ private function createHierarchyRelationTable(): Table private function createDimensionSpacePointsTable(): Table { - $table = self::createTable($this->contentGraphTableNames->dimensionSpacePoints(), [ + $table = self::createTable($this->tableNames->dimensionSpacePoints(), [ DbalSchemaFactory::columnForDimensionSpacePointHash('hash')->setNotnull(true), DbalSchemaFactory::columnForDimensionSpacePoint('dimensionspacepoint')->setNotnull(true) ]); @@ -95,7 +96,7 @@ private function createDimensionSpacePointsTable(): Table private function createReferenceRelationTable(): Table { - $table = self::createTable($this->contentGraphTableNames->referenceRelation(), [ + $table = self::createTable($this->tableNames->referenceRelation(), [ (new Column('name', self::type(Types::STRING)))->setLength(255)->setNotnull(true)->setPlatformOption('charset', 'ascii')->setPlatformOption('collation', 'ascii_general_ci'), (new Column('position', self::type(Types::INTEGER)))->setNotnull(true), DbalSchemaFactory::columnForNodeAnchorPoint('nodeanchorpoint'), @@ -107,6 +108,18 @@ private function createReferenceRelationTable(): Table ->setPrimaryKey(['name', 'position', 'nodeanchorpoint']); } + private function createWorkspaceTable(): Table + { + $workspaceTable = self::createTable($this->tableNames->workspace(), [ + DbalSchemaFactory::columnForWorkspaceName('workspacename')->setNotnull(true), + DbalSchemaFactory::columnForWorkspaceName('baseworkspacename')->setNotnull(false), + DbalSchemaFactory::columnForContentStreamId('currentcontentstreamid')->setNotNull(true), + (new Column('status', self::type(Types::BINARY)))->setLength(20)->setNotnull(false), + ]); + + return $workspaceTable->setPrimaryKey(['workspacename']); + } + /** * @param array $columns */ diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/Workspace.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/Workspace.php new file mode 100644 index 00000000000..bfe81c8dc5d --- /dev/null +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/Workspace.php @@ -0,0 +1,112 @@ +dbal->insert($this->tableNames->workspace(), [ + 'workspaceName' => $workspaceName->value, + 'baseWorkspaceName' => $baseWorkspaceName?->value, + 'currentContentStreamId' => $contentStreamId->value, + 'status' => WorkspaceStatus::UP_TO_DATE->value + ]); + } + + private function removeWorkspace(WorkspaceName $workspaceName): void + { + $this->dbal->delete( + $this->tableNames->workspace(), + ['workspaceName' => $workspaceName->value] + ); + } + + private function updateBaseWorkspace(WorkspaceName $workspaceName, WorkspaceName $baseWorkspaceName, ContentStreamId $newContentStreamId): void + { + $this->dbal->update( + $this->tableNames->workspace(), + [ + 'baseWorkspaceName' => $baseWorkspaceName->value, + 'currentContentStreamId' => $newContentStreamId->value, + ], + ['workspaceName' => $workspaceName->value] + ); + } + + private function updateWorkspaceContentStreamId( + WorkspaceName $workspaceName, + ContentStreamId $contentStreamId, + ): void { + $this->dbal->update($this->tableNames->workspace(), [ + 'currentContentStreamId' => $contentStreamId->value, + ], [ + 'workspaceName' => $workspaceName->value + ]); + } + + private function markWorkspaceAsUpToDate(WorkspaceName $workspaceName): void + { + $this->dbal->executeUpdate(' + UPDATE ' . $this->tableNames->workspace() . ' + SET status = :upToDate + WHERE + workspacename = :workspaceName + ', [ + 'upToDate' => WorkspaceStatus::UP_TO_DATE->value, + 'workspaceName' => $workspaceName->value + ]); + } + + private function markDependentWorkspacesAsOutdated(WorkspaceName $baseWorkspaceName): void + { + $this->dbal->executeUpdate(' + UPDATE ' . $this->tableNames->workspace() . ' + SET status = :outdated + WHERE + baseworkspacename = :baseWorkspaceName + ', [ + 'outdated' => WorkspaceStatus::OUTDATED->value, + 'baseWorkspaceName' => $baseWorkspaceName->value + ]); + } + + private function markWorkspaceAsOutdated(WorkspaceName $workspaceName): void + { + $this->dbal->executeUpdate(' + UPDATE ' . $this->tableNames->workspace() . ' + SET + status = :outdated + WHERE + workspacename = :workspaceName + ', [ + 'outdated' => WorkspaceStatus::OUTDATED->value, + 'workspaceName' => $workspaceName->value + ]); + } + + private function markWorkspaceAsOutdatedConflict(WorkspaceName $workspaceName): void + { + $this->dbal->executeUpdate(' + UPDATE ' . $this->tableNames->workspace() . ' + SET + status = :outdatedConflict + WHERE + workspacename = :workspaceName + ', [ + 'outdatedConflict' => WorkspaceStatus::OUTDATED_CONFLICT->value, + 'workspaceName' => $workspaceName->value + ]); + } +} diff --git a/Neos.ContentRepository.Core/Classes/Infrastructure/DbalSchemaFactory.php b/Neos.ContentRepository.Core/Classes/Infrastructure/DbalSchemaFactory.php index 34b59dc9ab0..767bde52864 100644 --- a/Neos.ContentRepository.Core/Classes/Infrastructure/DbalSchemaFactory.php +++ b/Neos.ContentRepository.Core/Classes/Infrastructure/DbalSchemaFactory.php @@ -63,6 +63,13 @@ public static function columnForContentStreamId(string $columnName): Column ->setLength(36); } + public static function columnForWorkspaceName(string $columnName): Column + { + return (new Column($columnName, Type::getType(Types::STRING))) + ->setLength(WorkspaceName::MAX_LENGTH) + ->setCustomSchemaOption('collation', 'utf8mb4_unicode_520_ci'); + } + /** * An anchorpoint can be used in a given projection to link two nodes, it is a purely internal identifier and should * be as performant as possible in queries, the current code uses UUIDs, From f3f95718d5e2a9afdf04bfb03fed1e23652c9c71 Mon Sep 17 00:00:00 2001 From: Bastian Waidelich Date: Sat, 25 May 2024 19:13:03 +0200 Subject: [PATCH 02/22] !!! TASK: Replace dedicated Workspace and ContentStream projections wip --- .../src/ContentGraphTableNames.php | 5 + .../DoctrineDbalContentGraphProjection.php | 80 ++++++++++- .../DoctrineDbalContentGraphSchemaBuilder.php | 13 ++ .../Projection/Feature/ContentStream.php | 54 ++++++++ .../Classes/CommandHandlingDependencies.php | 27 +++- .../Classes/ContentRepository.php | 23 ++-- .../Feature/Common/ConstraintChecks.php | 7 +- .../Feature/ContentStreamCommandHandler.php | 22 ++-- .../Feature/ContentStreamEventStreamName.php | 15 ++- .../Feature/WorkspaceCommandHandler.php | 124 ++++-------------- .../Command/ChangeWorkspaceOwner.php | 37 ------ .../Command/RenameWorkspace.php | 41 ------ .../Event/WorkspaceOwnerWasChanged.php | 3 +- .../Event/WorkspaceWasRenamed.php | 3 +- .../ContentStream/ContentStreamProjection.php | 16 +-- .../Projection/Workspace/Workspaces.php | 26 ++++ .../Service/ContentRepositoryBootstrapper.php | 4 +- .../Service/WorkspaceMaintenanceService.php | 23 ++-- .../Bootstrap/CrImportExportTrait.php | 3 +- .../src/ExportService.php | 8 +- .../src/ExportServiceFactory.php | 7 +- .../src/Processors/AssetExportProcessor.php | 11 +- .../src/Processors/EventExportProcessor.php | 11 +- .../src/NodeMigrationService.php | 2 +- .../Features/Bootstrap/CRTestSuiteTrait.php | 10 +- .../Features/Bootstrap/ProjectedNodeTrait.php | 1 + .../Classes/Controller/UsageController.php | 5 +- .../Service/AssetUsageSyncService.php | 5 +- .../Classes/Command/CrCommandController.php | 11 +- .../Command/WorkspaceCommandController.php | 24 ++-- .../Module/Administration/SitesController.php | 4 +- .../Domain/Service/SiteServiceInternals.php | 2 +- .../Classes/Fusion/Helper/CachingHelper.php | 4 +- Neos.Neos/Classes/Service/LinkingService.php | 4 +- .../Controller/WorkspaceController.php | 25 ++-- 35 files changed, 364 insertions(+), 296 deletions(-) create mode 100644 Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/ContentStream.php delete mode 100644 Neos.ContentRepository.Core/Classes/Feature/WorkspaceModification/Command/ChangeWorkspaceOwner.php delete mode 100644 Neos.ContentRepository.Core/Classes/Feature/WorkspaceModification/Command/RenameWorkspace.php diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php index e591fdec386..787b5d24665 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php @@ -47,6 +47,11 @@ public function workspace(): string return $this->tableNamePrefix . '_workspace'; } + public function contentStream(): string + { + return $this->tableNamePrefix . '_contentstream'; + } + 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 ed89f7adcf4..0b6225571e5 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -6,6 +6,7 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception as DBALException; +use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature\ContentStream; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature\NodeMove; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature\NodeRemoval; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature\NodeVariation; @@ -22,6 +23,10 @@ use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\EventStore\EventInterface; use Neos\ContentRepository\Core\Feature\Common\InterdimensionalSiblings; +use Neos\ContentRepository\Core\Feature\ContentStreamClosing\Event\ContentStreamWasClosed; +use Neos\ContentRepository\Core\Feature\ContentStreamClosing\Event\ContentStreamWasReopened; +use Neos\ContentRepository\Core\Feature\ContentStreamCreation\Event\ContentStreamWasCreated; +use Neos\ContentRepository\Core\Feature\ContentStreamEventStreamName; use Neos\ContentRepository\Core\Feature\ContentStreamForking\Event\ContentStreamWasForked; use Neos\ContentRepository\Core\Feature\ContentStreamRemoval\Event\ContentStreamWasRemoved; use Neos\ContentRepository\Core\Feature\DimensionSpaceAdjustment\Event\DimensionShineThroughWasAdded; @@ -65,6 +70,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\ContentStreamState; use Neos\EventStore\Model\Event\SequenceNumber; use Neos\EventStore\Model\EventEnvelope; @@ -74,6 +80,7 @@ */ final class DoctrineDbalContentGraphProjection implements ProjectionInterface { + use ContentStream; use NodeMove; use NodeRemoval; use NodeVariation; @@ -103,7 +110,7 @@ public function setUp(): void { $statements = $this->determineRequiredSqlStatements(); - // MIGRATION from 2024-05-23: copy data from "cr__p_workspace" to "cr__p_graph_workspace" table + // MIGRATION from 2024-05-25: copy data from "cr__p_workspace"/"cr__p_contentstream" to "cr__p_graph_workspace"/"cr__p_graph_contentstream" tables $legacyWorkspaceTableName = str_replace('_p_graph_workspace', '_p_workspace', $this->tableNames->workspace()); if ( $this->dbal->getSchemaManager()->tablesExist([$legacyWorkspaceTableName]) @@ -111,6 +118,13 @@ public function setUp(): void ) { $statements[] = 'INSERT INTO ' . $this->tableNames->workspace() . ' (workspacename, baseworkspacename, currentcontentstreamid, status) SELECT workspacename, baseworkspacename, currentcontentstreamid, status FROM ' . $legacyWorkspaceTableName; } + $legacyContentStreamTableName = str_replace('_p_graph_contentstream', '_p_contentstream', $this->tableNames->contentStream()); + if ( + $this->dbal->getSchemaManager()->tablesExist([$legacyContentStreamTableName]) + && !$this->dbal->getSchemaManager()->tablesExist([$this->tableNames->contentStream()]) + ) { + $statements[] = 'INSERT INTO ' . $this->tableNames->contentStream() . ' (contentStreamId, version, sourceContentStreamId, state, removed) SELECT contentStreamId, version, sourceContentStreamId, state, removed FROM ' . $legacyContentStreamTableName; + } // /MIGRATION foreach ($statements as $statement) { @@ -169,8 +183,11 @@ public function getState(): ContentGraphFinder public function canHandle(EventInterface $event): bool { return in_array($event::class, [ + ContentStreamWasClosed::class, + ContentStreamWasCreated::class, ContentStreamWasForked::class, ContentStreamWasRemoved::class, + ContentStreamWasReopened::class, DimensionShineThroughWasAdded::class, DimensionSpacePointWasMoved::class, NodeAggregateNameWasChanged::class, @@ -203,8 +220,11 @@ public function canHandle(EventInterface $event): bool public function apply(EventInterface $event, EventEnvelope $eventEnvelope): void { match ($event::class) { + ContentStreamWasClosed::class => $this->whenContentStreamWasClosed($event), + ContentStreamWasCreated::class => $this->whenContentStreamWasCreated($event), ContentStreamWasForked::class => $this->whenContentStreamWasForked($event), ContentStreamWasRemoved::class => $this->whenContentStreamWasRemoved($event), + ContentStreamWasReopened::class => $this->whenContentStreamWasReopened($event), DimensionShineThroughWasAdded::class => $this->whenDimensionShineThroughWasAdded($event), DimensionSpacePointWasMoved::class => $this->whenDimensionSpacePointWasMoved($event), NodeAggregateNameWasChanged::class => $this->whenNodeAggregateNameWasChanged($event, $eventEnvelope), @@ -233,6 +253,19 @@ public function apply(EventInterface $event, EventEnvelope $eventEnvelope): void WorkspaceWasRemoved::class => $this->whenWorkspaceWasRemoved($event), default => throw new \InvalidArgumentException(sprintf('Unsupported event %s', get_debug_type($event))), }; + if (ContentStreamEventStreamName::isContentStreamStreamName($eventEnvelope->streamName)) { + $this->updateContentStreamVersion(ContentStreamEventStreamName::extractContentStreamIdFromStreamName($eventEnvelope->streamName), $eventEnvelope->version); + } + } + + private function whenContentStreamWasClosed(ContentStreamWasClosed $event): void + { + $this->updateContentStreamState($event->contentStreamId, ContentStreamState::STATE_CLOSED); + } + + private function whenContentStreamWasCreated(ContentStreamWasCreated $event): void + { + $this->createContentStream($event->contentStreamId, ContentStreamState::STATE_CREATED); } private function whenContentStreamWasForked(ContentStreamWasForked $event): void @@ -270,6 +303,8 @@ private function whenContentStreamWasForked(ContentStreamWasForked $event): void // NOTE: as reference edges are attached to Relation Anchor Points (and they are lazily copy-on-written), // we do not need to copy reference edges here (but we need to do it during copy on write). + + $this->createContentStream($event->newContentStreamId, ContentStreamState::STATE_FORKED); } private function whenContentStreamWasRemoved(ContentStreamWasRemoved $event): void @@ -313,6 +348,13 @@ private function whenContentStreamWasRemoved(ContentStreamWasRemoved $event): vo } catch (DBALException $e) { throw new \RuntimeException(sprintf('Failed to delete non-referenced reference relations: %s', $e->getMessage()), 1716489328, $e); } + + $this->removeContentStream($event->contentStreamId); + } + + private function whenContentStreamWasReopened(ContentStreamWasReopened $event): void + { + $this->updateContentStreamState($event->contentStreamId, $event->previousState); } private function whenDimensionShineThroughWasAdded(DimensionShineThroughWasAdded $event): void @@ -667,6 +709,9 @@ private function whenRootNodeAggregateWithNodeWasCreated(RootNodeAggregateWithNo private function whenRootWorkspaceWasCreated(RootWorkspaceWasCreated $event): void { $this->createWorkspace($event->workspaceName, null, $event->newContentStreamId); + + // the content stream is in use now + $this->updateContentStreamState($event->newContentStreamId, ContentStreamState::STATE_IN_USE_BY_WORKSPACE); } private function whenSubtreeWasTagged(SubtreeWasTagged $event): void @@ -687,11 +732,15 @@ private function whenWorkspaceBaseWorkspaceWasChanged(WorkspaceBaseWorkspaceWasC private function whenWorkspaceRebaseFailed(WorkspaceRebaseFailed $event): void { $this->markWorkspaceAsOutdatedConflict($event->workspaceName); + $this->updateContentStreamState($event->candidateContentStreamId, ContentStreamState::STATE_REBASE_ERROR); } private function whenWorkspaceWasCreated(WorkspaceWasCreated $event): void { $this->createWorkspace($event->workspaceName, $event->baseWorkspaceName, $event->newContentStreamId); + + // the content stream is in use now + $this->updateContentStreamState($event->newContentStreamId, ContentStreamState::STATE_IN_USE_BY_WORKSPACE); } private function whenWorkspaceWasDiscarded(WorkspaceWasDiscarded $event): void @@ -699,12 +748,23 @@ private function whenWorkspaceWasDiscarded(WorkspaceWasDiscarded $event): void $this->updateWorkspaceContentStreamId($event->workspaceName, $event->newContentStreamId); $this->markWorkspaceAsOutdated($event->workspaceName); $this->markDependentWorkspacesAsOutdated($event->workspaceName); + + // the new content stream is in use now + $this->updateContentStreamState($event->newContentStreamId, ContentStreamState::STATE_IN_USE_BY_WORKSPACE); + // the previous content stream is no longer in use + $this->updateContentStreamState($event->previousContentStreamId, ContentStreamState::STATE_NO_LONGER_IN_USE); } private function whenWorkspaceWasPartiallyDiscarded(WorkspaceWasPartiallyDiscarded $event): void { $this->updateWorkspaceContentStreamId($event->workspaceName, $event->newContentStreamId); $this->markDependentWorkspacesAsOutdated($event->workspaceName); + + // the new content stream is in use now + $this->updateContentStreamState($event->newContentStreamId, ContentStreamState::STATE_IN_USE_BY_WORKSPACE); + + // the previous content stream is no longer in use + $this->updateContentStreamState($event->previousContentStreamId, ContentStreamState::STATE_NO_LONGER_IN_USE); } private function whenWorkspaceWasPartiallyPublished(WorkspaceWasPartiallyPublished $event): void @@ -717,6 +777,12 @@ private function whenWorkspaceWasPartiallyPublished(WorkspaceWasPartiallyPublish $this->markWorkspaceAsUpToDate($event->sourceWorkspaceName); $this->markDependentWorkspacesAsOutdated($event->sourceWorkspaceName); + + // the new content stream is in use now + $this->updateContentStreamState($event->newSourceContentStreamId, ContentStreamState::STATE_IN_USE_BY_WORKSPACE); + + // the previous content stream is no longer in use + $this->updateContentStreamState($event->previousSourceContentStreamId, ContentStreamState::STATE_NO_LONGER_IN_USE); } private function whenWorkspaceWasPublished(WorkspaceWasPublished $event): void @@ -729,6 +795,12 @@ private function whenWorkspaceWasPublished(WorkspaceWasPublished $event): void $this->markWorkspaceAsUpToDate($event->sourceWorkspaceName); $this->markDependentWorkspacesAsOutdated($event->sourceWorkspaceName); + + // the new content stream is in use now + $this->updateContentStreamState($event->newSourceContentStreamId, ContentStreamState::STATE_IN_USE_BY_WORKSPACE); + + // the previous content stream is no longer in use + $this->updateContentStreamState($event->previousSourceContentStreamId, ContentStreamState::STATE_NO_LONGER_IN_USE); } private function whenWorkspaceWasRebased(WorkspaceWasRebased $event): void @@ -738,6 +810,12 @@ private function whenWorkspaceWasRebased(WorkspaceWasRebased $event): void // When the rebase is successful, we can set the status of the workspace back to UP_TO_DATE. $this->markWorkspaceAsUpToDate($event->workspaceName); + + // the new content stream is in use now + $this->updateContentStreamState($event->newContentStreamId, ContentStreamState::STATE_IN_USE_BY_WORKSPACE); + + // the previous content stream is no longer in use + $this->updateContentStreamState($event->previousContentStreamId, ContentStreamState::STATE_NO_LONGER_IN_USE); } private function whenWorkspaceWasRemoved(WorkspaceWasRemoved $event): void diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php index 13b561e12f9..98c11ddc39a 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php @@ -38,6 +38,7 @@ public function buildSchema(AbstractSchemaManager $schemaManager): Schema $this->createReferenceRelationTable(), $this->createDimensionSpacePointsTable(), $this->createWorkspaceTable(), + $this->createContentStreamTable(), ]); } @@ -120,6 +121,18 @@ private function createWorkspaceTable(): Table return $workspaceTable->setPrimaryKey(['workspacename']); } + private function createContentStreamTable(): Table + { + return self::createTable($this->tableNames->contentStream(), [ + DbalSchemaFactory::columnForContentStreamId('contentStreamId')->setNotnull(true), + (new Column('version', Type::getType(Types::INTEGER)))->setNotnull(true), + DbalSchemaFactory::columnForContentStreamId('sourceContentStreamId')->setNotnull(false), + // Should become a DB ENUM (unclear how to configure with DBAL) or int (latter needs adaption to code) + (new Column('state', Type::getType(Types::BINARY)))->setLength(20)->setNotnull(true), + (new Column('removed', Type::getType(Types::BOOLEAN)))->setDefault(false)->setNotnull(false) + ]); + } + /** * @param array $columns */ diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/ContentStream.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/ContentStream.php new file mode 100644 index 00000000000..bc39979bf46 --- /dev/null +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/ContentStream.php @@ -0,0 +1,54 @@ +dbal->insert($this->tableNames->contentStream(), [ + 'contentStreamId' => $contentStreamId->value, + 'sourceContentStreamId' => $sourceContentStreamId?->value, + 'version' => 0, + 'state' => $state->value, + ]); + } + + private function updateContentStreamState(ContentStreamId $contentStreamId, ContentStreamState $state): void + { + $this->dbal->update($this->tableNames->contentStream(), [ + 'state' => $state->value, + ], [ + 'contentStreamId' => $contentStreamId->value + ]); + } + + private function removeContentStream(ContentStreamId $contentStreamId): void + { + $this->dbal->update($this->tableNames->contentStream(), [ + 'removed' => true, + ], [ + 'contentStreamId' => $contentStreamId->value + ]); + } + + private function updateContentStreamVersion(ContentStreamId $contentStreamId, Version $version): void + { + $this->dbal->update($this->tableNames->contentStream(), [ + 'version' => $version->value, + ], [ + 'contentStreamId' => $contentStreamId->value, + ]); + } +} diff --git a/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php b/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php index c0b5270cc37..3f3515e81f3 100644 --- a/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php +++ b/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php @@ -17,11 +17,12 @@ use Neos\ContentRepository\Core\CommandHandler\CommandInterface; use Neos\ContentRepository\Core\CommandHandler\CommandResult; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; -use Neos\ContentRepository\Core\Projection\ContentStream\ContentStreamFinder; -use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceFinder; +use Neos\ContentRepository\Core\Projection\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; 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\Event\Version; /** * An adapter to provide aceess to read projection data and delegate (sub) commands @@ -46,14 +47,28 @@ public function handle(CommandInterface $command): CommandResult return $this->contentRepository->handle($command); } - public function getWorkspaceFinder(): WorkspaceFinder + public function getContentStreamVersion(ContentStreamId $contentStreamId): Version { - return $this->contentRepository->getWorkspaceFinder(); + // TODO implement + return Version::fromInteger(1); } - public function getContentStreamFinder(): ContentStreamFinder + public function contentStreamExists(ContentStreamId $contentStreamId): bool { - return $this->contentRepository->getContentStreamFinder(); + // TODO implement + return false; + } + + public function getContentStreamState(ContentStreamId $contentStreamId): ContentStreamState + { + // TODO implement + return ContentStreamState::STATE_FORKED; + } + + public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace + { + // TODO implement + return null; } /** diff --git a/Neos.ContentRepository.Core/Classes/ContentRepository.php b/Neos.ContentRepository.Core/Classes/ContentRepository.php index 955cf80fe26..d107f399139 100644 --- a/Neos.ContentRepository.Core/Classes/ContentRepository.php +++ b/Neos.ContentRepository.Core/Classes/ContentRepository.php @@ -30,13 +30,13 @@ use Neos\ContentRepository\Core\Projection\CatchUp; use Neos\ContentRepository\Core\Projection\CatchUpOptions; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; -use Neos\ContentRepository\Core\Projection\ContentStream\ContentStreamFinder; use Neos\ContentRepository\Core\Projection\ProjectionInterface; use Neos\ContentRepository\Core\Projection\ProjectionsAndCatchUpHooks; use Neos\ContentRepository\Core\Projection\ProjectionStateInterface; use Neos\ContentRepository\Core\Projection\ProjectionStatuses; use Neos\ContentRepository\Core\Projection\WithMarkStaleInterface; -use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceFinder; +use Neos\ContentRepository\Core\Projection\Workspace\Workspace; +use Neos\ContentRepository\Core\Projection\Workspace\Workspaces; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryStatus; use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; @@ -239,22 +239,23 @@ public function getNodeTypeManager(): NodeTypeManager return $this->nodeTypeManager; } - /** - * @throws WorkspaceDoesNotExist if the workspace does not exist - */ - public function getContentGraph(WorkspaceName $workspaceName): ContentGraphInterface + public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace { - return $this->projectionState(ContentGraphFinder::class)->getByWorkspaceName($workspaceName); + // TODO implement + return null; } - public function getWorkspaceFinder(): WorkspaceFinder + public function getWorkspaces(): Workspaces { - return $this->projectionState(WorkspaceFinder::class); + // TODO implement } - public function getContentStreamFinder(): ContentStreamFinder + /** + * @throws WorkspaceDoesNotExist if the workspace does not exist + */ + public function getContentGraph(WorkspaceName $workspaceName): ContentGraphInterface { - return $this->projectionState(ContentStreamFinder::class); + return $this->projectionState(ContentGraphFinder::class)->getByWorkspaceName($workspaceName); } public function getVariationGraph(): InterDimensionalVariationGraph diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php index 9cb894f5d76..69259c8a5fa 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php @@ -81,14 +81,13 @@ protected function requireContentStream( CommandHandlingDependencies $commandHandlingDependencies ): ContentStreamId { $contentStreamId = $commandHandlingDependencies->getContentGraph($workspaceName)->getContentStreamId(); - $state = $commandHandlingDependencies->getContentStreamFinder()->findStateForContentStream($contentStreamId); - if ($state === null) { + if (!$commandHandlingDependencies->contentStreamExists($contentStreamId)) { throw new ContentStreamDoesNotExistYet( 'Content stream for "' . $workspaceName->value . '" does not exist yet.', 1521386692 ); } - + $state = $commandHandlingDependencies->getContentStreamState($contentStreamId); if ($state === ContentStreamState::STATE_CLOSED) { throw new ContentStreamIsClosed( 'Content stream "' . $contentStreamId->value . '" is closed.', @@ -676,7 +675,7 @@ protected function getExpectedVersionOfContentStream( ): ExpectedVersion { return ExpectedVersion::fromVersion( - $commandHandlingDependencies->getContentStreamFinder()->findVersionForContentStream($contentStreamId)->unwrap() + $commandHandlingDependencies->getContentStreamVersion($contentStreamId) ); } } diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php index 2c9d9160ccd..c1e33319d5f 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php @@ -137,7 +137,7 @@ private function handleForkContentStream( $this->requireContentStreamToNotBeClosed($command->sourceContentStreamId, $commandHandlingDependencies); $this->requireContentStreamToNotExistYet($command->newContentStreamId, $commandHandlingDependencies); - $sourceContentStreamVersion = $commandHandlingDependencies->getContentStreamFinder()->findVersionForContentStream($command->sourceContentStreamId); + $sourceContentStreamVersion = $commandHandlingDependencies->getContentStreamVersion($command->sourceContentStreamId); $streamName = ContentStreamEventStreamName::fromContentStreamId($command->newContentStreamId) ->getEventStreamName(); @@ -148,7 +148,7 @@ private function handleForkContentStream( new ContentStreamWasForked( $command->newContentStreamId, $command->sourceContentStreamId, - $sourceContentStreamVersion->unwrap(), + $sourceContentStreamVersion, ), ), // NO_STREAM to ensure the "fork" happens as the first event of the new content stream @@ -187,7 +187,7 @@ protected function requireContentStreamToNotExistYet( ContentStreamId $contentStreamId, CommandHandlingDependencies $commandHandlingDependencies ): void { - if ($commandHandlingDependencies->getContentStreamFinder()->hasContentStream($contentStreamId)) { + if ($commandHandlingDependencies->contentStreamExists($contentStreamId)) { throw new ContentStreamAlreadyExists( 'Content stream "' . $contentStreamId->value . '" already exists.', 1521386345 @@ -204,8 +204,7 @@ protected function requireContentStreamToExist( ContentStreamId $contentStreamId, CommandHandlingDependencies $commandHandlingDependencies ): void { - $maybeVersion = $commandHandlingDependencies->getContentStreamFinder()->findVersionForContentStream($contentStreamId); - if ($maybeVersion->isNothing()) { + if (!$commandHandlingDependencies->contentStreamExists($contentStreamId)) { throw new ContentStreamDoesNotExistYet( 'Content stream "' . $contentStreamId->value . '" does not exist yet.', 1521386692 @@ -217,8 +216,7 @@ protected function requireContentStreamToNotBeClosed( ContentStreamId $contentStreamId, CommandHandlingDependencies $commandHandlingDependencies ): void { - $contentStreamState = $commandHandlingDependencies->getContentStreamFinder()->findStateForContentStream($contentStreamId); - if ($contentStreamState === ContentStreamState::STATE_CLOSED) { + if ($commandHandlingDependencies->getContentStreamState($contentStreamId) === ContentStreamState::STATE_CLOSED) { throw new ContentStreamIsClosed( 'Content stream "' . $contentStreamId->value . '" is closed.', 1710260081 @@ -230,8 +228,7 @@ protected function requireContentStreamToBeClosed( ContentStreamId $contentStreamId, CommandHandlingDependencies $commandHandlingDependencies ): void { - $contentStreamState = $commandHandlingDependencies->getContentStreamFinder()->findStateForContentStream($contentStreamId); - if ($contentStreamState !== ContentStreamState::STATE_CLOSED) { + if ($commandHandlingDependencies->getContentStreamState($contentStreamId) !== ContentStreamState::STATE_CLOSED) { throw new ContentStreamIsNotClosed( 'Content stream "' . $contentStreamId->value . '" is not closed.', 1710405911 @@ -243,10 +240,7 @@ protected function getExpectedVersionOfContentStream( ContentStreamId $contentStreamId, CommandHandlingDependencies $commandHandlingDependencies ): ExpectedVersion { - $maybeVersion = $commandHandlingDependencies->getContentStreamFinder()->findVersionForContentStream($contentStreamId); - return ExpectedVersion::fromVersion( - $maybeVersion - ->unwrap() - ); + $version = $commandHandlingDependencies->getContentStreamVersion($contentStreamId); + return ExpectedVersion::fromVersion($version); } } diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamEventStreamName.php b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamEventStreamName.php index b1735fc01e9..395d7861959 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamEventStreamName.php +++ b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamEventStreamName.php @@ -24,7 +24,7 @@ */ final readonly class ContentStreamEventStreamName { - public const EVENT_STREAM_NAME_PREFIX = 'ContentStream:'; + private const EVENT_STREAM_NAME_PREFIX = 'ContentStream:'; private function __construct( public string $value @@ -36,6 +36,19 @@ public static function fromContentStreamId(ContentStreamId $contentStreamId): se return new self(self::EVENT_STREAM_NAME_PREFIX . $contentStreamId->value); } + public static function isContentStreamStreamName(StreamName $streamName): bool + { + return str_starts_with($streamName->value, self::EVENT_STREAM_NAME_PREFIX); + } + + public static function extractContentStreamIdFromStreamName(StreamName $streamName): ContentStreamId + { + if (!self::isContentStreamStreamName($streamName)) { + throw new \InvalidArgumentException(sprintf('Failed to extract content stream id from stream name "%s"', $streamName->value), 1716640692); + } + return ContentStreamId::fromString(substr($streamName->value, strlen(self::EVENT_STREAM_NAME_PREFIX))); + } + public function getEventStreamName(): StreamName { return StreamName::fromString($this->value); diff --git a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php index 3789512c04d..3ca93c83168 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php @@ -41,13 +41,10 @@ use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Exception\BaseWorkspaceDoesNotExist; use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Exception\WorkspaceAlreadyExists; use Neos\ContentRepository\Core\Feature\WorkspaceModification\Command\ChangeBaseWorkspace; -use Neos\ContentRepository\Core\Feature\WorkspaceModification\Command\ChangeWorkspaceOwner; use Neos\ContentRepository\Core\Feature\WorkspaceModification\Command\DeleteWorkspace; -use Neos\ContentRepository\Core\Feature\WorkspaceModification\Command\RenameWorkspace; use Neos\ContentRepository\Core\Feature\WorkspaceModification\Event\WorkspaceBaseWorkspaceWasChanged; use Neos\ContentRepository\Core\Feature\WorkspaceModification\Event\WorkspaceOwnerWasChanged; use Neos\ContentRepository\Core\Feature\WorkspaceModification\Event\WorkspaceWasRemoved; -use Neos\ContentRepository\Core\Feature\WorkspaceModification\Event\WorkspaceWasRenamed; use Neos\ContentRepository\Core\Feature\WorkspaceModification\Exception\BaseWorkspaceEqualsWorkspaceException; use Neos\ContentRepository\Core\Feature\WorkspaceModification\Exception\CircularRelationBetweenWorkspacesException; use Neos\ContentRepository\Core\Feature\WorkspaceModification\Exception\WorkspaceIsNotEmptyException; @@ -68,7 +65,6 @@ use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Event\WorkspaceWasRebased; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Exception\WorkspaceRebaseFailed; use Neos\ContentRepository\Core\Projection\Workspace\Workspace; -use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceFinder; use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamAlreadyExists; use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet; use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; @@ -103,7 +99,6 @@ public function handle(CommandInterface $command, CommandHandlingDependencies $c /** @phpstan-ignore-next-line */ return match ($command::class) { CreateWorkspace::class => $this->handleCreateWorkspace($command, $commandHandlingDependencies), - RenameWorkspace::class => $this->handleRenameWorkspace($command, $commandHandlingDependencies), CreateRootWorkspace::class => $this->handleCreateRootWorkspace($command, $commandHandlingDependencies), PublishWorkspace::class => $this->handlePublishWorkspace($command, $commandHandlingDependencies), RebaseWorkspace::class => $this->handleRebaseWorkspace($command, $commandHandlingDependencies), @@ -111,7 +106,6 @@ public function handle(CommandInterface $command, CommandHandlingDependencies $c DiscardIndividualNodesFromWorkspace::class => $this->handleDiscardIndividualNodesFromWorkspace($command, $commandHandlingDependencies), DiscardWorkspace::class => $this->handleDiscardWorkspace($command, $commandHandlingDependencies), DeleteWorkspace::class => $this->handleDeleteWorkspace($command, $commandHandlingDependencies), - ChangeWorkspaceOwner::class => $this->handleChangeWorkspaceOwner($command, $commandHandlingDependencies), ChangeBaseWorkspace::class => $this->handleChangeBaseWorkspace($command, $commandHandlingDependencies), }; } @@ -127,9 +121,7 @@ private function handleCreateWorkspace( CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { $this->requireWorkspaceToNotExist($command->workspaceName, $commandHandlingDependencies); - - $baseWorkspace = $commandHandlingDependencies->getWorkspaceFinder()->findOneByName($command->baseWorkspaceName); - if ($baseWorkspace === null) { + if ($commandHandlingDependencies->findWorkspaceByName($command->baseWorkspaceName) === null) { throw new BaseWorkspaceDoesNotExist(sprintf( 'The workspace %s (base workspace of %s) does not exist', $command->baseWorkspaceName->value, @@ -164,30 +156,6 @@ private function handleCreateWorkspace( ); } - /** - * @throws WorkspaceDoesNotExist - */ - private function handleRenameWorkspace( - RenameWorkspace $command, - CommandHandlingDependencies $commandHandlingDependencies - ): EventsToPublish { - $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies->getWorkspaceFinder()); - - $events = Events::with( - new WorkspaceWasRenamed( - $command->workspaceName, - $command->workspaceTitle, - $command->workspaceDescription, - ) - ); - - return new EventsToPublish( - WorkspaceEventStreamName::fromWorkspaceName($command->workspaceName)->getEventStreamName(), - $events, - ExpectedVersion::STREAM_EXISTS() - ); - } - /** * @param CreateRootWorkspace $command * @return EventsToPublish @@ -236,8 +204,8 @@ private function handlePublishWorkspace( PublishWorkspace $command, CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies->getWorkspaceFinder()); - $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies->getWorkspaceFinder()); + $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies); + $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies); $this->publishContentStream( $workspace->currentContentStreamId, @@ -353,14 +321,13 @@ private function handleRebaseWorkspace( RebaseWorkspace $command, CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies->getWorkspaceFinder()); - $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies->getWorkspaceFinder()); + $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies); + $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies); $oldWorkspaceContentStreamId = $workspace->currentContentStreamId; - $oldWorkspaceContentStreamIdState = $commandHandlingDependencies->getContentStreamFinder() - ->findStateForContentStream($oldWorkspaceContentStreamId); - if ($oldWorkspaceContentStreamIdState === null) { + if (!$commandHandlingDependencies->contentStreamExists($oldWorkspaceContentStreamId)) { throw new \DomainException('Cannot rebase a workspace with a stateless content stream', 1711718314); } + $oldWorkspaceContentStreamIdState = $commandHandlingDependencies->getContentStreamState($oldWorkspaceContentStreamId); // 0) close old content stream $commandHandlingDependencies->handle( @@ -488,13 +455,13 @@ private function handlePublishIndividualNodesFromWorkspace( CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); - $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies->getWorkspaceFinder()); + $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies); $oldWorkspaceContentStreamId = $workspace->currentContentStreamId; - $oldWorkspaceContentStreamIdState = $commandHandlingDependencies->getContentStreamFinder()->findStateForContentStream($oldWorkspaceContentStreamId); - if ($oldWorkspaceContentStreamIdState === null) { + if (!$commandHandlingDependencies->contentStreamExists($oldWorkspaceContentStreamId)) { throw new \DomainException('Cannot publish nodes on a workspace with a stateless content stream', 1710410114); } - $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies->getWorkspaceFinder()); + $oldWorkspaceContentStreamIdState = $commandHandlingDependencies->getContentStreamState($oldWorkspaceContentStreamId); + $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies); // 1) close old content stream $commandHandlingDependencies->handle( @@ -629,13 +596,13 @@ private function handleDiscardIndividualNodesFromWorkspace( CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); - $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies->getWorkspaceFinder()); + $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies); $oldWorkspaceContentStreamId = $contentGraph->getContentStreamId(); - $oldWorkspaceContentStreamIdState = $commandHandlingDependencies->getContentStreamFinder()->findStateForContentStream($contentGraph->getContentStreamId()); - if ($oldWorkspaceContentStreamIdState === null) { + if (!$commandHandlingDependencies->contentStreamExists($contentGraph->getContentStreamId())) { throw new \DomainException('Cannot discard nodes on a workspace with a stateless content stream', 1710408112); } - $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies->getWorkspaceFinder()); + $oldWorkspaceContentStreamIdState = $commandHandlingDependencies->getContentStreamState($contentGraph->getContentStreamId()); + $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies); // 1) close old content stream $commandHandlingDependencies->handle( @@ -773,8 +740,8 @@ private function handleDiscardWorkspace( DiscardWorkspace $command, CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies->getWorkspaceFinder()); - $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies->getWorkspaceFinder()); + $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies); + $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies); $newContentStream = $command->newContentStreamId; $commandHandlingDependencies->handle( @@ -815,12 +782,12 @@ private function handleChangeBaseWorkspace( ChangeBaseWorkspace $command, CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies->getWorkspaceFinder()); + $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies); $this->requireEmptyWorkspace($workspace); - $this->requireBaseWorkspace($workspace, $commandHandlingDependencies->getWorkspaceFinder()); - $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies->getWorkspaceFinder()); + $this->requireBaseWorkspace($workspace, $commandHandlingDependencies); + $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies); - $this->requireNonCircularRelationBetweenWorkspaces($workspace, $baseWorkspace, $commandHandlingDependencies->getWorkspaceFinder()); + $this->requireNonCircularRelationBetweenWorkspaces($workspace, $baseWorkspace, $commandHandlingDependencies); $commandHandlingDependencies->handle( ForkContentStream::create( @@ -852,7 +819,7 @@ private function handleDeleteWorkspace( DeleteWorkspace $command, CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies->getWorkspaceFinder()); + $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies); $commandHandlingDependencies->handle( RemoveContentStream::create( @@ -874,30 +841,6 @@ private function handleDeleteWorkspace( ); } - /** - * @throws WorkspaceDoesNotExist - */ - private function handleChangeWorkspaceOwner( - ChangeWorkspaceOwner $command, - CommandHandlingDependencies $commandHandlingDependencies - ): EventsToPublish { - $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies->getWorkspaceFinder()); - - $events = Events::with( - new WorkspaceOwnerWasChanged( - $command->workspaceName, - $command->newWorkspaceOwner - ) - ); - - $streamName = WorkspaceEventStreamName::fromWorkspaceName($command->workspaceName)->getEventStreamName(); - return new EventsToPublish( - $streamName, - $events, - ExpectedVersion::STREAM_EXISTS() - ); - } - private function requireWorkspaceToNotExist(WorkspaceName $workspaceName, CommandHandlingDependencies $commandHandlingDependencies): void { try { @@ -916,9 +859,9 @@ private function requireWorkspaceToNotExist(WorkspaceName $workspaceName, Comman /** * @throws WorkspaceDoesNotExist */ - private function requireWorkspace(WorkspaceName $workspaceName, WorkspaceFinder $workspaceFinder): Workspace + private function requireWorkspace(WorkspaceName $workspaceName, CommandHandlingDependencies $commandHandlingDependencies): Workspace { - $workspace = $workspaceFinder->findOneByName($workspaceName); + $workspace = $commandHandlingDependencies->findWorkspaceByName($workspaceName); if (is_null($workspace)) { throw WorkspaceDoesNotExist::butWasSupposedTo($workspaceName); } @@ -930,24 +873,15 @@ private function requireWorkspace(WorkspaceName $workspaceName, WorkspaceFinder * @throws WorkspaceHasNoBaseWorkspaceName * @throws BaseWorkspaceDoesNotExist */ - private function requireBaseWorkspace( - Workspace $workspace, - WorkspaceFinder $workspaceFinder - ): Workspace { + private function requireBaseWorkspace(Workspace $workspace, CommandHandlingDependencies $commandHandlingDependencies): Workspace + { if (is_null($workspace->baseWorkspaceName)) { throw WorkspaceHasNoBaseWorkspaceName::butWasSupposedTo($workspace->workspaceName); } - - try { - $baseWorkspace = $workspaceFinder->findOneByName($workspace->baseWorkspaceName); - } catch (WorkspaceDoesNotExist $_) { - $baseWorkspace = null; - } - + $baseWorkspace = $commandHandlingDependencies->findWorkspaceByName($workspace->baseWorkspaceName); if (is_null($baseWorkspace)) { throw BaseWorkspaceDoesNotExist::butWasSupposedTo($workspace->workspaceName); } - return $baseWorkspace; } @@ -955,7 +889,7 @@ private function requireBaseWorkspace( * @throws BaseWorkspaceEqualsWorkspaceException * @throws CircularRelationBetweenWorkspacesException */ - private function requireNonCircularRelationBetweenWorkspaces(Workspace $workspace, Workspace $baseWorkspace, WorkspaceFinder $workspaceFinder): void + private function requireNonCircularRelationBetweenWorkspaces(Workspace $workspace, Workspace $baseWorkspace, CommandHandlingDependencies $commandHandlingDependencies): 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)); @@ -965,7 +899,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 = $this->requireBaseWorkspace($nextBaseWorkspace, $workspaceFinder); + $nextBaseWorkspace = $this->requireBaseWorkspace($nextBaseWorkspace, $commandHandlingDependencies); } } diff --git a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceModification/Command/ChangeWorkspaceOwner.php b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceModification/Command/ChangeWorkspaceOwner.php deleted file mode 100644 index 740fed703b6..00000000000 --- a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceModification/Command/ChangeWorkspaceOwner.php +++ /dev/null @@ -1,37 +0,0 @@ -updateContentStreamVersion($event, $eventEnvelope); } match ($event::class) { + ContentStreamWasClosed::class => $this->whenContentStreamWasClosed($event, $eventEnvelope), ContentStreamWasCreated::class => $this->whenContentStreamWasCreated($event, $eventEnvelope), + ContentStreamWasForked::class => $this->whenContentStreamWasForked($event, $eventEnvelope), + ContentStreamWasRemoved::class => $this->whenContentStreamWasRemoved($event, $eventEnvelope), + ContentStreamWasReopened::class => $this->whenContentStreamWasReopened($event, $eventEnvelope), + DimensionShineThroughWasAdded::class => $this->whenDimensionShineThroughWasAdded($event, $eventEnvelope), RootWorkspaceWasCreated::class => $this->whenRootWorkspaceWasCreated($event), + WorkspaceRebaseFailed::class => $this->whenWorkspaceRebaseFailed($event), WorkspaceWasCreated::class => $this->whenWorkspaceWasCreated($event), - ContentStreamWasForked::class => $this->whenContentStreamWasForked($event, $eventEnvelope), WorkspaceWasDiscarded::class => $this->whenWorkspaceWasDiscarded($event), WorkspaceWasPartiallyDiscarded::class => $this->whenWorkspaceWasPartiallyDiscarded($event), WorkspaceWasPartiallyPublished::class => $this->whenWorkspaceWasPartiallyPublished($event), WorkspaceWasPublished::class => $this->whenWorkspaceWasPublished($event), WorkspaceWasRebased::class => $this->whenWorkspaceWasRebased($event), - WorkspaceRebaseFailed::class => $this->whenWorkspaceRebaseFailed($event), - ContentStreamWasClosed::class => $this->whenContentStreamWasClosed($event, $eventEnvelope), - ContentStreamWasReopened::class => $this->whenContentStreamWasReopened($event, $eventEnvelope), - ContentStreamWasRemoved::class => $this->whenContentStreamWasRemoved($event, $eventEnvelope), - DimensionShineThroughWasAdded::class => $this->whenDimensionShineThroughWasAdded($event, $eventEnvelope), default => $event instanceof EmbedsContentStreamId || throw new \InvalidArgumentException(sprintf('Unsupported event %s', get_debug_type($event))), }; } @@ -393,13 +393,13 @@ private static function extractVersion(EventEnvelope $eventEnvelope): int if ( !str_starts_with( $eventEnvelope->streamName->value, - ContentStreamEventStreamName::EVENT_STREAM_NAME_PREFIX + 'ContentStream:' ) ) { throw new \RuntimeException( 'Cannot extract version number, as it was projected on wrong stream "' . $eventEnvelope->streamName->value . '", but needs to start with ' - . ContentStreamEventStreamName::EVENT_STREAM_NAME_PREFIX + . 'ContentStream:' ); } return $eventEnvelope->version->value; diff --git a/Neos.ContentRepository.Core/Classes/Projection/Workspace/Workspaces.php b/Neos.ContentRepository.Core/Classes/Projection/Workspace/Workspaces.php index f8fea6db6db..82a9399355c 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/Workspace/Workspaces.php +++ b/Neos.ContentRepository.Core/Classes/Projection/Workspace/Workspaces.php @@ -100,8 +100,34 @@ public function getIterator(): \Traversable yield from array_values($this->workspaces); } + /** + * @param \Closure(Workspace): bool $callback + */ + public function filter(\Closure $callback): self + { + return new self(array_Filter($this->workspaces, $callback)); + } + + /** + * @param \Closure(Workspace): bool $callback + */ + public function find(\Closure $callback): ?Workspace + { + foreach ($this->workspaces as $workspace) { + if ($callback($workspace)) { + return $workspace; + } + } + return null; + } + public function count(): int { return count($this->workspaces); } + + public function isEmpty(): bool + { + return $this->workspaces === []; + } } diff --git a/Neos.ContentRepository.Core/Classes/Service/ContentRepositoryBootstrapper.php b/Neos.ContentRepository.Core/Classes/Service/ContentRepositoryBootstrapper.php index 3a835b5cb77..6dd837c9a81 100644 --- a/Neos.ContentRepository.Core/Classes/Service/ContentRepositoryBootstrapper.php +++ b/Neos.ContentRepository.Core/Classes/Service/ContentRepositoryBootstrapper.php @@ -40,7 +40,7 @@ public static function create(ContentRepository $contentRepository): self public function getOrCreateLiveWorkspace(): Workspace { $liveWorkspaceName = WorkspaceName::forLive(); - $liveWorkspace = $this->contentRepository->getWorkspaceFinder()->findOneByName($liveWorkspaceName); + $liveWorkspace = $this->contentRepository->findWorkspaceByName($liveWorkspaceName); if ($liveWorkspace instanceof Workspace) { return $liveWorkspace; } @@ -53,7 +53,7 @@ public function getOrCreateLiveWorkspace(): Workspace ContentStreamId::create() ) ); - $liveWorkspace = $this->contentRepository->getWorkspaceFinder()->findOneByName($liveWorkspaceName); + $liveWorkspace = $this->contentRepository->findWorkspaceByName($liveWorkspaceName); if (!$liveWorkspace) { throw new \Exception('Live workspace creation failed', 1699002435); } diff --git a/Neos.ContentRepository.Core/Classes/Service/WorkspaceMaintenanceService.php b/Neos.ContentRepository.Core/Classes/Service/WorkspaceMaintenanceService.php index 053d0dc3565..521af3a1a40 100644 --- a/Neos.ContentRepository.Core/Classes/Service/WorkspaceMaintenanceService.php +++ b/Neos.ContentRepository.Core/Classes/Service/WorkspaceMaintenanceService.php @@ -10,6 +10,8 @@ use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Command\RebaseWorkspace; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Dto\RebaseErrorHandlingStrategy; use Neos\ContentRepository\Core\Projection\Workspace\Workspace; +use Neos\ContentRepository\Core\Projection\Workspace\Workspaces; +use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceStatus; use Neos\EventStore\EventStoreInterface; /** @@ -24,17 +26,18 @@ public function __construct( } /** - * @return array the workspaces of the removed content streams - * @throws \Doctrine\DBAL\Driver\Exception - * @throws \Doctrine\DBAL\Exception - * @throws \Neos\ContentRepository\Core\Feature\WorkspaceCreation\Exception\BaseWorkspaceDoesNotExist - * @throws \Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist + * @return Workspaces the workspaces of the removed content streams */ - public function rebaseOutdatedWorkspaces(?RebaseErrorHandlingStrategy $strategy = null): array + public function rebaseOutdatedWorkspaces(?RebaseErrorHandlingStrategy $strategy = null): Workspaces { - $outdatedWorkspaces = $this->contentRepository->getWorkspaceFinder()->findOutdated(); - + $outdatedWorkspaces = $this->contentRepository->getWorkspaces()->filter( + fn (Workspace $workspace) => $workspace->status === WorkspaceStatus::OUTDATED + ); + /** @var Workspace $workspace */ foreach ($outdatedWorkspaces as $workspace) { + if ($workspace->status !== WorkspaceStatus::OUTDATED) { + continue; + } $rebaseCommand = RebaseWorkspace::create( $workspace->workspaceName, ); @@ -49,9 +52,7 @@ public function rebaseOutdatedWorkspaces(?RebaseErrorHandlingStrategy $strategy public function pruneAll(): void { - $workspaces = $this->contentRepository->getWorkspaceFinder()->findAll(); - - foreach ($workspaces as $workspace) { + foreach ($this->contentRepository->getWorkspaces() as $workspace) { $streamName = WorkspaceEventStreamName::fromWorkspaceName($workspace->workspaceName)->getEventStreamName(); $this->eventStore->deleteStream($streamName); } diff --git a/Neos.ContentRepository.Export/Tests/Behavior/Features/Bootstrap/CrImportExportTrait.php b/Neos.ContentRepository.Export/Tests/Behavior/Features/Bootstrap/CrImportExportTrait.php index 2b1ce7fd096..16b8155fe8f 100644 --- a/Neos.ContentRepository.Export/Tests/Behavior/Features/Bootstrap/CrImportExportTrait.php +++ b/Neos.ContentRepository.Export/Tests/Behavior/Features/Bootstrap/CrImportExportTrait.php @@ -21,6 +21,7 @@ use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryDependencies; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepository\Export\Event\ValueObject\ExportedEvents; use Neos\ContentRepository\Export\ProcessorResult; use Neos\ContentRepository\Export\Processors\EventExportProcessor; @@ -64,7 +65,7 @@ public function __construct(private readonly Filesystem $filesystem) public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): EventExportProcessor { return new EventExportProcessor( $this->filesystem, - $serviceFactoryDependencies->contentRepository->getWorkspaceFinder(), + $serviceFactoryDependencies->contentRepository->findWorkspaceByName(WorkspaceName::forLive())->currentContentStreamId, $serviceFactoryDependencies->eventStore ); } diff --git a/Neos.ContentRepository.Export/src/ExportService.php b/Neos.ContentRepository.Export/src/ExportService.php index 00a45bcbb16..51707a57a93 100644 --- a/Neos.ContentRepository.Export/src/ExportService.php +++ b/Neos.ContentRepository.Export/src/ExportService.php @@ -5,7 +5,7 @@ use League\Flysystem\Filesystem; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; -use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceFinder; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Export\Processors\AssetExportProcessor; use Neos\ContentRepository\Export\Processors\EventExportProcessor; use Neos\EventStore\EventStoreInterface; @@ -20,7 +20,7 @@ class ExportService implements ContentRepositoryServiceInterface public function __construct( private readonly Filesystem $filesystem, - private readonly WorkspaceFinder $workspaceFinder, + private readonly ContentStreamId $targetContentStreamId, private readonly AssetRepository $assetRepository, private readonly AssetUsageFinder $assetUsageFinder, private readonly EventStoreInterface $eventStore, @@ -33,13 +33,13 @@ public function runAllProcessors(\Closure $outputLineFn, bool $verbose = false): $processors = [ 'Exporting events' => new EventExportProcessor( $this->filesystem, - $this->workspaceFinder, + $this->targetContentStreamId, $this->eventStore ), 'Exporting assets' => new AssetExportProcessor( $this->filesystem, $this->assetRepository, - $this->workspaceFinder, + $this->targetContentStreamId, $this->assetUsageFinder ) ]; diff --git a/Neos.ContentRepository.Export/src/ExportServiceFactory.php b/Neos.ContentRepository.Export/src/ExportServiceFactory.php index baa013bca98..4e2dadd59a5 100644 --- a/Neos.ContentRepository.Export/src/ExportServiceFactory.php +++ b/Neos.ContentRepository.Export/src/ExportServiceFactory.php @@ -6,7 +6,8 @@ use League\Flysystem\Filesystem; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryDependencies; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; -use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceFinder; +use Neos\ContentRepository\Core\Projection\Workspace\Workspace; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\Media\Domain\Repository\AssetRepository; use Neos\Neos\AssetUsage\Projection\AssetUsageFinder; @@ -19,7 +20,7 @@ class ExportServiceFactory implements ContentRepositoryServiceFactoryInterface public function __construct( private readonly Filesystem $filesystem, - private readonly WorkspaceFinder $workspaceFinder, + private readonly ContentStreamId $targetContentStreamId, private readonly AssetRepository $assetRepository, private readonly AssetUsageFinder $assetUsageFinder, ) { @@ -29,7 +30,7 @@ public function build(ContentRepositoryServiceFactoryDependencies $serviceFactor { return new ExportService( $this->filesystem, - $this->workspaceFinder, + $this->targetContentStreamId, $this->assetRepository, $this->assetUsageFinder, $serviceFactoryDependencies->eventStore, diff --git a/Neos.ContentRepository.Export/src/Processors/AssetExportProcessor.php b/Neos.ContentRepository.Export/src/Processors/AssetExportProcessor.php index db7679a18b9..8373616c6d7 100644 --- a/Neos.ContentRepository.Export/src/Processors/AssetExportProcessor.php +++ b/Neos.ContentRepository.Export/src/Processors/AssetExportProcessor.php @@ -3,8 +3,7 @@ namespace Neos\ContentRepository\Export\Processors; use League\Flysystem\Filesystem; -use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceFinder; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Export\Asset\ValueObject\SerializedAsset; use Neos\ContentRepository\Export\Asset\ValueObject\SerializedImageVariant; use Neos\ContentRepository\Export\ProcessorInterface; @@ -31,7 +30,7 @@ final class AssetExportProcessor implements ProcessorInterface public function __construct( private readonly Filesystem $files, private readonly AssetRepository $assetRepository, - private readonly WorkspaceFinder $workspaceFinder, + private readonly ContentStreamId $targetContentStreamId, private readonly AssetUsageFinder $assetUsageFinder, ) {} @@ -43,11 +42,7 @@ public function onMessage(\Closure $callback): void public function run(): ProcessorResult { - $liveWorkspace = $this->workspaceFinder->findOneByName(WorkspaceName::forLive()); - if ($liveWorkspace === null) { - return ProcessorResult::error('Failed to find live workspace'); - } - $assetFilter = AssetUsageFilter::create()->withContentStream($liveWorkspace->currentContentStreamId)->groupByAsset(); + $assetFilter = AssetUsageFilter::create()->withContentStream($this->targetContentStreamId)->groupByAsset(); $numberOfExportedAssets = 0; $numberOfExportedImageVariants = 0; diff --git a/Neos.ContentRepository.Export/src/Processors/EventExportProcessor.php b/Neos.ContentRepository.Export/src/Processors/EventExportProcessor.php index 5bd6b142dec..d12fc5a1d08 100644 --- a/Neos.ContentRepository.Export/src/Processors/EventExportProcessor.php +++ b/Neos.ContentRepository.Export/src/Processors/EventExportProcessor.php @@ -6,8 +6,7 @@ use League\Flysystem\FilesystemException; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; use Neos\ContentRepository\Core\Feature\ContentStreamEventStreamName; -use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceFinder; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Export\Event\ValueObject\ExportedEvent; use Neos\ContentRepository\Export\ProcessorInterface; use Neos\ContentRepository\Export\ProcessorResult; @@ -24,7 +23,7 @@ final class EventExportProcessor implements ProcessorInterface, ContentRepositor public function __construct( private readonly Filesystem $files, - private readonly WorkspaceFinder $workspaceFinder, + private readonly ContentStreamId $targetContentStreamId, private readonly EventStoreInterface $eventStore, ) { } @@ -36,11 +35,7 @@ public function onMessage(\Closure $callback): void public function run(): ProcessorResult { - $liveWorkspace = $this->workspaceFinder->findOneByName(WorkspaceName::forLive()); - if ($liveWorkspace === null) { - return ProcessorResult::error('Failed to find live workspace'); - } - $streamName = ContentStreamEventStreamName::fromContentStreamId($liveWorkspace->currentContentStreamId)->getEventStreamName(); + $streamName = ContentStreamEventStreamName::fromContentStreamId($this->targetContentStreamId)->getEventStreamName(); $eventStream = $this->eventStore->load($streamName); $eventFileResource = fopen('php://temp/maxmemory:5242880', 'rb+'); diff --git a/Neos.ContentRepository.NodeMigration/src/NodeMigrationService.php b/Neos.ContentRepository.NodeMigration/src/NodeMigrationService.php index ba7279ce0c2..69ba9a5bf7c 100644 --- a/Neos.ContentRepository.NodeMigration/src/NodeMigrationService.php +++ b/Neos.ContentRepository.NodeMigration/src/NodeMigrationService.php @@ -61,7 +61,7 @@ public function __construct( public function executeMigration(ExecuteMigration $command): void { - $sourceWorkspace = $this->contentRepository->getWorkspaceFinder()->findOneByName($command->sourceWorkspaceName); + $sourceWorkspace = $this->contentRepository->findWorkspaceByName($command->sourceWorkspaceName); if ($sourceWorkspace === null) { throw new WorkspaceDoesNotExist(sprintf( 'The workspace %s does not exist', diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php index 1dbe5f71716..06a5fd3fd13 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php @@ -133,6 +133,9 @@ protected function readPayloadTable(TableNode $payloadTable): array */ public function iExpectTheContentStreamToNotExist(string $rawContentStreamId): void { + // todo use, but also it should be assertFAlse?!!! + // $this->contentRepository->getContentStreams() + // ->find(fn (ContentStream $contentStream) => $contentStream->id->equals(ContentStreamId::fromString($rawContentStreamId))), Assert::assertTrue( $this->currentContentRepository->getContentStreamFinder()->hasContentStream(ContentStreamId::fromString($rawContentStreamId)), sprintf('The content stream "%s" does exist.', $rawContentStreamId) @@ -144,9 +147,9 @@ public function iExpectTheContentStreamToNotExist(string $rawContentStreamId): v */ public function workspaceHasStatus(string $rawWorkspaceName, string $status): void { - $workspace = $this->currentContentRepository->getWorkspaceFinder()->findOneByName(WorkspaceName::fromString($rawWorkspaceName)); + $workspace = $this->currentContentRepository->findWorkspaceByName(WorkspaceName::fromString($rawWorkspaceName)); - Assert::assertSame($status, $workspace->status->value); + Assert::assertSame($status, $workspace?->status->value); } /** @@ -245,8 +248,7 @@ public function theCurrentContentStreamHasState(string $expectedState): void { $this->theContentStreamHasState( $this->currentContentRepository - ->getWorkspaceFinder() - ->findOneByName($this->currentWorkspaceName) + ->findWorkspaceByName($this->currentWorkspaceName) ->currentContentStreamId->value, $expectedState ); diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php index fcf91a05526..017327f22ed 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php @@ -80,6 +80,7 @@ public function iGetTheNodeAtPath(string $serializedNodePath): void public function iExpectANodeIdentifiedByXToExistInTheContentGraph(string $serializedNodeDiscriminator): void { $nodeDiscriminator = NodeDiscriminator::fromShorthand($serializedNodeDiscriminator); + // TODO Fix $workspaceName = $this->currentContentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId( $nodeDiscriminator->contentStreamId )->workspaceName; diff --git a/Neos.Media.Browser/Classes/Controller/UsageController.php b/Neos.Media.Browser/Classes/Controller/UsageController.php index 17baf481e21..c0fe9f11ae3 100644 --- a/Neos.Media.Browser/Classes/Controller/UsageController.php +++ b/Neos.Media.Browser/Classes/Controller/UsageController.php @@ -14,6 +14,7 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindClosestNodeFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; +use Neos\ContentRepository\Core\Projection\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeNotFound; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; @@ -106,7 +107,9 @@ public function relatedNodesAction(AssetInterface $asset) $contentRepository = $this->contentRepositoryRegistry->get($usage->getContentRepositoryId()); - $workspace = $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($usage->getContentStreamId()); + $workspace = $contentRepository->getWorkspaces()->find( + fn (Workspace $potentialWorkspace) => $potentialWorkspace->currentContentStreamId->equals($usage->getContentStreamId()) + ); // FIXME: AssetUsageReference->workspaceName ? $nodeAggregate = $contentRepository->getContentGraph($workspace->workspaceName)->findNodeAggregateById( diff --git a/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncService.php b/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncService.php index f73bf9865d5..cb4c17337ef 100644 --- a/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncService.php +++ b/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncService.php @@ -7,6 +7,7 @@ use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; +use Neos\ContentRepository\Core\Projection\Workspace\Workspace; use Neos\Media\Domain\Model\AssetInterface; use Neos\Media\Domain\Repository\AssetRepository; use Neos\Neos\AssetUsage\Dto\AssetUsage; @@ -56,7 +57,9 @@ public function isAssetUsageStillValid(AssetUsage $usage): bool $dimensionSpacePoint = $usage->originDimensionSpacePoint->toDimensionSpacePoint(); // FIXME: AssetUsage->workspaceName ? - $workspace = $this->contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($usage->contentStreamId); + $workspace = $this->contentRepository->getWorkspaces()->find( + fn (Workspace $potentialWorkspace) => $potentialWorkspace->currentContentStreamId->equals($usage->contentStreamId) + ); if (is_null($workspace)) { return false; } diff --git a/Neos.Neos/Classes/Command/CrCommandController.php b/Neos.Neos/Classes/Command/CrCommandController.php index af2719e83e4..af42afec9af 100644 --- a/Neos.Neos/Classes/Command/CrCommandController.php +++ b/Neos.Neos/Classes/Command/CrCommandController.php @@ -9,6 +9,7 @@ use Neos\ContentRepository\Core\Projection\CatchUpOptions; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepository\Export\ExportService; use Neos\ContentRepository\Export\ExportServiceFactory; use Neos\ContentRepository\Export\ImportService; @@ -54,18 +55,22 @@ public function __construct( public function exportCommand(string $path, string $contentRepository = 'default', bool $verbose = false): void { $contentRepositoryId = ContentRepositoryId::fromString($contentRepository); - $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); + $contentRepositoryInstance = $this->contentRepositoryRegistry->get($contentRepositoryId); Files::createDirectoryRecursively($path); $filesystem = new Filesystem(new LocalFilesystemAdapter($path)); + $liveWorkspace = $contentRepositoryInstance->findWorkspaceByName(WorkspaceName::forLive()); + if ($liveWorkspace === null) { + throw new \RuntimeException('Failed to find live workspace', 1716652280); + } $exportService = $this->contentRepositoryRegistry->buildService( $contentRepositoryId, new ExportServiceFactory( $filesystem, - $contentRepository->getWorkspaceFinder(), + $liveWorkspace->currentContentStreamId, $this->assetRepository, - $contentRepository->projectionState(AssetUsageFinder::class), + $contentRepositoryInstance->projectionState(AssetUsageFinder::class), ) ); assert($exportService instanceof ExportService); diff --git a/Neos.Neos/Classes/Command/WorkspaceCommandController.php b/Neos.Neos/Classes/Command/WorkspaceCommandController.php index cc9742a49c5..eab83907d49 100644 --- a/Neos.Neos/Classes/Command/WorkspaceCommandController.php +++ b/Neos.Neos/Classes/Command/WorkspaceCommandController.php @@ -21,6 +21,8 @@ use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Dto\RebaseErrorHandlingStrategy; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Exception\WorkspaceRebaseFailed; use Neos\ContentRepository\Core\Projection\Workspace\Workspace; +use Neos\ContentRepository\Core\Projection\Workspace\Workspaces; +use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceStatus; use Neos\ContentRepository\Core\Service\WorkspaceMaintenanceServiceFactory; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; @@ -249,7 +251,7 @@ public function deleteCommand(string $workspace, bool $force = false, string $co $this->quit(2); } - $crWorkspace = $contentRepositoryInstance->getWorkspaceFinder()->findOneByName($workspaceName); + $crWorkspace = $contentRepositoryInstance->findWorkspaceByName($workspaceName); if ($crWorkspace === null) { $this->outputLine('Workspace "%s" does not exist', [$workspaceName->value]); $this->quit(1); @@ -265,8 +267,10 @@ public function deleteCommand(string $workspace, bool $force = false, string $co $this->quit(2); } - $dependentWorkspaces = $contentRepositoryInstance->getWorkspaceFinder()->findByBaseWorkspace($workspaceName); - if (count($dependentWorkspaces) > 0) { + $dependentWorkspaces = $contentRepository->getWorkspaces()->filter( + static fn (Workspace $potentiallyDependentWorkspace) => $potentiallyDependentWorkspace->baseWorkspaceName?->equals($workspaceName) ?? false + ); + if (!$dependentWorkspaces->isEmpty()) { $this->outputLine( 'Workspace "%s" cannot be deleted because the following workspaces are based on it:', [$workspaceName->value] @@ -333,12 +337,12 @@ public function rebaseOutdatedCommand(string $contentRepository = 'default', boo $force ? RebaseErrorHandlingStrategy::STRATEGY_FORCE : RebaseErrorHandlingStrategy::STRATEGY_FAIL ); - if (!count($outdatedWorkspaces)) { + if ($outdatedWorkspaces->isEmpty()) { $this->outputLine('There are no outdated workspaces.'); - } else { - foreach ($outdatedWorkspaces as $outdatedWorkspace) { - $this->outputFormatted('Rebased workspace %s', [$outdatedWorkspace->workspaceName->value]); - } + return; + } + foreach ($outdatedWorkspaces as $outdatedWorkspace) { + $this->outputFormatted('Rebased workspace %s', [$outdatedWorkspace->workspaceName->value]); } } @@ -353,9 +357,9 @@ public function listCommand(string $contentRepository = 'default'): void $contentRepositoryId = ContentRepositoryId::fromString($contentRepository); $contentRepositoryInstance = $this->contentRepositoryRegistry->get($contentRepositoryId); - $workspaces = $contentRepositoryInstance->getWorkspaceFinder()->findAll(); + $workspaces = $this->contentRepositoryRegistry->get($contentRepositoryId)->getWorkspaces(); - if (count($workspaces) === 0) { + if ($workspaces->isEmpty()) { $this->outputLine('No workspaces found.'); $this->quit(0); } diff --git a/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php b/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php index 3fe98412645..5afd08ec56e 100755 --- a/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php +++ b/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php @@ -176,7 +176,7 @@ public function updateSiteAction(Site $site, $newSiteNodeName) if ($site->getNodeName()->value !== $newSiteNodeName) { $contentRepository = $this->contentRepositoryRegistry->get($site->getConfiguration()->contentRepositoryId); - $liveWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName(WorkspaceName::forLive()); + $liveWorkspace = $contentRepository->findWorkspaceByName(WorkspaceName::forLive()); if (!$liveWorkspace instanceof Workspace) { throw new \InvalidArgumentException( 'Cannot update a site without the live workspace being present.', @@ -202,7 +202,7 @@ public function updateSiteAction(Site $site, $newSiteNodeName) ); } - foreach ($contentRepository->getWorkspaceFinder()->findAll() as $workspace) { + foreach ($contentRepository->getWorkspaces() as $workspace) { $siteNodeAggregate = $contentRepository->getContentGraph($workspace->workspaceName)->findChildNodeAggregateByName( $sitesNode->nodeAggregateId, $site->getNodeName()->toNodeName() diff --git a/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php b/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php index 3337686ebeb..9ab73ee61fd 100644 --- a/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php +++ b/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php @@ -55,7 +55,7 @@ public function removeSiteNode(SiteNodeName $siteNodeName): void ); } - foreach ($this->contentRepository->getWorkspaceFinder()->findAll() as $workspace) { + foreach ($this->contentRepository->getWorkspaces() as $workspace) { $contentGraph = $this->contentRepository->getContentGraph($workspace->workspaceName); $sitesNodeAggregate = $contentGraph->findRootNodeAggregateByType( NodeTypeNameFactory::forSites() diff --git a/Neos.Neos/Classes/Fusion/Helper/CachingHelper.php b/Neos.Neos/Classes/Fusion/Helper/CachingHelper.php index f921df9b4ef..c1a9f11c6ec 100644 --- a/Neos.Neos/Classes/Fusion/Helper/CachingHelper.php +++ b/Neos.Neos/Classes/Fusion/Helper/CachingHelper.php @@ -176,7 +176,7 @@ public function getWorkspaceChain(?Node $node): array $node->contentRepositoryId ); - $currentWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName( + $currentWorkspace = $contentRepository->findWorkspaceByName( $node->workspaceName ); $workspaceChain = []; @@ -184,7 +184,7 @@ public function getWorkspaceChain(?Node $node): array while ($currentWorkspace !== null) { $workspaceChain[$currentWorkspace->workspaceName->value] = $currentWorkspace; $currentWorkspace = $currentWorkspace->baseWorkspaceName - ? $contentRepository->getWorkspaceFinder()->findOneByName($currentWorkspace->baseWorkspaceName) + ? $contentRepository->findWorkspaceByName($currentWorkspace->baseWorkspaceName) : null; } diff --git a/Neos.Neos/Classes/Service/LinkingService.php b/Neos.Neos/Classes/Service/LinkingService.php index 6354c69b937..f112a02e641 100644 --- a/Neos.Neos/Classes/Service/LinkingService.php +++ b/Neos.Neos/Classes/Service/LinkingService.php @@ -309,7 +309,7 @@ public function createNodeUri( )->contentRepositoryId; $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); $nodeAddress = NodeAddress::fromJsonString($nodeString); - $workspace = $contentRepository->getWorkspaceFinder()->findOneByName($nodeAddress->workspaceName); + $workspace = $contentRepository->findWorkspaceByName($nodeAddress->workspaceName); $subgraph = $contentRepository->getContentGraph($nodeAddress->workspaceName)->getSubgraph( $nodeAddress->dimensionSpacePoint, $workspace && !$workspace->isPublicWorkspace() @@ -348,7 +348,7 @@ public function createNodeUri( $contentRepository = $this->contentRepositoryRegistry->get( $node->contentRepositoryId ); - $workspace = $contentRepository->getWorkspaceFinder()->findOneByName( + $workspace = $contentRepository->findWorkspaceByName( $node->workspaceName ); $mainRequest = $controllerContext->getRequest()->getMainRequest(); diff --git a/Neos.Workspace.Ui/Classes/Controller/WorkspaceController.php b/Neos.Workspace.Ui/Classes/Controller/WorkspaceController.php index f07732d5d96..160d1dbadd1 100644 --- a/Neos.Workspace.Ui/Classes/Controller/WorkspaceController.php +++ b/Neos.Workspace.Ui/Classes/Controller/WorkspaceController.php @@ -17,9 +17,7 @@ use Doctrine\DBAL\Exception as DBALException; use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Exception\WorkspaceAlreadyExists; -use Neos\ContentRepository\Core\Feature\WorkspaceModification\Command\ChangeWorkspaceOwner; use Neos\ContentRepository\Core\Feature\WorkspaceModification\Command\DeleteWorkspace; -use Neos\ContentRepository\Core\Feature\WorkspaceModification\Command\RenameWorkspace; use Neos\ContentRepository\Core\Feature\WorkspacePublication\Command\DiscardIndividualNodesFromWorkspace; use Neos\ContentRepository\Core\Feature\WorkspacePublication\Command\PublishIndividualNodesFromWorkspace; use Neos\ContentRepository\Core\Feature\WorkspacePublication\Dto\NodeIdsToPublishOrDiscard; @@ -165,7 +163,7 @@ public function showAction(WorkspaceName $workspace): void $contentRepositoryId = SiteDetectionResult::fromRequest($this->request->getHttpRequest())->contentRepositoryId; $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); - $workspaceObj = $contentRepository->getWorkspaceFinder()->findOneByName($workspace); + $workspaceObj = $contentRepository->findWorkspaceByName($workspace); if (is_null($workspaceObj)) { /** @todo add flash message */ $this->redirect('index'); @@ -238,7 +236,7 @@ public function editAction(WorkspaceName $workspaceName): void ->contentRepositoryId; $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); - $workspace = $contentRepository->getWorkspaceFinder()->findOneByName($workspaceName); + $workspace = $contentRepository->findWorkspaceByName($workspaceName); if (is_null($workspace)) { $this->addFlashMessage('Failed to find workspace "%s"', 'Error', Message::SEVERITY_ERROR, [$workspaceName->value]); $this->redirect('index'); @@ -275,7 +273,7 @@ public function updateAction( $title = WorkspaceTitle::fromString($workspaceName->value); } - $workspace = $contentRepository->getWorkspaceFinder()->findOneByName($workspaceName); + $workspace = $contentRepository->findWorkspaceByName($workspaceName); if ($workspace === null) { $this->addFlashMessage( $this->getModuleLabel('workspaces.workspaceDoesNotExist'), @@ -315,7 +313,7 @@ public function deleteAction(WorkspaceName $workspaceName): void $contentRepositoryId = SiteDetectionResult::fromRequest($this->request->getHttpRequest())->contentRepositoryId; $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); - $workspace = $contentRepository->getWorkspaceFinder()->findOneByName($workspaceName); + $workspace = $contentRepository->findWorkspaceByName($workspaceName); if ($workspace === null) { $this->addFlashMessage( $this->getModuleLabel('workspaces.workspaceDoesNotExist'), @@ -331,9 +329,12 @@ public function deleteAction(WorkspaceName $workspaceName): void $this->redirect('index'); } - $dependentWorkspaces = $contentRepository->getWorkspaceFinder()->findByBaseWorkspace($workspace->workspaceName); - if (count($dependentWorkspaces) > 0) { + $dependentWorkspaces = $contentRepository->getWorkspaces()->filter( + static fn (Workspace $potentiallyDependentWorkspace) => $potentiallyDependentWorkspace->baseWorkspaceName?->equals($workspaceName) ?? false + ); + if (!$dependentWorkspaces->isEmpty()) { $dependentWorkspaceTitles = []; + /** @var Workspace $dependentWorkspace */ foreach ($dependentWorkspaces as $dependentWorkspace) { $dependentWorkspaceMetadata = $this->workspaceService->getWorkspaceMetadata($contentRepositoryId, $dependentWorkspace->workspaceName); $dependentWorkspaceTitles[] = $dependentWorkspaceMetadata->title->value; @@ -832,8 +833,8 @@ protected function renderContentChanges( ContentStreamId $contentStreamIdOfOriginalNode, ContentRepository $contentRepository, ): array { - $currentWorkspace = $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId( - $contentStreamIdOfOriginalNode + $currentWorkspace = $contentRepository->getWorkspaces()->find( + fn (Workspace $potentialWorkspace) => $potentialWorkspace->currentContentStreamId->equals($contentStreamIdOfOriginalNode) ); $originalNode = null; if ($currentWorkspace !== null) { @@ -1016,7 +1017,7 @@ protected function prepareBaseWorkspaceOptions( ): array { $user = $this->userService->getCurrentUser(); $baseWorkspaceOptions = []; - $workspaces = $contentRepository->getWorkspaceFinder()->findAll(); + $workspaces = $contentRepository->getWorkspaces(); foreach ($workspaces as $workspace) { if ($excludedWorkspace !== null) { if ($workspace->workspaceName->equals($excludedWorkspace)) { @@ -1066,7 +1067,7 @@ private function getBaseWorkspaceWhenSureItExists( /** @var WorkspaceName $baseWorkspaceName We expect this to exist */ $baseWorkspaceName = $workspace->baseWorkspaceName; /** @var Workspace $baseWorkspace We expect this to exist */ - $baseWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName($baseWorkspaceName); + $baseWorkspace = $contentRepository->findWorkspaceByName($baseWorkspaceName); return $baseWorkspace; } From fab9d2d6f7fe3c3f7e39795254ad6cba95b11413 Mon Sep 17 00:00:00 2001 From: Bastian Waidelich Date: Sun, 2 Jun 2024 17:57:44 +0200 Subject: [PATCH 03/22] wip --- .../src/ContentGraphFactory.php | 165 ++++++- .../DoctrineDbalContentGraphProjection.php | 52 +-- ...trineDbalContentGraphProjectionFactory.php | 4 +- .../DoctrineDbalContentGraphSchemaBuilder.php | 12 +- .../Projection/Feature/ContentStream.php | 18 +- .../Domain/Projection/Feature/Workspace.php | 14 +- .../src/ContentHyperGraphFactory.php | 38 +- .../Projection/HypergraphProjection.php | 10 +- .../src/HypergraphProjectionFactory.php | 4 +- .../Command/PerformanceMeasurementService.php | 4 - .../NodeRemoval/RemoveNodesFromAggregate.wip | 2 - .../Workspaces/PruneContentStreams.feature | 10 +- .../RemoveNodeAggregateWithDimensions.wip | 2 - .../Workspaces/RemoveNodesFromAggregate.wip | 2 - .../WorkspaceWritingDuringPublication.php | 6 - .../Classes/CommandHandlingDependencies.php | 40 +- .../Classes/ContentGraphAdapter.php | 139 ++++++ .../Classes/ContentGraphFactoryInterface.php | 18 + .../Classes/ContentGraphFinder.php | 61 --- .../Classes/ContentRepository.php | 28 +- .../Feature/Common/ConstraintChecks.php | 6 +- .../Command/ReopenContentStream.php | 12 +- .../Event/ContentStreamWasReopened.php | 6 +- .../Feature/ContentStreamCommandHandler.php | 6 +- .../Feature/ContentStreamEventStreamName.php | 2 + .../Feature/WorkspaceCommandHandler.php | 14 +- .../Command/CreateRootWorkspace.php | 12 +- .../Command/CreateWorkspace.php | 16 +- .../Event/RootWorkspaceWasCreated.php | 6 - .../Event/WorkspaceWasCreated.php | 9 - .../Event/WorkspaceWasRenamed.php | 9 +- .../ContentStream/ContentStreamFinder.monopic | Bin 2857 -> 0 bytes .../ContentStream/ContentStreamFinder.php | 192 --------- .../ContentStream/ContentStreamProjection.php | 407 ------------------ .../ContentStreamProjectionFactory.php | 50 --- .../Projection/Workspace/Workspace.php | 132 ------ .../Projection/Workspace/WorkspaceFinder.php | 197 --------- .../Workspace/WorkspaceProjection.php | 386 ----------------- .../Workspace/WorkspaceProjectionFactory.php | 50 --- .../Workspace/WorkspaceRuntimeCache.php | 77 ---- .../Service/ContentRepositoryBootstrapper.php | 6 +- .../Classes/Service/ContentStreamPruner.php | 50 +-- .../Service/ContentStreamPrunerFactory.php | 5 +- .../Service/WorkspaceMaintenanceService.php | 6 +- .../SharedModel/Workspace/ContentStream.php | 36 ++ ...treamState.php => ContentStreamStatus.php} | 16 +- .../SharedModel/Workspace/ContentStreams.php | 99 +++++ .../SharedModel/Workspace/Workspace.php | 63 +++ .../Workspace/WorkspaceDescription.php | 52 --- .../SharedModel/Workspace/WorkspaceFilter.php | 37 ++ .../SharedModel/Workspace/WorkspaceName.php | 1 + .../Workspace/WorkspaceStatus.php | 2 +- .../SharedModel/Workspace/WorkspaceTitle.php | 52 --- .../Workspace/Workspaces.php | 25 +- .../src/ExportServiceFactory.php | 2 +- .../Processors/EventStoreImportProcessor.php | 6 - .../Behavior/Bootstrap/FeatureContext.php | 6 +- .../src/NodeMigrationService.php | 11 +- .../Bootstrap/CRTestSuiteRuntimeVariables.php | 6 +- .../Features/Bootstrap/CRTestSuiteTrait.php | 36 +- .../Bootstrap/Features/WorkspaceCreation.php | 8 - .../Features/Bootstrap/ProjectedNodeTrait.php | 20 +- .../Configuration/Objects.yaml | 17 - .../Configuration/Settings.yaml | 4 - .../Classes/Controller/UsageController.php | 2 +- .../Private/Templates/Usage/RelatedNodes.html | 2 +- .../Service/AssetUsageSyncService.php | 2 +- .../Command/WorkspaceCommandController.php | 9 +- .../Module/Administration/SitesController.php | 2 +- .../Classes/Domain/Service/UserService.php | 2 +- .../FrontendRouting/NodeAddressFactory.php | 1 + .../Classes/Fusion/Helper/CachingHelper.php | 2 +- .../Routing/Cache/RouteCacheFlusher.php | 2 +- Neos.Neos/Classes/Service/LinkingService.php | 4 +- .../Service/TimeableNodeVisibilityService.php | 1 + 75 files changed, 827 insertions(+), 1986 deletions(-) create mode 100644 Neos.ContentRepository.Core/Classes/ContentGraphAdapter.php delete mode 100644 Neos.ContentRepository.Core/Classes/ContentGraphFinder.php delete mode 100644 Neos.ContentRepository.Core/Classes/Projection/ContentStream/ContentStreamFinder.monopic delete mode 100644 Neos.ContentRepository.Core/Classes/Projection/ContentStream/ContentStreamFinder.php delete mode 100644 Neos.ContentRepository.Core/Classes/Projection/ContentStream/ContentStreamProjection.php delete mode 100644 Neos.ContentRepository.Core/Classes/Projection/ContentStream/ContentStreamProjectionFactory.php delete mode 100644 Neos.ContentRepository.Core/Classes/Projection/Workspace/Workspace.php delete mode 100644 Neos.ContentRepository.Core/Classes/Projection/Workspace/WorkspaceFinder.php delete mode 100644 Neos.ContentRepository.Core/Classes/Projection/Workspace/WorkspaceProjection.php delete mode 100644 Neos.ContentRepository.Core/Classes/Projection/Workspace/WorkspaceProjectionFactory.php delete mode 100644 Neos.ContentRepository.Core/Classes/Projection/Workspace/WorkspaceRuntimeCache.php create mode 100644 Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStream.php rename Neos.ContentRepository.Core/Classes/SharedModel/Workspace/{ContentStreamState.php => ContentStreamStatus.php} (90%) create mode 100644 Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStreams.php create mode 100644 Neos.ContentRepository.Core/Classes/SharedModel/Workspace/Workspace.php delete mode 100644 Neos.ContentRepository.Core/Classes/SharedModel/Workspace/WorkspaceDescription.php create mode 100644 Neos.ContentRepository.Core/Classes/SharedModel/Workspace/WorkspaceFilter.php rename Neos.ContentRepository.Core/Classes/{Projection => SharedModel}/Workspace/WorkspaceStatus.php (97%) delete mode 100644 Neos.ContentRepository.Core/Classes/SharedModel/Workspace/WorkspaceTitle.php rename Neos.ContentRepository.Core/Classes/{Projection => SharedModel}/Workspace/Workspaces.php (78%) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php index 4aee9e1b41c..0599dba402f 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php @@ -22,12 +22,19 @@ use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStream; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreams; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamStatus; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspaces; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceStatus; +use Neos\EventStore\Model\Event\Version; /** * @internal only used inside the - * @see ContentGraphFinder + * @see ContentGraphAdapter */ final readonly class ContentGraphFactory implements ContentGraphFactoryInterface { @@ -42,32 +49,168 @@ public function __construct( public function buildForWorkspace(WorkspaceName $workspaceName): ContentGraph { - $currentContentStreamIdStatement = <<findWorkspaceByName($workspaceName); + + if ($workspace === null) { + throw WorkspaceDoesNotExist::butWasSupposedTo($workspaceName); + } + return $this->buildForWorkspaceAndContentStream($workspace->workspaceName, $workspace->currentContentStreamId); + } + + public function buildForWorkspaceAndContentStream(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraph + { + return new ContentGraph($this->dbal, $this->nodeFactory, $this->contentRepositoryId, $this->nodeTypeManager, $this->tableNames, $workspaceName, $contentStreamId); + } + + public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace + { + $workspaceByNameStatement = <<tableNames->workspace()} WHERE - workspaceName = :workspaceName + name = :workspaceName LIMIT 1 SQL; try { - $currentContentStreamId = $this->dbal->fetchOne($currentContentStreamIdStatement, [ + $row = $this->dbal->fetchAssociative($workspaceByNameStatement, [ 'workspaceName' => $workspaceName->value, ]); } catch (Exception $e) { - throw new \RuntimeException(sprintf('Failed to load workspace content stream id from database: %s', $e->getMessage()), 1716486077, $e); + throw new \RuntimeException(sprintf('Failed to load workspace from database: %s', $e->getMessage()), 1716486077, $e); + } + if ($row === false) { + return null; } + return self::workspaceFromDatabaseRow($row); + } - if ($currentContentStreamId === false) { - throw WorkspaceDoesNotExist::butWasSupposedTo($workspaceName); + public function getWorkspaces(): Workspaces + { + $workspacesStatement = <<tableNames->workspace()} + SQL; + try { + $rows = $this->dbal->fetchAllAssociative($workspacesStatement); + } catch (Exception $e) { + throw new \RuntimeException(sprintf('Failed to load workspaces from database: %s', $e->getMessage()), 1716902981, $e); + } + return Workspaces::fromArray(array_map(self::workspaceFromDatabaseRow(...), $rows)); + } + + public function findContentStreamById(ContentStreamId $contentStreamId): ?ContentStream + { + $contentStreamByIdStatement = <<tableNames->contentStream()} + WHERE + id = :contentStreamId + LIMIT 1 + SQL; + try { + $row = $this->dbal->fetchAssociative($contentStreamByIdStatement, [ + 'contentStreamId' => $contentStreamId->value, + ]); + } catch (Exception $e) { + throw new \RuntimeException(sprintf('Failed to load content stream from database: %s', $e->getMessage()), 1716903166, $e); } + if ($row === false) { + return null; + } + return self::contentStreamFromDatabaseRow($row); + } - return $this->buildForWorkspaceAndContentStream($workspaceName, ContentStreamId::fromString($currentContentStreamId)); + public function getContentStreams(): ContentStreams + { + $contentStreamsStatement = <<tableNames->contentStream()} + SQL; + try { + $rows = $this->dbal->fetchAllAssociative($contentStreamsStatement); + } catch (Exception $e) { + throw new \RuntimeException(sprintf('Failed to load content streams from database: %s', $e->getMessage()), 1716903042, $e); + } + return ContentStreams::fromArray(array_map(self::contentStreamFromDatabaseRow(...), $rows)); } - public function buildForWorkspaceAndContentStream(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraph + public function getUnusedAndRemovedContentStreamIds(): iterable { - return new ContentGraph($this->dbal, $this->nodeFactory, $this->contentRepositoryId, $this->nodeTypeManager, $this->tableNames, $workspaceName, $contentStreamId); + $removedContentStreamIdsStatement = <<tableNames->contentStream()} + WHERE + status = :inUseStatus + AND removed = false + UNION + -- now, when a content stream is in use by a workspace, its source content stream is + -- also "transitively" in use. + SELECT + sourceContentStreamId + FROM + {$this->tableNames->contentStream()} + JOIN transitiveUsedContentStreams ON {$this->tableNames->contentStream()}.id = transitiveUsedContentStreams.id + WHERE + {$this->tableNames->contentStream()}.sourceContentStreamId IS NOT NULL + ) + -- now, we check for removed content streams which we do not need anymore transitively + SELECT + id + FROM + {$this->tableNames->contentStream()} AS cs + WHERE + removed = true + AND NOT EXISTS ( + SELECT 1 + FROM transitiveUsedContentStreams + WHERE cs.id = transitiveUsedContentStreams.id + ) + SQL; + try { + $contentStreamIds = $this->dbal->fetchFirstColumn($removedContentStreamIdsStatement, [ + 'inUseStatus' => ContentStreamStatus::IN_USE_BY_WORKSPACE->value + ]); + } catch (Exception $e) { + throw new \RuntimeException(sprintf('Failed to load unused and removed content stream ids from database: %s', $e->getMessage()), 1716914615, $e); + } + return array_map(ContentStreamId::fromString(...), $contentStreamIds); + } + + /** + * @param array $row + */ + private static function workspaceFromDatabaseRow(array $row): Workspace + { + return new Workspace( + WorkspaceName::fromString($row['name']), + isset($row['baseWorkspaceName']) ? WorkspaceName::fromString($row['baseWorkspaceName']) : null, + ContentStreamId::fromString($row['currentContentStreamId']), + WorkspaceStatus::from($row['status']), + ); + } + + /** + * @param array $row + */ + private static function contentStreamFromDatabaseRow(array $row): ContentStream + { + return new ContentStream( + ContentStreamId::fromString($row['id']), + isset($row['sourceContentStreamId']) ? ContentStreamId::fromString($row['sourceContentStreamId']) : null, + ContentStreamStatus::from($row['status']), + Version::fromInteger((int)$row['version']), + ); } } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index 0b6225571e5..0b096bf1dce 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\ContentGraphFinder; +use Neos\ContentRepository\Core\ContentGraphAdapter; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; @@ -70,12 +70,12 @@ 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\ContentStreamState; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamStatus; use Neos\EventStore\Model\Event\SequenceNumber; use Neos\EventStore\Model\EventEnvelope; /** - * @implements ProjectionInterface + * @implements ProjectionInterface * @internal but the graph projection is api */ final class DoctrineDbalContentGraphProjection implements ProjectionInterface @@ -97,7 +97,7 @@ public function __construct( private readonly ProjectionContentGraph $projectionContentGraph, private readonly ContentGraphTableNames $tableNames, private readonly DimensionSpacePointsRepository $dimensionSpacePointsRepository, - private readonly ContentGraphFinder $contentGraphFinder + private readonly ContentGraphAdapter $contentGraphAdapter ) { $this->checkpointStorage = new DbalCheckpointStorage( $this->dbal, @@ -116,14 +116,14 @@ public function setUp(): void $this->dbal->getSchemaManager()->tablesExist([$legacyWorkspaceTableName]) && !$this->dbal->getSchemaManager()->tablesExist([$this->tableNames->workspace()]) ) { - $statements[] = 'INSERT INTO ' . $this->tableNames->workspace() . ' (workspacename, baseworkspacename, currentcontentstreamid, status) SELECT workspacename, baseworkspacename, currentcontentstreamid, status FROM ' . $legacyWorkspaceTableName; + $statements[] = 'INSERT INTO ' . $this->tableNames->workspace() . ' (name, baseWorkspaceName, currentContentStreamId, status) SELECT workspaceName AS name, baseWorkspaceName, currentContentStreamId, status FROM ' . $legacyWorkspaceTableName; } $legacyContentStreamTableName = str_replace('_p_graph_contentstream', '_p_contentstream', $this->tableNames->contentStream()); if ( $this->dbal->getSchemaManager()->tablesExist([$legacyContentStreamTableName]) && !$this->dbal->getSchemaManager()->tablesExist([$this->tableNames->contentStream()]) ) { - $statements[] = 'INSERT INTO ' . $this->tableNames->contentStream() . ' (contentStreamId, version, sourceContentStreamId, state, removed) SELECT contentStreamId, version, sourceContentStreamId, state, removed FROM ' . $legacyContentStreamTableName; + $statements[] = 'INSERT INTO ' . $this->tableNames->contentStream() . ' (id, version, sourceContentStreamId, status, removed) SELECT contentStreamId AS id, version, sourceContentStreamId, state AS status, removed FROM ' . $legacyContentStreamTableName; } // /MIGRATION @@ -175,9 +175,9 @@ public function getCheckpointStorage(): DbalCheckpointStorage return $this->checkpointStorage; } - public function getState(): ContentGraphFinder + public function getState(): ContentGraphAdapter { - return $this->contentGraphFinder; + return $this->contentGraphAdapter; } public function canHandle(EventInterface $event): bool @@ -215,6 +215,7 @@ public function canHandle(EventInterface $event): bool WorkspaceWasRebased::class, WorkspaceWasRemoved::class, ]); + // todo handle all EmbedsContentStreamId?? } public function apply(EventInterface $event, EventEnvelope $eventEnvelope): void @@ -260,12 +261,12 @@ public function apply(EventInterface $event, EventEnvelope $eventEnvelope): void private function whenContentStreamWasClosed(ContentStreamWasClosed $event): void { - $this->updateContentStreamState($event->contentStreamId, ContentStreamState::STATE_CLOSED); + $this->updateContentStreamStatus($event->contentStreamId, ContentStreamStatus::CLOSED); } private function whenContentStreamWasCreated(ContentStreamWasCreated $event): void { - $this->createContentStream($event->contentStreamId, ContentStreamState::STATE_CREATED); + $this->createContentStream($event->contentStreamId, ContentStreamStatus::CREATED); } private function whenContentStreamWasForked(ContentStreamWasForked $event): void @@ -304,7 +305,7 @@ private function whenContentStreamWasForked(ContentStreamWasForked $event): void // NOTE: as reference edges are attached to Relation Anchor Points (and they are lazily copy-on-written), // we do not need to copy reference edges here (but we need to do it during copy on write). - $this->createContentStream($event->newContentStreamId, ContentStreamState::STATE_FORKED); + $this->createContentStream($event->newContentStreamId, ContentStreamStatus::FORKED, $event->sourceContentStreamId); } private function whenContentStreamWasRemoved(ContentStreamWasRemoved $event): void @@ -354,7 +355,7 @@ private function whenContentStreamWasRemoved(ContentStreamWasRemoved $event): vo private function whenContentStreamWasReopened(ContentStreamWasReopened $event): void { - $this->updateContentStreamState($event->contentStreamId, $event->previousState); + $this->updateContentStreamStatus($event->contentStreamId, $event->previousState); } private function whenDimensionShineThroughWasAdded(DimensionShineThroughWasAdded $event): void @@ -711,7 +712,7 @@ private function whenRootWorkspaceWasCreated(RootWorkspaceWasCreated $event): vo $this->createWorkspace($event->workspaceName, null, $event->newContentStreamId); // the content stream is in use now - $this->updateContentStreamState($event->newContentStreamId, ContentStreamState::STATE_IN_USE_BY_WORKSPACE); + $this->updateContentStreamStatus($event->newContentStreamId, ContentStreamStatus::IN_USE_BY_WORKSPACE); } private function whenSubtreeWasTagged(SubtreeWasTagged $event): void @@ -732,7 +733,7 @@ private function whenWorkspaceBaseWorkspaceWasChanged(WorkspaceBaseWorkspaceWasC private function whenWorkspaceRebaseFailed(WorkspaceRebaseFailed $event): void { $this->markWorkspaceAsOutdatedConflict($event->workspaceName); - $this->updateContentStreamState($event->candidateContentStreamId, ContentStreamState::STATE_REBASE_ERROR); + $this->updateContentStreamStatus($event->candidateContentStreamId, ContentStreamStatus::REBASE_ERROR); } private function whenWorkspaceWasCreated(WorkspaceWasCreated $event): void @@ -740,7 +741,7 @@ private function whenWorkspaceWasCreated(WorkspaceWasCreated $event): void $this->createWorkspace($event->workspaceName, $event->baseWorkspaceName, $event->newContentStreamId); // the content stream is in use now - $this->updateContentStreamState($event->newContentStreamId, ContentStreamState::STATE_IN_USE_BY_WORKSPACE); + $this->updateContentStreamStatus($event->newContentStreamId, ContentStreamStatus::IN_USE_BY_WORKSPACE); } private function whenWorkspaceWasDiscarded(WorkspaceWasDiscarded $event): void @@ -750,9 +751,9 @@ private function whenWorkspaceWasDiscarded(WorkspaceWasDiscarded $event): void $this->markDependentWorkspacesAsOutdated($event->workspaceName); // the new content stream is in use now - $this->updateContentStreamState($event->newContentStreamId, ContentStreamState::STATE_IN_USE_BY_WORKSPACE); + $this->updateContentStreamStatus($event->newContentStreamId, ContentStreamStatus::IN_USE_BY_WORKSPACE); // the previous content stream is no longer in use - $this->updateContentStreamState($event->previousContentStreamId, ContentStreamState::STATE_NO_LONGER_IN_USE); + $this->updateContentStreamStatus($event->previousContentStreamId, ContentStreamStatus::NO_LONGER_IN_USE); } private function whenWorkspaceWasPartiallyDiscarded(WorkspaceWasPartiallyDiscarded $event): void @@ -761,10 +762,10 @@ private function whenWorkspaceWasPartiallyDiscarded(WorkspaceWasPartiallyDiscard $this->markDependentWorkspacesAsOutdated($event->workspaceName); // the new content stream is in use now - $this->updateContentStreamState($event->newContentStreamId, ContentStreamState::STATE_IN_USE_BY_WORKSPACE); + $this->updateContentStreamStatus($event->newContentStreamId, ContentStreamStatus::IN_USE_BY_WORKSPACE); // the previous content stream is no longer in use - $this->updateContentStreamState($event->previousContentStreamId, ContentStreamState::STATE_NO_LONGER_IN_USE); + $this->updateContentStreamStatus($event->previousContentStreamId, ContentStreamStatus::NO_LONGER_IN_USE); } private function whenWorkspaceWasPartiallyPublished(WorkspaceWasPartiallyPublished $event): void @@ -779,10 +780,10 @@ private function whenWorkspaceWasPartiallyPublished(WorkspaceWasPartiallyPublish $this->markDependentWorkspacesAsOutdated($event->sourceWorkspaceName); // the new content stream is in use now - $this->updateContentStreamState($event->newSourceContentStreamId, ContentStreamState::STATE_IN_USE_BY_WORKSPACE); + $this->updateContentStreamStatus($event->newSourceContentStreamId, ContentStreamStatus::IN_USE_BY_WORKSPACE); // the previous content stream is no longer in use - $this->updateContentStreamState($event->previousSourceContentStreamId, ContentStreamState::STATE_NO_LONGER_IN_USE); + $this->updateContentStreamStatus($event->previousSourceContentStreamId, ContentStreamStatus::NO_LONGER_IN_USE); } private function whenWorkspaceWasPublished(WorkspaceWasPublished $event): void @@ -797,10 +798,10 @@ private function whenWorkspaceWasPublished(WorkspaceWasPublished $event): void $this->markDependentWorkspacesAsOutdated($event->sourceWorkspaceName); // the new content stream is in use now - $this->updateContentStreamState($event->newSourceContentStreamId, ContentStreamState::STATE_IN_USE_BY_WORKSPACE); + $this->updateContentStreamStatus($event->newSourceContentStreamId, ContentStreamStatus::IN_USE_BY_WORKSPACE); // the previous content stream is no longer in use - $this->updateContentStreamState($event->previousSourceContentStreamId, ContentStreamState::STATE_NO_LONGER_IN_USE); + $this->updateContentStreamStatus($event->previousSourceContentStreamId, ContentStreamStatus::NO_LONGER_IN_USE); } private function whenWorkspaceWasRebased(WorkspaceWasRebased $event): void @@ -812,10 +813,10 @@ private function whenWorkspaceWasRebased(WorkspaceWasRebased $event): void $this->markWorkspaceAsUpToDate($event->workspaceName); // the new content stream is in use now - $this->updateContentStreamState($event->newContentStreamId, ContentStreamState::STATE_IN_USE_BY_WORKSPACE); + $this->updateContentStreamStatus($event->newContentStreamId, ContentStreamStatus::IN_USE_BY_WORKSPACE); // the previous content stream is no longer in use - $this->updateContentStreamState($event->previousContentStreamId, ContentStreamState::STATE_NO_LONGER_IN_USE); + $this->updateContentStreamStatus($event->previousContentStreamId, ContentStreamStatus::NO_LONGER_IN_USE); } private function whenWorkspaceWasRemoved(WorkspaceWasRemoved $event): void @@ -843,6 +844,7 @@ private function truncateDatabaseTables(): void $this->dbal->executeQuery('TRUNCATE table ' . $this->tableNames->referenceRelation()); $this->dbal->executeQuery('TRUNCATE table ' . $this->tableNames->dimensionSpacePoints()); $this->dbal->executeQuery('TRUNCATE table ' . $this->tableNames->workspace()); + $this->dbal->executeQuery('TRUNCATE table ' . $this->tableNames->contentStream()); } catch (DBALException $e) { throw new \RuntimeException(sprintf('Failed to truncate database tables for projection %s: %s', self::class, $e->getMessage()), 1716478318, $e); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php index f7788df7089..4e063b7e412 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php @@ -8,7 +8,7 @@ use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\DimensionSpacePointsRepository; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\NodeFactory; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ProjectionContentGraph; -use Neos\ContentRepository\Core\ContentGraphFinder; +use Neos\ContentRepository\Core\ContentGraphAdapter; use Neos\ContentRepository\Core\Factory\ProjectionFactoryDependencies; use Neos\ContentRepository\Core\Projection\ProjectionFactoryInterface; @@ -58,7 +58,7 @@ public function build( ), $tableNames, $dimensionSpacePointsRepository, - new ContentGraphFinder($contentGraphFactory) + new ContentGraphAdapter($contentGraphFactory) ); } } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php index 98c11ddc39a..e10b4f16c62 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php @@ -112,23 +112,23 @@ private function createReferenceRelationTable(): Table private function createWorkspaceTable(): Table { $workspaceTable = self::createTable($this->tableNames->workspace(), [ - DbalSchemaFactory::columnForWorkspaceName('workspacename')->setNotnull(true), - DbalSchemaFactory::columnForWorkspaceName('baseworkspacename')->setNotnull(false), - DbalSchemaFactory::columnForContentStreamId('currentcontentstreamid')->setNotNull(true), + DbalSchemaFactory::columnForWorkspaceName('name')->setNotnull(true), + DbalSchemaFactory::columnForWorkspaceName('baseWorkspaceName')->setNotnull(false), + DbalSchemaFactory::columnForContentStreamId('currentContentStreamId')->setNotNull(true), (new Column('status', self::type(Types::BINARY)))->setLength(20)->setNotnull(false), ]); - return $workspaceTable->setPrimaryKey(['workspacename']); + return $workspaceTable->setPrimaryKey(['name']); } private function createContentStreamTable(): Table { return self::createTable($this->tableNames->contentStream(), [ - DbalSchemaFactory::columnForContentStreamId('contentStreamId')->setNotnull(true), + DbalSchemaFactory::columnForContentStreamId('id')->setNotnull(true), (new Column('version', Type::getType(Types::INTEGER)))->setNotnull(true), DbalSchemaFactory::columnForContentStreamId('sourceContentStreamId')->setNotnull(false), // Should become a DB ENUM (unclear how to configure with DBAL) or int (latter needs adaption to code) - (new Column('state', Type::getType(Types::BINARY)))->setLength(20)->setNotnull(true), + (new Column('status', Type::getType(Types::BINARY)))->setLength(20)->setNotnull(true), (new Column('removed', Type::getType(Types::BOOLEAN)))->setDefault(false)->setNotnull(false) ]); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/ContentStream.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/ContentStream.php index bc39979bf46..dfe1f7f46d5 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/ContentStream.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/ContentStream.php @@ -5,7 +5,7 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamState; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamStatus; use Neos\EventStore\Model\Event\Version; /** @@ -15,22 +15,22 @@ */ trait ContentStream { - private function createContentStream(ContentStreamId $contentStreamId, ContentStreamState $state, ?ContentStreamId $sourceContentStreamId = null): void + private function createContentStream(ContentStreamId $contentStreamId, ContentStreamStatus $status, ?ContentStreamId $sourceContentStreamId = null): void { $this->dbal->insert($this->tableNames->contentStream(), [ - 'contentStreamId' => $contentStreamId->value, + 'id' => $contentStreamId->value, 'sourceContentStreamId' => $sourceContentStreamId?->value, 'version' => 0, - 'state' => $state->value, + 'status' => $status->value, ]); } - private function updateContentStreamState(ContentStreamId $contentStreamId, ContentStreamState $state): void + private function updateContentStreamStatus(ContentStreamId $contentStreamId, ContentStreamStatus $status): void { $this->dbal->update($this->tableNames->contentStream(), [ - 'state' => $state->value, + 'status' => $status->value, ], [ - 'contentStreamId' => $contentStreamId->value + 'id' => $contentStreamId->value ]); } @@ -39,7 +39,7 @@ private function removeContentStream(ContentStreamId $contentStreamId): void $this->dbal->update($this->tableNames->contentStream(), [ 'removed' => true, ], [ - 'contentStreamId' => $contentStreamId->value + 'id' => $contentStreamId->value ]); } @@ -48,7 +48,7 @@ private function updateContentStreamVersion(ContentStreamId $contentStreamId, Ve $this->dbal->update($this->tableNames->contentStream(), [ 'version' => $version->value, ], [ - 'contentStreamId' => $contentStreamId->value, + 'id' => $contentStreamId->value, ]); } } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/Workspace.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/Workspace.php index bfe81c8dc5d..e4c28fd6cec 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/Workspace.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/Workspace.php @@ -4,7 +4,7 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\Feature; -use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceStatus; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceStatus; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; @@ -18,7 +18,7 @@ trait Workspace private function createWorkspace(WorkspaceName $workspaceName, ?WorkspaceName $baseWorkspaceName, ContentStreamId $contentStreamId): void { $this->dbal->insert($this->tableNames->workspace(), [ - 'workspaceName' => $workspaceName->value, + 'name' => $workspaceName->value, 'baseWorkspaceName' => $baseWorkspaceName?->value, 'currentContentStreamId' => $contentStreamId->value, 'status' => WorkspaceStatus::UP_TO_DATE->value @@ -29,7 +29,7 @@ private function removeWorkspace(WorkspaceName $workspaceName): void { $this->dbal->delete( $this->tableNames->workspace(), - ['workspaceName' => $workspaceName->value] + ['name' => $workspaceName->value] ); } @@ -52,7 +52,7 @@ private function updateWorkspaceContentStreamId( $this->dbal->update($this->tableNames->workspace(), [ 'currentContentStreamId' => $contentStreamId->value, ], [ - 'workspaceName' => $workspaceName->value + 'name' => $workspaceName->value ]); } @@ -62,7 +62,7 @@ private function markWorkspaceAsUpToDate(WorkspaceName $workspaceName): void UPDATE ' . $this->tableNames->workspace() . ' SET status = :upToDate WHERE - workspacename = :workspaceName + name = :workspaceName ', [ 'upToDate' => WorkspaceStatus::UP_TO_DATE->value, 'workspaceName' => $workspaceName->value @@ -89,7 +89,7 @@ private function markWorkspaceAsOutdated(WorkspaceName $workspaceName): void SET status = :outdated WHERE - workspacename = :workspaceName + name = :workspaceName ', [ 'outdated' => WorkspaceStatus::OUTDATED->value, 'workspaceName' => $workspaceName->value @@ -103,7 +103,7 @@ private function markWorkspaceAsOutdatedConflict(WorkspaceName $workspaceName): SET status = :outdatedConflict WHERE - workspacename = :workspaceName + name = :workspaceName ', [ 'outdatedConflict' => WorkspaceStatus::OUTDATED_CONFLICT->value, 'workspaceName' => $workspaceName->value diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperGraphFactory.php b/Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperGraphFactory.php index 974c84d17ca..f9d28d0dd68 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperGraphFactory.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperGraphFactory.php @@ -8,17 +8,21 @@ use Neos\ContentGraph\PostgreSQLAdapter\Domain\Repository\ContentHypergraph; use Neos\ContentGraph\PostgreSQLAdapter\Domain\Repository\NodeFactory; use Neos\ContentRepository\Core\ContentGraphFactoryInterface; -use Neos\ContentRepository\Core\ContentGraphFinder; +use Neos\ContentRepository\Core\ContentGraphAdapter; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStream; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreams; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspaces; /** * @internal only used within - * @see ContentGraphFinder + * @see ContentGraphAdapter */ final readonly class ContentHyperGraphFactory implements ContentGraphFactoryInterface { @@ -61,4 +65,34 @@ public function buildForWorkspaceAndContentStream(WorkspaceName $workspaceName, { return new ContentHyperGraph($this->dbal, $this->nodeFactory, $this->contentRepositoryId, $this->nodeTypeManager, $this->tableNamePrefix, $workspaceName, $contentStreamId); } + + public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace + { + // TODO: Implement findWorkspaceByName() method. + return null; + } + + public function getWorkspaces(): Workspaces + { + // TODO: Implement getWorkspaces() method. + return Workspaces::createEmpty(); + } + + public function findContentStreamById(ContentStreamId $contentStreamId): ?ContentStream + { + // TODO: Implement findContentStreamById() method. + return null; + } + + public function getContentStreams(): ContentStreams + { + // TODO: Implement getContentStreams() method. + return ContentStreams::createEmpty(); + } + + public function getUnusedAndRemovedContentStreamIds(): iterable + { + // TODO: Implement getUnusedAndRemovedContentStreamIds() method. + return []; + } } diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Projection/HypergraphProjection.php b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Projection/HypergraphProjection.php index 6b683bbd08d..afc1b9c0249 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\ContentGraphFinder; +use Neos\ContentRepository\Core\ContentGraphAdapter; use Neos\ContentRepository\Core\EventStore\EventInterface; use Neos\ContentRepository\Core\Feature\ContentStreamForking\Event\ContentStreamWasForked; use Neos\ContentRepository\Core\Feature\NodeCreation\Event\NodeAggregateWithNodeWasCreated; @@ -52,7 +52,7 @@ /** * The alternate reality-aware hypergraph projector for the PostgreSQL backend via Doctrine DBAL * - * @implements ProjectionInterface + * @implements ProjectionInterface * @internal the parent Content Graph is public */ final class HypergraphProjection implements ProjectionInterface @@ -73,7 +73,7 @@ final class HypergraphProjection implements ProjectionInterface public function __construct( private readonly Connection $dbal, private readonly string $tableNamePrefix, - private readonly ContentGraphFinder $contentGraphFinder + private readonly ContentGraphAdapter $contentGraphAdapter ) { $this->projectionHypergraph = new ProjectionHypergraph($this->dbal, $this->tableNamePrefix); $this->checkpointStorage = new DbalCheckpointStorage( @@ -219,9 +219,9 @@ public function getCheckpointStorage(): DbalCheckpointStorage return $this->checkpointStorage; } - public function getState(): ContentGraphFinder + public function getState(): ContentGraphAdapter { - return $this->contentGraphFinder; + return $this->contentGraphAdapter; } protected function getProjectionHypergraph(): ProjectionHypergraph diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/HypergraphProjectionFactory.php b/Neos.ContentGraph.PostgreSQLAdapter/src/HypergraphProjectionFactory.php index edbb84fbb8e..83aab2d3553 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/HypergraphProjectionFactory.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/HypergraphProjectionFactory.php @@ -7,7 +7,7 @@ use Doctrine\DBAL\Connection; use Neos\ContentGraph\PostgreSQLAdapter\Domain\Projection\HypergraphProjection; use Neos\ContentGraph\PostgreSQLAdapter\Domain\Repository\NodeFactory; -use Neos\ContentRepository\Core\ContentGraphFinder; +use Neos\ContentRepository\Core\ContentGraphAdapter; use Neos\ContentRepository\Core\Factory\ProjectionFactoryDependencies; use Neos\ContentRepository\Core\Projection\ProjectionFactoryInterface; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; @@ -45,7 +45,7 @@ public function build( return new HypergraphProjection( $this->dbal, $tableNamePrefix, - new ContentGraphFinder(new ContentHyperGraphFactory($this->dbal, $nodeFactory, $projectionFactoryDependencies->contentRepositoryId, $projectionFactoryDependencies->nodeTypeManager, $tableNamePrefix)) + new ContentGraphAdapter(new ContentHyperGraphFactory($this->dbal, $nodeFactory, $projectionFactoryDependencies->contentRepositoryId, $projectionFactoryDependencies->nodeTypeManager, $tableNamePrefix)) ); } } diff --git a/Neos.ContentRepository.BehavioralTests/Classes/Command/PerformanceMeasurementService.php b/Neos.ContentRepository.BehavioralTests/Classes/Command/PerformanceMeasurementService.php index 21c344d2f9d..ff37b268bce 100644 --- a/Neos.ContentRepository.BehavioralTests/Classes/Command/PerformanceMeasurementService.php +++ b/Neos.ContentRepository.BehavioralTests/Classes/Command/PerformanceMeasurementService.php @@ -36,9 +36,7 @@ use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateClassification; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceDescription; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceTitle; use Neos\ContentRepositoryRegistry\Factory\EventStore\DoctrineEventStoreFactory; use Neos\EventStore\Model\EventStream\ExpectedVersion; @@ -81,8 +79,6 @@ public function createNodesForPerformanceTest(int $nodesPerLevel, int $levels): { $this->contentRepository->handle(CreateRootWorkspace::create( WorkspaceName::forLive(), - WorkspaceTitle::fromString('live'), - WorkspaceDescription::fromString(''), $this->contentStreamId )); diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRemoval/RemoveNodesFromAggregate.wip b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRemoval/RemoveNodesFromAggregate.wip index 3fe26bf8051..abf1480df35 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRemoval/RemoveNodesFromAggregate.wip +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRemoval/RemoveNodesFromAggregate.wip @@ -26,8 +26,6 @@ Feature: Remove Nodes from Aggregate And the command CreateRootWorkspace is executed with payload: | Key | Value | | workspaceName | "live" | - | workspaceTitle | "Live" | - | workspaceDescription | "The live workspace" | | newContentStreamId | "live-cs-identifier" | And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/PruneContentStreams.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/PruneContentStreams.feature index 4cedde28f3c..b275725c374 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/PruneContentStreams.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/PruneContentStreams.feature @@ -20,8 +20,8 @@ Feature: If content streams are not in use anymore by the workspace, they can be | nodeTypeName | "Neos.ContentRepository:Root" | Scenario: content streams are marked as IN_USE_BY_WORKSPACE properly after creation - Then the content stream "cs-identifier" has state "IN_USE_BY_WORKSPACE" - Then the content stream "non-existing" has state "" + Then the content stream "cs-identifier" has status "IN_USE_BY_WORKSPACE" + Then the content stream "non-existing" does not exist Scenario: on creating a nested workspace, the new content stream is marked as IN_USE_BY_WORKSPACE. When the command CreateWorkspace is executed with payload: @@ -30,7 +30,7 @@ Feature: If content streams are not in use anymore by the workspace, they can be | baseWorkspaceName | "live" | | newContentStreamId | "user-cs-identifier" | - Then the content stream "user-cs-identifier" has state "IN_USE_BY_WORKSPACE" + Then the content stream "user-cs-identifier" has status "IN_USE_BY_WORKSPACE" Scenario: when rebasing a nested workspace, the new content stream will be marked as IN_USE_BY_WORKSPACE; and the old content stream is NO_LONGER_IN_USE. When the command CreateWorkspace is executed with payload: @@ -43,8 +43,8 @@ Feature: If content streams are not in use anymore by the workspace, they can be | workspaceName | "user-test" | When I am in workspace "user-test" and dimension space point {} - Then the current content stream has state "IN_USE_BY_WORKSPACE" - And the content stream "user-cs-identifier" has state "NO_LONGER_IN_USE" + Then the current content stream has status "IN_USE_BY_WORKSPACE" + And the content stream "user-cs-identifier" has status "NO_LONGER_IN_USE" Scenario: when pruning content streams, NO_LONGER_IN_USE content streams will be properly cleaned from the graph projection. diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/RemoveNodeAggregateWithDimensions.wip b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/RemoveNodeAggregateWithDimensions.wip index 30f13ab7578..397a2c0262c 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/RemoveNodeAggregateWithDimensions.wip +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/RemoveNodeAggregateWithDimensions.wip @@ -15,8 +15,6 @@ Feature: Remove NodeAggregate And the command CreateRootWorkspace is executed with payload: | Key | Value | | workspaceName | "live" | - | workspaceTitle | "Live" | - | workspaceDescription | "The live workspace" | | newContentStreamId | "live-cs-identifier" | And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/RemoveNodesFromAggregate.wip b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/RemoveNodesFromAggregate.wip index 3fe26bf8051..abf1480df35 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/RemoveNodesFromAggregate.wip +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/RemoveNodesFromAggregate.wip @@ -26,8 +26,6 @@ Feature: Remove Nodes from Aggregate And the command CreateRootWorkspace is executed with payload: | Key | Value | | workspaceName | "live" | - | workspaceTitle | "Live" | - | workspaceDescription | "The live workspace" | | newContentStreamId | "live-cs-identifier" | And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Functional/Feature/WorkspacePublication/WorkspaceWritingDuringPublication.php b/Neos.ContentRepository.BehavioralTests/Tests/Functional/Feature/WorkspacePublication/WorkspaceWritingDuringPublication.php index c77ce33d13a..366084abb7b 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Functional/Feature/WorkspacePublication/WorkspaceWritingDuringPublication.php +++ b/Neos.ContentRepository.BehavioralTests/Tests/Functional/Feature/WorkspacePublication/WorkspaceWritingDuringPublication.php @@ -37,9 +37,7 @@ use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamIsClosed; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceDescription; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceTitle; use Neos\ContentRepository\TestSuite\Behavior\Features\Bootstrap\CRTestSuiteTrait; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\EventStore\Exception\ConcurrencyException; @@ -106,8 +104,6 @@ public function getContentDimensionsOrderedByPriority(): array $origin = OriginDimensionSpacePoint::createWithoutDimensions(); $contentRepository->handle(CreateRootWorkspace::create( WorkspaceName::forLive(), - new WorkspaceTitle('Live'), - new WorkspaceDescription('The live workspace'), ContentStreamId::fromString('live-cs-id') )); $contentRepository->handle(CreateRootNodeAggregateWithNode::create( @@ -128,8 +124,6 @@ public function getContentDimensionsOrderedByPriority(): array $contentRepository->handle(CreateWorkspace::create( WorkspaceName::fromString('user-test'), WorkspaceName::forLive(), - new WorkspaceTitle('User'), - new WorkspaceDescription('The user workspace'), ContentStreamId::fromString('user-cs-id') )); for ($i = 0; $i <= 1000; $i++) { diff --git a/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php b/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php index 3f3515e81f3..896fbfb938f 100644 --- a/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php +++ b/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php @@ -17,10 +17,10 @@ use Neos\ContentRepository\Core\CommandHandler\CommandInterface; use Neos\ContentRepository\Core\CommandHandler\CommandResult; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; -use Neos\ContentRepository\Core\Projection\Workspace\Workspace; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamState; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamStatus; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\EventStore\Model\Event\Version; @@ -36,7 +36,7 @@ final class CommandHandlingDependencies * WorkspaceName->value to ContentGraphInterface * @var array */ - private array $overridenContentGraphInstances = []; + private array $overriddenContentGraphInstances = []; public function __construct(private readonly ContentRepository $contentRepository) { @@ -49,26 +49,30 @@ public function handle(CommandInterface $command): CommandResult public function getContentStreamVersion(ContentStreamId $contentStreamId): Version { - // TODO implement - return Version::fromInteger(1); + $contentStream = $this->contentRepository->findContentStreamById($contentStreamId); + if ($contentStream === null) { + throw new \InvalidArgumentException(sprintf('Failed to find content stream with id "%s"', $contentStreamId->value), 1716902051); + } + return $contentStream->version; } public function contentStreamExists(ContentStreamId $contentStreamId): bool { - // TODO implement - return false; + return $this->contentRepository->findContentStreamById($contentStreamId) !== null; } - public function getContentStreamState(ContentStreamId $contentStreamId): ContentStreamState + public function getContentStreamStatus(ContentStreamId $contentStreamId): ContentStreamStatus { - // TODO implement - return ContentStreamState::STATE_FORKED; + $contentStream = $this->contentRepository->findContentStreamById($contentStreamId); + if ($contentStream === null) { + throw new \InvalidArgumentException(sprintf('Failed to find content stream with id "%s"', $contentStreamId->value), 1716902219); + } + return $contentStream->status; } public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace { - // TODO implement - return null; + return $this->contentRepository->findWorkspaceByName($workspaceName); } /** @@ -76,8 +80,8 @@ public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace */ public function getContentGraph(WorkspaceName $workspaceName): ContentGraphInterface { - if (isset($this->overridenContentGraphInstances[$workspaceName->value])) { - return $this->overridenContentGraphInstances[$workspaceName->value]; + if (isset($this->overriddenContentGraphInstances[$workspaceName->value])) { + return $this->overriddenContentGraphInstances[$workspaceName->value]; } return $this->contentRepository->getContentGraph($workspaceName); @@ -94,17 +98,17 @@ public function getContentGraph(WorkspaceName $workspaceName): ContentGraphInter */ public function overrideContentStreamId(WorkspaceName $workspaceName, ContentStreamId $contentStreamId, \Closure $fn): void { - if (isset($this->overridenContentGraphInstances[$workspaceName->value])) { + if (isset($this->overriddenContentGraphInstances[$workspaceName->value])) { throw new \RuntimeException('Contentstream override for this workspace already in effect, nesting not allowed.', 1715170938); } - $contentGraph = $this->contentRepository->projectionState(ContentGraphFinder::class)->getByWorkspaceNameAndContentStreamId($workspaceName, $contentStreamId); - $this->overridenContentGraphInstances[$workspaceName->value] = $contentGraph; + $contentGraph = $this->contentRepository->projectionState(ContentGraphAdapter::class)->getContentGraphByWorkspaceNameAndContentStreamId($workspaceName, $contentStreamId); + $this->overriddenContentGraphInstances[$workspaceName->value] = $contentGraph; try { $fn(); } finally { - unset($this->overridenContentGraphInstances[$workspaceName->value]); + unset($this->overriddenContentGraphInstances[$workspaceName->value]); } } } diff --git a/Neos.ContentRepository.Core/Classes/ContentGraphAdapter.php b/Neos.ContentRepository.Core/Classes/ContentGraphAdapter.php new file mode 100644 index 00000000000..4e9f8cc27d1 --- /dev/null +++ b/Neos.ContentRepository.Core/Classes/ContentGraphAdapter.php @@ -0,0 +1,139 @@ + Runtime cache for {@see ContentGraphInterface} instances, indexed by their workspace name + */ + private array $contentGraphInstancesByWorkspaceName = []; + + /** + * @var array Runtime cache for {@see Workspace} instances, indexed by their name + */ + private array $workspaceInstancesByName = []; + + /** + * @var array Runtime cache for {@see ContentStream} instances, indexed by their name + */ + private array $contentStreamInstancesById = []; + + public function __construct( + private readonly ContentGraphFactoryInterface $contentGraphFactory + ) { + } + + /** + * To release all held instances, in case a workspace/content stream relation needs to be reset + * + * @internal Should only be needed after write operations (which should take care on their own) + */ + public function forgetInstances(): void + { + // todo do we need to reintroduce the cache??? https://github.com/neos/neos-development-collection/pull/5246 + $this->contentGraphInstancesByWorkspaceName = []; + $this->workspaceInstancesByName = []; + $this->contentStreamInstancesById = []; + } + + public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace + { + if (!array_key_exists($workspaceName->value, $this->workspaceInstancesByName)) { + $workspace = $this->contentGraphFactory->findWorkspaceByName($workspaceName); + if ($workspace === null) { + return null; + } + $this->workspaceInstancesByName[$workspaceName->value] = $workspace; + } + return $this->workspaceInstancesByName[$workspaceName->value]; + } + + public function getWorkspaces(): Workspaces + { + return $this->contentGraphFactory->getWorkspaces(); + } + + public function findContentStreamById(ContentStreamId $contentStreamId): ?ContentStream + { + if (!array_key_exists($contentStreamId->value, $this->contentStreamInstancesById)) { + $contentStream = $this->contentGraphFactory->findContentStreamById($contentStreamId); + if ($contentStream === null) { + return null; + } + $this->contentStreamInstancesById[$contentStreamId->value] = $contentStream; + } + return $this->contentStreamInstancesById[$contentStreamId->value]; + } + + public function getContentStreams(): ContentStreams + { + return $this->contentGraphFactory->getContentStreams(); + } + + /** + * @return iterable + * @internal This is currently only used by the {@see ContentStreamPruner} and might be removed in the future! + */ + public function getUnusedAndRemovedContentStreamIds(): iterable + { + return $this->contentGraphFactory->getUnusedAndRemovedContentStreamIds(); + } + + /** + * 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 + { + if (!array_key_exists($workspaceName->value, $this->contentGraphInstancesByWorkspaceName)) { + $this->contentGraphInstancesByWorkspaceName[$workspaceName->value] = $this->contentGraphFactory->buildForWorkspace($workspaceName); + } + return $this->contentGraphInstancesByWorkspaceName[$workspaceName->value]; + } + + /** + * 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->contentGraphFactory->buildForWorkspaceAndContentStream($workspaceName, $contentStreamId); + } +} diff --git a/Neos.ContentRepository.Core/Classes/ContentGraphFactoryInterface.php b/Neos.ContentRepository.Core/Classes/ContentGraphFactoryInterface.php index 2a766579751..2bf5dab4edc 100644 --- a/Neos.ContentRepository.Core/Classes/ContentGraphFactoryInterface.php +++ b/Neos.ContentRepository.Core/Classes/ContentGraphFactoryInterface.php @@ -16,8 +16,12 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStream; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreams; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspaces; /** * Create implementations of ContentGraphs bound to a specific Workspace and/or ContentStream @@ -32,4 +36,18 @@ interface ContentGraphFactoryInterface public function buildForWorkspace(WorkspaceName $workspaceName): ContentGraphInterface; public function buildForWorkspaceAndContentStream(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphInterface; + + public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace; + + public function getWorkspaces(): Workspaces; + + public function findContentStreamById(ContentStreamId $contentStreamId): ?ContentStream; + + public function getContentStreams(): ContentStreams; + + /** + * @return iterable + * @internal This is currently only used by the {@see ContentStreamPruner} and might be removed in the future! + */ + public function getUnusedAndRemovedContentStreamIds(): iterable; } diff --git a/Neos.ContentRepository.Core/Classes/ContentGraphFinder.php b/Neos.ContentRepository.Core/Classes/ContentGraphFinder.php deleted file mode 100644 index 36a7da4fea1..00000000000 --- a/Neos.ContentRepository.Core/Classes/ContentGraphFinder.php +++ /dev/null @@ -1,61 +0,0 @@ -contentGraphFactory->buildForWorkspace($workspaceName); - } - - /** - * 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 the write side during publishing {@see \Neos\ContentRepository\Core\CommandHandlingDependencies::overrideContentStreamId} - */ - public function getByWorkspaceNameAndContentStreamId(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphInterface - { - return $this->contentGraphFactory->buildForWorkspaceAndContentStream($workspaceName, $contentStreamId); - } -} diff --git a/Neos.ContentRepository.Core/Classes/ContentRepository.php b/Neos.ContentRepository.Core/Classes/ContentRepository.php index d107f399139..12756b66312 100644 --- a/Neos.ContentRepository.Core/Classes/ContentRepository.php +++ b/Neos.ContentRepository.Core/Classes/ContentRepository.php @@ -35,13 +35,15 @@ use Neos\ContentRepository\Core\Projection\ProjectionStateInterface; use Neos\ContentRepository\Core\Projection\ProjectionStatuses; use Neos\ContentRepository\Core\Projection\WithMarkStaleInterface; -use Neos\ContentRepository\Core\Projection\Workspace\Workspace; -use Neos\ContentRepository\Core\Projection\Workspace\Workspaces; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryStatus; use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepository\Core\SharedModel\User\UserIdProviderInterface; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStream; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreams; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspaces; use Neos\EventStore\EventStoreInterface; use Neos\EventStore\Model\Event\EventMetadata; use Neos\EventStore\Model\EventEnvelope; @@ -241,13 +243,22 @@ public function getNodeTypeManager(): NodeTypeManager public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace { - // TODO implement - return null; + return $this->getContentGraphAdapter()->findWorkspaceByName($workspaceName); } public function getWorkspaces(): Workspaces { - // TODO implement + return $this->getContentGraphAdapter()->getWorkspaces(); + } + + public function findContentStreamById(ContentStreamId $contentStreamId): ?ContentStream + { + return $this->getContentGraphAdapter()->findContentStreamById($contentStreamId); + } + + public function getContentStreams(): ContentStreams + { + return $this->getContentGraphAdapter()->getContentStreams(); } /** @@ -255,7 +266,7 @@ public function getWorkspaces(): Workspaces */ public function getContentGraph(WorkspaceName $workspaceName): ContentGraphInterface { - return $this->projectionState(ContentGraphFinder::class)->getByWorkspaceName($workspaceName); + return $this->getContentGraphAdapter()->getContentGraphByWorkspaceName($workspaceName); } public function getVariationGraph(): InterDimensionalVariationGraph @@ -267,4 +278,9 @@ public function getContentDimensionSource(): ContentDimensionSourceInterface { return $this->contentDimensionSource; } + + private function getContentGraphAdapter(): ContentGraphAdapter + { + return $this->projectionState(ContentGraphAdapter::class); + } } diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php index 69259c8a5fa..428ac575ee8 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php @@ -60,7 +60,7 @@ use Neos\ContentRepository\Core\SharedModel\Node\PropertyName; use Neos\ContentRepository\Core\SharedModel\Node\ReferenceName; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamState; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamStatus; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\EventStore\Model\EventStream\ExpectedVersion; @@ -87,8 +87,8 @@ protected function requireContentStream( 1521386692 ); } - $state = $commandHandlingDependencies->getContentStreamState($contentStreamId); - if ($state === ContentStreamState::STATE_CLOSED) { + $state = $commandHandlingDependencies->getContentStreamStatus($contentStreamId); + if ($state === ContentStreamStatus::CLOSED) { throw new ContentStreamIsClosed( 'Content stream "' . $contentStreamId->value . '" is closed.', 1710260081 diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamClosing/Command/ReopenContentStream.php b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamClosing/Command/ReopenContentStream.php index 41436f34d75..10280ae9890 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamClosing/Command/ReopenContentStream.php +++ b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamClosing/Command/ReopenContentStream.php @@ -16,7 +16,7 @@ use Neos\ContentRepository\Core\CommandHandler\CommandInterface; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamState; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamStatus; /** * @internal implementation detail. You must not use this command directly. @@ -27,21 +27,21 @@ { /** * @param ContentStreamId $contentStreamId The id of the content stream to reopen - * @param ContentStreamState $previousState The state the content stream was in before closing and is to be reset to + * @param ContentStreamStatus $previousState The state the content stream was in before closing and is to be reset to */ private function __construct( public ContentStreamId $contentStreamId, - public ContentStreamState $previousState + public ContentStreamStatus $previousState ) { } /** * @param ContentStreamId $contentStreamId The id of the content stream to reopen - * @param ContentStreamState $previousState The state the content stream was in before closing and is to be reset to + * @param ContentStreamStatus $previousState The state the content stream was in before closing and is to be reset to */ public static function create( ContentStreamId $contentStreamId, - ContentStreamState $previousState + ContentStreamStatus $previousState ): self { return new self( $contentStreamId, @@ -57,7 +57,7 @@ public static function fromArray(array $array): self { return new self( ContentStreamId::fromString($array['contentStreamId']), - ContentStreamState::from($array['previousState']), + ContentStreamStatus::from($array['previousState']), ); } } diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamClosing/Event/ContentStreamWasReopened.php b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamClosing/Event/ContentStreamWasReopened.php index 7e727cf998f..4b0248803f7 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamClosing/Event/ContentStreamWasReopened.php +++ b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamClosing/Event/ContentStreamWasReopened.php @@ -17,7 +17,7 @@ use Neos\ContentRepository\Core\EventStore\EventInterface; use Neos\ContentRepository\Core\Feature\Common\EmbedsContentStreamId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamState; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamStatus; /** * @api events are the persistence-API of the content repository @@ -26,7 +26,7 @@ { public function __construct( public ContentStreamId $contentStreamId, - public ContentStreamState $previousState, + public ContentStreamStatus $previousState, ) { } @@ -39,7 +39,7 @@ public static function fromArray(array $values): self { return new self( ContentStreamId::fromString($values['contentStreamId']), - ContentStreamState::from($values['previousState']), + ContentStreamStatus::from($values['previousState']), ); } diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php index c1e33319d5f..76d48439e76 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php @@ -35,7 +35,7 @@ use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamIsClosed; use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamIsNotClosed; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamState; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamStatus; use Neos\EventStore\Model\EventStream\ExpectedVersion; /** @@ -216,7 +216,7 @@ protected function requireContentStreamToNotBeClosed( ContentStreamId $contentStreamId, CommandHandlingDependencies $commandHandlingDependencies ): void { - if ($commandHandlingDependencies->getContentStreamState($contentStreamId) === ContentStreamState::STATE_CLOSED) { + if ($commandHandlingDependencies->getContentStreamStatus($contentStreamId) === ContentStreamStatus::CLOSED) { throw new ContentStreamIsClosed( 'Content stream "' . $contentStreamId->value . '" is closed.', 1710260081 @@ -228,7 +228,7 @@ protected function requireContentStreamToBeClosed( ContentStreamId $contentStreamId, CommandHandlingDependencies $commandHandlingDependencies ): void { - if ($commandHandlingDependencies->getContentStreamState($contentStreamId) !== ContentStreamState::STATE_CLOSED) { + if ($commandHandlingDependencies->getContentStreamStatus($contentStreamId) !== ContentStreamStatus::CLOSED) { throw new ContentStreamIsNotClosed( 'Content stream "' . $contentStreamId->value . '" is not closed.', 1710405911 diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamEventStreamName.php b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamEventStreamName.php index 395d7861959..902defb7b3a 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamEventStreamName.php +++ b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamEventStreamName.php @@ -14,6 +14,7 @@ namespace Neos\ContentRepository\Core\Feature; +use Neos\ContentRepository\Core\Feature\Common\EmbedsContentStreamId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\EventStore\Model\Event\StreamName; @@ -41,6 +42,7 @@ public static function isContentStreamStreamName(StreamName $streamName): bool return str_starts_with($streamName->value, self::EVENT_STREAM_NAME_PREFIX); } + /** @todo rather use {@see EmbedsContentStreamId} instead!? */ public static function extractContentStreamIdFromStreamName(StreamName $streamName): ContentStreamId { if (!self::isContentStreamStreamName($streamName)) { diff --git a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php index 3ca93c83168..ea4f29c6232 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php @@ -43,7 +43,6 @@ use Neos\ContentRepository\Core\Feature\WorkspaceModification\Command\ChangeBaseWorkspace; use Neos\ContentRepository\Core\Feature\WorkspaceModification\Command\DeleteWorkspace; use Neos\ContentRepository\Core\Feature\WorkspaceModification\Event\WorkspaceBaseWorkspaceWasChanged; -use Neos\ContentRepository\Core\Feature\WorkspaceModification\Event\WorkspaceOwnerWasChanged; use Neos\ContentRepository\Core\Feature\WorkspaceModification\Event\WorkspaceWasRemoved; use Neos\ContentRepository\Core\Feature\WorkspaceModification\Exception\BaseWorkspaceEqualsWorkspaceException; use Neos\ContentRepository\Core\Feature\WorkspaceModification\Exception\CircularRelationBetweenWorkspacesException; @@ -64,7 +63,7 @@ use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Dto\RebaseErrorHandlingStrategy; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Event\WorkspaceWasRebased; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Exception\WorkspaceRebaseFailed; -use Neos\ContentRepository\Core\Projection\Workspace\Workspace; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamAlreadyExists; use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet; use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; @@ -142,10 +141,7 @@ private function handleCreateWorkspace( new WorkspaceWasCreated( $command->workspaceName, $command->baseWorkspaceName, - $command->workspaceTitle, - $command->workspaceDescription, $command->newContentStreamId, - $command->workspaceOwner ) ); @@ -178,8 +174,6 @@ private function handleCreateRootWorkspace( $events = Events::with( new RootWorkspaceWasCreated( $command->workspaceName, - $command->workspaceTitle, - $command->workspaceDescription, $newContentStreamId ) ); @@ -327,7 +321,7 @@ private function handleRebaseWorkspace( if (!$commandHandlingDependencies->contentStreamExists($oldWorkspaceContentStreamId)) { throw new \DomainException('Cannot rebase a workspace with a stateless content stream', 1711718314); } - $oldWorkspaceContentStreamIdState = $commandHandlingDependencies->getContentStreamState($oldWorkspaceContentStreamId); + $oldWorkspaceContentStreamIdState = $commandHandlingDependencies->getContentStreamStatus($oldWorkspaceContentStreamId); // 0) close old content stream $commandHandlingDependencies->handle( @@ -460,7 +454,7 @@ private function handlePublishIndividualNodesFromWorkspace( if (!$commandHandlingDependencies->contentStreamExists($oldWorkspaceContentStreamId)) { throw new \DomainException('Cannot publish nodes on a workspace with a stateless content stream', 1710410114); } - $oldWorkspaceContentStreamIdState = $commandHandlingDependencies->getContentStreamState($oldWorkspaceContentStreamId); + $oldWorkspaceContentStreamIdState = $commandHandlingDependencies->getContentStreamStatus($oldWorkspaceContentStreamId); $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies); // 1) close old content stream @@ -601,7 +595,7 @@ private function handleDiscardIndividualNodesFromWorkspace( if (!$commandHandlingDependencies->contentStreamExists($contentGraph->getContentStreamId())) { throw new \DomainException('Cannot discard nodes on a workspace with a stateless content stream', 1710408112); } - $oldWorkspaceContentStreamIdState = $commandHandlingDependencies->getContentStreamState($contentGraph->getContentStreamId()); + $oldWorkspaceContentStreamIdState = $commandHandlingDependencies->getContentStreamStatus($contentGraph->getContentStreamId()); $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies); // 1) close old content stream diff --git a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCreation/Command/CreateRootWorkspace.php b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCreation/Command/CreateRootWorkspace.php index 748c7585684..2a36c7655cb 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCreation/Command/CreateRootWorkspace.php +++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCreation/Command/CreateRootWorkspace.php @@ -16,9 +16,7 @@ use Neos\ContentRepository\Core\CommandHandler\CommandInterface; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceDescription; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceTitle; /** * Command to create a root workspace. @@ -31,26 +29,20 @@ { /** * @param WorkspaceName $workspaceName Unique name of the workspace to create - * @param WorkspaceTitle $workspaceTitle Human-readable title of the workspace to create (can be changed) - * @param WorkspaceDescription $workspaceDescription Description of the workspace to create (can be changed) * @param ContentStreamId $newContentStreamId The id of the content stream the new workspace is assigned to initially */ private function __construct( public WorkspaceName $workspaceName, - public WorkspaceTitle $workspaceTitle, - public WorkspaceDescription $workspaceDescription, public ContentStreamId $newContentStreamId ) { } /** * @param WorkspaceName $workspaceName Name of the workspace to create - * @param WorkspaceTitle $workspaceTitle Human-readable title of the workspace to create (can be changed) - * @param WorkspaceDescription $workspaceDescription Description of the workspace to create (can be changed) * @param ContentStreamId $newContentStreamId The id of the content stream the new workspace is assigned to initially */ - public static function create(WorkspaceName $workspaceName, WorkspaceTitle $workspaceTitle, WorkspaceDescription $workspaceDescription, ContentStreamId $newContentStreamId): self + public static function create(WorkspaceName $workspaceName, ContentStreamId $newContentStreamId): self { - return new self($workspaceName, $workspaceTitle, $workspaceDescription, $newContentStreamId); + return new self($workspaceName, $newContentStreamId); } } diff --git a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCreation/Command/CreateWorkspace.php b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCreation/Command/CreateWorkspace.php index 74ec7248408..4add3ccf28f 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCreation/Command/CreateWorkspace.php +++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCreation/Command/CreateWorkspace.php @@ -15,11 +15,8 @@ namespace Neos\ContentRepository\Core\Feature\WorkspaceCreation\Command; use Neos\ContentRepository\Core\CommandHandler\CommandInterface; -use Neos\ContentRepository\Core\SharedModel\User\UserId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceDescription; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceTitle; /** * Create a new workspace, based on an existing baseWorkspace @@ -31,31 +28,22 @@ /** * @param WorkspaceName $workspaceName Unique name of the workspace to create * @param WorkspaceName $baseWorkspaceName Name of the base workspace - * @param WorkspaceTitle $workspaceTitle Human-readable title of the workspace to create (can be changed) - * @param WorkspaceDescription $workspaceDescription Description of the workspace to create (can be changed) * @param ContentStreamId $newContentStreamId The id of the content stream the new workspace is assigned to initially - * @param UserId|null $workspaceOwner Owner of the new workspace (optional) */ private function __construct( public WorkspaceName $workspaceName, public WorkspaceName $baseWorkspaceName, - public WorkspaceTitle $workspaceTitle, - public WorkspaceDescription $workspaceDescription, public ContentStreamId $newContentStreamId, - public ?UserId $workspaceOwner, ) { } /** * @param WorkspaceName $workspaceName Unique name of the workspace to create * @param WorkspaceName $baseWorkspaceName Name of the base workspace - * @param WorkspaceTitle $workspaceTitle Human-readable title of the workspace to create (can be changed) - * @param WorkspaceDescription $workspaceDescription Description of the workspace to create (can be changed) * @param ContentStreamId $newContentStreamId The id of the content stream the new workspace is assigned to initially - * @param UserId|null $workspaceOwner Owner of the new workspace (optional) */ - public static function create(WorkspaceName $workspaceName, WorkspaceName $baseWorkspaceName, WorkspaceTitle $workspaceTitle, WorkspaceDescription $workspaceDescription, ContentStreamId $newContentStreamId, ?UserId $workspaceOwner = null): self + public static function create(WorkspaceName $workspaceName, WorkspaceName $baseWorkspaceName, ContentStreamId $newContentStreamId): self { - return new self($workspaceName, $baseWorkspaceName, $workspaceTitle, $workspaceDescription, $newContentStreamId, $workspaceOwner); + return new self($workspaceName, $baseWorkspaceName, $newContentStreamId); } } diff --git a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCreation/Event/RootWorkspaceWasCreated.php b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCreation/Event/RootWorkspaceWasCreated.php index a1a5197d265..e168401cc8d 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCreation/Event/RootWorkspaceWasCreated.php +++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCreation/Event/RootWorkspaceWasCreated.php @@ -19,9 +19,7 @@ use Neos\ContentRepository\Core\Feature\Common\EmbedsWorkspaceName; use Neos\ContentRepository\Core\Feature\ContentStreamCreation\Event\ContentStreamWasCreated; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceDescription; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceTitle; /** * Event triggered to indicate that a root workspace, i.e. a workspace without base workspace, was created. @@ -35,8 +33,6 @@ { public function __construct( public WorkspaceName $workspaceName, - public WorkspaceTitle $workspaceTitle, - public WorkspaceDescription $workspaceDescription, public ContentStreamId $newContentStreamId ) { } @@ -50,8 +46,6 @@ public static function fromArray(array $values): self { return new self( WorkspaceName::fromString($values['workspaceName']), - WorkspaceTitle::fromString($values['workspaceTitle']), - WorkspaceDescription::fromString($values['workspaceDescription']), ContentStreamId::fromString($values['newContentStreamId']), ); } diff --git a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCreation/Event/WorkspaceWasCreated.php b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCreation/Event/WorkspaceWasCreated.php index 8cefa918108..c30a9d39796 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCreation/Event/WorkspaceWasCreated.php +++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCreation/Event/WorkspaceWasCreated.php @@ -17,11 +17,8 @@ use Neos\ContentRepository\Core\EventStore\EventInterface; use Neos\ContentRepository\Core\Feature\Common\EmbedsWorkspaceName; use Neos\ContentRepository\Core\Feature\ContentStreamForking\Event\ContentStreamWasForked; -use Neos\ContentRepository\Core\SharedModel\User\UserId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceDescription; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceTitle; /** * Event triggered to indicate that a workspace was created, based on a base workspace. @@ -36,10 +33,7 @@ public function __construct( public WorkspaceName $workspaceName, public WorkspaceName $baseWorkspaceName, - public WorkspaceTitle $workspaceTitle, - public WorkspaceDescription $workspaceDescription, public ContentStreamId $newContentStreamId, - public ?UserId $workspaceOwner = null ) { } @@ -53,10 +47,7 @@ public static function fromArray(array $values): self return new self( WorkspaceName::fromString($values['workspaceName']), WorkspaceName::fromString($values['baseWorkspaceName']), - WorkspaceTitle::fromString($values['workspaceTitle']), - WorkspaceDescription::fromString($values['workspaceDescription']), ContentStreamId::fromString($values['newContentStreamId']), - $values['workspaceOwner'] ? UserId::fromString($values['workspaceOwner']) : null ); } diff --git a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceModification/Event/WorkspaceWasRenamed.php b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceModification/Event/WorkspaceWasRenamed.php index cc90b47f89e..3d0bfe5f34e 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceModification/Event/WorkspaceWasRenamed.php +++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceModification/Event/WorkspaceWasRenamed.php @@ -8,7 +8,6 @@ use Neos\ContentRepository\Core\Feature\Common\EmbedsWorkspaceName; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceDescription; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceTitle; /** * Event triggered to indicate that a workspace title or description has changed. @@ -20,8 +19,8 @@ { public function __construct( public WorkspaceName $workspaceName, - public WorkspaceTitle $workspaceTitle, - public WorkspaceDescription $workspaceDescription, + public string $workspaceTitle, + public string $workspaceDescription, ) { } @@ -34,8 +33,8 @@ public static function fromArray(array $values): self { return new self( WorkspaceName::fromString($values['workspaceName']), - WorkspaceTitle::fromString($values['workspaceTitle']), - WorkspaceDescription::fromString($values['workspaceDescription']), + $values['workspaceTitle'], + $values['workspaceDescription'], ); } diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentStream/ContentStreamFinder.monopic b/Neos.ContentRepository.Core/Classes/Projection/ContentStream/ContentStreamFinder.monopic deleted file mode 100644 index d5448e332f088d0b7812a0d8ec5d1cda20edfad6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2857 zcmV+^3)b}iO;1iwP)S1pABzY8000000u$|B+iv5?5&ac`PYFN=eJ9(%2AG{VL6F7F z!n46*kqii0qHOI-qC(QM;|vVsYw}_Fl5{uOO;RGgyiA*dBtO^^O%|&!JXL+Fy7IYbFGd+$8{E~B#koX8n{j(&JT8Ry2@7Z*13krb+V^j z6s#8c7yYkbxeo7zVEF7RF%(MC=2Y%B-$qE!76$BL4M2=r-I$L9|$9m1D)J^LQO6&NV@v4>Gf|;snmw(>S1fc(R`)BRlyk*hIT%@ZuXL7b zh@;<$vuD(CYaJ{`_8zq=>ntEJnHP0TB`0@V13}={S;rv0iJ#V_1%D9FJ&~NHMbxVa%S@c#-Ku;-+H(y@UBIuY$4zR&wy1E9w&6 zu^11N!AlXWG%P!b7y?4TALws1PM^M7e0Hv{s_jEWtH$j-Gu+;Dj^M?{N*fE?Y%Jl0 ziKAc|8@s1K9~(=NeH$xmTOS(>`+K>xD=HFT&m2N(=h3FflFvc#$=TWOqa>55Km`|ni3XYCFDw2U z2fz5M!3YBV0qPH0B-ApY77DdgsKr7p7iz&!OGa8U(vp#ujI?A#6clzxwnHp?5wX-E z^XU7-91etBsAnkhEW#dZ@MaM3arK9c_ilV_4PL2l3kCVGv18cv_1O7*5LHA@Z=v(& z_qTt$`IA}UD3jQNiJN#4t%Kq^)r<1c-|W&tjYgn4cu4oXh(f~rm;0vcoK$+*lYmnwqiz}8CHY-z2jz^D2lvd&;fU=SoCAEh3x(R^&~{Ne zs+D8=<55_Oa$G>8?}ER7xG|c|<76v`0@gfL^(f~vLz#(+kFG39e(hdUU$AoZkt|eev;~cB zzNgKPug#CI&5y6mj{v}r=0^3^QsZK%-dpQh7v^iI(&I2r19{QR*ITJYR%TZD?DTVO zs)EhH(1Rf_o{+;Qt*)qsKwBIVY@sNy*Mp(}jqHav2<#yk7Ee%Dh3lQbxG1YQkZJU< zVEeeaixSl{O#O=}%s_><=3df1C`Yn3p~Yg+=+?#-aWtCI!N{6ZfQdZ<4GbJ+Oi#|J z{}63j%YWI7|uiU6NH|ZV@JG^jvXBz zgF(JE>(UFrAR|YHU@}T}f)FM~Y$SxSVF>ni9fjM+U1!vS3{_!^au~E48HiVm@SidGmAdS;8>a^QMl=jcX@b5;~jAknE>u80VNYrDnm+TObL=RgF6*7 z8{@3ON{&YvAi{e;aD0G>z+qmY_)N;QDKa?PhDz$Cv!9-AhqWlw=V zNiCjlKdHs@`X!EfC&vo@ZW$-DB#yILF%X4S`F$DvLnqg0+2+j9q4kCvUerS^Y&sdu zrW4Or0`fqAZ7L~fP7?nm;iQq z$sV^i-@f^96WrY1zP~lO*VJD8WuWtT#VUE-mO>&Bf`Q6aJX>xH+#$Bdq~cz&AaG>7 z1I8C3wTX5Z$T}41UtM_x>{n?8l@0mov;x+42J2W4)+-OJb5XH(UW@|fV+jQUZ9$ne z0Oze6ru?xfLjyK?b~hxTVoeGJm{f`Vi$DqlU2tpm9|6V3+K>mj(Bj6+V&56~&adIV0-(@7$EpT#%upWAQ#I4i#8 zNrvHiJ&Uu4D4Fe&_~#rcOm+n7#o^&&qwo;DM2x;7gZK9K=FJa%LPm9}Zjz7@(he8W z90Srzd88BZ(V3BX7>*1X`M_Q!S9>`LtXDij?JGN~cm!q_H2aDhi4N;ebxyI*cOhzE zW5DGTgdj}B8)8FFVzajho4rK@oLQ(sWp0iORj`xu01xW9m~7_@K`qG})<)j&vPRlO zB_sDW=ocM%g`$zNTuQG5do(P68QF>;`h7eD@7VI8XJ|f%j3bTB_H=Bvr(<+x z>tvNgKgxPyd+$$TZTm7A9BV_ErM-&WqREHQ8so~_thJ9vdlQh@d({OUU9^e&1#dbaZ8k5IId=WOTq}VC~5E zA`+c9Z=hOjqANU&LwIgAhY%Yb5b@e9<#@)S*QR8CYz1mLJkeTcWMnNgIt*mGWGemd zKmYB#2IaM)wcJ%3L|++kxL#v&T>*{jtuI_By$Yp;(0ZGj0oUT+NTJ@26HC8_x~ z$x({q(<`i|^FTF$bU{mrmVWE@pYpAE=CydxycbW_^PFZQP)(V>c_r%QeYW#T5bC!P z$X0L=SgHh#ZM`UO05RWr6GU~|8zO-Jk3rz987E>9Lnpe{54H;DDFVi#2TB5@%c0K1HDo6c`^GTS}=jZ - */ - public function findAllIds(): iterable - { - $contentStreamIds = $this->dbal->executeQuery('SELECT contentstreamid FROM ' . $this->tableName)->fetchFirstColumn(); - return array_map(ContentStreamId::fromString(...), $contentStreamIds); - } - - /** - * @param bool $findTemporaryContentStreams if TRUE, will find all content streams not bound to a workspace - * @return array - */ - public function findUnusedContentStreams(bool $findTemporaryContentStreams): iterable - { - $states = [ - ContentStreamState::STATE_NO_LONGER_IN_USE, - ContentStreamState::STATE_REBASE_ERROR, - ]; - - if ($findTemporaryContentStreams === true) { - $states[] = ContentStreamState::STATE_CREATED; - $states[] = ContentStreamState::STATE_FORKED; - } - - $contentStreamIds = $this->dbal->executeQuery( - ' - SELECT contentstreamid FROM ' . $this->tableName . ' - WHERE removed = FALSE - AND state IN (:states) - ', - [ - 'states' => array_map( - fn (ContentStreamState $contentStreamState): string => $contentStreamState->value, - $states - ) - ], - [ - 'states' => Connection::PARAM_STR_ARRAY - ] - )->fetchFirstColumn(); - - return array_map(ContentStreamId::fromString(...), $contentStreamIds); - } - - public function findStateForContentStream(ContentStreamId $contentStreamId): ?ContentStreamState - { - /* @var $state string|false */ - $state = $this->dbal->executeQuery( - ' - SELECT state FROM ' . $this->tableName . ' - WHERE contentstreamid = :contentStreamId - AND removed = FALSE - ', - [ - 'contentStreamId' => $contentStreamId->value, - ] - )->fetchOne(); - - return ContentStreamState::tryFrom($state ?: ''); - } - - /** - * @return array - */ - public function findUnusedAndRemovedContentStreams(): iterable - { - $contentStreamIds = $this->dbal->executeQuery( - ' - WITH RECURSIVE transitiveUsedContentStreams (contentstreamid) AS ( - -- initial case: find all content streams currently in direct use by a workspace - SELECT contentstreamid FROM ' . $this->tableName . ' - WHERE - state = :inUseState - AND removed = false - UNION - -- now, when a content stream is in use by a workspace, its source content stream is - -- also "transitively" in use. - SELECT sourceContentStreamId FROM ' . $this->tableName . ' - JOIN transitiveUsedContentStreams - ON ' . $this->tableName . '.contentStreamId - = transitiveUsedContentStreams.contentStreamId - WHERE - ' . $this->tableName . '.sourceContentStreamId IS NOT NULL - ) - - -- now, we check for removed content streams which we do not need anymore transitively - SELECT contentstreamid FROM ' . $this->tableName . ' AS cs - WHERE removed = true - AND NOT EXISTS ( - SELECT 1 - FROM transitiveUsedContentStreams - WHERE - cs.contentstreamid = transitiveUsedContentStreams.contentstreamid - ) - ', - [ - 'inUseState' => ContentStreamState::STATE_IN_USE_BY_WORKSPACE->value - ] - )->fetchFirstColumn(); - return array_map(ContentStreamId::fromString(...), $contentStreamIds); - } - - public function findVersionForContentStream(ContentStreamId $contentStreamId): MaybeVersion - { - /* @var $state string|false */ - $version = $this->dbal->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 - { - /* @var $state string|false */ - $version = $this->dbal->executeQuery( - ' - SELECT version FROM ' . $this->tableName . ' - WHERE contentStreamId = :contentStreamId - ', - [ - 'contentStreamId' => $contentStreamId->value - ] - )->fetchOne(); - - return $version !== false; - } -} diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentStream/ContentStreamProjection.php b/Neos.ContentRepository.Core/Classes/Projection/ContentStream/ContentStreamProjection.php deleted file mode 100644 index 96a509de9f4..00000000000 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentStream/ContentStreamProjection.php +++ /dev/null @@ -1,407 +0,0 @@ - - */ -class ContentStreamProjection implements ProjectionInterface -{ - /** - * @var ContentStreamFinder|null Cache for the content stream finder returned by {@see getState()}, - * so that always the same instance is returned - */ - private ?ContentStreamFinder $contentStreamFinder = null; - private DbalCheckpointStorage $checkpointStorage; - - public function __construct( - private readonly Connection $dbal, - private readonly string $tableName - ) { - $this->checkpointStorage = new DbalCheckpointStorage( - $this->dbal, - $this->tableName . '_checkpoint', - self::class - ); - } - - public function setUp(): void - { - $statements = $this->determineRequiredSqlStatements(); - // MIGRATIONS - if ($this->dbal->getSchemaManager()->tablesExist([$this->tableName])) { - // added 2023-04-01 - $statements[] = sprintf("UPDATE %s SET state='FORKED' WHERE state='REBASING'; ", $this->tableName); - } - foreach ($statements as $statement) { - $this->dbal->executeStatement($statement); - } - $this->checkpointStorage->setUp(); - } - - public function status(): ProjectionStatus - { - $checkpointStorageStatus = $this->checkpointStorage->status(); - if ($checkpointStorageStatus->type === CheckpointStorageStatusType::ERROR) { - return ProjectionStatus::error($checkpointStorageStatus->details); - } - if ($checkpointStorageStatus->type === CheckpointStorageStatusType::SETUP_REQUIRED) { - return ProjectionStatus::setupRequired($checkpointStorageStatus->details); - } - try { - $this->dbal->connect(); - } catch (\Throwable $e) { - return ProjectionStatus::error(sprintf('Failed to connect to database: %s', $e->getMessage())); - } - try { - $requiredSqlStatements = $this->determineRequiredSqlStatements(); - } catch (\Throwable $e) { - return ProjectionStatus::error(sprintf('Failed to determine required SQL statements: %s', $e->getMessage())); - } - if ($requiredSqlStatements !== []) { - return ProjectionStatus::setupRequired(sprintf('The following SQL statement%s required: %s', count($requiredSqlStatements) !== 1 ? 's are' : ' is', implode(chr(10), $requiredSqlStatements))); - } - return ProjectionStatus::ok(); - } - - /** - * @return array - */ - private function determineRequiredSqlStatements(): array - { - $schemaManager = $this->dbal->createSchemaManager(); - $schema = DbalSchemaFactory::createSchemaWithTables($schemaManager, [ - (new Table($this->tableName, [ - DbalSchemaFactory::columnForContentStreamId('contentStreamId')->setNotnull(true), - (new Column('version', Type::getType(Types::INTEGER)))->setNotnull(true), - DbalSchemaFactory::columnForContentStreamId('sourceContentStreamId')->setNotnull(false), - // Should become a DB ENUM (unclear how to configure with DBAL) or int (latter needs adaption to code) - (new Column('state', Type::getType(Types::BINARY)))->setLength(20)->setNotnull(true), - (new Column('removed', Type::getType(Types::BOOLEAN)))->setDefault(false)->setNotnull(false) - ])) - ]); - - return DbalSchemaDiff::determineRequiredSqlStatements($this->dbal, $schema); - } - - public function reset(): void - { - $this->dbal->executeStatement('TRUNCATE table ' . $this->tableName); - $this->checkpointStorage->acquireLock(); - $this->checkpointStorage->updateAndReleaseLock(SequenceNumber::none()); - } - - public function canHandle(EventInterface $event): bool - { - return in_array($event::class, [ - ContentStreamWasCreated::class, - RootWorkspaceWasCreated::class, - WorkspaceWasCreated::class, - ContentStreamWasForked::class, - WorkspaceWasDiscarded::class, - WorkspaceWasPartiallyDiscarded::class, - WorkspaceWasPartiallyPublished::class, - WorkspaceWasPublished::class, - WorkspaceWasRebased::class, - WorkspaceRebaseFailed::class, - ContentStreamWasClosed::class, - ContentStreamWasReopened::class, - ContentStreamWasRemoved::class, - DimensionShineThroughWasAdded::class, - ]) - || $event instanceof EmbedsContentStreamId; - } - - public function apply(EventInterface $event, EventEnvelope $eventEnvelope): void - { - if ($event instanceof EmbedsContentStreamId) { - $this->updateContentStreamVersion($event, $eventEnvelope); - } - match ($event::class) { - ContentStreamWasClosed::class => $this->whenContentStreamWasClosed($event, $eventEnvelope), - ContentStreamWasCreated::class => $this->whenContentStreamWasCreated($event, $eventEnvelope), - ContentStreamWasForked::class => $this->whenContentStreamWasForked($event, $eventEnvelope), - ContentStreamWasRemoved::class => $this->whenContentStreamWasRemoved($event, $eventEnvelope), - ContentStreamWasReopened::class => $this->whenContentStreamWasReopened($event, $eventEnvelope), - DimensionShineThroughWasAdded::class => $this->whenDimensionShineThroughWasAdded($event, $eventEnvelope), - RootWorkspaceWasCreated::class => $this->whenRootWorkspaceWasCreated($event), - WorkspaceRebaseFailed::class => $this->whenWorkspaceRebaseFailed($event), - WorkspaceWasCreated::class => $this->whenWorkspaceWasCreated($event), - WorkspaceWasDiscarded::class => $this->whenWorkspaceWasDiscarded($event), - WorkspaceWasPartiallyDiscarded::class => $this->whenWorkspaceWasPartiallyDiscarded($event), - WorkspaceWasPartiallyPublished::class => $this->whenWorkspaceWasPartiallyPublished($event), - WorkspaceWasPublished::class => $this->whenWorkspaceWasPublished($event), - WorkspaceWasRebased::class => $this->whenWorkspaceWasRebased($event), - default => $event instanceof EmbedsContentStreamId || throw new \InvalidArgumentException(sprintf('Unsupported event %s', get_debug_type($event))), - }; - } - - public function getCheckpointStorage(): CheckpointStorageInterface - { - return $this->checkpointStorage; - } - - public function getState(): ProjectionStateInterface - { - if (!$this->contentStreamFinder) { - $this->contentStreamFinder = new ContentStreamFinder( - $this->dbal, - $this->tableName - ); - } - return $this->contentStreamFinder; - } - - private function whenContentStreamWasCreated(ContentStreamWasCreated $event, EventEnvelope $eventEnvelope): void - { - $this->dbal->insert($this->tableName, [ - 'contentStreamId' => $event->contentStreamId->value, - 'version' => self::extractVersion($eventEnvelope), - 'state' => ContentStreamState::STATE_CREATED->value, - ]); - } - - private function whenRootWorkspaceWasCreated(RootWorkspaceWasCreated $event): void - { - // the content stream is in use now - $this->updateStateForContentStream( - $event->newContentStreamId, - ContentStreamState::STATE_IN_USE_BY_WORKSPACE, - ); - } - - private function whenWorkspaceWasCreated(WorkspaceWasCreated $event): void - { - // the content stream is in use now - $this->updateStateForContentStream( - $event->newContentStreamId, - ContentStreamState::STATE_IN_USE_BY_WORKSPACE, - ); - } - - private function whenContentStreamWasForked(ContentStreamWasForked $event, EventEnvelope $eventEnvelope): void - { - $this->dbal->insert($this->tableName, [ - 'contentStreamId' => $event->newContentStreamId->value, - 'version' => self::extractVersion($eventEnvelope), - 'sourceContentStreamId' => $event->sourceContentStreamId->value, - 'state' => ContentStreamState::STATE_FORKED->value - ]); - } - - private function whenWorkspaceWasDiscarded(WorkspaceWasDiscarded $event): void - { - // the new content stream is in use now - $this->updateStateForContentStream( - $event->newContentStreamId, - ContentStreamState::STATE_IN_USE_BY_WORKSPACE - ); - - // the previous content stream is no longer in use - $this->updateStateForContentStream( - $event->previousContentStreamId, - ContentStreamState::STATE_NO_LONGER_IN_USE - ); - } - - private function whenWorkspaceWasPartiallyDiscarded(WorkspaceWasPartiallyDiscarded $event): void - { - // the new content stream is in use now - $this->updateStateForContentStream( - $event->newContentStreamId, - ContentStreamState::STATE_IN_USE_BY_WORKSPACE - ); - - // the previous content stream is no longer in use - $this->updateStateForContentStream( - $event->previousContentStreamId, - ContentStreamState::STATE_NO_LONGER_IN_USE - ); - } - - private function whenWorkspaceWasPartiallyPublished(WorkspaceWasPartiallyPublished $event): void - { - // the new content stream is in use now - $this->updateStateForContentStream( - $event->newSourceContentStreamId, - ContentStreamState::STATE_IN_USE_BY_WORKSPACE - ); - - // the previous content stream is no longer in use - $this->updateStateForContentStream( - $event->previousSourceContentStreamId, - ContentStreamState::STATE_NO_LONGER_IN_USE - ); - } - - private function whenWorkspaceWasPublished(WorkspaceWasPublished $event): void - { - // the new content stream is in use now - $this->updateStateForContentStream( - $event->newSourceContentStreamId, - ContentStreamState::STATE_IN_USE_BY_WORKSPACE - ); - - // the previous content stream is no longer in use - $this->updateStateForContentStream( - $event->previousSourceContentStreamId, - ContentStreamState::STATE_NO_LONGER_IN_USE - ); - } - - private function whenWorkspaceWasRebased(WorkspaceWasRebased $event): void - { - // the new content stream is in use now - $this->updateStateForContentStream( - $event->newContentStreamId, - ContentStreamState::STATE_IN_USE_BY_WORKSPACE - ); - - // the previous content stream is no longer in use - $this->updateStateForContentStream( - $event->previousContentStreamId, - ContentStreamState::STATE_NO_LONGER_IN_USE - ); - } - - private function whenWorkspaceRebaseFailed(WorkspaceRebaseFailed $event): void - { - $this->updateStateForContentStream( - $event->candidateContentStreamId, - ContentStreamState::STATE_REBASE_ERROR - ); - } - - private function whenContentStreamWasClosed(ContentStreamWasClosed $event, EventEnvelope $eventEnvelope): void - { - $this->updateStateForContentStream( - $event->contentStreamId, - ContentStreamState::STATE_CLOSED, - ); - $this->dbal->update($this->tableName, [ - 'version' => self::extractVersion($eventEnvelope), - ], [ - 'contentStreamId' => $event->contentStreamId->value - ]); - } - - private function whenContentStreamWasReopened(ContentStreamWasReopened $event, EventEnvelope $eventEnvelope): void - { - $this->updateStateForContentStream( - $event->contentStreamId, - $event->previousState, - ); - $this->dbal->update($this->tableName, [ - 'version' => self::extractVersion($eventEnvelope), - ], [ - 'contentStreamId' => $event->contentStreamId->value - ]); - } - - private function whenContentStreamWasRemoved(ContentStreamWasRemoved $event, EventEnvelope $eventEnvelope): void - { - $this->dbal->update($this->tableName, [ - 'removed' => true, - 'version' => self::extractVersion($eventEnvelope), - ], [ - 'contentStreamId' => $event->contentStreamId->value - ]); - } - - private function whenDimensionShineThroughWasAdded(DimensionShineThroughWasAdded $event, EventEnvelope $eventEnvelope): void - { - $this->dbal->update($this->tableName, [ - 'version' => self::extractVersion($eventEnvelope), - ], [ - 'contentStreamId' => $event->contentStreamId->value - ]); - } - - private function updateStateForContentStream(ContentStreamId $contentStreamId, ContentStreamState $state): void - { - $this->dbal->update($this->tableName, [ - 'state' => $state->value, - ], [ - 'contentStreamId' => $contentStreamId->value - ]); - } - - private function updateContentStreamVersion( - EmbedsContentStreamId $eventInstance, - EventEnvelope $eventEnvelope - ): void { - $this->dbal->update($this->tableName, [ - 'version' => self::extractVersion($eventEnvelope), - ], [ - 'contentStreamId' => $eventInstance->getContentStreamId()->value - ]); - } - - - private static function extractVersion(EventEnvelope $eventEnvelope): int - { - if ( - !str_starts_with( - $eventEnvelope->streamName->value, - 'ContentStream:' - ) - ) { - throw new \RuntimeException( - 'Cannot extract version number, as it was projected on wrong stream "' - . $eventEnvelope->streamName->value . '", but needs to start with ' - . 'ContentStream:' - ); - } - return $eventEnvelope->version->value; - } -} diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentStream/ContentStreamProjectionFactory.php b/Neos.ContentRepository.Core/Classes/Projection/ContentStream/ContentStreamProjectionFactory.php deleted file mode 100644 index a7fa2c6bcd5..00000000000 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentStream/ContentStreamProjectionFactory.php +++ /dev/null @@ -1,50 +0,0 @@ - - * @internal - */ -class ContentStreamProjectionFactory implements ProjectionFactoryInterface -{ - public function __construct( - private readonly Connection $dbal, - ) { - } - - public function build( - ProjectionFactoryDependencies $projectionFactoryDependencies, - array $options, - ): ContentStreamProjection { - $projectionShortName = strtolower(str_replace( - 'Projection', - '', - (new \ReflectionClass(ContentStreamProjection::class))->getShortName() - )); - return new ContentStreamProjection( - $this->dbal, - sprintf( - 'cr_%s_p_%s', - $projectionFactoryDependencies->contentRepositoryId->value, - $projectionShortName - ), - ); - } -} diff --git a/Neos.ContentRepository.Core/Classes/Projection/Workspace/Workspace.php b/Neos.ContentRepository.Core/Classes/Projection/Workspace/Workspace.php deleted file mode 100644 index ddb826aff52..00000000000 --- a/Neos.ContentRepository.Core/Classes/Projection/Workspace/Workspace.php +++ /dev/null @@ -1,132 +0,0 @@ -workspaceName = $workspaceName; - $this->baseWorkspaceName = $baseWorkspaceName; - $this->workspaceTitle = $workspaceTitle; - $this->workspaceDescription = $workspaceDescription; - $this->currentContentStreamId = $currentContentStreamId; - $this->status = $status; - $this->workspaceOwner = $workspaceOwner; - } - - /** - * Checks if this workspace is a user's personal workspace - * @deprecated with 9.0.0-beta14 owners/collaborators should be assigned to workspaces outside the Content Repository core - */ - public function isPersonalWorkspace(): bool - { - return str_starts_with($this->workspaceName->value, static::PERSONAL_WORKSPACE_PREFIX); - } - - /** - * Checks if this workspace is shared only across users with access to internal workspaces, for example "reviewers" - * - * @return bool - * @deprecated with 9.0.0-beta14 owners/collaborators should be assigned to workspaces outside the Content Repository core - */ - public function isPrivateWorkspace(): bool - { - return $this->workspaceOwner !== null && !$this->isPersonalWorkspace(); - } - - /** - * Checks if this workspace is shared across all editors - * - * @return boolean - * @deprecated with 9.0.0-beta14 owners/collaborators should be assigned to workspaces outside the Content Repository core - */ - public function isInternalWorkspace(): bool - { - return $this->baseWorkspaceName !== null && $this->workspaceOwner === null; - } - - /** - * Checks if this workspace is public to everyone, even without authentication - * - * @return boolean - * @deprecated with 9.0.0-beta14 owners/collaborators should be assigned to workspaces outside the Content Repository core - */ - public function isPublicWorkspace(): bool - { - return $this->baseWorkspaceName === null && $this->workspaceOwner === null; - } -} diff --git a/Neos.ContentRepository.Core/Classes/Projection/Workspace/WorkspaceFinder.php b/Neos.ContentRepository.Core/Classes/Projection/Workspace/WorkspaceFinder.php deleted file mode 100644 index bd87127f8d7..00000000000 --- a/Neos.ContentRepository.Core/Classes/Projection/Workspace/WorkspaceFinder.php +++ /dev/null @@ -1,197 +0,0 @@ -workspaceRuntimeCache->getWorkspaceByName($name); - if ($workspace !== null) { - return $workspace; - } - - $workspaceRow = $this->dbal->executeQuery( - ' - SELECT * FROM ' . $this->tableName . ' - WHERE workspaceName = :workspaceName - ', - [ - 'workspaceName' => $name->value, - ] - )->fetchAssociative(); - - if ($workspaceRow === false) { - return null; - } - - $workspace = $this->createWorkspaceFromDatabaseRow($workspaceRow); - $this->workspaceRuntimeCache->setWorkspace($workspace); - return $workspace; - } - - public function findOneByCurrentContentStreamId( - ContentStreamId $contentStreamId - ): ?Workspace { - $workspace = $this->workspaceRuntimeCache->getByCurrentContentStreamId($contentStreamId); - if ($workspace !== null) { - return $workspace; - } - - $workspaceRow = $this->dbal->executeQuery( - ' - SELECT * FROM ' . $this->tableName . ' - WHERE currentContentStreamId = :currentContentStreamId - ', - [ - 'currentContentStreamId' => $contentStreamId->value - ] - )->fetchAssociative(); - - if ($workspaceRow === false) { - return null; - } - - $workspace = $this->createWorkspaceFromDatabaseRow($workspaceRow); - $this->workspaceRuntimeCache->setWorkspace($workspace); - return $workspace; - } - - /** - * @return array - * @throws DBALException - */ - public function findByBaseWorkspace(WorkspaceName $baseWorkspace): array - { - $result = []; - - $workspaceRows = $this->dbal->executeQuery( - ' - SELECT * FROM ' . $this->tableName . ' - WHERE baseWorkspaceName = :workspaceName - ', - [ - 'workspaceName' => $baseWorkspace->value, - ] - )->fetchAllAssociative(); - - foreach ($workspaceRows as $workspaceRow) { - $similarlyNamedWorkspace = $this->createWorkspaceFromDatabaseRow($workspaceRow); - $result[$similarlyNamedWorkspace->workspaceName->value] = $similarlyNamedWorkspace; - } - - return $result; - } - - public function findOneByWorkspaceOwner(string $owner): ?Workspace - { - $workspaceRow = $this->dbal->executeQuery( - ' - SELECT * FROM ' . $this->tableName . ' - WHERE workspaceOwner = :workspaceOwner - ', - [ - 'workspaceOwner' => $owner - ] - )->fetchAssociative(); - - if ($workspaceRow === false) { - return null; - } - - return $this->createWorkspaceFromDatabaseRow($workspaceRow); - } - - public function findAll(): Workspaces - { - $result = []; - - $workspaceRows = $this->dbal->executeQuery( - ' - SELECT * FROM ' . $this->tableName . ' - ' - )->fetchAllAssociative(); - - foreach ($workspaceRows as $workspaceRow) { - $similarlyNamedWorkspace = $this->createWorkspaceFromDatabaseRow($workspaceRow); - $result[$similarlyNamedWorkspace->workspaceName->value] = $similarlyNamedWorkspace; - } - - return Workspaces::fromArray($result); - } - - /** - * @return array - * @throws \Doctrine\DBAL\Driver\Exception - * @throws DBALException - */ - public function findOutdated(): array - { - $result = []; - - $workspaceRows = $this->dbal->executeQuery( - ' - SELECT * FROM ' . $this->tableName . ' WHERE status = :outdated - ', - [ - 'outdated' => WorkspaceStatus::OUTDATED->value - ] - )->fetchAllAssociative(); - - foreach ($workspaceRows as $workspaceRow) { - $similarlyNamedWorkspace = $this->createWorkspaceFromDatabaseRow($workspaceRow); - $result[$similarlyNamedWorkspace->workspaceName->value] = $similarlyNamedWorkspace; - } - - return $result; - } - - /** - * @param array $row - */ - private function createWorkspaceFromDatabaseRow(array $row): Workspace - { - 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.ContentRepository.Core/Classes/Projection/Workspace/WorkspaceProjection.php b/Neos.ContentRepository.Core/Classes/Projection/Workspace/WorkspaceProjection.php deleted file mode 100644 index 04e58933399..00000000000 --- a/Neos.ContentRepository.Core/Classes/Projection/Workspace/WorkspaceProjection.php +++ /dev/null @@ -1,386 +0,0 @@ - - */ -class WorkspaceProjection implements ProjectionInterface, WithMarkStaleInterface -{ - private const DEFAULT_TEXT_COLLATION = 'utf8mb4_unicode_520_ci'; - - /** - * @var WorkspaceFinder|null Cache for the workspace finder returned by {@see getState()}, - * so that always the same instance is returned - */ - private ?WorkspaceFinder $workspaceFinder = null; - private DbalCheckpointStorage $checkpointStorage; - private WorkspaceRuntimeCache $workspaceRuntimeCache; - - public function __construct( - private readonly Connection $dbal, - private readonly string $tableName, - ) { - $this->checkpointStorage = new DbalCheckpointStorage( - $this->dbal, - $this->tableName . '_checkpoint', - self::class - ); - $this->workspaceRuntimeCache = new WorkspaceRuntimeCache(); - } - - public function setUp(): void - { - foreach ($this->determineRequiredSqlStatements() as $statement) { - $this->dbal->executeStatement($statement); - } - $this->checkpointStorage->setUp(); - } - - public function status(): ProjectionStatus - { - $checkpointStorageStatus = $this->checkpointStorage->status(); - if ($checkpointStorageStatus->type === CheckpointStorageStatusType::ERROR) { - return ProjectionStatus::error($checkpointStorageStatus->details); - } - if ($checkpointStorageStatus->type === CheckpointStorageStatusType::SETUP_REQUIRED) { - return ProjectionStatus::setupRequired($checkpointStorageStatus->details); - } - try { - $this->dbal->connect(); - } catch (\Throwable $e) { - return ProjectionStatus::error(sprintf('Failed to connect to database: %s', $e->getMessage())); - } - try { - $requiredSqlStatements = $this->determineRequiredSqlStatements(); - } catch (\Throwable $e) { - return ProjectionStatus::error(sprintf('Failed to determine required SQL statements: %s', $e->getMessage())); - } - if ($requiredSqlStatements !== []) { - return ProjectionStatus::setupRequired(sprintf('The following SQL statement%s required: %s', count($requiredSqlStatements) !== 1 ? 's are' : ' is', implode(chr(10), $requiredSqlStatements))); - } - return ProjectionStatus::ok(); - } - - /** - * @return array - */ - private function determineRequiredSqlStatements(): array - { - $schemaManager = $this->dbal->createSchemaManager(); - $workspaceTable = new Table($this->tableName, [ - DbalSchemaFactory::columnForWorkspaceName('workspacename'), - DbalSchemaFactory::columnForWorkspaceName('baseworkspacename')->setNotNull(false), - (new Column('workspacetitle', Type::getType(Types::STRING)))->setLength(255)->setNotnull(true)->setPlatformOption('collation', self::DEFAULT_TEXT_COLLATION), - (new Column('workspacedescription', Type::getType(Types::STRING)))->setLength(255)->setNotnull(true)->setPlatformOption('collation', self::DEFAULT_TEXT_COLLATION), - (new Column('workspaceowner', Type::getType(Types::STRING)))->setLength(255)->setNotnull(false)->setPlatformOption('collation', self::DEFAULT_TEXT_COLLATION), - DbalSchemaFactory::columnForContentStreamId('currentcontentstreamid')->setNotNull(true), - (new Column('status', Type::getType(Types::BINARY)))->setLength(20)->setNotnull(false) - ]); - $workspaceTable->setPrimaryKey(['workspacename']); - - $schema = DbalSchemaFactory::createSchemaWithTables($schemaManager, [$workspaceTable]); - return DbalSchemaDiff::determineRequiredSqlStatements($this->dbal, $schema); - } - - public function reset(): void - { - $this->dbal->exec('TRUNCATE ' . $this->tableName); - $this->checkpointStorage->acquireLock(); - $this->checkpointStorage->updateAndReleaseLock(SequenceNumber::none()); - } - - public function canHandle(EventInterface $event): bool - { - return in_array($event::class, [ - WorkspaceWasCreated::class, - WorkspaceWasRenamed::class, - RootWorkspaceWasCreated::class, - WorkspaceWasDiscarded::class, - WorkspaceWasPartiallyDiscarded::class, - WorkspaceWasPartiallyPublished::class, - WorkspaceWasPublished::class, - WorkspaceWasRebased::class, - WorkspaceRebaseFailed::class, - WorkspaceWasRemoved::class, - WorkspaceOwnerWasChanged::class, - WorkspaceBaseWorkspaceWasChanged::class, - ]); - } - - public function apply(EventInterface $event, EventEnvelope $eventEnvelope): void - { - match ($event::class) { - WorkspaceWasCreated::class => $this->whenWorkspaceWasCreated($event), - WorkspaceWasRenamed::class => $this->whenWorkspaceWasRenamed($event), - RootWorkspaceWasCreated::class => $this->whenRootWorkspaceWasCreated($event), - WorkspaceWasDiscarded::class => $this->whenWorkspaceWasDiscarded($event), - WorkspaceWasPartiallyDiscarded::class => $this->whenWorkspaceWasPartiallyDiscarded($event), - WorkspaceWasPartiallyPublished::class => $this->whenWorkspaceWasPartiallyPublished($event), - WorkspaceWasPublished::class => $this->whenWorkspaceWasPublished($event), - WorkspaceWasRebased::class => $this->whenWorkspaceWasRebased($event), - WorkspaceRebaseFailed::class => $this->whenWorkspaceRebaseFailed($event), - WorkspaceWasRemoved::class => $this->whenWorkspaceWasRemoved($event), - WorkspaceOwnerWasChanged::class => $this->whenWorkspaceOwnerWasChanged($event), - WorkspaceBaseWorkspaceWasChanged::class => $this->whenWorkspaceBaseWorkspaceWasChanged($event), - default => throw new \InvalidArgumentException(sprintf('Unsupported event %s', get_debug_type($event))), - }; - } - - public function getCheckpointStorage(): DbalCheckpointStorage - { - return $this->checkpointStorage; - } - - public function getState(): WorkspaceFinder - { - if (!$this->workspaceFinder) { - $this->workspaceFinder = new WorkspaceFinder( - $this->dbal, - $this->workspaceRuntimeCache, - $this->tableName - ); - } - return $this->workspaceFinder; - } - - public function markStale(): void - { - $this->workspaceRuntimeCache->disableCache(); - } - - private function whenWorkspaceWasCreated(WorkspaceWasCreated $event): void - { - $this->dbal->insert($this->tableName, [ - 'workspaceName' => $event->workspaceName->value, - 'baseWorkspaceName' => $event->baseWorkspaceName->value, - 'workspaceTitle' => $event->workspaceTitle->value, - 'workspaceDescription' => $event->workspaceDescription->value, - 'workspaceOwner' => $event->workspaceOwner?->value, - 'currentContentStreamId' => $event->newContentStreamId->value, - 'status' => WorkspaceStatus::UP_TO_DATE->value - ]); - } - - private function whenWorkspaceWasRenamed(WorkspaceWasRenamed $event): void - { - $this->dbal->update( - $this->tableName, - [ - 'workspaceTitle' => $event->workspaceTitle->value, - 'workspaceDescription' => $event->workspaceDescription->value, - ], - ['workspaceName' => $event->workspaceName->value] - ); - } - - private function whenRootWorkspaceWasCreated(RootWorkspaceWasCreated $event): void - { - $this->dbal->insert($this->tableName, [ - 'workspaceName' => $event->workspaceName->value, - 'workspaceTitle' => $event->workspaceTitle->value, - 'workspaceDescription' => $event->workspaceDescription->value, - 'currentContentStreamId' => $event->newContentStreamId->value, - 'status' => WorkspaceStatus::UP_TO_DATE->value - ]); - } - - private function whenWorkspaceWasDiscarded(WorkspaceWasDiscarded $event): void - { - $this->updateContentStreamId($event->newContentStreamId, $event->workspaceName); - $this->markWorkspaceAsOutdated($event->workspaceName); - $this->markDependentWorkspacesAsOutdated($event->workspaceName); - } - - private function whenWorkspaceWasPartiallyDiscarded(WorkspaceWasPartiallyDiscarded $event): void - { - $this->updateContentStreamId($event->newContentStreamId, $event->workspaceName); - $this->markDependentWorkspacesAsOutdated($event->workspaceName); - } - - private function whenWorkspaceWasPartiallyPublished(WorkspaceWasPartiallyPublished $event): void - { - // TODO: How do we test this method? - // It's hard to design a BDD testcase failing if this method is commented out... - $this->updateContentStreamId( - $event->newSourceContentStreamId, - $event->sourceWorkspaceName - ); - - $this->markDependentWorkspacesAsOutdated($event->targetWorkspaceName); - - // NASTY: we need to set the source workspace name as non-outdated; as it has been made up-to-date again. - $this->markWorkspaceAsUpToDate($event->sourceWorkspaceName); - - $this->markDependentWorkspacesAsOutdated($event->sourceWorkspaceName); - } - - private function whenWorkspaceWasPublished(WorkspaceWasPublished $event): void - { - // TODO: How do we test this method? - // It's hard to design a BDD testcase failing if this method is commented out... - $this->updateContentStreamId( - $event->newSourceContentStreamId, - $event->sourceWorkspaceName - ); - - $this->markDependentWorkspacesAsOutdated($event->targetWorkspaceName); - - // NASTY: we need to set the source workspace name as non-outdated; as it has been made up-to-date again. - $this->markWorkspaceAsUpToDate($event->sourceWorkspaceName); - - $this->markDependentWorkspacesAsOutdated($event->sourceWorkspaceName); - } - - private function whenWorkspaceWasRebased(WorkspaceWasRebased $event): void - { - $this->updateContentStreamId($event->newContentStreamId, $event->workspaceName); - $this->markDependentWorkspacesAsOutdated($event->workspaceName); - - // When the rebase is successful, we can set the status of the workspace back to UP_TO_DATE. - $this->markWorkspaceAsUpToDate($event->workspaceName); - } - - private function whenWorkspaceRebaseFailed(WorkspaceRebaseFailed $event): void - { - $this->markWorkspaceAsOutdatedConflict($event->workspaceName); - } - - private function whenWorkspaceWasRemoved(WorkspaceWasRemoved $event): void - { - $this->dbal->delete( - $this->tableName, - ['workspaceName' => $event->workspaceName->value] - ); - } - - private function whenWorkspaceOwnerWasChanged(WorkspaceOwnerWasChanged $event): void - { - $this->dbal->update( - $this->tableName, - ['workspaceOwner' => $event->newWorkspaceOwner], - ['workspaceName' => $event->workspaceName->value] - ); - } - - private function whenWorkspaceBaseWorkspaceWasChanged(WorkspaceBaseWorkspaceWasChanged $event): void - { - $this->dbal->update( - $this->tableName, - [ - 'baseWorkspaceName' => $event->baseWorkspaceName->value, - 'currentContentStreamId' => $event->newContentStreamId->value, - ], - ['workspaceName' => $event->workspaceName->value] - ); - } - - private function updateContentStreamId( - ContentStreamId $contentStreamId, - WorkspaceName $workspaceName, - ): void { - $this->dbal->update($this->tableName, [ - 'currentContentStreamId' => $contentStreamId->value, - ], [ - 'workspaceName' => $workspaceName->value - ]); - } - - private function markWorkspaceAsUpToDate(WorkspaceName $workspaceName): void - { - $this->dbal->executeUpdate(' - UPDATE ' . $this->tableName . ' - SET status = :upToDate - WHERE - workspacename = :workspaceName - ', [ - 'upToDate' => WorkspaceStatus::UP_TO_DATE->value, - 'workspaceName' => $workspaceName->value - ]); - } - - private function markDependentWorkspacesAsOutdated(WorkspaceName $baseWorkspaceName): void - { - $this->dbal->executeUpdate(' - UPDATE ' . $this->tableName . ' - SET status = :outdated - WHERE - baseworkspacename = :baseWorkspaceName - ', [ - 'outdated' => WorkspaceStatus::OUTDATED->value, - 'baseWorkspaceName' => $baseWorkspaceName->value - ]); - } - - private function markWorkspaceAsOutdated(WorkspaceName $workspaceName): void - { - $this->dbal->executeUpdate(' - UPDATE ' . $this->tableName . ' - SET - status = :outdated - WHERE - workspacename = :workspaceName - ', [ - 'outdated' => WorkspaceStatus::OUTDATED->value, - 'workspaceName' => $workspaceName->value - ]); - } - - private function markWorkspaceAsOutdatedConflict(WorkspaceName $workspaceName): void - { - $this->dbal->executeUpdate(' - UPDATE ' . $this->tableName . ' - SET - status = :outdatedConflict - WHERE - workspacename = :workspaceName - ', [ - 'outdatedConflict' => WorkspaceStatus::OUTDATED_CONFLICT->value, - 'workspaceName' => $workspaceName->value - ]); - } -} diff --git a/Neos.ContentRepository.Core/Classes/Projection/Workspace/WorkspaceProjectionFactory.php b/Neos.ContentRepository.Core/Classes/Projection/Workspace/WorkspaceProjectionFactory.php deleted file mode 100644 index 094ee2e4e56..00000000000 --- a/Neos.ContentRepository.Core/Classes/Projection/Workspace/WorkspaceProjectionFactory.php +++ /dev/null @@ -1,50 +0,0 @@ - - * @internal - */ -class WorkspaceProjectionFactory implements ProjectionFactoryInterface -{ - public function __construct( - private readonly Connection $dbal, - ) { - } - - public function build( - ProjectionFactoryDependencies $projectionFactoryDependencies, - array $options, - ): WorkspaceProjection { - $projectionShortName = strtolower(str_replace( - 'Projection', - '', - (new \ReflectionClass(WorkspaceProjection::class))->getShortName() - )); - return new WorkspaceProjection( - $this->dbal, - sprintf( - 'cr_%s_p_%s', - $projectionFactoryDependencies->contentRepositoryId->value, - $projectionShortName - ), - ); - } -} diff --git a/Neos.ContentRepository.Core/Classes/Projection/Workspace/WorkspaceRuntimeCache.php b/Neos.ContentRepository.Core/Classes/Projection/Workspace/WorkspaceRuntimeCache.php deleted file mode 100644 index 5db459a2fff..00000000000 --- a/Neos.ContentRepository.Core/Classes/Projection/Workspace/WorkspaceRuntimeCache.php +++ /dev/null @@ -1,77 +0,0 @@ - - */ - private array $cachedWorkspacesByName = []; - - /** - * @var array - */ - private array $cachedWorkspacesByContentStreamId = []; - - /** - * @return void - */ - public function disableCache(): void - { - $this->cacheEnabled = false; - $this->cachedWorkspacesByName = []; - $this->cachedWorkspacesByContentStreamId = []; - } - - public function getWorkspaceByName(WorkspaceName $name): ?Workspace - { - if ($this->cacheEnabled === true && isset($this->cachedWorkspacesByName[$name->value])) { - return $this->cachedWorkspacesByName[$name->value]; - } - return null; - } - - public function setWorkspace(Workspace $workspace): void - { - if ($this->cacheEnabled === true) { - $this->cachedWorkspacesByName[$workspace->workspaceName->value] = $workspace; - $this->cachedWorkspacesByContentStreamId[ - $workspace->currentContentStreamId->value - ] = $workspace; - } - } - - public function getByCurrentContentStreamId(ContentStreamId $contentStreamId): ?Workspace - { - if ( - $this->cacheEnabled === true - && isset($this->cachedWorkspacesByContentStreamId[$contentStreamId->value]) - ) { - return $this->cachedWorkspacesByContentStreamId[$contentStreamId->value]; - } - return null; - } -} diff --git a/Neos.ContentRepository.Core/Classes/Service/ContentRepositoryBootstrapper.php b/Neos.ContentRepository.Core/Classes/Service/ContentRepositoryBootstrapper.php index 6dd837c9a81..bcd01b31acd 100644 --- a/Neos.ContentRepository.Core/Classes/Service/ContentRepositoryBootstrapper.php +++ b/Neos.ContentRepository.Core/Classes/Service/ContentRepositoryBootstrapper.php @@ -8,12 +8,10 @@ use Neos\ContentRepository\Core\Feature\RootNodeCreation\Command\CreateRootNodeAggregateWithNode; use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Command\CreateRootWorkspace; use Neos\ContentRepository\Core\NodeType\NodeTypeName; -use Neos\ContentRepository\Core\Projection\Workspace\Workspace; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceDescription; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceTitle; /** * Utility class that provides functionality to initialize a Content Repository instance (i.e. create the first @@ -48,8 +46,6 @@ public function getOrCreateLiveWorkspace(): Workspace $this->contentRepository->handle( CreateRootWorkspace::create( $liveWorkspaceName, - WorkspaceTitle::fromString('Live'), - WorkspaceDescription::fromString('Public live workspace'), ContentStreamId::create() ) ); diff --git a/Neos.ContentRepository.Core/Classes/Service/ContentStreamPruner.php b/Neos.ContentRepository.Core/Classes/Service/ContentStreamPruner.php index eb5f5540917..cd151bf631d 100644 --- a/Neos.ContentRepository.Core/Classes/Service/ContentStreamPruner.php +++ b/Neos.ContentRepository.Core/Classes/Service/ContentStreamPruner.php @@ -4,27 +4,27 @@ namespace Neos\ContentRepository\Core\Service; -use Neos\ContentRepository\Core\CommandHandler\CommandResult; +use Neos\ContentRepository\Core\ContentGraphAdapter; use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; use Neos\ContentRepository\Core\Feature\ContentStreamEventStreamName; use Neos\ContentRepository\Core\Feature\ContentStreamRemoval\Command\RemoveContentStream; -use Neos\ContentRepository\Core\Projection\ContentStream\ContentStreamFinder; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStream; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamStatus; use Neos\EventStore\EventStoreInterface; /** - * For internal implementation details, see {@see ContentStreamFinder}. + * For internal implementation details, see {@see ContentGraphAdapter}. * * @api */ class ContentStreamPruner implements ContentRepositoryServiceInterface { - private ?CommandResult $lastCommandResult; - public function __construct( private readonly ContentRepository $contentRepository, private readonly EventStoreInterface $eventStore, + private readonly ContentGraphAdapter $contentGraphAdapter, ) { } @@ -46,17 +46,23 @@ public function __construct( */ public function prune(bool $removeTemporary = false): iterable { - $unusedContentStreams = $this->contentRepository->getContentStreamFinder()->findUnusedContentStreams( - $removeTemporary + $status = [ContentStreamStatus::NO_LONGER_IN_USE, ContentStreamStatus::REBASE_ERROR]; + if ($removeTemporary) { + $status[] = ContentStreamStatus::CREATED; + $status[] = ContentStreamStatus::FORKED; + } + $unusedContentStreams = $this->contentRepository->getContentStreams()->filter( + static fn (ContentStream $contentStream) => in_array($contentStream->status, $status, true), ); - + $unusedContentStreamIds = []; foreach ($unusedContentStreams as $contentStream) { - $this->lastCommandResult = $this->contentRepository->handle( - RemoveContentStream::create($contentStream) + $this->contentRepository->handle( + RemoveContentStream::create($contentStream->id) ); + $unusedContentStreamIds[] = $contentStream->id; } - return $unusedContentStreams; + return $unusedContentStreamIds; } /** @@ -68,34 +74,24 @@ public function prune(bool $removeTemporary = false): iterable * * - Otherwise, we cannot replay the other content streams correctly (if the base content streams are missing). * - * @return iterable the identifiers of the removed content streams + * @return iterable the identifiers of the removed content streams */ public function pruneRemovedFromEventStream(): iterable { - $removedContentStreams = $this->contentRepository->getContentStreamFinder() - ->findUnusedAndRemovedContentStreams(); - - foreach ($removedContentStreams as $removedContentStream) { + $removedContentStreamIds = $this->contentGraphAdapter->getUnusedAndRemovedContentStreamIds(); + foreach ($removedContentStreamIds as $removedContentStream) { $streamName = ContentStreamEventStreamName::fromContentStreamId($removedContentStream) ->getEventStreamName(); $this->eventStore->deleteStream($streamName); } - - return $removedContentStreams; + return $removedContentStreamIds; } public function pruneAll(): void { - $contentStreamIds = $this->contentRepository->getContentStreamFinder()->findAllIds(); - - foreach ($contentStreamIds as $contentStreamId) { - $streamName = ContentStreamEventStreamName::fromContentStreamId($contentStreamId)->getEventStreamName(); + foreach ($this->contentRepository->getContentStreams() as $contentStream) { + $streamName = ContentStreamEventStreamName::fromContentStreamId($contentStream->id)->getEventStreamName(); $this->eventStore->deleteStream($streamName); } } - - public function getLastCommandResult(): ?CommandResult - { - return $this->lastCommandResult; - } } diff --git a/Neos.ContentRepository.Core/Classes/Service/ContentStreamPrunerFactory.php b/Neos.ContentRepository.Core/Classes/Service/ContentStreamPrunerFactory.php index 42c4b2c48a7..4f72263e21f 100644 --- a/Neos.ContentRepository.Core/Classes/Service/ContentStreamPrunerFactory.php +++ b/Neos.ContentRepository.Core/Classes/Service/ContentStreamPrunerFactory.php @@ -4,6 +4,7 @@ namespace Neos\ContentRepository\Core\Service; +use Neos\ContentRepository\Core\ContentGraphAdapter; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryDependencies; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; @@ -15,9 +16,11 @@ class ContentStreamPrunerFactory implements ContentRepositoryServiceFactoryInter { public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): ContentStreamPruner { + $contentGraphAdapter = $serviceFactoryDependencies->contentRepository->projectionState(ContentGraphAdapter::class); return new ContentStreamPruner( $serviceFactoryDependencies->contentRepository, - $serviceFactoryDependencies->eventStore + $serviceFactoryDependencies->eventStore, + $contentGraphAdapter, ); } } diff --git a/Neos.ContentRepository.Core/Classes/Service/WorkspaceMaintenanceService.php b/Neos.ContentRepository.Core/Classes/Service/WorkspaceMaintenanceService.php index 521af3a1a40..3dd792237aa 100644 --- a/Neos.ContentRepository.Core/Classes/Service/WorkspaceMaintenanceService.php +++ b/Neos.ContentRepository.Core/Classes/Service/WorkspaceMaintenanceService.php @@ -9,9 +9,9 @@ use Neos\ContentRepository\Core\Feature\WorkspaceEventStreamName; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Command\RebaseWorkspace; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Dto\RebaseErrorHandlingStrategy; -use Neos\ContentRepository\Core\Projection\Workspace\Workspace; -use Neos\ContentRepository\Core\Projection\Workspace\Workspaces; -use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceStatus; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspaces; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceStatus; use Neos\EventStore\EventStoreInterface; /** diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStream.php b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStream.php new file mode 100644 index 00000000000..1623280473a --- /dev/null +++ b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStream.php @@ -0,0 +1,36 @@ + + * + * @api + */ + +final class ContentStreams implements \IteratorAggregate, \Countable +{ + /** + * @var array + */ + private array $contentStreams; + + /** + * @param iterable $collection + */ + private function __construct(iterable $collection) + { + $contentStreams = []; + foreach ($collection as $item) { + if (!$item instanceof ContentStream) { + throw new \InvalidArgumentException(sprintf('ContentStreams must only consist of %s objects, got: %s', ContentStream::class, get_debug_type($item)), 1716900709); + } + $contentStreams[$item->id->value] = $item; + } + + $this->contentStreams = $contentStreams; + } + + /** + * @param array $contentStreams + */ + public static function fromArray(array $contentStreams): self + { + return new self($contentStreams); + } + + public static function createEmpty(): self + { + return new self([]); + } + + /** + * @return \Traversable + */ + public function getIterator(): \Traversable + { + yield from array_values($this->contentStreams); + } + + /** + * @param \Closure(ContentStream): bool $callback + */ + public function filter(\Closure $callback): self + { + return new self(array_filter($this->contentStreams, $callback)); + } + + /** + * @param \Closure(ContentStream): bool $callback + */ + public function find(\Closure $callback): ?ContentStream + { + foreach ($this->contentStreams as $contentStream) { + if ($callback($contentStream)) { + return $contentStream; + } + } + return null; + } + + public function count(): int + { + return count($this->contentStreams); + } + + public function isEmpty(): bool + { + return $this->contentStreams === []; + } +} diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/Workspace.php b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/Workspace.php new file mode 100644 index 00000000000..79240373951 --- /dev/null +++ b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/Workspace.php @@ -0,0 +1,63 @@ +workspaceName = $workspaceName; + $this->baseWorkspaceName = $baseWorkspaceName; + $this->currentContentStreamId = $currentContentStreamId; + $this->status = $status; + } + + public function isBaseWorkspace(): bool + { + return $this->baseWorkspaceName === null; + } +} diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/WorkspaceDescription.php b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/WorkspaceDescription.php deleted file mode 100644 index 3181b351960..00000000000 --- a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/WorkspaceDescription.php +++ /dev/null @@ -1,52 +0,0 @@ -value) !== 1) { - throw new \InvalidArgumentException('Invalid workspace description given.', 1505831660363); - } - } - - public static function fromString(string $value): self - { - return new self($value); - } - - public function jsonSerialize(): string - { - return $this->value; - } - - public function equals(self $other): bool - { - return $this->value === $other->value; - } - - public function __toString(): string - { - return $this->value; - } -} diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/WorkspaceFilter.php b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/WorkspaceFilter.php new file mode 100644 index 00000000000..70e6a1e55b6 --- /dev/null +++ b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/WorkspaceFilter.php @@ -0,0 +1,37 @@ +value) !== 1) { - throw new \InvalidArgumentException('Invalid workspace title given.', 1505827170288); - } - } - - public static function fromString(string $value): self - { - return new self($value); - } - - public function jsonSerialize(): string - { - return $this->value; - } - - public function equals(self $other): bool - { - return $this->value === $other->value; - } - - public function __toString(): string - { - return $this->value; - } -} diff --git a/Neos.ContentRepository.Core/Classes/Projection/Workspace/Workspaces.php b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/Workspaces.php similarity index 78% rename from Neos.ContentRepository.Core/Classes/Projection/Workspace/Workspaces.php rename to Neos.ContentRepository.Core/Classes/SharedModel/Workspace/Workspaces.php index 82a9399355c..671f8ab1ed4 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/Workspace/Workspaces.php +++ b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/Workspaces.php @@ -12,14 +12,12 @@ declare(strict_types=1); -namespace Neos\ContentRepository\Core\Projection\Workspace; - -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; +namespace Neos\ContentRepository\Core\SharedModel\Workspace; /** * An immutable, type-safe collection of Workspace objects * - * @implements \IteratorAggregate + * @implements \IteratorAggregate * * @api */ @@ -32,17 +30,14 @@ final class Workspaces implements \IteratorAggregate, \Countable private array $workspaces; /** - * @param iterable $collection + * @param iterable $collection */ private function __construct(iterable $collection) { $workspaces = []; foreach ($collection as $item) { if (!$item instanceof Workspace) { - throw new \InvalidArgumentException( - 'Workspaces can only consist of ' . Workspace::class . ' objects.', - 1677833509 - ); + throw new \InvalidArgumentException(sprintf('Workspaces must only consist of %s objects, got: %s', Workspace::class, get_debug_type($item)), 1677833509); } $workspaces[$item->workspaceName->value] = $item; } @@ -51,7 +46,7 @@ private function __construct(iterable $collection) } /** - * @param array $workspaces + * @param array $workspaces */ public static function fromArray(array $workspaces): self { @@ -77,10 +72,10 @@ public function getBaseWorkspaces(WorkspaceName $workspaceName): Workspaces $workspace = $this->get($workspaceName); if (!$workspace) { - return Workspaces::createEmpty(); + return self::createEmpty(); } $baseWorkspaceName = $workspace->baseWorkspaceName; - while ($baseWorkspaceName != null) { + while ($baseWorkspaceName !== null) { $baseWorkspace = $this->get($baseWorkspaceName); if ($baseWorkspace) { $baseWorkspaces[] = $baseWorkspace; @@ -89,11 +84,11 @@ public function getBaseWorkspaces(WorkspaceName $workspaceName): Workspaces $baseWorkspaceName = null; } } - return Workspaces::fromArray($baseWorkspaces); + return self::fromArray($baseWorkspaces); } /** - * @return \Traversable + * @return \Traversable */ public function getIterator(): \Traversable { @@ -105,7 +100,7 @@ public function getIterator(): \Traversable */ public function filter(\Closure $callback): self { - return new self(array_Filter($this->workspaces, $callback)); + return new self(array_filter($this->workspaces, $callback)); } /** diff --git a/Neos.ContentRepository.Export/src/ExportServiceFactory.php b/Neos.ContentRepository.Export/src/ExportServiceFactory.php index 4e2dadd59a5..798c8752430 100644 --- a/Neos.ContentRepository.Export/src/ExportServiceFactory.php +++ b/Neos.ContentRepository.Export/src/ExportServiceFactory.php @@ -6,7 +6,7 @@ use League\Flysystem\Filesystem; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryDependencies; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; -use Neos\ContentRepository\Core\Projection\Workspace\Workspace; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\Media\Domain\Repository\AssetRepository; use Neos\Neos\AssetUsage\Projection\AssetUsageFinder; diff --git a/Neos.ContentRepository.Export/src/Processors/EventStoreImportProcessor.php b/Neos.ContentRepository.Export/src/Processors/EventStoreImportProcessor.php index df2101f2dc5..50f4474f485 100644 --- a/Neos.ContentRepository.Export/src/Processors/EventStoreImportProcessor.php +++ b/Neos.ContentRepository.Export/src/Processors/EventStoreImportProcessor.php @@ -5,9 +5,7 @@ use League\Flysystem\Filesystem; use Neos\ContentRepository\Core\EventStore\DecoratedEvent; -use Neos\ContentRepository\Core\EventStore\EventInterface; use Neos\ContentRepository\Core\EventStore\EventNormalizer; -use Neos\ContentRepository\Core\EventStore\EventPersister; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; use Neos\ContentRepository\Core\Feature\ContentStreamCreation\Event\ContentStreamWasCreated; use Neos\ContentRepository\Core\Feature\ContentStreamEventStreamName; @@ -16,9 +14,7 @@ use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Event\RootWorkspaceWasCreated; use Neos\ContentRepository\Core\Feature\WorkspaceEventStreamName; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceDescription; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceTitle; use Neos\ContentRepository\Export\Event\ValueObject\ExportedEvent; use Neos\ContentRepository\Export\ProcessorInterface; use Neos\ContentRepository\Export\ProcessorResult; @@ -138,8 +134,6 @@ public function run(): ProcessorResult $this->eventNormalizer->normalize( new RootWorkspaceWasCreated( $workspaceName, - WorkspaceTitle::fromString('live workspace'), - WorkspaceDescription::fromString('live workspace'), $this->contentStreamId ) ) diff --git a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Bootstrap/FeatureContext.php b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Bootstrap/FeatureContext.php index 0c9d8dd9770..3f575418453 100644 --- a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Bootstrap/FeatureContext.php +++ b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Bootstrap/FeatureContext.php @@ -123,11 +123,11 @@ public function iRunTheEventMigration(string $contentStream = null): void { $nodeTypeManager = $this->currentContentRepository->getNodeTypeManager(); $propertyMapper = $this->getObject(PropertyMapper::class); - $contentGraphFinder = $this->currentContentRepository->projectionState(\Neos\ContentRepository\Core\ContentGraphFinder::class); + $contentGraphAdapter = $this->currentContentRepository->projectionState(\Neos\ContentRepository\Core\ContentGraphAdapter::class); // FIXME: Dirty - $contentGraphFactory = (new \ReflectionClass($contentGraphFinder)) + $contentGraphFactory = (new \ReflectionClass($contentGraphAdapter)) ->getProperty('contentGraphFactory') - ->getValue($contentGraphFinder); + ->getValue($contentGraphAdapter); $nodeFactory = (new \ReflectionClass($contentGraphFactory)) ->getProperty('nodeFactory') ->getValue($contentGraphFactory); diff --git a/Neos.ContentRepository.NodeMigration/src/NodeMigrationService.php b/Neos.ContentRepository.NodeMigration/src/NodeMigrationService.php index 69ba9a5bf7c..5fb2eaa06ab 100644 --- a/Neos.ContentRepository.NodeMigration/src/NodeMigrationService.php +++ b/Neos.ContentRepository.NodeMigration/src/NodeMigrationService.php @@ -7,14 +7,11 @@ use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Command\CreateWorkspace; +use Neos\ContentRepository\Core\Feature\WorkspaceModification\Command\DeleteWorkspace; +use Neos\ContentRepository\Core\Feature\WorkspacePublication\Command\PublishWorkspace; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; -use Neos\ContentRepository\Core\Projection\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceDescription; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceTitle; -use Neos\ContentRepository\Core\Feature\WorkspacePublication\Command\PublishWorkspace; -use Neos\ContentRepository\Core\Feature\WorkspaceModification\Command\DeleteWorkspace; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; use Neos\ContentRepository\NodeMigration\Command\ExecuteMigration; use Neos\ContentRepository\NodeMigration\Filter\FiltersFactory; use Neos\ContentRepository\NodeMigration\Filter\InvalidMigrationFilterSpecified; @@ -80,8 +77,6 @@ public function executeMigration(ExecuteMigration $command): void CreateWorkspace::create( $command->targetWorkspaceName, $sourceWorkspace->workspaceName, - WorkspaceTitle::fromString($command->targetWorkspaceName->value), - WorkspaceDescription::fromString(''), $command->contentStreamId, ) ); diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php index a9520df0c93..4c6e2877555 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php @@ -14,7 +14,7 @@ namespace Neos\ContentRepository\TestSuite\Behavior\Features\Bootstrap; -use Neos\ContentRepository\Core\ContentGraphFinder; +use Neos\ContentRepository\Core\ContentGraphAdapter; use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface; @@ -125,9 +125,9 @@ public function visibilityConstraintsAreSetTo(string $restrictionType): void public function getCurrentSubgraph(): ContentSubgraphInterface { - $contentGraphFinder = $this->currentContentRepository->projectionState(ContentGraphFinder::class); + $contentGraphAdapter = $this->currentContentRepository->projectionState(ContentGraphAdapter::class); - return $contentGraphFinder->getByWorkspaceName($this->currentWorkspaceName)->getSubgraph( + return $contentGraphAdapter->getContentGraphByWorkspaceName($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 06a5fd3fd13..ded9ac804fa 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php @@ -25,11 +25,12 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\NodeType\NodeTypeCriteria; use Neos\ContentRepository\Core\Projection\ContentGraph\Subtree; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; use Neos\ContentRepository\Core\Service\ContentStreamPruner; use Neos\ContentRepository\Core\Service\ContentStreamPrunerFactory; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamState; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamStatus; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepository\TestSuite\Behavior\Features\Bootstrap\Features\ContentStreamClosing; use Neos\ContentRepository\TestSuite\Behavior\Features\Bootstrap\Features\ContentStreamForking; @@ -133,7 +134,7 @@ protected function readPayloadTable(TableNode $payloadTable): array */ public function iExpectTheContentStreamToNotExist(string $rawContentStreamId): void { - // todo use, but also it should be assertFAlse?!!! + // todo use, but also it should be assertFAlse?!!! And what about theContentStreamDoesNotExist?? // $this->contentRepository->getContentStreams() // ->find(fn (ContentStream $contentStream) => $contentStream->id->equals(ContentStreamId::fromString($rawContentStreamId))), Assert::assertTrue( @@ -142,6 +143,15 @@ public function iExpectTheContentStreamToNotExist(string $rawContentStreamId): v ); } + /** + * @Then the content stream :contentStreamId does not exist + */ + public function theContentStreamDoesNotExist(string $contentStreamId): void + { + $contentStream = $this->currentContentRepository->findContentStreamById(ContentStreamId::fromString($contentStreamId)); + Assert::assertNull($contentStream, sprintf('Content stream "%s" was not expected to exist, but it does', $contentStreamId)); + } + /** * @Then /^workspace ([^"]*) has status ([^"]*)$/ */ @@ -230,27 +240,27 @@ protected function getRootNodeAggregateId(): ?NodeAggregateId } /** - * @Then the content stream :contentStreamId has state :expectedState + * @Then the content stream :contentStreamId has status :expectedState */ - public function theContentStreamHasState(string $contentStreamId, string $expectedState): void + public function theContentStreamHasStatus(string $contentStreamId, string $expectedStatus): void { - $contentStreamId = ContentStreamId::fromString($contentStreamId); - $contentStreamFinder = $this->currentContentRepository->getContentStreamFinder(); - - $actual = $contentStreamFinder->findStateForContentStream($contentStreamId); - Assert::assertSame(ContentStreamState::tryFrom($expectedState), $actual); + $contentStream = $this->currentContentRepository->findContentStreamById(ContentStreamId::fromString($contentStreamId)); + if ($contentStream === null) { + Assert::fail(sprintf('Expected content stream "%s" to have status "%s" but it does not exist', $contentStreamId, $expectedStatus)); + } + Assert::assertSame(ContentStreamStatus::tryFrom($expectedStatus), $contentStream->status); } /** - * @Then the current content stream has state :expectedState + * @Then the current content stream has status :expectedStatus */ - public function theCurrentContentStreamHasState(string $expectedState): void + public function theCurrentContentStreamHasStatus(string $expectedStatus): void { - $this->theContentStreamHasState( + $this->theContentStreamHasStatus( $this->currentContentRepository ->findWorkspaceByName($this->currentWorkspaceName) ->currentContentStreamId->value, - $expectedState + $expectedStatus ); } diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/WorkspaceCreation.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/WorkspaceCreation.php index bdfcd1dc5e4..b876e98a7b4 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/WorkspaceCreation.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/WorkspaceCreation.php @@ -21,11 +21,8 @@ use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Command\CreateWorkspace; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Command\RebaseWorkspace; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Dto\RebaseErrorHandlingStrategy; -use Neos\ContentRepository\Core\SharedModel\User\UserId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceDescription; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceTitle; use Neos\ContentRepository\TestSuite\Behavior\Features\Bootstrap\CRTestSuiteRuntimeVariables; use Neos\EventStore\Model\Event\StreamName; @@ -67,8 +64,6 @@ public function theCommandCreateRootWorkspaceIsExecutedWithPayload(TableNode $pa $command = CreateRootWorkspace::create( WorkspaceName::fromString($commandArguments['workspaceName']), - new WorkspaceTitle($commandArguments['workspaceTitle'] ?? ucfirst($commandArguments['workspaceName'])), - new WorkspaceDescription($commandArguments['workspaceDescription'] ?? ('The workspace "' . $commandArguments['workspaceName'] . '"')), ContentStreamId::fromString($commandArguments['newContentStreamId']) ); @@ -99,10 +94,7 @@ public function theCommandCreateWorkspaceIsExecutedWithPayload(TableNode $payloa $command = CreateWorkspace::create( WorkspaceName::fromString($commandArguments['workspaceName']), WorkspaceName::fromString($commandArguments['baseWorkspaceName']), - new WorkspaceTitle($commandArguments['workspaceTitle'] ?? ucfirst($commandArguments['workspaceName'])), - new WorkspaceDescription($commandArguments['workspaceDescription'] ?? 'The workspace "' . $commandArguments['workspaceName'] . '"'), ContentStreamId::fromString($commandArguments['newContentStreamId']), - isset($commandArguments['workspaceOwner']) ? UserId::fromString($commandArguments['workspaceOwner']) : null ); $this->currentContentRepository->handle($command); diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php index 017327f22ed..fcc37c84219 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php @@ -16,7 +16,7 @@ use Behat\Gherkin\Node\TableNode; use GuzzleHttp\Psr7\Uri; -use Neos\ContentRepository\Core\ContentGraphFinder; +use Neos\ContentRepository\Core\ContentGraphAdapter; use Neos\ContentRepository\Core\Feature\SubtreeTagging\Dto\SubtreeTag; use Neos\ContentRepository\Core\NodeType\NodeTypeName; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface; @@ -32,6 +32,8 @@ 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\Workspace; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceFilter; use Neos\ContentRepository\Core\Tests\Behavior\Fixtures\DayOfWeek; use Neos\ContentRepository\Core\Tests\Behavior\Fixtures\PostalAddress; use Neos\ContentRepository\Core\Tests\Behavior\Fixtures\PriceSpecification; @@ -80,12 +82,16 @@ public function iGetTheNodeAtPath(string $serializedNodePath): void public function iExpectANodeIdentifiedByXToExistInTheContentGraph(string $serializedNodeDiscriminator): void { $nodeDiscriminator = NodeDiscriminator::fromShorthand($serializedNodeDiscriminator); - // TODO Fix - $workspaceName = $this->currentContentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId( - $nodeDiscriminator->contentStreamId - )->workspaceName; - $contentGraph = $this->currentContentRepository->getContentGraph($workspaceName); - $currentNodeAggregate = $contentGraph->findNodeAggregateById( + $contentGraphAdapter = $this->currentContentRepository->projectionState(ContentGraphAdapter::class); + $contentGraphAdapter->forgetInstances(); + $matchingWorkspace = $this->currentContentRepository->getWorkspaces()->find( + static fn (Workspace $workspace) => $workspace->currentContentStreamId->equals($nodeDiscriminator->contentStreamId) + ); + if ($matchingWorkspace === null) { + Assert::fail(sprintf('Failed to find workspace for content stream id "%s"', $nodeDiscriminator->contentStreamId->value)); + } + $workspaceName = $matchingWorkspace->workspaceName; + $currentNodeAggregate = $this->currentContentRepository->getContentGraph($workspaceName)->findNodeAggregateById( $nodeDiscriminator->nodeAggregateId ); Assert::assertTrue( diff --git a/Neos.ContentRepositoryRegistry/Configuration/Objects.yaml b/Neos.ContentRepositoryRegistry/Configuration/Objects.yaml index 113f828b6d6..df582d41eba 100644 --- a/Neos.ContentRepositoryRegistry/Configuration/Objects.yaml +++ b/Neos.ContentRepositoryRegistry/Configuration/Objects.yaml @@ -4,23 +4,6 @@ Neos\ContentRepositoryRegistry\ContentRepositoryRegistry: setting: Neos.ContentRepositoryRegistry # !!! UGLY WORKAROUNDS, because we cannot wire non-Flow class constructor arguments here. -Neos\ContentRepository\Core\Projection\ContentStream\ContentStreamProjectionFactory: - scope: singleton - factoryObjectName: Neos\ContentRepositoryRegistry\Infrastructure\GenericObjectFactory - arguments: - 1: - value: Neos\ContentRepository\Core\Projection\ContentStream\ContentStreamProjectionFactory - 2: - object: 'Doctrine\DBAL\Connection' - -Neos\ContentRepository\Core\Projection\Workspace\WorkspaceProjectionFactory: - scope: singleton - factoryObjectName: Neos\ContentRepositoryRegistry\Infrastructure\GenericObjectFactory - arguments: - 1: - value: Neos\ContentRepository\Core\Projection\Workspace\WorkspaceProjectionFactory - 2: - object: 'Doctrine\DBAL\Connection' # This adds a soft-dependency to the neos/contentgraph-doctrinedbaladapter package Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphProjectionFactory: diff --git a/Neos.ContentRepositoryRegistry/Configuration/Settings.yaml b/Neos.ContentRepositoryRegistry/Configuration/Settings.yaml index 70b0fe0c5d8..44e28699641 100644 --- a/Neos.ContentRepositoryRegistry/Configuration/Settings.yaml +++ b/Neos.ContentRepositoryRegistry/Configuration/Settings.yaml @@ -69,10 +69,6 @@ Neos: className: Neos\ContentRepositoryRegistry\Infrastructure\Property\Normalizer\ProxyAwareObjectNormalizer projections: - 'Neos.ContentRepository:ContentStream': - factoryObjectName: Neos\ContentRepository\Core\Projection\ContentStream\ContentStreamProjectionFactory - 'Neos.ContentRepository:Workspace': - factoryObjectName: Neos\ContentRepository\Core\Projection\Workspace\WorkspaceProjectionFactory # 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 diff --git a/Neos.Media.Browser/Classes/Controller/UsageController.php b/Neos.Media.Browser/Classes/Controller/UsageController.php index c0fe9f11ae3..ef9cc14553f 100644 --- a/Neos.Media.Browser/Classes/Controller/UsageController.php +++ b/Neos.Media.Browser/Classes/Controller/UsageController.php @@ -14,7 +14,7 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindClosestNodeFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; -use Neos\ContentRepository\Core\Projection\Workspace\Workspace; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeNotFound; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; diff --git a/Neos.Media.Browser/Resources/Private/Templates/Usage/RelatedNodes.html b/Neos.Media.Browser/Resources/Private/Templates/Usage/RelatedNodes.html index 921537fb5c8..50396e7223a 100644 --- a/Neos.Media.Browser/Resources/Private/Templates/Usage/RelatedNodes.html +++ b/Neos.Media.Browser/Resources/Private/Templates/Usage/RelatedNodes.html @@ -142,7 +142,7 @@ - {nodeInformation.workspace.workspaceTitle.value -> f:format.crop(maxCharacters: 25, append: '…')} + {nodeInformation.workspace.name.value -> f:format.crop(maxCharacters: 25, append: '…')} diff --git a/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncService.php b/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncService.php index cb4c17337ef..34abd7997a8 100644 --- a/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncService.php +++ b/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncService.php @@ -7,7 +7,7 @@ use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; -use Neos\ContentRepository\Core\Projection\Workspace\Workspace; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; use Neos\Media\Domain\Model\AssetInterface; use Neos\Media\Domain\Repository\AssetRepository; use Neos\Neos\AssetUsage\Dto\AssetUsage; diff --git a/Neos.Neos/Classes/Command/WorkspaceCommandController.php b/Neos.Neos/Classes/Command/WorkspaceCommandController.php index eab83907d49..426bc07d670 100644 --- a/Neos.Neos/Classes/Command/WorkspaceCommandController.php +++ b/Neos.Neos/Classes/Command/WorkspaceCommandController.php @@ -20,16 +20,12 @@ use Neos\ContentRepository\Core\Feature\WorkspaceModification\Command\DeleteWorkspace; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Dto\RebaseErrorHandlingStrategy; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Exception\WorkspaceRebaseFailed; -use Neos\ContentRepository\Core\Projection\Workspace\Workspace; -use Neos\ContentRepository\Core\Projection\Workspace\Workspaces; -use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceStatus; use Neos\ContentRepository\Core\Service\WorkspaceMaintenanceServiceFactory; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceDescription; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceTitle; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; use Neos\Flow\Cli\CommandController; @@ -203,10 +199,7 @@ public function createCommand( $contentRepositoryInstance->handle(CreateWorkspace::create( WorkspaceName::fromString($workspace), WorkspaceName::fromString($baseWorkspace), - WorkspaceTitle::fromString($title ?: $workspace), - WorkspaceDescription::fromString($description ?: $workspace), ContentStreamId::create(), - $workspaceOwnerUserId !== null ? \Neos\ContentRepository\Core\SharedModel\User\UserId::fromString($workspaceOwnerUserId->value) : null, )); } catch (WorkspaceAlreadyExists $workspaceAlreadyExists) { $this->outputLine('Workspace "%s" already exists', [$workspace]); diff --git a/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php b/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php index 5afd08ec56e..7dabc86f97d 100755 --- a/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php +++ b/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php @@ -16,7 +16,7 @@ use Neos\ContentRepository\Core\Feature\NodeRenaming\Command\ChangeNodeAggregateName; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; -use Neos\ContentRepository\Core\Projection\Workspace\Workspace; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Exception\NodeNameIsAlreadyCovered; use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeNotFound; diff --git a/Neos.Neos/Classes/Domain/Service/UserService.php b/Neos.Neos/Classes/Domain/Service/UserService.php index 8ad74db35ec..ea3f627c86d 100644 --- a/Neos.Neos/Classes/Domain/Service/UserService.php +++ b/Neos.Neos/Classes/Domain/Service/UserService.php @@ -14,7 +14,7 @@ namespace Neos\Neos\Domain\Service; -use Neos\ContentRepository\Core\Projection\Workspace\Workspace; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; use Neos\Flow\Annotations as Flow; use Neos\Flow\Persistence\Exception\IllegalObjectTypeException; use Neos\Flow\Persistence\PersistenceManagerInterface; diff --git a/Neos.Neos/Classes/FrontendRouting/NodeAddressFactory.php b/Neos.Neos/Classes/FrontendRouting/NodeAddressFactory.php index b5169ae51c1..bb0087d66e3 100644 --- a/Neos.Neos/Classes/FrontendRouting/NodeAddressFactory.php +++ b/Neos.Neos/Classes/FrontendRouting/NodeAddressFactory.php @@ -18,6 +18,7 @@ use Neos\Neos\FrontendRouting\NodeAddress as LegacyNodeAddress; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; diff --git a/Neos.Neos/Classes/Fusion/Helper/CachingHelper.php b/Neos.Neos/Classes/Fusion/Helper/CachingHelper.php index c1a9f11c6ec..b5d720bc013 100644 --- a/Neos.Neos/Classes/Fusion/Helper/CachingHelper.php +++ b/Neos.Neos/Classes/Fusion/Helper/CachingHelper.php @@ -17,7 +17,7 @@ use Neos\ContentRepository\Core\NodeType\NodeTypeNames; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\Nodes; -use Neos\ContentRepository\Core\Projection\Workspace\Workspace; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Eel\ProtectedContextAwareInterface; diff --git a/Neos.Neos/Classes/Routing/Cache/RouteCacheFlusher.php b/Neos.Neos/Classes/Routing/Cache/RouteCacheFlusher.php index aa6e4e5b20e..fe347de81b9 100644 --- a/Neos.Neos/Classes/Routing/Cache/RouteCacheFlusher.php +++ b/Neos.Neos/Classes/Routing/Cache/RouteCacheFlusher.php @@ -15,7 +15,7 @@ namespace Neos\Neos\Routing\Cache; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; -use Neos\ContentRepository\Core\Projection\Workspace\Workspace; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; use Neos\Flow\Mvc\Routing\RouterCachingService; diff --git a/Neos.Neos/Classes/Service/LinkingService.php b/Neos.Neos/Classes/Service/LinkingService.php index f112a02e641..dbfa0c9ad49 100644 --- a/Neos.Neos/Classes/Service/LinkingService.php +++ b/Neos.Neos/Classes/Service/LinkingService.php @@ -312,7 +312,7 @@ public function createNodeUri( $workspace = $contentRepository->findWorkspaceByName($nodeAddress->workspaceName); $subgraph = $contentRepository->getContentGraph($nodeAddress->workspaceName)->getSubgraph( $nodeAddress->dimensionSpacePoint, - $workspace && !$workspace->isPublicWorkspace() + $workspace && !$workspace->workspaceName->isLive() ? VisibilityConstraints::withoutRestrictions() : VisibilityConstraints::frontend() ); @@ -354,7 +354,7 @@ public function createNodeUri( $mainRequest = $controllerContext->getRequest()->getMainRequest(); $uriBuilder = clone $controllerContext->getUriBuilder(); $uriBuilder->setRequest($mainRequest); - $createLiveUri = $workspace && $workspace->isPublicWorkspace() && $node->tags->contain(SubtreeTag::disabled()); + $createLiveUri = $workspace && $workspace->workspaceName->isLive() && $node->tags->contain(SubtreeTag::disabled()); if ($addQueryString === true) { // legacy feature see https://github.com/neos/neos-development-collection/issues/5076 diff --git a/Neos.TimeableNodeVisibility/Classes/Service/TimeableNodeVisibilityService.php b/Neos.TimeableNodeVisibility/Classes/Service/TimeableNodeVisibilityService.php index 47a40a73100..92561a17464 100644 --- a/Neos.TimeableNodeVisibility/Classes/Service/TimeableNodeVisibilityService.php +++ b/Neos.TimeableNodeVisibility/Classes/Service/TimeableNodeVisibilityService.php @@ -16,6 +16,7 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\PropertyValueLessThanOrEqual; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Node\NodeVariantSelectionStrategy; use Neos\ContentRepository\Core\SharedModel\Node\PropertyName; From a3e641479b33934115f42fcb4008de561d5f77e7 Mon Sep 17 00:00:00 2001 From: Bastian Waidelich Date: Mon, 3 Jun 2024 14:40:27 +0200 Subject: [PATCH 04/22] wip --- ... => ContentRepositoryReadModelAdapter.php} | 24 +++-------- .../DoctrineDbalContentGraphProjection.php | 10 ++--- ...trineDbalContentGraphProjectionFactory.php | 6 +-- .../Domain/Projection/Feature/Workspace.php | 8 ++-- ...ontentHyperRepositoryReadModelAdapter.php} | 43 ++++--------------- .../Projection/HypergraphProjection.php | 10 ++--- .../src/HypergraphProjectionFactory.php | 4 +- .../Classes/CommandHandlingDependencies.php | 2 +- .../Classes/ContentRepository.php | 14 +++--- ...ter.php => ContentRepositoryReadModel.php} | 28 ++++++------ ...ntRepositoryReadModelAdapterInterface.php} | 15 +++---- .../Classes/Service/ContentStreamPruner.php | 8 ++-- .../Service/ContentStreamPrunerFactory.php | 6 +-- .../SharedModel/Workspace/WorkspaceFilter.php | 37 ---------------- .../Behavior/Bootstrap/FeatureContext.php | 37 ++++++++-------- .../Bootstrap/CRTestSuiteRuntimeVariables.php | 6 +-- .../Features/Bootstrap/ProjectedNodeTrait.php | 7 ++- .../Classes/Domain/Service/UserService.php | 9 ++++ .../ChangeProjection.php | 4 +- 19 files changed, 107 insertions(+), 171 deletions(-) rename Neos.ContentGraph.DoctrineDbalAdapter/src/{ContentGraphFactory.php => ContentRepositoryReadModelAdapter.php} (89%) rename Neos.ContentGraph.PostgreSQLAdapter/src/{ContentHyperGraphFactory.php => ContentHyperRepositoryReadModelAdapter.php} (57%) rename Neos.ContentRepository.Core/Classes/{ContentGraphAdapter.php => ContentRepositoryReadModel.php} (81%) rename Neos.ContentRepository.Core/Classes/{ContentGraphFactoryInterface.php => ContentRepositoryReadModelAdapterInterface.php} (73%) delete mode 100644 Neos.ContentRepository.Core/Classes/SharedModel/Workspace/WorkspaceFilter.php diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentRepositoryReadModelAdapter.php similarity index 89% rename from Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php rename to Neos.ContentGraph.DoctrineDbalAdapter/src/ContentRepositoryReadModelAdapter.php index 0599dba402f..528b38d7c36 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentRepositoryReadModelAdapter.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\ContentGraphFactoryInterface; +use Neos\ContentRepository\Core\ContentRepositoryReadModelAdapterInterface; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; @@ -34,9 +34,9 @@ /** * @internal only used inside the - * @see ContentGraphAdapter + * @see ContentRepositoryReadModel */ -final readonly class ContentGraphFactory implements ContentGraphFactoryInterface +final readonly class ContentRepositoryReadModelAdapter implements ContentRepositoryReadModelAdapterInterface { public function __construct( private Connection $dbal, @@ -47,17 +47,7 @@ public function __construct( ) { } - public function buildForWorkspace(WorkspaceName $workspaceName): ContentGraph - { - $workspace = $this->findWorkspaceByName($workspaceName); - - if ($workspace === null) { - throw WorkspaceDoesNotExist::butWasSupposedTo($workspaceName); - } - return $this->buildForWorkspaceAndContentStream($workspace->workspaceName, $workspace->currentContentStreamId); - } - - public function buildForWorkspaceAndContentStream(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraph + public function buildContentGraph(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraph { return new ContentGraph($this->dbal, $this->nodeFactory, $this->contentRepositoryId, $this->nodeTypeManager, $this->tableNames, $workspaceName, $contentStreamId); } @@ -86,7 +76,7 @@ public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace return self::workspaceFromDatabaseRow($row); } - public function getWorkspaces(): Workspaces + public function findWorkspaces(): Workspaces { $workspacesStatement = << + * @implements ProjectionInterface * @internal but the graph projection is api */ final class DoctrineDbalContentGraphProjection implements ProjectionInterface @@ -97,7 +97,7 @@ public function __construct( private readonly ProjectionContentGraph $projectionContentGraph, private readonly ContentGraphTableNames $tableNames, private readonly DimensionSpacePointsRepository $dimensionSpacePointsRepository, - private readonly ContentGraphAdapter $contentGraphAdapter + private readonly ContentRepositoryReadModel $contentRepositoryReadModel ) { $this->checkpointStorage = new DbalCheckpointStorage( $this->dbal, @@ -175,9 +175,9 @@ public function getCheckpointStorage(): DbalCheckpointStorage return $this->checkpointStorage; } - public function getState(): ContentGraphAdapter + public function getState(): ContentRepositoryReadModel { - return $this->contentGraphAdapter; + return $this->contentRepositoryReadModel; } public function canHandle(EventInterface $event): bool diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php index 4e063b7e412..1b750c114a4 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php @@ -8,7 +8,7 @@ use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\DimensionSpacePointsRepository; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\NodeFactory; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ProjectionContentGraph; -use Neos\ContentRepository\Core\ContentGraphAdapter; +use Neos\ContentRepository\Core\ContentRepositoryReadModel; use Neos\ContentRepository\Core\Factory\ProjectionFactoryDependencies; use Neos\ContentRepository\Core\Projection\ProjectionFactoryInterface; @@ -42,7 +42,7 @@ public function build( $dimensionSpacePointsRepository ); - $contentGraphFactory = new ContentGraphFactory( + $contentRepositoryReadModelAdapter = new ContentRepositoryReadModelAdapter( $this->dbal, $nodeFactory, $projectionFactoryDependencies->contentRepositoryId, @@ -58,7 +58,7 @@ public function build( ), $tableNames, $dimensionSpacePointsRepository, - new ContentGraphAdapter($contentGraphFactory) + new ContentRepositoryReadModel($contentRepositoryReadModelAdapter) ); } } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/Workspace.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/Workspace.php index e4c28fd6cec..6fe5bbce656 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/Workspace.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/Workspace.php @@ -58,7 +58,7 @@ private function updateWorkspaceContentStreamId( private function markWorkspaceAsUpToDate(WorkspaceName $workspaceName): void { - $this->dbal->executeUpdate(' + $this->dbal->executeStatement(' UPDATE ' . $this->tableNames->workspace() . ' SET status = :upToDate WHERE @@ -71,7 +71,7 @@ private function markWorkspaceAsUpToDate(WorkspaceName $workspaceName): void private function markDependentWorkspacesAsOutdated(WorkspaceName $baseWorkspaceName): void { - $this->dbal->executeUpdate(' + $this->dbal->executeStatement(' UPDATE ' . $this->tableNames->workspace() . ' SET status = :outdated WHERE @@ -84,7 +84,7 @@ private function markDependentWorkspacesAsOutdated(WorkspaceName $baseWorkspaceN private function markWorkspaceAsOutdated(WorkspaceName $workspaceName): void { - $this->dbal->executeUpdate(' + $this->dbal->executeStatement(' UPDATE ' . $this->tableNames->workspace() . ' SET status = :outdated @@ -98,7 +98,7 @@ private function markWorkspaceAsOutdated(WorkspaceName $workspaceName): void private function markWorkspaceAsOutdatedConflict(WorkspaceName $workspaceName): void { - $this->dbal->executeUpdate(' + $this->dbal->executeStatement(' UPDATE ' . $this->tableNames->workspace() . ' SET status = :outdatedConflict diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperGraphFactory.php b/Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperRepositoryReadModelAdapter.php similarity index 57% rename from Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperGraphFactory.php rename to Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperRepositoryReadModelAdapter.php index f9d28d0dd68..11b5e9ea536 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperGraphFactory.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperRepositoryReadModelAdapter.php @@ -7,12 +7,11 @@ use Doctrine\DBAL\Connection; use Neos\ContentGraph\PostgreSQLAdapter\Domain\Repository\ContentHypergraph; use Neos\ContentGraph\PostgreSQLAdapter\Domain\Repository\NodeFactory; -use Neos\ContentRepository\Core\ContentGraphFactoryInterface; -use Neos\ContentRepository\Core\ContentGraphAdapter; +use Neos\ContentRepository\Core\ContentRepositoryReadModel; +use Neos\ContentRepository\Core\ContentRepositoryReadModelAdapterInterface; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; -use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStream; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreams; @@ -22,9 +21,9 @@ /** * @internal only used within - * @see ContentGraphAdapter + * @see ContentRepositoryReadModel */ -final readonly class ContentHyperGraphFactory implements ContentGraphFactoryInterface +final readonly class ContentHyperRepositoryReadModelAdapter implements ContentRepositoryReadModelAdapterInterface { public function __construct( private Connection $dbal, @@ -35,33 +34,7 @@ public function __construct( ) { } - public function buildForWorkspace(WorkspaceName $workspaceName): ContentGraphInterface - { - // FIXME: Should be part of this projection, this is forbidden - $tableName = strtolower(sprintf( - 'cr_%s_p_%s', - $this->contentRepositoryId->value, - 'Workspace' - )); - - $row = $this->dbal->executeQuery( - ' - SELECT * FROM ' . $tableName . ' - WHERE workspaceName = :workspaceName - ', - [ - 'workspaceName' => $workspaceName->value, - ] - )->fetchAssociative(); - - if ($row === false) { - throw new ContentStreamDoesNotExistYet('The workspace "' . $workspaceName->value . '" does not exist.', 1714839710); - } - - return $this->buildForWorkspaceAndContentStream($workspaceName, ContentStreamId::fromString($row['currentcontentstreamid'])); - } - - public function buildForWorkspaceAndContentStream(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphInterface + public function buildContentGraph(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphInterface { return new ContentHyperGraph($this->dbal, $this->nodeFactory, $this->contentRepositoryId, $this->nodeTypeManager, $this->tableNamePrefix, $workspaceName, $contentStreamId); } @@ -72,7 +45,7 @@ public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace return null; } - public function getWorkspaces(): Workspaces + public function findWorkspaces(): Workspaces { // TODO: Implement getWorkspaces() method. return Workspaces::createEmpty(); @@ -84,13 +57,13 @@ public function findContentStreamById(ContentStreamId $contentStreamId): ?Conten return null; } - public function getContentStreams(): ContentStreams + public function findContentStreams(): ContentStreams { // TODO: Implement getContentStreams() method. return ContentStreams::createEmpty(); } - public function getUnusedAndRemovedContentStreamIds(): iterable + public function findUnusedAndRemovedContentStreamIds(): iterable { // TODO: Implement getUnusedAndRemovedContentStreamIds() method. return []; diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Projection/HypergraphProjection.php b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Projection/HypergraphProjection.php index afc1b9c0249..d5e9fb999bf 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\ContentGraphAdapter; +use Neos\ContentRepository\Core\ContentRepositoryReadModel; use Neos\ContentRepository\Core\EventStore\EventInterface; use Neos\ContentRepository\Core\Feature\ContentStreamForking\Event\ContentStreamWasForked; use Neos\ContentRepository\Core\Feature\NodeCreation\Event\NodeAggregateWithNodeWasCreated; @@ -52,7 +52,7 @@ /** * The alternate reality-aware hypergraph projector for the PostgreSQL backend via Doctrine DBAL * - * @implements ProjectionInterface + * @implements ProjectionInterface * @internal the parent Content Graph is public */ final class HypergraphProjection implements ProjectionInterface @@ -73,7 +73,7 @@ final class HypergraphProjection implements ProjectionInterface public function __construct( private readonly Connection $dbal, private readonly string $tableNamePrefix, - private readonly ContentGraphAdapter $contentGraphAdapter + private readonly ContentRepositoryReadModel $contentRepositoryReadModel ) { $this->projectionHypergraph = new ProjectionHypergraph($this->dbal, $this->tableNamePrefix); $this->checkpointStorage = new DbalCheckpointStorage( @@ -219,9 +219,9 @@ public function getCheckpointStorage(): DbalCheckpointStorage return $this->checkpointStorage; } - public function getState(): ContentGraphAdapter + public function getState(): ContentRepositoryReadModel { - return $this->contentGraphAdapter; + return $this->contentRepositoryReadModel; } protected function getProjectionHypergraph(): ProjectionHypergraph diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/HypergraphProjectionFactory.php b/Neos.ContentGraph.PostgreSQLAdapter/src/HypergraphProjectionFactory.php index 83aab2d3553..1e3fbdb872a 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/HypergraphProjectionFactory.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/HypergraphProjectionFactory.php @@ -7,7 +7,7 @@ use Doctrine\DBAL\Connection; use Neos\ContentGraph\PostgreSQLAdapter\Domain\Projection\HypergraphProjection; use Neos\ContentGraph\PostgreSQLAdapter\Domain\Repository\NodeFactory; -use Neos\ContentRepository\Core\ContentGraphAdapter; +use Neos\ContentRepository\Core\ContentRepositoryReadModel; use Neos\ContentRepository\Core\Factory\ProjectionFactoryDependencies; use Neos\ContentRepository\Core\Projection\ProjectionFactoryInterface; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; @@ -45,7 +45,7 @@ public function build( return new HypergraphProjection( $this->dbal, $tableNamePrefix, - new ContentGraphAdapter(new ContentHyperGraphFactory($this->dbal, $nodeFactory, $projectionFactoryDependencies->contentRepositoryId, $projectionFactoryDependencies->nodeTypeManager, $tableNamePrefix)) + new ContentRepositoryReadModel(new ContentHyperRepositoryReadModelAdapter($this->dbal, $nodeFactory, $projectionFactoryDependencies->contentRepositoryId, $projectionFactoryDependencies->nodeTypeManager, $tableNamePrefix)) ); } } diff --git a/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php b/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php index 896fbfb938f..1c778159fcd 100644 --- a/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php +++ b/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php @@ -102,7 +102,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(ContentGraphAdapter::class)->getContentGraphByWorkspaceNameAndContentStreamId($workspaceName, $contentStreamId); + $contentGraph = $this->contentRepository->projectionState(ContentRepositoryReadModel::class)->getContentGraphByWorkspaceNameAndContentStreamId($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 12756b66312..e12aed5f549 100644 --- a/Neos.ContentRepository.Core/Classes/ContentRepository.php +++ b/Neos.ContentRepository.Core/Classes/ContentRepository.php @@ -243,22 +243,22 @@ public function getNodeTypeManager(): NodeTypeManager public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace { - return $this->getContentGraphAdapter()->findWorkspaceByName($workspaceName); + return $this->getContentRepositoryReadModel()->findWorkspaceByName($workspaceName); } public function getWorkspaces(): Workspaces { - return $this->getContentGraphAdapter()->getWorkspaces(); + return $this->getContentRepositoryReadModel()->findWorkspaces(); } public function findContentStreamById(ContentStreamId $contentStreamId): ?ContentStream { - return $this->getContentGraphAdapter()->findContentStreamById($contentStreamId); + return $this->getContentRepositoryReadModel()->findContentStreamById($contentStreamId); } public function getContentStreams(): ContentStreams { - return $this->getContentGraphAdapter()->getContentStreams(); + return $this->getContentRepositoryReadModel()->findContentStreams(); } /** @@ -266,7 +266,7 @@ public function getContentStreams(): ContentStreams */ public function getContentGraph(WorkspaceName $workspaceName): ContentGraphInterface { - return $this->getContentGraphAdapter()->getContentGraphByWorkspaceName($workspaceName); + return $this->getContentRepositoryReadModel()->getContentGraphByWorkspaceName($workspaceName); } public function getVariationGraph(): InterDimensionalVariationGraph @@ -279,8 +279,8 @@ public function getContentDimensionSource(): ContentDimensionSourceInterface return $this->contentDimensionSource; } - private function getContentGraphAdapter(): ContentGraphAdapter + private function getContentRepositoryReadModel(): ContentRepositoryReadModel { - return $this->projectionState(ContentGraphAdapter::class); + return $this->projectionState(ContentRepositoryReadModel::class); } } diff --git a/Neos.ContentRepository.Core/Classes/ContentGraphAdapter.php b/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModel.php similarity index 81% rename from Neos.ContentRepository.Core/Classes/ContentGraphAdapter.php rename to Neos.ContentRepository.Core/Classes/ContentRepositoryReadModel.php index 4e9f8cc27d1..66f56033ce0 100644 --- a/Neos.ContentRepository.Core/Classes/ContentGraphAdapter.php +++ b/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModel.php @@ -32,7 +32,7 @@ * @internal User land code should not use this directly. * @see ContentRepository::getContentGraph() */ -final class ContentGraphAdapter implements ProjectionStateInterface +final class ContentRepositoryReadModel implements ProjectionStateInterface { /** * @var array Runtime cache for {@see ContentGraphInterface} instances, indexed by their workspace name @@ -50,7 +50,7 @@ final class ContentGraphAdapter implements ProjectionStateInterface private array $contentStreamInstancesById = []; public function __construct( - private readonly ContentGraphFactoryInterface $contentGraphFactory + private readonly ContentRepositoryReadModelAdapterInterface $adapter ) { } @@ -70,7 +70,7 @@ public function forgetInstances(): void public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace { if (!array_key_exists($workspaceName->value, $this->workspaceInstancesByName)) { - $workspace = $this->contentGraphFactory->findWorkspaceByName($workspaceName); + $workspace = $this->adapter->findWorkspaceByName($workspaceName); if ($workspace === null) { return null; } @@ -79,15 +79,15 @@ public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace return $this->workspaceInstancesByName[$workspaceName->value]; } - public function getWorkspaces(): Workspaces + public function findWorkspaces(): Workspaces { - return $this->contentGraphFactory->getWorkspaces(); + return $this->adapter->findWorkspaces(); } public function findContentStreamById(ContentStreamId $contentStreamId): ?ContentStream { if (!array_key_exists($contentStreamId->value, $this->contentStreamInstancesById)) { - $contentStream = $this->contentGraphFactory->findContentStreamById($contentStreamId); + $contentStream = $this->adapter->findContentStreamById($contentStreamId); if ($contentStream === null) { return null; } @@ -96,18 +96,18 @@ public function findContentStreamById(ContentStreamId $contentStreamId): ?Conten return $this->contentStreamInstancesById[$contentStreamId->value]; } - public function getContentStreams(): ContentStreams + public function findContentStreams(): ContentStreams { - return $this->contentGraphFactory->getContentStreams(); + return $this->adapter->findContentStreams(); } /** * @return iterable * @internal This is currently only used by the {@see ContentStreamPruner} and might be removed in the future! */ - public function getUnusedAndRemovedContentStreamIds(): iterable + public function findUnusedAndRemovedContentStreamIds(): iterable { - return $this->contentGraphFactory->getUnusedAndRemovedContentStreamIds(); + return $this->adapter->findUnusedAndRemovedContentStreamIds(); } /** @@ -120,7 +120,11 @@ public function getUnusedAndRemovedContentStreamIds(): iterable public function getContentGraphByWorkspaceName(WorkspaceName $workspaceName): ContentGraphInterface { if (!array_key_exists($workspaceName->value, $this->contentGraphInstancesByWorkspaceName)) { - $this->contentGraphInstancesByWorkspaceName[$workspaceName->value] = $this->contentGraphFactory->buildForWorkspace($workspaceName); + $workspace = $this->findWorkspaceByName($workspaceName); + if ($workspace === null) { + throw WorkspaceDoesNotExist::butWasSupposedTo($workspaceName); + } + $this->contentGraphInstancesByWorkspaceName[$workspaceName->value] = $this->adapter->buildContentGraph($workspace->workspaceName, $workspace->currentContentStreamId); } return $this->contentGraphInstancesByWorkspaceName[$workspaceName->value]; } @@ -134,6 +138,6 @@ public function getContentGraphByWorkspaceName(WorkspaceName $workspaceName): Co */ public function getContentGraphByWorkspaceNameAndContentStreamId(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphInterface { - return $this->contentGraphFactory->buildForWorkspaceAndContentStream($workspaceName, $contentStreamId); + return $this->adapter->buildContentGraph($workspaceName, $contentStreamId); } } diff --git a/Neos.ContentRepository.Core/Classes/ContentGraphFactoryInterface.php b/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModelAdapterInterface.php similarity index 73% rename from Neos.ContentRepository.Core/Classes/ContentGraphFactoryInterface.php rename to Neos.ContentRepository.Core/Classes/ContentRepositoryReadModelAdapterInterface.php index 2bf5dab4edc..ec2c8c855f1 100644 --- a/Neos.ContentRepository.Core/Classes/ContentGraphFactoryInterface.php +++ b/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModelAdapterInterface.php @@ -28,26 +28,21 @@ * * @internal This is just an implementation detail to delegate creating the specific implementations of a ContentGraph. */ -interface ContentGraphFactoryInterface +interface ContentRepositoryReadModelAdapterInterface { - /** - * @throws WorkspaceDoesNotExist if the workspace does not exist - */ - public function buildForWorkspace(WorkspaceName $workspaceName): ContentGraphInterface; - - public function buildForWorkspaceAndContentStream(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphInterface; + public function buildContentGraph(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphInterface; public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace; - public function getWorkspaces(): Workspaces; + public function findWorkspaces(): Workspaces; public function findContentStreamById(ContentStreamId $contentStreamId): ?ContentStream; - public function getContentStreams(): ContentStreams; + public function findContentStreams(): ContentStreams; /** * @return iterable * @internal This is currently only used by the {@see ContentStreamPruner} and might be removed in the future! */ - public function getUnusedAndRemovedContentStreamIds(): iterable; + public function findUnusedAndRemovedContentStreamIds(): iterable; } diff --git a/Neos.ContentRepository.Core/Classes/Service/ContentStreamPruner.php b/Neos.ContentRepository.Core/Classes/Service/ContentStreamPruner.php index cd151bf631d..328f46720cd 100644 --- a/Neos.ContentRepository.Core/Classes/Service/ContentStreamPruner.php +++ b/Neos.ContentRepository.Core/Classes/Service/ContentStreamPruner.php @@ -4,7 +4,7 @@ namespace Neos\ContentRepository\Core\Service; -use Neos\ContentRepository\Core\ContentGraphAdapter; +use Neos\ContentRepository\Core\ContentRepositoryReadModel; use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; use Neos\ContentRepository\Core\Feature\ContentStreamEventStreamName; @@ -15,7 +15,7 @@ use Neos\EventStore\EventStoreInterface; /** - * For internal implementation details, see {@see ContentGraphAdapter}. + * For internal implementation details, see {@see ContentRepositoryReadModel}. * * @api */ @@ -24,7 +24,7 @@ class ContentStreamPruner implements ContentRepositoryServiceInterface public function __construct( private readonly ContentRepository $contentRepository, private readonly EventStoreInterface $eventStore, - private readonly ContentGraphAdapter $contentGraphAdapter, + private readonly ContentRepositoryReadModel $contentRepositoryReadModel, ) { } @@ -78,7 +78,7 @@ public function prune(bool $removeTemporary = false): iterable */ public function pruneRemovedFromEventStream(): iterable { - $removedContentStreamIds = $this->contentGraphAdapter->getUnusedAndRemovedContentStreamIds(); + $removedContentStreamIds = $this->contentRepositoryReadModel->findUnusedAndRemovedContentStreamIds(); foreach ($removedContentStreamIds as $removedContentStream) { $streamName = ContentStreamEventStreamName::fromContentStreamId($removedContentStream) ->getEventStreamName(); diff --git a/Neos.ContentRepository.Core/Classes/Service/ContentStreamPrunerFactory.php b/Neos.ContentRepository.Core/Classes/Service/ContentStreamPrunerFactory.php index 4f72263e21f..16afae4e370 100644 --- a/Neos.ContentRepository.Core/Classes/Service/ContentStreamPrunerFactory.php +++ b/Neos.ContentRepository.Core/Classes/Service/ContentStreamPrunerFactory.php @@ -4,7 +4,7 @@ namespace Neos\ContentRepository\Core\Service; -use Neos\ContentRepository\Core\ContentGraphAdapter; +use Neos\ContentRepository\Core\ContentRepositoryReadModel; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryDependencies; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; @@ -16,11 +16,11 @@ class ContentStreamPrunerFactory implements ContentRepositoryServiceFactoryInter { public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): ContentStreamPruner { - $contentGraphAdapter = $serviceFactoryDependencies->contentRepository->projectionState(ContentGraphAdapter::class); + $contentRepositoryReadModel = $serviceFactoryDependencies->contentRepository->projectionState(ContentRepositoryReadModel::class); return new ContentStreamPruner( $serviceFactoryDependencies->contentRepository, $serviceFactoryDependencies->eventStore, - $contentGraphAdapter, + $contentRepositoryReadModel, ); } } diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/WorkspaceFilter.php b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/WorkspaceFilter.php deleted file mode 100644 index 70e6a1e55b6..00000000000 --- a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/WorkspaceFilter.php +++ /dev/null @@ -1,37 +0,0 @@ -currentContentRepository->getNodeTypeManager(); $propertyMapper = $this->getObject(PropertyMapper::class); - $contentGraphAdapter = $this->currentContentRepository->projectionState(\Neos\ContentRepository\Core\ContentGraphAdapter::class); - // FIXME: Dirty - $contentGraphFactory = (new \ReflectionClass($contentGraphAdapter)) - ->getProperty('contentGraphFactory') - ->getValue($contentGraphAdapter); - $nodeFactory = (new \ReflectionClass($contentGraphFactory)) - ->getProperty('nodeFactory') - ->getValue($contentGraphFactory); - $propertyConverter = (new \ReflectionClass($nodeFactory)) - ->getProperty('propertyConverter') - ->getValue($nodeFactory); - $interDimensionalVariationGraph = $this->currentContentRepository->getVariationGraph(); - - $eventNormalizer = $this->getObject(EventNormalizer::class); + + // HACK to access the property converter + $propertyConverterAccess = new class implements ContentRepositoryServiceFactoryInterface { + public PropertyConverter|null $propertyConverter; + public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): ContentRepositoryServiceInterface + { + $this->propertyConverter = $serviceFactoryDependencies->propertyConverter; + return new class implements ContentRepositoryServiceInterface + { + }; + } + }; + $this->getContentRepositoryService($propertyConverterAccess); + $migration = new NodeDataToEventsProcessor( $nodeTypeManager, $propertyMapper, - $propertyConverter, - $interDimensionalVariationGraph, - $eventNormalizer, + $propertyConverterAccess->propertyConverter, + $this->currentContentRepository->getVariationGraph(), + $this->getObject(EventNormalizer::class), $this->mockFilesystem, $this->nodeDataRows ); diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php index 4c6e2877555..12ebd6b0a5f 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php @@ -14,7 +14,7 @@ namespace Neos\ContentRepository\TestSuite\Behavior\Features\Bootstrap; -use Neos\ContentRepository\Core\ContentGraphAdapter; +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 +125,9 @@ public function visibilityConstraintsAreSetTo(string $restrictionType): void public function getCurrentSubgraph(): ContentSubgraphInterface { - $contentGraphAdapter = $this->currentContentRepository->projectionState(ContentGraphAdapter::class); + $contentRepositoryReadModel = $this->currentContentRepository->projectionState(ContentRepositoryReadModel::class); - return $contentGraphAdapter->getContentGraphByWorkspaceName($this->currentWorkspaceName)->getSubgraph( + return $contentRepositoryReadModel->getContentGraphByWorkspaceName($this->currentWorkspaceName)->getSubgraph( $this->currentDimensionSpacePoint, $this->currentVisibilityConstraints ); diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php index fcc37c84219..84f92067e16 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php @@ -16,7 +16,7 @@ use Behat\Gherkin\Node\TableNode; use GuzzleHttp\Psr7\Uri; -use Neos\ContentRepository\Core\ContentGraphAdapter; +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; @@ -33,7 +33,6 @@ use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\ContentRepository\Core\SharedModel\Node\PropertyName; use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceFilter; use Neos\ContentRepository\Core\Tests\Behavior\Fixtures\DayOfWeek; use Neos\ContentRepository\Core\Tests\Behavior\Fixtures\PostalAddress; use Neos\ContentRepository\Core\Tests\Behavior\Fixtures\PriceSpecification; @@ -82,8 +81,8 @@ public function iGetTheNodeAtPath(string $serializedNodePath): void public function iExpectANodeIdentifiedByXToExistInTheContentGraph(string $serializedNodeDiscriminator): void { $nodeDiscriminator = NodeDiscriminator::fromShorthand($serializedNodeDiscriminator); - $contentGraphAdapter = $this->currentContentRepository->projectionState(ContentGraphAdapter::class); - $contentGraphAdapter->forgetInstances(); + $contentRepositoryReadModel = $this->currentContentRepository->projectionState(ContentRepositoryReadModel::class); + $contentRepositoryReadModel->forgetInstances(); $matchingWorkspace = $this->currentContentRepository->getWorkspaces()->find( static fn (Workspace $workspace) => $workspace->currentContentStreamId->equals($nodeDiscriminator->contentStreamId) ); diff --git a/Neos.Neos/Classes/Domain/Service/UserService.php b/Neos.Neos/Classes/Domain/Service/UserService.php index ea3f627c86d..086764939d4 100644 --- a/Neos.Neos/Classes/Domain/Service/UserService.php +++ b/Neos.Neos/Classes/Domain/Service/UserService.php @@ -15,6 +15,7 @@ namespace Neos\Neos\Domain\Service; use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\Flow\Annotations as Flow; use Neos\Flow\Persistence\Exception\IllegalObjectTypeException; use Neos\Flow\Persistence\PersistenceManagerInterface; @@ -831,4 +832,12 @@ private function getNeosUserForAccount(Account $account): ?User $user = $this->partyService->getAssignedPartyOfAccount($account); return ($user instanceof User) ? $user : null; } + + /** + * Checks if this workspace is a user's personal workspace + */ + private static function isPersonalWorkspace(WorkspaceName $workspaceName): bool + { + return str_starts_with($workspaceName->value, 'user-'); + } } diff --git a/Neos.Neos/Classes/PendingChangesProjection/ChangeProjection.php b/Neos.Neos/Classes/PendingChangesProjection/ChangeProjection.php index 310319f1768..22cd97b9e7f 100644 --- a/Neos.Neos/Classes/PendingChangesProjection/ChangeProjection.php +++ b/Neos.Neos/Classes/PendingChangesProjection/ChangeProjection.php @@ -300,7 +300,7 @@ private function whenNodeAggregateWasRemoved(NodeAggregateWasRemoved $event): vo return; } - $this->dbal->executeUpdate( + $this->dbal->executeStatement( 'DELETE FROM ' . $this->tableNamePrefix . ' WHERE contentStreamId = :contentStreamId @@ -319,7 +319,7 @@ private function whenNodeAggregateWasRemoved(NodeAggregateWasRemoved $event): vo ); foreach ($event->affectedOccupiedDimensionSpacePoints as $occupiedDimensionSpacePoint) { - $this->dbal->executeUpdate( + $this->dbal->executeStatement( 'INSERT INTO ' . $this->tableNamePrefix . ' (contentStreamId, nodeAggregateId, originDimensionSpacePoint, originDimensionSpacePointHash, created, deleted, changed, moved, removalAttachmentPoint) From 17e39a8711cd22a001609ebb9cc967319a1529d2 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Tue, 1 Oct 2024 19:44:29 +0200 Subject: [PATCH 05/22] TASK: Post rebase phpstan adjustments --- .../Classes/ContentRepository.php | 1 + .../Classes/Infrastructure/DbalSchemaFactory.php | 7 ------- .../src/NodeMigrationService.php | 4 ++-- .../Classes/Command/WorkspaceCommandController.php | 4 ++-- Neos.Neos/Classes/Domain/Service/UserService.php | 10 ---------- .../Domain/Service/WorkspacePublishingService.php | 4 ++-- .../Classes/Domain/Service/WorkspaceService.php | 13 +++---------- .../Cache/AssetChangeHandlerForCacheFlushing.php | 5 ++++- .../Classes/Controller/WorkspaceController.php | 14 +++++++++----- 9 files changed, 23 insertions(+), 39 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/ContentRepository.php b/Neos.ContentRepository.Core/Classes/ContentRepository.php index e12aed5f549..bf3b69731b2 100644 --- a/Neos.ContentRepository.Core/Classes/ContentRepository.php +++ b/Neos.ContentRepository.Core/Classes/ContentRepository.php @@ -42,6 +42,7 @@ use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStream; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreams; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepository\Core\SharedModel\Workspace\Workspaces; use Neos\EventStore\EventStoreInterface; diff --git a/Neos.ContentRepository.Core/Classes/Infrastructure/DbalSchemaFactory.php b/Neos.ContentRepository.Core/Classes/Infrastructure/DbalSchemaFactory.php index 767bde52864..34b59dc9ab0 100644 --- a/Neos.ContentRepository.Core/Classes/Infrastructure/DbalSchemaFactory.php +++ b/Neos.ContentRepository.Core/Classes/Infrastructure/DbalSchemaFactory.php @@ -63,13 +63,6 @@ public static function columnForContentStreamId(string $columnName): Column ->setLength(36); } - public static function columnForWorkspaceName(string $columnName): Column - { - return (new Column($columnName, Type::getType(Types::STRING))) - ->setLength(WorkspaceName::MAX_LENGTH) - ->setCustomSchemaOption('collation', 'utf8mb4_unicode_520_ci'); - } - /** * An anchorpoint can be used in a given projection to link two nodes, it is a purely internal identifier and should * be as performant as possible in queries, the current code uses UUIDs, diff --git a/Neos.ContentRepository.NodeMigration/src/NodeMigrationService.php b/Neos.ContentRepository.NodeMigration/src/NodeMigrationService.php index 5fb2eaa06ab..63669814819 100644 --- a/Neos.ContentRepository.NodeMigration/src/NodeMigrationService.php +++ b/Neos.ContentRepository.NodeMigration/src/NodeMigrationService.php @@ -67,7 +67,7 @@ public function executeMigration(ExecuteMigration $command): void } $targetWorkspaceWasCreated = false; - if ($targetWorkspace = $this->contentRepository->getWorkspaceFinder()->findOneByName($command->targetWorkspaceName)) { + if ($targetWorkspace = $this->contentRepository->findWorkspaceByName($command->targetWorkspaceName)) { if (!$this->workspaceIsEmpty($targetWorkspace)) { throw new MigrationException(sprintf('Target workspace "%s" already exists an is not empty. Please clear the workspace before.', $targetWorkspace->workspaceName->value)); } @@ -80,7 +80,7 @@ public function executeMigration(ExecuteMigration $command): void $command->contentStreamId, ) ); - $targetWorkspace = $this->contentRepository->getWorkspaceFinder()->findOneByName($command->targetWorkspaceName); + $targetWorkspace = $this->contentRepository->findWorkspaceByName($command->targetWorkspaceName); $targetWorkspaceWasCreated = true; } diff --git a/Neos.Neos/Classes/Command/WorkspaceCommandController.php b/Neos.Neos/Classes/Command/WorkspaceCommandController.php index 426bc07d670..49b52451523 100644 --- a/Neos.Neos/Classes/Command/WorkspaceCommandController.php +++ b/Neos.Neos/Classes/Command/WorkspaceCommandController.php @@ -260,7 +260,7 @@ public function deleteCommand(string $workspace, bool $force = false, string $co $this->quit(2); } - $dependentWorkspaces = $contentRepository->getWorkspaces()->filter( + $dependentWorkspaces = $contentRepositoryInstance->getWorkspaces()->filter( static fn (Workspace $potentiallyDependentWorkspace) => $potentiallyDependentWorkspace->baseWorkspaceName?->equals($workspaceName) ?? false ); if (!$dependentWorkspaces->isEmpty()) { @@ -390,7 +390,7 @@ public function showCommand(string $workspace, string $contentRepository = 'defa $contentRepositoryInstance = $this->contentRepositoryRegistry->get($contentRepositoryId); $workspaceName = WorkspaceName::fromString($workspace); - $workspacesInstance = $contentRepositoryInstance->getWorkspaceFinder()->findOneByName($workspaceName); + $workspacesInstance = $contentRepositoryInstance->findWorkspaceByName($workspaceName); if ($workspacesInstance === null) { $this->outputLine('Workspace "%s" not found.', [$workspaceName->value]); diff --git a/Neos.Neos/Classes/Domain/Service/UserService.php b/Neos.Neos/Classes/Domain/Service/UserService.php index 086764939d4..dfa1616d003 100644 --- a/Neos.Neos/Classes/Domain/Service/UserService.php +++ b/Neos.Neos/Classes/Domain/Service/UserService.php @@ -14,8 +14,6 @@ namespace Neos\Neos\Domain\Service; -use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\Flow\Annotations as Flow; use Neos\Flow\Persistence\Exception\IllegalObjectTypeException; use Neos\Flow\Persistence\PersistenceManagerInterface; @@ -832,12 +830,4 @@ private function getNeosUserForAccount(Account $account): ?User $user = $this->partyService->getAssignedPartyOfAccount($account); return ($user instanceof User) ? $user : null; } - - /** - * Checks if this workspace is a user's personal workspace - */ - private static function isPersonalWorkspace(WorkspaceName $workspaceName): bool - { - return str_starts_with($workspaceName->value, 'user-'); - } } diff --git a/Neos.Neos/Classes/Domain/Service/WorkspacePublishingService.php b/Neos.Neos/Classes/Domain/Service/WorkspacePublishingService.php index fd7cb7e1453..05d4aa9face 100644 --- a/Neos.Neos/Classes/Domain/Service/WorkspacePublishingService.php +++ b/Neos.Neos/Classes/Domain/Service/WorkspacePublishingService.php @@ -29,7 +29,7 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindClosestNodeFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; -use Neos\ContentRepository\Core\Projection\Workspace\Workspace as ContentRepositoryWorkspace; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace as ContentRepositoryWorkspace; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregateCurrentlyDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; @@ -278,7 +278,7 @@ private function requireContentRepositoryWorkspace( ContentRepository $contentRepository, WorkspaceName $workspaceName ): ContentRepositoryWorkspace { - $workspace = $contentRepository->getWorkspaceFinder()->findOneByName($workspaceName); + $workspace = $contentRepository->findWorkspaceByName($workspaceName); if (!$workspace instanceof ContentRepositoryWorkspace) { throw WorkspaceDoesNotExist::butWasSupposedTo($workspaceName); } diff --git a/Neos.Neos/Classes/Domain/Service/WorkspaceService.php b/Neos.Neos/Classes/Domain/Service/WorkspaceService.php index f32e36864d2..462230a0145 100644 --- a/Neos.Neos/Classes/Domain/Service/WorkspaceService.php +++ b/Neos.Neos/Classes/Domain/Service/WorkspaceService.php @@ -20,12 +20,10 @@ use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Command\CreateRootWorkspace; use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Command\CreateWorkspace; -use Neos\ContentRepository\Core\Projection\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceDescription as DeprecatedWorkspaceDescription; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceTitle as DeprecatedWorkspaceTitle; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; use Neos\Flow\Security\Exception\NoSuchRoleException; @@ -104,8 +102,6 @@ public function createRootWorkspace(ContentRepositoryId $contentRepositoryId, Wo $contentRepository->handle( CreateRootWorkspace::create( $workspaceName, - DeprecatedWorkspaceTitle::fromString($title->value), - DeprecatedWorkspaceDescription::fromString($description->value), ContentStreamId::create() ) ); @@ -239,8 +235,6 @@ private function createWorkspace(ContentRepositoryId $contentRepositoryId, Works CreateWorkspace::create( $workspaceName, $baseWorkspaceName, - DeprecatedWorkspaceTitle::fromString($title->value), - DeprecatedWorkspaceDescription::fromString($description->value), ContentStreamId::create() ) ); @@ -335,7 +329,7 @@ private static function getUniqueWorkspaceName(ContentRepository $contentReposit $workspaceName = $workspaceNameCandidate; $attempt = 1; do { - if ($contentRepository->getWorkspaceFinder()->findOneByName($workspaceName) === null) { + if ($contentRepository->findWorkspaceByName($workspaceName) === null) { return $workspaceName; } if ($attempt === 1) { @@ -355,8 +349,7 @@ private function requireWorkspace(ContentRepositoryId $contentRepositoryId, Work { $workspace = $this->contentRepositoryRegistry ->get($contentRepositoryId) - ->getWorkspaceFinder() - ->findOneByName($workspaceName); + ->findWorkspaceByName($workspaceName); if ($workspace === null) { throw new \RuntimeException(sprintf('Failed to find workspace with name "%s" for content repository "%s"', $workspaceName->value, $contentRepositoryId->value), 1718379722); } diff --git a/Neos.Neos/Classes/Fusion/Cache/AssetChangeHandlerForCacheFlushing.php b/Neos.Neos/Classes/Fusion/Cache/AssetChangeHandlerForCacheFlushing.php index e17c2b685b4..a62582fbcb0 100644 --- a/Neos.Neos/Classes/Fusion/Cache/AssetChangeHandlerForCacheFlushing.php +++ b/Neos.Neos/Classes/Fusion/Cache/AssetChangeHandlerForCacheFlushing.php @@ -9,6 +9,7 @@ use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateIds; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Persistence\PersistenceManagerInterface; @@ -51,7 +52,9 @@ public function registerAssetChange(AssetInterface $asset): void // TODO: Remove when WorkspaceName is part of the AssetUsageProjection $workspaceName = $workspaceNamesByContentStreamId[$contentRepositoryId][$usage->contentStreamId->value] ?? null; if ($workspaceName === null) { - $workspace = $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($usage->contentStreamId); + $workspace = $contentRepository->getWorkspaces()->find( + fn (Workspace $potentialWorkspace) => $potentialWorkspace->currentContentStreamId->equals($usage->contentStreamId) + ); if ($workspace === null) { continue; } diff --git a/Neos.Workspace.Ui/Classes/Controller/WorkspaceController.php b/Neos.Workspace.Ui/Classes/Controller/WorkspaceController.php index 160d1dbadd1..132bb38300e 100644 --- a/Neos.Workspace.Ui/Classes/Controller/WorkspaceController.php +++ b/Neos.Workspace.Ui/Classes/Controller/WorkspaceController.php @@ -26,7 +26,7 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Node; 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\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; @@ -134,12 +134,16 @@ public function indexAction(): void $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); $items = []; - foreach ($contentRepository->getWorkspaceFinder()->findAll() as $workspace) { + $allWorkspaces = $contentRepository->getWorkspaces(); + foreach ($allWorkspaces as $workspace) { $workspaceMetadata = $this->workspaceService->getWorkspaceMetadata($contentRepositoryId, $workspace->workspaceName); $permissions = $this->workspaceService->getWorkspacePermissionsForUser($contentRepositoryId, $workspace->workspaceName, $currentUser); if (!$permissions->read) { continue; } + $hasDependantWorkspaces = (bool)$allWorkspaces->find( + static fn (Workspace $potentiallyDependentWorkspace) => $potentiallyDependentWorkspace->baseWorkspaceName?->equals($workspace->workspaceName) ?? false + ); $items[] = new WorkspaceListItem( name: $workspace->workspaceName->value, classification: $workspaceMetadata->classification->name, @@ -147,7 +151,7 @@ classification: $workspaceMetadata->classification->name, description: $workspaceMetadata->description->value, baseWorkspaceName: $workspace->baseWorkspaceName?->value, pendingChanges: $this->computePendingChanges($workspace, $contentRepository), - hasDependantWorkspaces: count($contentRepository->getWorkspaceFinder()->findByBaseWorkspace($workspace->workspaceName)) > 0, + hasDependantWorkspaces: $hasDependantWorkspaces, permissions: $permissions, ); } @@ -172,7 +176,7 @@ public function showAction(WorkspaceName $workspace): void $baseWorkspaceMetadata = null; $baseWorkspacePermissions = null; if ($workspaceObj->baseWorkspaceName !== null) { - $baseWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName($workspaceObj->baseWorkspaceName); + $baseWorkspace = $contentRepository->findWorkspaceByName($workspaceObj->baseWorkspaceName); assert($baseWorkspace !== null); $baseWorkspaceMetadata = $this->workspaceService->getWorkspaceMetadata($contentRepositoryId, $baseWorkspace->workspaceName); $baseWorkspacePermissions = $this->workspaceService->getWorkspacePermissionsForUser($contentRepositoryId, $baseWorkspace->workspaceName, $currentUser); @@ -1038,7 +1042,7 @@ protected function prepareBaseWorkspaceOptions( if (!$permissions->manage) { continue; } - $baseWorkspaceOptions[$workspace->workspaceName->value] = $workspace->workspaceTitle->value; + $baseWorkspaceOptions[$workspace->workspaceName->value] = $workspaceMetadata->title->value; } return $baseWorkspaceOptions; From 2a781d24aefe0218df66688b00ea60627ece739c Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Tue, 1 Oct 2024 23:05:59 +0200 Subject: [PATCH 06/22] TASK: Reintroduce content graph / cr read model cache With https://github.com/neos/neos-development-collection/pull/5246 the cache was naively removed ^^ But with the additional complexity of fetching workspaces etc it makes sense to reintroduce this cache layer. --- .../src/DoctrineDbalContentGraphProjection.php | 9 ++++++++- .../Classes/ContentRepositoryReadModel.php | 4 ++-- .../Behavior/Features/Bootstrap/ProjectedNodeTrait.php | 2 -- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index c05f6399f6d..fb795a1b723 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -66,6 +66,7 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Timestamps; use Neos\ContentRepository\Core\Projection\ProjectionInterface; use Neos\ContentRepository\Core\Projection\ProjectionStatus; +use Neos\ContentRepository\Core\Projection\WithMarkStaleInterface; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateClassification; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; @@ -78,7 +79,7 @@ * @implements ProjectionInterface * @internal but the graph projection is api */ -final class DoctrineDbalContentGraphProjection implements ProjectionInterface +final class DoctrineDbalContentGraphProjection implements ProjectionInterface, WithMarkStaleInterface { use ContentStream; use NodeMove; @@ -168,6 +169,12 @@ public function reset(): void $this->checkpointStorage->acquireLock(); $this->checkpointStorage->updateAndReleaseLock(SequenceNumber::none()); + $this->getState()->forgetInstances(); + } + + public function markStale(): void + { + $this->getState()->forgetInstances(); } public function getCheckpointStorage(): DbalCheckpointStorage diff --git a/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModel.php b/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModel.php index 66f56033ce0..c8bb3d6cb1c 100644 --- a/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModel.php +++ b/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModel.php @@ -16,6 +16,7 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ProjectionStateInterface; +use Neos\ContentRepository\Core\Projection\WithMarkStaleInterface; use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStream; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; @@ -57,11 +58,10 @@ public function __construct( /** * To release all held instances, in case a workspace/content stream relation needs to be reset * - * @internal Should only be needed after write operations (which should take care on their own) + * @internal Must be invoked by the projection {@see WithMarkStaleInterface::markStale()} to ensure a flush after write operations */ public function forgetInstances(): void { - // todo do we need to reintroduce the cache??? https://github.com/neos/neos-development-collection/pull/5246 $this->contentGraphInstancesByWorkspaceName = []; $this->workspaceInstancesByName = []; $this->contentStreamInstancesById = []; diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php index 84f92067e16..cbd7721db64 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php @@ -81,8 +81,6 @@ public function iGetTheNodeAtPath(string $serializedNodePath): void public function iExpectANodeIdentifiedByXToExistInTheContentGraph(string $serializedNodeDiscriminator): void { $nodeDiscriminator = NodeDiscriminator::fromShorthand($serializedNodeDiscriminator); - $contentRepositoryReadModel = $this->currentContentRepository->projectionState(ContentRepositoryReadModel::class); - $contentRepositoryReadModel->forgetInstances(); $matchingWorkspace = $this->currentContentRepository->getWorkspaces()->find( static fn (Workspace $workspace) => $workspace->currentContentStreamId->equals($nodeDiscriminator->contentStreamId) ); From 80a302fedfe6bbeea4c62ea664a27598f29ab8c6 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Tue, 1 Oct 2024 23:08:12 +0200 Subject: [PATCH 07/22] TASK: Fix `I expect the content stream "user-cs-identifier" to not exist` The step was previously introduced via https://github.com/neos/neos-development-collection/commit/433f3698d0c66499666f9a31fdd60d63e6f38c04 but wrongly implemented. see also https://github.com/neos/neos-development-collection/pull/5096#discussion_r1783514808 --- .../src/ContentRepositoryReadModelAdapter.php | 3 +++ .../Workspaces/PruneContentStreams.feature | 2 +- .../Features/Bootstrap/CRTestSuiteTrait.php | 18 ++---------------- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentRepositoryReadModelAdapter.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentRepositoryReadModelAdapter.php index 528b38d7c36..71bbb243405 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentRepositoryReadModelAdapter.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentRepositoryReadModelAdapter.php @@ -101,6 +101,7 @@ public function findContentStreamById(ContentStreamId $contentStreamId): ?Conten {$this->tableNames->contentStream()} WHERE id = :contentStreamId + AND removed = FALSE LIMIT 1 SQL; try { @@ -123,6 +124,8 @@ public function findContentStreams(): ContentStreams id, sourceContentStreamId, status, version FROM {$this->tableNames->contentStream()} + WHERE + removed = FALSE SQL; try { $rows = $this->dbal->fetchAllAssociative($contentStreamsStatement); diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/PruneContentStreams.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/PruneContentStreams.feature index b275725c374..67fa06bbd93 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/PruneContentStreams.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/PruneContentStreams.feature @@ -21,7 +21,7 @@ Feature: If content streams are not in use anymore by the workspace, they can be Scenario: content streams are marked as IN_USE_BY_WORKSPACE properly after creation Then the content stream "cs-identifier" has status "IN_USE_BY_WORKSPACE" - Then the content stream "non-existing" does not exist + Then I expect the content stream "non-existing" to not exist Scenario: on creating a nested workspace, the new content stream is marked as IN_USE_BY_WORKSPACE. When the command CreateWorkspace is executed with payload: diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php index ded9ac804fa..6dbe16daf40 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php @@ -134,22 +134,8 @@ protected function readPayloadTable(TableNode $payloadTable): array */ public function iExpectTheContentStreamToNotExist(string $rawContentStreamId): void { - // todo use, but also it should be assertFAlse?!!! And what about theContentStreamDoesNotExist?? - // $this->contentRepository->getContentStreams() - // ->find(fn (ContentStream $contentStream) => $contentStream->id->equals(ContentStreamId::fromString($rawContentStreamId))), - Assert::assertTrue( - $this->currentContentRepository->getContentStreamFinder()->hasContentStream(ContentStreamId::fromString($rawContentStreamId)), - sprintf('The content stream "%s" does exist.', $rawContentStreamId) - ); - } - - /** - * @Then the content stream :contentStreamId does not exist - */ - public function theContentStreamDoesNotExist(string $contentStreamId): void - { - $contentStream = $this->currentContentRepository->findContentStreamById(ContentStreamId::fromString($contentStreamId)); - Assert::assertNull($contentStream, sprintf('Content stream "%s" was not expected to exist, but it does', $contentStreamId)); + $contentStream = $this->currentContentRepository->findContentStreamById(ContentStreamId::fromString($rawContentStreamId)); + Assert::assertNull($contentStream, sprintf('Content stream "%s" was not expected to exist, but it does', $rawContentStreamId)); } /** From d0839e4fcdc13db5cfe410a9fa9d7d3e67a3b23b Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 2 Oct 2024 19:17:08 +0200 Subject: [PATCH 08/22] TASK: Use `EmbedsContentStreamId` like previously to `updateContentStreamVersion` `EmbedsContentStreamId` is available since https://github.com/neos/neos-development-collection/pull/5226 --- .../src/DoctrineDbalContentGraphProjection.php | 8 ++++---- .../Classes/Feature/ContentStreamEventStreamName.php | 10 ---------- .../Event/RootWorkspaceWasCreated.php | 1 - 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index fb795a1b723..06abd291652 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -22,6 +22,7 @@ use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\EventStore\EventInterface; +use Neos\ContentRepository\Core\Feature\Common\EmbedsContentStreamId; use Neos\ContentRepository\Core\Feature\Common\InterdimensionalSiblings; use Neos\ContentRepository\Core\Feature\ContentStreamClosing\Event\ContentStreamWasClosed; use Neos\ContentRepository\Core\Feature\ContentStreamClosing\Event\ContentStreamWasReopened; @@ -221,8 +222,7 @@ public function canHandle(EventInterface $event): bool WorkspaceWasPublished::class, WorkspaceWasRebased::class, WorkspaceWasRemoved::class, - ]); - // todo handle all EmbedsContentStreamId?? + ]) || $event instanceof EmbedsContentStreamId; } public function apply(EventInterface $event, EventEnvelope $eventEnvelope): void @@ -261,8 +261,8 @@ public function apply(EventInterface $event, EventEnvelope $eventEnvelope): void WorkspaceWasRemoved::class => $this->whenWorkspaceWasRemoved($event), default => throw new \InvalidArgumentException(sprintf('Unsupported event %s', get_debug_type($event))), }; - if (ContentStreamEventStreamName::isContentStreamStreamName($eventEnvelope->streamName)) { - $this->updateContentStreamVersion(ContentStreamEventStreamName::extractContentStreamIdFromStreamName($eventEnvelope->streamName), $eventEnvelope->version); + if ($event instanceof EmbedsContentStreamId && ContentStreamEventStreamName::isContentStreamStreamName($eventEnvelope->streamName)) { + $this->updateContentStreamVersion($event->getContentStreamId(), $eventEnvelope->version); } } diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamEventStreamName.php b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamEventStreamName.php index 902defb7b3a..5e3a7acad57 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamEventStreamName.php +++ b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamEventStreamName.php @@ -14,7 +14,6 @@ namespace Neos\ContentRepository\Core\Feature; -use Neos\ContentRepository\Core\Feature\Common\EmbedsContentStreamId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\EventStore\Model\Event\StreamName; @@ -42,15 +41,6 @@ public static function isContentStreamStreamName(StreamName $streamName): bool return str_starts_with($streamName->value, self::EVENT_STREAM_NAME_PREFIX); } - /** @todo rather use {@see EmbedsContentStreamId} instead!? */ - public static function extractContentStreamIdFromStreamName(StreamName $streamName): ContentStreamId - { - if (!self::isContentStreamStreamName($streamName)) { - throw new \InvalidArgumentException(sprintf('Failed to extract content stream id from stream name "%s"', $streamName->value), 1716640692); - } - return ContentStreamId::fromString(substr($streamName->value, strlen(self::EVENT_STREAM_NAME_PREFIX))); - } - public function getEventStreamName(): StreamName { return StreamName::fromString($this->value); diff --git a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCreation/Event/RootWorkspaceWasCreated.php b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCreation/Event/RootWorkspaceWasCreated.php index e168401cc8d..0e3c4d832f1 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCreation/Event/RootWorkspaceWasCreated.php +++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCreation/Event/RootWorkspaceWasCreated.php @@ -15,7 +15,6 @@ namespace Neos\ContentRepository\Core\Feature\WorkspaceCreation\Event; use Neos\ContentRepository\Core\EventStore\EventInterface; -use Neos\ContentRepository\Core\Feature\Common\EmbedsContentStreamId; use Neos\ContentRepository\Core\Feature\Common\EmbedsWorkspaceName; use Neos\ContentRepository\Core\Feature\ContentStreamCreation\Event\ContentStreamWasCreated; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; From 468c80d78d19b6647d55ac32a909d2f35a76748e Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 2 Oct 2024 19:28:09 +0200 Subject: [PATCH 09/22] TASK: Remove obsolete import --- .../Feature/WorkspaceModification/Event/WorkspaceWasRenamed.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceModification/Event/WorkspaceWasRenamed.php b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceModification/Event/WorkspaceWasRenamed.php index 3d0bfe5f34e..89b40c585bb 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceModification/Event/WorkspaceWasRenamed.php +++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceModification/Event/WorkspaceWasRenamed.php @@ -6,7 +6,6 @@ use Neos\ContentRepository\Core\EventStore\EventInterface; use Neos\ContentRepository\Core\Feature\Common\EmbedsWorkspaceName; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceDescription; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** From db7861f683f7e308f8515d4be955ccc57dc04fd1 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Thu, 10 Oct 2024 09:26:10 +0200 Subject: [PATCH 10/22] TASK: Fixup after merge of asset usage refactoring Initially the exporter got passed the `WorkspaceFinder` and had to fetch the live workspace themselves to determine the content stream id. Temporarily we then passed the $targetContentStreamId, but some places now need the workspace name as well. Thus we pass the whole $targetWorkspace. Also a utility `getDependantWorkspaces` was introduced to simplify the `filter` usage from the caller. --- .../SharedModel/Workspace/Workspaces.php | 20 +++++++++++++++++++ .../Bootstrap/CrImportExportTrait.php | 2 +- .../src/ExportService.php | 8 ++++---- .../src/ExportServiceFactory.php | 5 ++--- .../src/Processors/AssetExportProcessor.php | 11 +++------- .../src/Processors/EventExportProcessor.php | 6 +++--- .../AssetUsageIndexingProcessor.php | 7 +++---- .../Service/AssetUsageIndexingService.php | 14 ++++++------- .../Classes/Command/CrCommandController.php | 17 ++++++++-------- .../Command/WorkspaceCommandController.php | 4 +--- .../AssetChangeHandlerForCacheFlushing.php | 8 +++++--- .../Features/Bootstrap/RoutingTrait.php | 13 +++++++++--- .../Bootstrap/WorkspaceServiceTrait.php | 6 ------ .../Controller/WorkspaceController.php | 9 ++------- 14 files changed, 69 insertions(+), 61 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/Workspaces.php b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/Workspaces.php index 671f8ab1ed4..236b2170f83 100644 --- a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/Workspaces.php +++ b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/Workspaces.php @@ -87,6 +87,16 @@ public function getBaseWorkspaces(WorkspaceName $workspaceName): Workspaces return self::fromArray($baseWorkspaces); } + /** + * Get all dependent workspaces (if they are included in this result set). + */ + public function getDependantWorkspaces(WorkspaceName $workspaceName): Workspaces + { + return $this->filter( + static fn (Workspace $potentiallyDependentWorkspace) => $potentiallyDependentWorkspace->baseWorkspaceName?->equals($workspaceName) ?? false + ); + } + /** * @return \Traversable */ @@ -116,6 +126,16 @@ public function find(\Closure $callback): ?Workspace return null; } + /** + * @template T + * @param \Closure(Workspace): T $callback + * @return list + */ + public function map(\Closure $callback): array + { + return array_map($callback, array_values($this->workspaces)); + } + public function count(): int { return count($this->workspaces); diff --git a/Neos.ContentRepository.Export/Tests/Behavior/Features/Bootstrap/CrImportExportTrait.php b/Neos.ContentRepository.Export/Tests/Behavior/Features/Bootstrap/CrImportExportTrait.php index 16b8155fe8f..0aa571b20ea 100644 --- a/Neos.ContentRepository.Export/Tests/Behavior/Features/Bootstrap/CrImportExportTrait.php +++ b/Neos.ContentRepository.Export/Tests/Behavior/Features/Bootstrap/CrImportExportTrait.php @@ -65,7 +65,7 @@ public function __construct(private readonly Filesystem $filesystem) public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): EventExportProcessor { return new EventExportProcessor( $this->filesystem, - $serviceFactoryDependencies->contentRepository->findWorkspaceByName(WorkspaceName::forLive())->currentContentStreamId, + $serviceFactoryDependencies->contentRepository->findWorkspaceByName(WorkspaceName::forLive()), $serviceFactoryDependencies->eventStore ); } diff --git a/Neos.ContentRepository.Export/src/ExportService.php b/Neos.ContentRepository.Export/src/ExportService.php index 97dcff6bc87..6f7f6491531 100644 --- a/Neos.ContentRepository.Export/src/ExportService.php +++ b/Neos.ContentRepository.Export/src/ExportService.php @@ -5,8 +5,8 @@ use League\Flysystem\Filesystem; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; -use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceFinder; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; use Neos\ContentRepository\Export\Processors\AssetExportProcessor; use Neos\ContentRepository\Export\Processors\EventExportProcessor; use Neos\EventStore\EventStoreInterface; @@ -22,7 +22,7 @@ class ExportService implements ContentRepositoryServiceInterface public function __construct( private readonly ContentRepositoryId $contentRepositoryId, private readonly Filesystem $filesystem, - private readonly WorkspaceFinder $workspaceFinder, + private readonly Workspace $targetWorkspace, private readonly AssetRepository $assetRepository, private readonly AssetUsageService $assetUsageService, private readonly EventStoreInterface $eventStore, @@ -35,14 +35,14 @@ public function runAllProcessors(\Closure $outputLineFn, bool $verbose = false): $processors = [ 'Exporting events' => new EventExportProcessor( $this->filesystem, - $this->workspaceFinder, + $this->targetWorkspace, $this->eventStore ), 'Exporting assets' => new AssetExportProcessor( $this->contentRepositoryId, $this->filesystem, $this->assetRepository, - $this->workspaceFinder, + $this->targetWorkspace, $this->assetUsageService ) ]; diff --git a/Neos.ContentRepository.Export/src/ExportServiceFactory.php b/Neos.ContentRepository.Export/src/ExportServiceFactory.php index 945177a7c60..e36d50be983 100644 --- a/Neos.ContentRepository.Export/src/ExportServiceFactory.php +++ b/Neos.ContentRepository.Export/src/ExportServiceFactory.php @@ -7,7 +7,6 @@ use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryDependencies; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\Media\Domain\Repository\AssetRepository; use Neos\Neos\AssetUsage\AssetUsageService; @@ -20,7 +19,7 @@ class ExportServiceFactory implements ContentRepositoryServiceFactoryInterface public function __construct( private readonly Filesystem $filesystem, - private readonly ContentStreamId $targetContentStreamId, + private readonly Workspace $targetWorkspace, private readonly AssetRepository $assetRepository, private readonly AssetUsageService $assetUsageService, ) { @@ -31,7 +30,7 @@ public function build(ContentRepositoryServiceFactoryDependencies $serviceFactor return new ExportService( $serviceFactoryDependencies->contentRepositoryId, $this->filesystem, - $this->targetContentStreamId, + $this->targetWorkspace, $this->assetRepository, $this->assetUsageService, $serviceFactoryDependencies->eventStore, diff --git a/Neos.ContentRepository.Export/src/Processors/AssetExportProcessor.php b/Neos.ContentRepository.Export/src/Processors/AssetExportProcessor.php index ff2fa71dab9..3bceb6dc77f 100644 --- a/Neos.ContentRepository.Export/src/Processors/AssetExportProcessor.php +++ b/Neos.ContentRepository.Export/src/Processors/AssetExportProcessor.php @@ -3,9 +3,8 @@ namespace Neos\ContentRepository\Export\Processors; use League\Flysystem\Filesystem; -use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceFinder; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; use Neos\ContentRepository\Export\Asset\ValueObject\SerializedAsset; use Neos\ContentRepository\Export\Asset\ValueObject\SerializedImageVariant; use Neos\ContentRepository\Export\ProcessorInterface; @@ -33,7 +32,7 @@ public function __construct( private readonly ContentRepositoryId $contentRepositoryId, private readonly Filesystem $files, private readonly AssetRepository $assetRepository, - private readonly WorkspaceFinder $workspaceFinder, + private readonly Workspace $targetWorkspace, private readonly AssetUsageService $assetUsageService, ) {} @@ -45,11 +44,7 @@ public function onMessage(\Closure $callback): void public function run(): ProcessorResult { - $liveWorkspace = $this->workspaceFinder->findOneByName(WorkspaceName::forLive()); - if ($liveWorkspace === null) { - return ProcessorResult::error('Failed to find live workspace'); - } - $assetFilter = AssetUsageFilter::create()->withWorkspaceName($liveWorkspace->workspaceName)->groupByAsset(); + $assetFilter = AssetUsageFilter::create()->withWorkspaceName($this->targetWorkspace->workspaceName)->groupByAsset(); $numberOfExportedAssets = 0; $numberOfExportedImageVariants = 0; diff --git a/Neos.ContentRepository.Export/src/Processors/EventExportProcessor.php b/Neos.ContentRepository.Export/src/Processors/EventExportProcessor.php index d12fc5a1d08..b7d0b486188 100644 --- a/Neos.ContentRepository.Export/src/Processors/EventExportProcessor.php +++ b/Neos.ContentRepository.Export/src/Processors/EventExportProcessor.php @@ -6,7 +6,7 @@ use League\Flysystem\FilesystemException; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; use Neos\ContentRepository\Core\Feature\ContentStreamEventStreamName; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; use Neos\ContentRepository\Export\Event\ValueObject\ExportedEvent; use Neos\ContentRepository\Export\ProcessorInterface; use Neos\ContentRepository\Export\ProcessorResult; @@ -23,7 +23,7 @@ final class EventExportProcessor implements ProcessorInterface, ContentRepositor public function __construct( private readonly Filesystem $files, - private readonly ContentStreamId $targetContentStreamId, + private readonly Workspace $targetWorkspace, private readonly EventStoreInterface $eventStore, ) { } @@ -35,7 +35,7 @@ public function onMessage(\Closure $callback): void public function run(): ProcessorResult { - $streamName = ContentStreamEventStreamName::fromContentStreamId($this->targetContentStreamId)->getEventStreamName(); + $streamName = ContentStreamEventStreamName::fromContentStreamId($this->targetWorkspace->currentContentStreamId)->getEventStreamName(); $eventStream = $this->eventStore->load($streamName); $eventFileResource = fopen('php://temp/maxmemory:5242880', 'rb+'); diff --git a/Neos.Neos/Classes/AssetUsage/AssetUsageIndexingProcessor.php b/Neos.Neos/Classes/AssetUsage/AssetUsageIndexingProcessor.php index 9a05c7260c8..9c8af9bdde6 100644 --- a/Neos.Neos/Classes/AssetUsage/AssetUsageIndexingProcessor.php +++ b/Neos.Neos/Classes/AssetUsage/AssetUsageIndexingProcessor.php @@ -9,7 +9,6 @@ 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\Exception\NodeAggregateCurrentlyDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\Neos\AssetUsage\Service\AssetUsageIndexingService; @@ -28,8 +27,8 @@ public function buildIndex(ContentRepository $contentRepository, NodeTypeName $n { $variationGraph = $contentRepository->getVariationGraph(); - $workspaceFinder = $contentRepository->getWorkspaceFinder(); - $liveWorkspace = $workspaceFinder->findOneByName(WorkspaceName::forLive()); + $allWorkspaces = $contentRepository->getWorkspaces(); + $liveWorkspace = $contentRepository->findWorkspaceByName(WorkspaceName::forLive()); if ($liveWorkspace === null) { throw WorkspaceDoesNotExist::butWasSupposedTo(WorkspaceName::forLive()); } @@ -73,7 +72,7 @@ public function buildIndex(ContentRepository $contentRepository, NodeTypeName $n } } - array_push($workspaces, ...array_values($workspaceFinder->findByBaseWorkspace($workspace->workspaceName))); + array_push($workspaces, ...iterator_to_array($allWorkspaces->getDependantWorkspaces($workspace->workspaceName))); } } diff --git a/Neos.Neos/Classes/AssetUsage/Service/AssetUsageIndexingService.php b/Neos.Neos/Classes/AssetUsage/Service/AssetUsageIndexingService.php index 56c8aa7bff4..20ad0bababe 100644 --- a/Neos.Neos/Classes/AssetUsage/Service/AssetUsageIndexingService.php +++ b/Neos.Neos/Classes/AssetUsage/Service/AssetUsageIndexingService.php @@ -9,10 +9,10 @@ use Neos\ContentRepository\Core\NodeType\NodeType; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\PropertyCollection; -use Neos\ContentRepository\Core\Projection\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Persistence\Doctrine\PersistenceManager; @@ -201,8 +201,8 @@ public function pruneIndex(ContentRepositoryId $contentRepositoryId): void private function getWorkspaceBasesAndWorkspace(ContentRepositoryId $contentRepositoryId, WorkspaceName $workspaceName): array { if (!isset($this->workspaceBases[$contentRepositoryId->value][$workspaceName->value])) { - $workspaceFinder = $this->contentRepositoryRegistry->get($contentRepositoryId)->getWorkspaceFinder(); - $workspace = $workspaceFinder->findOneByName($workspaceName); + $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); + $workspace = $contentRepository->findWorkspaceByName($workspaceName); if ($workspace === null) { throw WorkspaceDoesNotExist::butWasSupposedTo($workspaceName); } @@ -214,7 +214,7 @@ private function getWorkspaceBasesAndWorkspace(ContentRepositoryId $contentRepos while ($stack !== []) { $workspace = array_shift($stack); if ($workspace->baseWorkspaceName) { - $ancestor = $workspaceFinder->findOneByName($workspace->baseWorkspaceName); + $ancestor = $contentRepository->findWorkspaceByName($workspace->baseWorkspaceName); if ($ancestor === null) { throw WorkspaceDoesNotExist::butWasSupposedTo($workspace->baseWorkspaceName); } @@ -235,8 +235,8 @@ private function getWorkspaceBasesAndWorkspace(ContentRepositoryId $contentRepos private function getWorkspaceDependents(ContentRepositoryId $contentRepositoryId, WorkspaceName $workspaceName): array { if (!isset($this->workspaceDependents[$contentRepositoryId->value][$workspaceName->value])) { - $workspaceFinder = $this->contentRepositoryRegistry->get($contentRepositoryId)->getWorkspaceFinder(); - $workspace = $workspaceFinder->findOneByName($workspaceName); + $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); + $workspace = $contentRepository->findWorkspaceByName($workspaceName); if ($workspace === null) { throw WorkspaceDoesNotExist::butWasSupposedTo($workspaceName); } @@ -246,7 +246,7 @@ private function getWorkspaceDependents(ContentRepositoryId $contentRepositoryId while ($stack !== []) { /** @var Workspace $workspace */ $workspace = array_shift($stack); - $descendants = $workspaceFinder->findByBaseWorkspace($workspace->workspaceName); + $descendants = $contentRepository->getWorkspaces()->getDependantWorkspaces($workspace->workspaceName); foreach ($descendants as $descendant) { $collectedWorkspaceNames[] = $descendant->workspaceName; $stack[] = $descendant; diff --git a/Neos.Neos/Classes/Command/CrCommandController.php b/Neos.Neos/Classes/Command/CrCommandController.php index ba7bb7e9b27..23a75318b7c 100644 --- a/Neos.Neos/Classes/Command/CrCommandController.php +++ b/Neos.Neos/Classes/Command/CrCommandController.php @@ -9,13 +9,13 @@ use Neos\ContentRepository\Core\Projection\CatchUpOptions; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepository\Export\ExportService; use Neos\ContentRepository\Export\ExportServiceFactory; use Neos\ContentRepository\Export\ImportService; use Neos\ContentRepository\Export\ImportServiceFactory; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\ContentRepositoryRegistry\Service\ProjectionReplayServiceFactory; -use Neos\Flow\Annotations as Flow; use Neos\Flow\Cli\CommandController; use Neos\Flow\Persistence\PersistenceManagerInterface; use Neos\Flow\ResourceManagement\ResourceManager; @@ -26,12 +26,6 @@ class CrCommandController extends CommandController { - /** - * @var array - */ - #[Flow\InjectConfiguration(package: 'Neos.Flow')] - protected array $flowSettings; - public function __construct( private readonly AssetRepository $assetRepository, private readonly ResourceRepository $resourceRepository, @@ -55,16 +49,21 @@ public function __construct( public function exportCommand(string $path, string $contentRepository = 'default', bool $verbose = false): void { $contentRepositoryId = ContentRepositoryId::fromString($contentRepository); - $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); + $contentRepositoryInstance = $this->contentRepositoryRegistry->get($contentRepositoryId); Files::createDirectoryRecursively($path); $filesystem = new Filesystem(new LocalFilesystemAdapter($path)); + $liveWorkspace = $contentRepositoryInstance->findWorkspaceByName(WorkspaceName::forLive()); + if ($liveWorkspace === null) { + throw new \RuntimeException('Failed to find live workspace', 1716652280); + } + $exportService = $this->contentRepositoryRegistry->buildService( $contentRepositoryId, new ExportServiceFactory( $filesystem, - $contentRepository->getWorkspaceFinder(), + $liveWorkspace, $this->assetRepository, $this->assetUsageService, ) diff --git a/Neos.Neos/Classes/Command/WorkspaceCommandController.php b/Neos.Neos/Classes/Command/WorkspaceCommandController.php index a1e6851f040..c717dd90204 100644 --- a/Neos.Neos/Classes/Command/WorkspaceCommandController.php +++ b/Neos.Neos/Classes/Command/WorkspaceCommandController.php @@ -376,9 +376,7 @@ public function deleteCommand(string $workspace, bool $force = false, string $co $this->quit(2); } - $dependentWorkspaces = $contentRepositoryInstance->getWorkspaces()->filter( - static fn (Workspace $potentiallyDependentWorkspace) => $potentiallyDependentWorkspace->baseWorkspaceName?->equals($workspaceName) ?? false - ); + $dependentWorkspaces = $contentRepositoryInstance->getWorkspaces()->getDependantWorkspaces($workspaceName); if (!$dependentWorkspaces->isEmpty()) { $this->outputLine('Workspace "%s" cannot be deleted because the following workspaces are based on it:', [$workspaceName->value]); diff --git a/Neos.Neos/Classes/Fusion/Cache/AssetChangeHandlerForCacheFlushing.php b/Neos.Neos/Classes/Fusion/Cache/AssetChangeHandlerForCacheFlushing.php index da3a7c05c55..a3f93115dba 100644 --- a/Neos.Neos/Classes/Fusion/Cache/AssetChangeHandlerForCacheFlushing.php +++ b/Neos.Neos/Classes/Fusion/Cache/AssetChangeHandlerForCacheFlushing.php @@ -9,7 +9,9 @@ use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateIds; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspaces; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Persistence\PersistenceManagerInterface; use Neos\Media\Domain\Model\AssetInterface; @@ -19,7 +21,7 @@ class AssetChangeHandlerForCacheFlushing { - /** @var array > */ + /** @var array> */ private array $workspaceRuntimeCache = []; public function __construct( @@ -100,7 +102,7 @@ private function getWorkspaceNameAndChildWorkspaceNames(ContentRepository $conte { if (!isset($this->workspaceRuntimeCache[$contentRepository->id->value][$workspaceName->value])) { $workspaceNames = []; - $workspace = $contentRepository->getWorkspaceFinder()->findOneByName($workspaceName); + $workspace = $contentRepository->findWorkspaceByName($workspaceName); if ($workspace !== null) { $stack[] = $workspace; @@ -108,7 +110,7 @@ private function getWorkspaceNameAndChildWorkspaceNames(ContentRepository $conte $workspace = array_shift($stack); $workspaceNames[] = $workspace->workspaceName; - $stack = array_merge($stack, array_values($contentRepository->getWorkspaceFinder()->findByBaseWorkspace($workspace->workspaceName))); + $stack = array_merge($stack, iterator_to_array($contentRepository->getWorkspaces()->getDependantWorkspaces($workspace->workspaceName))); } } $this->workspaceRuntimeCache[$contentRepository->id->value][$workspaceName->value] = $workspaceNames; diff --git a/Neos.Neos/Tests/Behavior/Features/Bootstrap/RoutingTrait.php b/Neos.Neos/Tests/Behavior/Features/Bootstrap/RoutingTrait.php index b3a8a1b399e..3614993ec10 100644 --- a/Neos.Neos/Tests/Behavior/Features/Bootstrap/RoutingTrait.php +++ b/Neos.Neos/Tests/Behavior/Features/Bootstrap/RoutingTrait.php @@ -21,6 +21,7 @@ use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; use Neos\ContentRepository\TestSuite\Behavior\Features\Bootstrap\CRTestSuiteRuntimeVariables; use Neos\Flow\Configuration\ConfigurationManager; use Neos\Flow\Http\ServerRequestAttributes; @@ -185,7 +186,9 @@ public function theMatchedNodeShouldBeInContentStreamAndOriginDimension(string $ Assert::assertTrue($matchedNodeAddress->workspaceName->isLive()); Assert::assertSame($nodeAggregateId, $matchedNodeAddress->aggregateId->value); // todo useless check? - $workspace = $this->currentContentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId(ContentStreamId::fromString($contentStreamId)); + $workspace = $this->currentContentRepository->getWorkspaces()->find( + fn (Workspace $potentialWorkspace) => $potentialWorkspace->currentContentStreamId->equals(ContentStreamId::fromString($contentStreamId)) + ); Assert::assertSame($contentStreamId, $workspace?->currentContentStreamId->value); Assert::assertSame( DimensionSpacePoint::fromJsonString($dimensionSpacePoint), @@ -218,7 +221,9 @@ public function theUrlShouldMatchTheNodeInContentStreamAndDimension(string $url, Assert::assertEquals(NodeAggregateId::fromString($nodeAggregateId), $matchedNodeAddress->aggregateId, 'Expected nodeAggregateId doesn\'t match.'); // todo use workspace name instead here: - $workspace = $this->currentContentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId(ContentStreamId::fromString($contentStreamId)); + $workspace = $this->currentContentRepository->getWorkspaces()->find( + fn (Workspace $potentialWorkspace) => $potentialWorkspace->currentContentStreamId->equals(ContentStreamId::fromString($contentStreamId)) + ); Assert::assertEquals($workspace->workspaceName, $matchedNodeAddress->workspaceName, 'Expected workspace doesn\'t match.'); Assert::assertTrue($matchedNodeAddress->dimensionSpacePoint->equals(DimensionSpacePoint::fromJsonString($dimensionSpacePoint)), 'Expected dimensionSpacePoint doesn\'t match.'); } @@ -304,7 +309,9 @@ private function resolveUrl(string $nodeAggregateId, string $contentStreamId, st if ($this->requestUrl === null) { $this->iAmOnUrl('/'); } - $workspace = $this->currentContentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId(ContentStreamId::fromString($contentStreamId)); + $workspace = $this->currentContentRepository->getWorkspaces()->find( + fn (Workspace $potentialWorkspace) => $potentialWorkspace->currentContentStreamId->equals(ContentStreamId::fromString($contentStreamId)) + ); $nodeAddress = NodeAddress::create( $this->currentContentRepository->id, diff --git a/Neos.Neos/Tests/Behavior/Features/Bootstrap/WorkspaceServiceTrait.php b/Neos.Neos/Tests/Behavior/Features/Bootstrap/WorkspaceServiceTrait.php index 0570d6ee3b9..167c09ff269 100644 --- a/Neos.Neos/Tests/Behavior/Features/Bootstrap/WorkspaceServiceTrait.php +++ b/Neos.Neos/Tests/Behavior/Features/Bootstrap/WorkspaceServiceTrait.php @@ -17,9 +17,7 @@ use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Command\CreateRootWorkspace; use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Command\CreateWorkspace; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceDescription as DeprecatedWorkspaceDescription; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceTitle as DeprecatedWorkspaceTitle; use Neos\Neos\Domain\Model\UserId; use Neos\Neos\Domain\Model\WorkspaceDescription; use Neos\Neos\Domain\Model\WorkspaceRole; @@ -98,8 +96,6 @@ public function aRootWorkspaceExistsWithoutMetadata(string $workspaceName): void { $this->currentContentRepository->handle(CreateRootWorkspace::create( WorkspaceName::fromString($workspaceName), - DeprecatedWorkspaceTitle::fromString($workspaceName), - DeprecatedWorkspaceDescription::fromString(''), ContentStreamId::create(), )); } @@ -112,8 +108,6 @@ public function aWorkspaceWithBaseWorkspaceExistsWithoutMetadata(string $workspa $this->currentContentRepository->handle(CreateWorkspace::create( WorkspaceName::fromString($workspaceName), WorkspaceName::fromString($baseWorkspaceName), - DeprecatedWorkspaceTitle::fromString($workspaceName), - DeprecatedWorkspaceDescription::fromString(''), ContentStreamId::create(), )); } diff --git a/Neos.Workspace.Ui/Classes/Controller/WorkspaceController.php b/Neos.Workspace.Ui/Classes/Controller/WorkspaceController.php index 9196557d23e..d00224b7cba 100644 --- a/Neos.Workspace.Ui/Classes/Controller/WorkspaceController.php +++ b/Neos.Workspace.Ui/Classes/Controller/WorkspaceController.php @@ -141,9 +141,6 @@ public function indexAction(): void if (!$permissions->read) { continue; } - $hasDependantWorkspaces = (bool)$allWorkspaces->find( - static fn (Workspace $potentiallyDependentWorkspace) => $potentiallyDependentWorkspace->baseWorkspaceName?->equals($workspace->workspaceName) ?? false - ); $items[] = new WorkspaceListItem( name: $workspace->workspaceName->value, classification: $workspaceMetadata->classification->name, @@ -151,7 +148,7 @@ classification: $workspaceMetadata->classification->name, description: $workspaceMetadata->description->value, baseWorkspaceName: $workspace->baseWorkspaceName?->value, pendingChanges: $this->computePendingChanges($workspace, $contentRepository), - hasDependantWorkspaces: $hasDependantWorkspaces, + hasDependantWorkspaces: !$allWorkspaces->getDependantWorkspaces($workspace->workspaceName)->isEmpty(), permissions: $permissions, ); } @@ -353,9 +350,7 @@ public function deleteAction(WorkspaceName $workspaceName): void $this->redirect('index'); } - $dependentWorkspaces = $contentRepository->getWorkspaces()->filter( - static fn (Workspace $potentiallyDependentWorkspace) => $potentiallyDependentWorkspace->baseWorkspaceName?->equals($workspaceName) ?? false - ); + $dependentWorkspaces = $contentRepository->getWorkspaces()->getDependantWorkspaces($workspaceName); if (!$dependentWorkspaces->isEmpty()) { $dependentWorkspaceTitles = []; /** @var Workspace $dependentWorkspace */ From c34456b47d544e1eef12a969472ecbe3b3fa140e Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Thu, 10 Oct 2024 20:08:41 +0200 Subject: [PATCH 11/22] TASK: Adjust to `find*` vs `get*` convention > "all operations which are slow should be named find*", vs. "all operations which operate on already-loaded data should be named get*". see https://github.com/neos/neos-development-collection/pull/5279#discussion_r1795281709 --- Neos.ContentRepository.Core/Classes/ContentRepository.php | 2 +- .../Classes/Service/WorkspaceMaintenanceService.php | 4 ++-- .../Behavior/Features/Bootstrap/ProjectedNodeTrait.php | 2 +- .../Classes/AssetUsage/AssetUsageIndexingProcessor.php | 2 +- .../AssetUsage/Service/AssetUsageIndexingService.php | 2 +- Neos.Neos/Classes/Command/WorkspaceCommandController.php | 4 ++-- .../Controller/Module/Administration/SitesController.php | 2 +- Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php | 2 +- .../Fusion/Cache/AssetChangeHandlerForCacheFlushing.php | 2 +- .../Tests/Behavior/Features/Bootstrap/RoutingTrait.php | 6 +++--- .../Classes/Controller/WorkspaceController.php | 8 ++++---- 11 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/ContentRepository.php b/Neos.ContentRepository.Core/Classes/ContentRepository.php index bf3b69731b2..0914e76bb88 100644 --- a/Neos.ContentRepository.Core/Classes/ContentRepository.php +++ b/Neos.ContentRepository.Core/Classes/ContentRepository.php @@ -247,7 +247,7 @@ public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace return $this->getContentRepositoryReadModel()->findWorkspaceByName($workspaceName); } - public function getWorkspaces(): Workspaces + public function findWorkspaces(): Workspaces { return $this->getContentRepositoryReadModel()->findWorkspaces(); } diff --git a/Neos.ContentRepository.Core/Classes/Service/WorkspaceMaintenanceService.php b/Neos.ContentRepository.Core/Classes/Service/WorkspaceMaintenanceService.php index 3dd792237aa..0c2ee14feab 100644 --- a/Neos.ContentRepository.Core/Classes/Service/WorkspaceMaintenanceService.php +++ b/Neos.ContentRepository.Core/Classes/Service/WorkspaceMaintenanceService.php @@ -30,7 +30,7 @@ public function __construct( */ public function rebaseOutdatedWorkspaces(?RebaseErrorHandlingStrategy $strategy = null): Workspaces { - $outdatedWorkspaces = $this->contentRepository->getWorkspaces()->filter( + $outdatedWorkspaces = $this->contentRepository->findWorkspaces()->filter( fn (Workspace $workspace) => $workspace->status === WorkspaceStatus::OUTDATED ); /** @var Workspace $workspace */ @@ -52,7 +52,7 @@ public function rebaseOutdatedWorkspaces(?RebaseErrorHandlingStrategy $strategy public function pruneAll(): void { - foreach ($this->contentRepository->getWorkspaces() as $workspace) { + foreach ($this->contentRepository->findWorkspaces() as $workspace) { $streamName = WorkspaceEventStreamName::fromWorkspaceName($workspace->workspaceName)->getEventStreamName(); $this->eventStore->deleteStream($streamName); } diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php index cbd7721db64..d5fde970ec4 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php @@ -81,7 +81,7 @@ public function iGetTheNodeAtPath(string $serializedNodePath): void public function iExpectANodeIdentifiedByXToExistInTheContentGraph(string $serializedNodeDiscriminator): void { $nodeDiscriminator = NodeDiscriminator::fromShorthand($serializedNodeDiscriminator); - $matchingWorkspace = $this->currentContentRepository->getWorkspaces()->find( + $matchingWorkspace = $this->currentContentRepository->findWorkspaces()->find( static fn (Workspace $workspace) => $workspace->currentContentStreamId->equals($nodeDiscriminator->contentStreamId) ); if ($matchingWorkspace === null) { diff --git a/Neos.Neos/Classes/AssetUsage/AssetUsageIndexingProcessor.php b/Neos.Neos/Classes/AssetUsage/AssetUsageIndexingProcessor.php index 9c8af9bdde6..17c8482dabb 100644 --- a/Neos.Neos/Classes/AssetUsage/AssetUsageIndexingProcessor.php +++ b/Neos.Neos/Classes/AssetUsage/AssetUsageIndexingProcessor.php @@ -27,7 +27,7 @@ public function buildIndex(ContentRepository $contentRepository, NodeTypeName $n { $variationGraph = $contentRepository->getVariationGraph(); - $allWorkspaces = $contentRepository->getWorkspaces(); + $allWorkspaces = $contentRepository->findWorkspaces(); $liveWorkspace = $contentRepository->findWorkspaceByName(WorkspaceName::forLive()); if ($liveWorkspace === null) { throw WorkspaceDoesNotExist::butWasSupposedTo(WorkspaceName::forLive()); diff --git a/Neos.Neos/Classes/AssetUsage/Service/AssetUsageIndexingService.php b/Neos.Neos/Classes/AssetUsage/Service/AssetUsageIndexingService.php index 20ad0bababe..a6c42ef5523 100644 --- a/Neos.Neos/Classes/AssetUsage/Service/AssetUsageIndexingService.php +++ b/Neos.Neos/Classes/AssetUsage/Service/AssetUsageIndexingService.php @@ -246,7 +246,7 @@ private function getWorkspaceDependents(ContentRepositoryId $contentRepositoryId while ($stack !== []) { /** @var Workspace $workspace */ $workspace = array_shift($stack); - $descendants = $contentRepository->getWorkspaces()->getDependantWorkspaces($workspace->workspaceName); + $descendants = $contentRepository->findWorkspaces()->getDependantWorkspaces($workspace->workspaceName); foreach ($descendants as $descendant) { $collectedWorkspaceNames[] = $descendant->workspaceName; $stack[] = $descendant; diff --git a/Neos.Neos/Classes/Command/WorkspaceCommandController.php b/Neos.Neos/Classes/Command/WorkspaceCommandController.php index c717dd90204..2fdd87931c2 100644 --- a/Neos.Neos/Classes/Command/WorkspaceCommandController.php +++ b/Neos.Neos/Classes/Command/WorkspaceCommandController.php @@ -376,7 +376,7 @@ public function deleteCommand(string $workspace, bool $force = false, string $co $this->quit(2); } - $dependentWorkspaces = $contentRepositoryInstance->getWorkspaces()->getDependantWorkspaces($workspaceName); + $dependentWorkspaces = $contentRepositoryInstance->findWorkspaces()->getDependantWorkspaces($workspaceName); if (!$dependentWorkspaces->isEmpty()) { $this->outputLine('Workspace "%s" cannot be deleted because the following workspaces are based on it:', [$workspaceName->value]); @@ -460,7 +460,7 @@ public function listCommand(string $contentRepository = 'default'): void { $contentRepositoryId = ContentRepositoryId::fromString($contentRepository); - $workspaces = $this->contentRepositoryRegistry->get($contentRepositoryId)->getWorkspaces(); + $workspaces = $this->contentRepositoryRegistry->get($contentRepositoryId)->findWorkspaces(); if ($workspaces->isEmpty()) { $this->outputLine('No workspaces found.'); diff --git a/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php b/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php index 7dabc86f97d..e124b5cd0a1 100755 --- a/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php +++ b/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php @@ -202,7 +202,7 @@ public function updateSiteAction(Site $site, $newSiteNodeName) ); } - foreach ($contentRepository->getWorkspaces() as $workspace) { + foreach ($contentRepository->findWorkspaces() as $workspace) { $siteNodeAggregate = $contentRepository->getContentGraph($workspace->workspaceName)->findChildNodeAggregateByName( $sitesNode->nodeAggregateId, $site->getNodeName()->toNodeName() diff --git a/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php b/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php index 9ab73ee61fd..e90df40aecf 100644 --- a/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php +++ b/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php @@ -55,7 +55,7 @@ public function removeSiteNode(SiteNodeName $siteNodeName): void ); } - foreach ($this->contentRepository->getWorkspaces() as $workspace) { + foreach ($this->contentRepository->findWorkspaces() as $workspace) { $contentGraph = $this->contentRepository->getContentGraph($workspace->workspaceName); $sitesNodeAggregate = $contentGraph->findRootNodeAggregateByType( NodeTypeNameFactory::forSites() diff --git a/Neos.Neos/Classes/Fusion/Cache/AssetChangeHandlerForCacheFlushing.php b/Neos.Neos/Classes/Fusion/Cache/AssetChangeHandlerForCacheFlushing.php index a3f93115dba..13a240c39d7 100644 --- a/Neos.Neos/Classes/Fusion/Cache/AssetChangeHandlerForCacheFlushing.php +++ b/Neos.Neos/Classes/Fusion/Cache/AssetChangeHandlerForCacheFlushing.php @@ -110,7 +110,7 @@ private function getWorkspaceNameAndChildWorkspaceNames(ContentRepository $conte $workspace = array_shift($stack); $workspaceNames[] = $workspace->workspaceName; - $stack = array_merge($stack, iterator_to_array($contentRepository->getWorkspaces()->getDependantWorkspaces($workspace->workspaceName))); + $stack = array_merge($stack, iterator_to_array($contentRepository->findWorkspaces()->getDependantWorkspaces($workspace->workspaceName))); } } $this->workspaceRuntimeCache[$contentRepository->id->value][$workspaceName->value] = $workspaceNames; diff --git a/Neos.Neos/Tests/Behavior/Features/Bootstrap/RoutingTrait.php b/Neos.Neos/Tests/Behavior/Features/Bootstrap/RoutingTrait.php index 3614993ec10..3eb2dc1a93a 100644 --- a/Neos.Neos/Tests/Behavior/Features/Bootstrap/RoutingTrait.php +++ b/Neos.Neos/Tests/Behavior/Features/Bootstrap/RoutingTrait.php @@ -186,7 +186,7 @@ public function theMatchedNodeShouldBeInContentStreamAndOriginDimension(string $ Assert::assertTrue($matchedNodeAddress->workspaceName->isLive()); Assert::assertSame($nodeAggregateId, $matchedNodeAddress->aggregateId->value); // todo useless check? - $workspace = $this->currentContentRepository->getWorkspaces()->find( + $workspace = $this->currentContentRepository->findWorkspaces()->find( fn (Workspace $potentialWorkspace) => $potentialWorkspace->currentContentStreamId->equals(ContentStreamId::fromString($contentStreamId)) ); Assert::assertSame($contentStreamId, $workspace?->currentContentStreamId->value); @@ -221,7 +221,7 @@ public function theUrlShouldMatchTheNodeInContentStreamAndDimension(string $url, Assert::assertEquals(NodeAggregateId::fromString($nodeAggregateId), $matchedNodeAddress->aggregateId, 'Expected nodeAggregateId doesn\'t match.'); // todo use workspace name instead here: - $workspace = $this->currentContentRepository->getWorkspaces()->find( + $workspace = $this->currentContentRepository->findWorkspaces()->find( fn (Workspace $potentialWorkspace) => $potentialWorkspace->currentContentStreamId->equals(ContentStreamId::fromString($contentStreamId)) ); Assert::assertEquals($workspace->workspaceName, $matchedNodeAddress->workspaceName, 'Expected workspace doesn\'t match.'); @@ -309,7 +309,7 @@ private function resolveUrl(string $nodeAggregateId, string $contentStreamId, st if ($this->requestUrl === null) { $this->iAmOnUrl('/'); } - $workspace = $this->currentContentRepository->getWorkspaces()->find( + $workspace = $this->currentContentRepository->findWorkspaces()->find( fn (Workspace $potentialWorkspace) => $potentialWorkspace->currentContentStreamId->equals(ContentStreamId::fromString($contentStreamId)) ); diff --git a/Neos.Workspace.Ui/Classes/Controller/WorkspaceController.php b/Neos.Workspace.Ui/Classes/Controller/WorkspaceController.php index d00224b7cba..8a47a321f53 100644 --- a/Neos.Workspace.Ui/Classes/Controller/WorkspaceController.php +++ b/Neos.Workspace.Ui/Classes/Controller/WorkspaceController.php @@ -134,7 +134,7 @@ public function indexAction(): void $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); $items = []; - $allWorkspaces = $contentRepository->getWorkspaces(); + $allWorkspaces = $contentRepository->findWorkspaces(); foreach ($allWorkspaces as $workspace) { $workspaceMetadata = $this->workspaceService->getWorkspaceMetadata($contentRepositoryId, $workspace->workspaceName); $permissions = $this->workspaceService->getWorkspacePermissionsForUser($contentRepositoryId, $workspace->workspaceName, $currentUser); @@ -350,7 +350,7 @@ public function deleteAction(WorkspaceName $workspaceName): void $this->redirect('index'); } - $dependentWorkspaces = $contentRepository->getWorkspaces()->getDependantWorkspaces($workspaceName); + $dependentWorkspaces = $contentRepository->findWorkspaces()->getDependantWorkspaces($workspaceName); if (!$dependentWorkspaces->isEmpty()) { $dependentWorkspaceTitles = []; /** @var Workspace $dependentWorkspace */ @@ -852,7 +852,7 @@ protected function renderContentChanges( ContentStreamId $contentStreamIdOfOriginalNode, ContentRepository $contentRepository, ): array { - $currentWorkspace = $contentRepository->getWorkspaces()->find( + $currentWorkspace = $contentRepository->findWorkspaces()->find( fn (Workspace $potentialWorkspace) => $potentialWorkspace->currentContentStreamId->equals($contentStreamIdOfOriginalNode) ); $originalNode = null; @@ -1036,7 +1036,7 @@ protected function prepareBaseWorkspaceOptions( ): array { $user = $this->userService->getCurrentUser(); $baseWorkspaceOptions = []; - $workspaces = $contentRepository->getWorkspaces(); + $workspaces = $contentRepository->findWorkspaces(); foreach ($workspaces as $workspace) { if ($excludedWorkspace !== null) { if ($workspace->workspaceName->equals($excludedWorkspace)) { From 55a8e570fe702c5fc80a31a3f668653946ced064 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Thu, 10 Oct 2024 20:17:15 +0200 Subject: [PATCH 12/22] TASK: Adjust to `find*` vs `get*` convention (in `findContentStreams`) --- Neos.ContentRepository.Core/Classes/ContentRepository.php | 2 +- .../Classes/Service/ContentStreamPruner.php | 4 ++-- .../Classes/SharedModel/Workspace/Workspace.php | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/ContentRepository.php b/Neos.ContentRepository.Core/Classes/ContentRepository.php index 67213b276c0..54e81fb63a3 100644 --- a/Neos.ContentRepository.Core/Classes/ContentRepository.php +++ b/Neos.ContentRepository.Core/Classes/ContentRepository.php @@ -259,7 +259,7 @@ public function findContentStreamById(ContentStreamId $contentStreamId): ?Conten return $this->getContentRepositoryReadModel()->findContentStreamById($contentStreamId); } - public function getContentStreams(): ContentStreams + public function findContentStreams(): ContentStreams { return $this->getContentRepositoryReadModel()->findContentStreams(); } diff --git a/Neos.ContentRepository.Core/Classes/Service/ContentStreamPruner.php b/Neos.ContentRepository.Core/Classes/Service/ContentStreamPruner.php index 328f46720cd..e3e6b059ce5 100644 --- a/Neos.ContentRepository.Core/Classes/Service/ContentStreamPruner.php +++ b/Neos.ContentRepository.Core/Classes/Service/ContentStreamPruner.php @@ -51,7 +51,7 @@ public function prune(bool $removeTemporary = false): iterable $status[] = ContentStreamStatus::CREATED; $status[] = ContentStreamStatus::FORKED; } - $unusedContentStreams = $this->contentRepository->getContentStreams()->filter( + $unusedContentStreams = $this->contentRepository->findContentStreams()->filter( static fn (ContentStream $contentStream) => in_array($contentStream->status, $status, true), ); $unusedContentStreamIds = []; @@ -89,7 +89,7 @@ public function pruneRemovedFromEventStream(): iterable public function pruneAll(): void { - foreach ($this->contentRepository->getContentStreams() as $contentStream) { + foreach ($this->contentRepository->findContentStreams() as $contentStream) { $streamName = ContentStreamEventStreamName::fromContentStreamId($contentStream->id)->getEventStreamName(); $this->eventStore->deleteStream($streamName); } diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/Workspace.php b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/Workspace.php index 79240373951..22209528932 100644 --- a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/Workspace.php +++ b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/Workspace.php @@ -56,8 +56,8 @@ public function __construct( $this->status = $status; } - public function isBaseWorkspace(): bool + public function isRootWorkspace(): bool { - return $this->baseWorkspaceName === null; + return $this->baseWorkspaceName !== null; } } From a0dd44c66a0ae4305dbfee16aad6cd5d145971e1 Mon Sep 17 00:00:00 2001 From: Bastian Waidelich Date: Tue, 15 Oct 2024 11:02:51 +0200 Subject: [PATCH 13/22] Replace `WorkspaceFinder` usage in `UsageController` --- Neos.Media.Browser/Classes/Controller/UsageController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neos.Media.Browser/Classes/Controller/UsageController.php b/Neos.Media.Browser/Classes/Controller/UsageController.php index 1cf5cda4df5..b208e2b8885 100644 --- a/Neos.Media.Browser/Classes/Controller/UsageController.php +++ b/Neos.Media.Browser/Classes/Controller/UsageController.php @@ -104,7 +104,7 @@ public function relatedNodesAction(AssetInterface $asset) $contentRepository = $this->contentRepositoryRegistry->get($usage->getContentRepositoryId()); - $workspace = $contentRepository->getWorkspaceFinder()->findOneByName($usage->getWorkspaceName()); + $workspace = $contentRepository->findWorkspaceByName($usage->getWorkspaceName()); $nodeAggregate = $contentRepository->getContentGraph($workspace->workspaceName)->findNodeAggregateById( $usage->getNodeAggregateId() From 25afe96088a2e87a6245a608273e82e3dc536f9d Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Tue, 15 Oct 2024 11:28:45 +0200 Subject: [PATCH 14/22] TASK: Php-stanize UsageController and fix php error --- .../Classes/Controller/UsageController.php | 45 ++++++++++--------- phpstan.neon.dist | 2 + 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/Neos.Media.Browser/Classes/Controller/UsageController.php b/Neos.Media.Browser/Classes/Controller/UsageController.php index b208e2b8885..8f439588409 100644 --- a/Neos.Media.Browser/Classes/Controller/UsageController.php +++ b/Neos.Media.Browser/Classes/Controller/UsageController.php @@ -1,5 +1,7 @@ request->getHttpRequest())->contentRepositoryId; $currentContentRepository = $this->contentRepositoryRegistry->get($currentContentRepositoryId); - $userId = $this->userService->getBackendUser()?->getId(); - assert($userId !== null); - $userWorkspace = $this->workspaceService->getPersonalWorkspaceForUser($currentContentRepositoryId, $userId); + $currentUser = $this->userService->getBackendUser(); + assert($currentUser !== null); + $userWorkspace = $this->workspaceService->getPersonalWorkspaceForUser($currentContentRepositoryId, $currentUser->getId()); $usageReferences = $this->assetService->getUsageReferences($asset); $relatedNodes = []; @@ -104,27 +100,31 @@ public function relatedNodesAction(AssetInterface $asset) $contentRepository = $this->contentRepositoryRegistry->get($usage->getContentRepositoryId()); - $workspace = $contentRepository->findWorkspaceByName($usage->getWorkspaceName()); - - $nodeAggregate = $contentRepository->getContentGraph($workspace->workspaceName)->findNodeAggregateById( + $nodeAggregate = $contentRepository->getContentGraph($usage->getWorkspaceName())->findNodeAggregateById( $usage->getNodeAggregateId() ); - $nodeType = $contentRepository->getNodeTypeManager()->getNodeType($nodeAggregate->nodeTypeName); + $nodeType = $nodeAggregate ? $contentRepository->getNodeTypeManager()->getNodeType($nodeAggregate->nodeTypeName) : null; - $accessible = $this->domainUserService->currentUserCanReadWorkspace($workspace); + $workspacePermissions = $this->workspaceService->getWorkspacePermissionsForUser( + $currentContentRepositoryId, + $usage->getWorkspaceName(), + $currentUser + ); + + $workspace = $contentRepository->findWorkspaceByName($usage->getWorkspaceName()); $inaccessibleRelation['nodeIdentifier'] = $usage->getNodeAggregateId()->value; - $inaccessibleRelation['workspaceName'] = $workspace->workspaceName->value; + $inaccessibleRelation['workspaceName'] = $usage->getWorkspaceName()->value; $inaccessibleRelation['workspace'] = $workspace; $inaccessibleRelation['nodeType'] = $nodeType; - $inaccessibleRelation['accessible'] = $accessible; + $inaccessibleRelation['accessible'] = $workspacePermissions->read; - if (!$accessible) { + if (!$workspacePermissions->read) { $inaccessibleRelations[] = $inaccessibleRelation; continue; } - $subgraph = $contentRepository->getContentGraph($workspace->workspaceName)->getSubgraph( + $subgraph = $contentRepository->getContentGraph($usage->getWorkspaceName())->getSubgraph( $usage->getOriginDimensionSpacePoint()->toDimensionSpacePoint(), VisibilityConstraints::withoutRestrictions() ); @@ -145,16 +145,21 @@ public function relatedNodesAction(AssetInterface $asset) $siteNode = $subgraph->findClosestNode($node->aggregateId, FindClosestNodeFilter::create(nodeTypes: NodeTypeNameFactory::NAME_SITE)); // this should actually never happen, too. :D - if (!$siteNode) { + if (!$siteNode || !$siteNode->name) { $inaccessibleRelations[] = $inaccessibleRelation; continue; } + $site = null; foreach ($existingSites as $existingSite) { - /** @var Site $existingSite * */ if ($siteNode->name->equals($existingSite->getNodeName()->toNodeName())) { $site = $existingSite; } } + // guessed it? this should actually never as well ^^ + if (!$site) { + $inaccessibleRelations[] = $inaccessibleRelation; + continue; + } $relatedNodes[$site->getNodeName()->value]['site'] = $site; $relatedNodes[$site->getNodeName()->value]['nodes'][] = [ diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 396ed51b88a..e824e787f1a 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -23,6 +23,8 @@ parameters: - Neos.NodeTypes.Form/Classes # todo lint whole fusion package - Neos.Fusion/Classes/Core + # todo lint whole media browser package, now we only lint the parts that interact with the CR + - Neos.Media.Browser/Classes/Controller/UsageController.php bootstrapFiles: - ../Framework/bootstrap-phpstan.php rules: From b41f5b584b6b63bbc8dd9d497edba4987cde9184 Mon Sep 17 00:00:00 2001 From: Bastian Waidelich Date: Tue, 15 Oct 2024 12:02:56 +0200 Subject: [PATCH 15/22] Fix parameter name in content graph projection --- .../src/Domain/Projection/Feature/Workspace.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/Workspace.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/Workspace.php index 6fe5bbce656..a9e528277fb 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/Workspace.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/Workspace.php @@ -41,7 +41,7 @@ private function updateBaseWorkspace(WorkspaceName $workspaceName, WorkspaceName 'baseWorkspaceName' => $baseWorkspaceName->value, 'currentContentStreamId' => $newContentStreamId->value, ], - ['workspaceName' => $workspaceName->value] + ['name' => $workspaceName->value] ); } From 2a8b49038cfaa46c7c84237bbdd771366f9a548e Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Tue, 15 Oct 2024 12:02:24 +0200 Subject: [PATCH 16/22] Revert "TASK: Reintroduce content graph / cr read model cache" In commit 2a781d24aefe0218df66688b00ea60627ece739c the cache invalidation was partly repaired But as we removed the content graph cache layer currently in 9.0 https://github.com/neos/neos-development-collection/pull/5246 A reintroduction will be discussed as part of another change. Related https://github.com/neos/neos-development-collection/issues/5039 --- .../DoctrineDbalContentGraphProjection.php | 9 +-- .../Classes/ContentRepositoryReadModel.php | 57 ++----------------- 2 files changed, 7 insertions(+), 59 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index 06abd291652..20669f82c65 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -67,7 +67,6 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Timestamps; use Neos\ContentRepository\Core\Projection\ProjectionInterface; use Neos\ContentRepository\Core\Projection\ProjectionStatus; -use Neos\ContentRepository\Core\Projection\WithMarkStaleInterface; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateClassification; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; @@ -80,7 +79,7 @@ * @implements ProjectionInterface * @internal but the graph projection is api */ -final class DoctrineDbalContentGraphProjection implements ProjectionInterface, WithMarkStaleInterface +final class DoctrineDbalContentGraphProjection implements ProjectionInterface { use ContentStream; use NodeMove; @@ -170,12 +169,6 @@ public function reset(): void $this->checkpointStorage->acquireLock(); $this->checkpointStorage->updateAndReleaseLock(SequenceNumber::none()); - $this->getState()->forgetInstances(); - } - - public function markStale(): void - { - $this->getState()->forgetInstances(); } public function getCheckpointStorage(): DbalCheckpointStorage diff --git a/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModel.php b/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModel.php index c8bb3d6cb1c..95584a07f76 100644 --- a/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModel.php +++ b/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModel.php @@ -16,7 +16,6 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ProjectionStateInterface; -use Neos\ContentRepository\Core\Projection\WithMarkStaleInterface; use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStream; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; @@ -35,48 +34,14 @@ */ final class ContentRepositoryReadModel implements ProjectionStateInterface { - /** - * @var array Runtime cache for {@see ContentGraphInterface} instances, indexed by their workspace name - */ - private array $contentGraphInstancesByWorkspaceName = []; - - /** - * @var array Runtime cache for {@see Workspace} instances, indexed by their name - */ - private array $workspaceInstancesByName = []; - - /** - * @var array Runtime cache for {@see ContentStream} instances, indexed by their name - */ - private array $contentStreamInstancesById = []; - public function __construct( private readonly ContentRepositoryReadModelAdapterInterface $adapter ) { } - /** - * To release all held instances, in case a workspace/content stream relation needs to be reset - * - * @internal Must be invoked by the projection {@see WithMarkStaleInterface::markStale()} to ensure a flush after write operations - */ - public function forgetInstances(): void - { - $this->contentGraphInstancesByWorkspaceName = []; - $this->workspaceInstancesByName = []; - $this->contentStreamInstancesById = []; - } - public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace { - if (!array_key_exists($workspaceName->value, $this->workspaceInstancesByName)) { - $workspace = $this->adapter->findWorkspaceByName($workspaceName); - if ($workspace === null) { - return null; - } - $this->workspaceInstancesByName[$workspaceName->value] = $workspace; - } - return $this->workspaceInstancesByName[$workspaceName->value]; + return $this->adapter->findWorkspaceByName($workspaceName); } public function findWorkspaces(): Workspaces @@ -86,14 +51,7 @@ public function findWorkspaces(): Workspaces public function findContentStreamById(ContentStreamId $contentStreamId): ?ContentStream { - if (!array_key_exists($contentStreamId->value, $this->contentStreamInstancesById)) { - $contentStream = $this->adapter->findContentStreamById($contentStreamId); - if ($contentStream === null) { - return null; - } - $this->contentStreamInstancesById[$contentStreamId->value] = $contentStream; - } - return $this->contentStreamInstancesById[$contentStreamId->value]; + return $this->adapter->findContentStreamById($contentStreamId); } public function findContentStreams(): ContentStreams @@ -119,14 +77,11 @@ public function findUnusedAndRemovedContentStreamIds(): iterable */ public function getContentGraphByWorkspaceName(WorkspaceName $workspaceName): ContentGraphInterface { - if (!array_key_exists($workspaceName->value, $this->contentGraphInstancesByWorkspaceName)) { - $workspace = $this->findWorkspaceByName($workspaceName); - if ($workspace === null) { - throw WorkspaceDoesNotExist::butWasSupposedTo($workspaceName); - } - $this->contentGraphInstancesByWorkspaceName[$workspaceName->value] = $this->adapter->buildContentGraph($workspace->workspaceName, $workspace->currentContentStreamId); + $workspace = $this->findWorkspaceByName($workspaceName); + if ($workspace === null) { + throw WorkspaceDoesNotExist::butWasSupposedTo($workspaceName); } - return $this->contentGraphInstancesByWorkspaceName[$workspaceName->value]; + return $this->adapter->buildContentGraph($workspace->workspaceName, $workspace->currentContentStreamId); } /** From 041f23926f41fa0ad2dad6e62f16efb45aa2e836 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 16 Oct 2024 21:49:07 +0200 Subject: [PATCH 17/22] TASK: Migrate findUnusedAndRemovedContentStreams from SQL to PHP Instead of messing with the rather lean API just for this usecase the calculation is done in PHP. This will be slower and consume more memory, but it should not be an issue even with thousands of content streams for this pure cleanup CLI command. Further this calculation is better placed in the Core package than to be done in each adapter because writing the specification for the interface would already be too complex. Additionally, the docs were updated in the `ContentStream` readmodel and now also reference the migrated core `findUnusedAndRemovedContentStreams` logic which also explains the `removed` flag. --- .../src/ContentRepositoryReadModelAdapter.php | 55 +--------------- ...ContentHyperRepositoryReadModelAdapter.php | 6 -- .../Classes/ContentRepositoryReadModel.php | 9 --- ...entRepositoryReadModelAdapterInterface.php | 6 -- .../Classes/Service/ContentStreamPruner.php | 62 +++++++++++++++--- .../Service/ContentStreamPrunerFactory.php | 3 - .../Workspace/ContentStream.monopic | Bin 0 -> 2850 bytes .../SharedModel/Workspace/ContentStream.php | 49 ++++++++++++++ .../Workspace/ContentStreamStatus.php | 36 +--------- .../SharedModel/Workspace/ContentStreams.php | 1 - .../Features/Bootstrap/CRTestSuiteTrait.php | 3 +- .../ContentStreamCommandController.php | 2 +- 12 files changed, 110 insertions(+), 122 deletions(-) create mode 100644 Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStream.monopic diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentRepositoryReadModelAdapter.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentRepositoryReadModelAdapter.php index 71bbb243405..56ad85a9d63 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentRepositoryReadModelAdapter.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentRepositoryReadModelAdapter.php @@ -21,7 +21,6 @@ use Neos\ContentRepository\Core\ContentRepositoryReadModelAdapterInterface; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; -use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStream; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreams; @@ -96,12 +95,11 @@ public function findContentStreamById(ContentStreamId $contentStreamId): ?Conten { $contentStreamByIdStatement = <<tableNames->contentStream()} WHERE id = :contentStreamId - AND removed = FALSE LIMIT 1 SQL; try { @@ -121,11 +119,9 @@ public function findContentStreams(): ContentStreams { $contentStreamsStatement = <<tableNames->contentStream()} - WHERE - removed = FALSE SQL; try { $rows = $this->dbal->fetchAllAssociative($contentStreamsStatement); @@ -135,52 +131,6 @@ public function findContentStreams(): ContentStreams return ContentStreams::fromArray(array_map(self::contentStreamFromDatabaseRow(...), $rows)); } - public function findUnusedAndRemovedContentStreamIds(): iterable - { - $removedContentStreamIdsStatement = <<tableNames->contentStream()} - WHERE - status = :inUseStatus - AND removed = false - UNION - -- now, when a content stream is in use by a workspace, its source content stream is - -- also "transitively" in use. - SELECT - sourceContentStreamId - FROM - {$this->tableNames->contentStream()} - JOIN transitiveUsedContentStreams ON {$this->tableNames->contentStream()}.id = transitiveUsedContentStreams.id - WHERE - {$this->tableNames->contentStream()}.sourceContentStreamId IS NOT NULL - ) - -- now, we check for removed content streams which we do not need anymore transitively - SELECT - id - FROM - {$this->tableNames->contentStream()} AS cs - WHERE - removed = true - AND NOT EXISTS ( - SELECT 1 - FROM transitiveUsedContentStreams - WHERE cs.id = transitiveUsedContentStreams.id - ) - SQL; - try { - $contentStreamIds = $this->dbal->fetchFirstColumn($removedContentStreamIdsStatement, [ - 'inUseStatus' => ContentStreamStatus::IN_USE_BY_WORKSPACE->value - ]); - } catch (Exception $e) { - throw new \RuntimeException(sprintf('Failed to load unused and removed content stream ids from database: %s', $e->getMessage()), 1716914615, $e); - } - return array_map(ContentStreamId::fromString(...), $contentStreamIds); - } - /** * @param array $row */ @@ -204,6 +154,7 @@ private static function contentStreamFromDatabaseRow(array $row): ContentStream isset($row['sourceContentStreamId']) ? ContentStreamId::fromString($row['sourceContentStreamId']) : null, ContentStreamStatus::from($row['status']), Version::fromInteger((int)$row['version']), + (bool)$row['removed'] ); } } diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperRepositoryReadModelAdapter.php b/Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperRepositoryReadModelAdapter.php index 11b5e9ea536..245ddd82d1f 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperRepositoryReadModelAdapter.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperRepositoryReadModelAdapter.php @@ -62,10 +62,4 @@ public function findContentStreams(): ContentStreams // TODO: Implement getContentStreams() method. return ContentStreams::createEmpty(); } - - public function findUnusedAndRemovedContentStreamIds(): iterable - { - // TODO: Implement getUnusedAndRemovedContentStreamIds() method. - return []; - } } diff --git a/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModel.php b/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModel.php index 95584a07f76..38733eaf7a7 100644 --- a/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModel.php +++ b/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModel.php @@ -59,15 +59,6 @@ public function findContentStreams(): ContentStreams return $this->adapter->findContentStreams(); } - /** - * @return iterable - * @internal This is currently only used by the {@see ContentStreamPruner} and might be removed in the future! - */ - public function findUnusedAndRemovedContentStreamIds(): iterable - { - return $this->adapter->findUnusedAndRemovedContentStreamIds(); - } - /** * The default way to get a content graph to operate on. * The currently assigned ContentStreamId for the given Workspace is resolved internally. diff --git a/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModelAdapterInterface.php b/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModelAdapterInterface.php index ec2c8c855f1..7d266fdec9d 100644 --- a/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModelAdapterInterface.php +++ b/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModelAdapterInterface.php @@ -39,10 +39,4 @@ public function findWorkspaces(): Workspaces; public function findContentStreamById(ContentStreamId $contentStreamId): ?ContentStream; public function findContentStreams(): ContentStreams; - - /** - * @return iterable - * @internal This is currently only used by the {@see ContentStreamPruner} and might be removed in the future! - */ - public function findUnusedAndRemovedContentStreamIds(): iterable; } diff --git a/Neos.ContentRepository.Core/Classes/Service/ContentStreamPruner.php b/Neos.ContentRepository.Core/Classes/Service/ContentStreamPruner.php index e3e6b059ce5..15d7667ae5e 100644 --- a/Neos.ContentRepository.Core/Classes/Service/ContentStreamPruner.php +++ b/Neos.ContentRepository.Core/Classes/Service/ContentStreamPruner.php @@ -4,18 +4,18 @@ namespace Neos\ContentRepository\Core\Service; -use Neos\ContentRepository\Core\ContentRepositoryReadModel; use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; use Neos\ContentRepository\Core\Feature\ContentStreamEventStreamName; use Neos\ContentRepository\Core\Feature\ContentStreamRemoval\Command\RemoveContentStream; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStream; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreams; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamStatus; use Neos\EventStore\EventStoreInterface; /** - * For internal implementation details, see {@see ContentRepositoryReadModel}. + * For implementation details of the content stream states and removed state, see {@see ContentStream}. * * @api */ @@ -24,7 +24,6 @@ class ContentStreamPruner implements ContentRepositoryServiceInterface public function __construct( private readonly ContentRepository $contentRepository, private readonly EventStoreInterface $eventStore, - private readonly ContentRepositoryReadModel $contentRepositoryReadModel, ) { } @@ -74,17 +73,17 @@ public function prune(bool $removeTemporary = false): iterable * * - Otherwise, we cannot replay the other content streams correctly (if the base content streams are missing). * - * @return iterable the identifiers of the removed content streams + * @return ContentStreams the removed content streams */ - public function pruneRemovedFromEventStream(): iterable + public function pruneRemovedFromEventStream(): ContentStreams { - $removedContentStreamIds = $this->contentRepositoryReadModel->findUnusedAndRemovedContentStreamIds(); - foreach ($removedContentStreamIds as $removedContentStream) { - $streamName = ContentStreamEventStreamName::fromContentStreamId($removedContentStream) + $removedContentStreams = $this->findUnusedAndRemovedContentStreams(); + foreach ($removedContentStreams as $removedContentStream) { + $streamName = ContentStreamEventStreamName::fromContentStreamId($removedContentStream->id) ->getEventStreamName(); $this->eventStore->deleteStream($streamName); } - return $removedContentStreamIds; + return $removedContentStreams; } public function pruneAll(): void @@ -94,4 +93,49 @@ public function pruneAll(): void $this->eventStore->deleteStream($streamName); } } + + private function findUnusedAndRemovedContentStreams(): ContentStreams + { + $allContentStreams = $this->contentRepository->findContentStreams(); + + /** @var array $transitiveUsedStreams */ + $transitiveUsedStreams = []; + /** @var list $contentStreamIdsStack */ + $contentStreamIdsStack = []; + + // Step 1: Find all content streams currently in direct use by a workspace + foreach ($allContentStreams as $stream) { + if ($stream->status === ContentStreamStatus::IN_USE_BY_WORKSPACE && !$stream->removed) { + $contentStreamIdsStack[] = $stream->id; + } + } + + // Step 2: When a content stream is in use by a workspace, its source content stream is also "transitively" in use. + while ($contentStreamIdsStack !== []) { + $currentStreamId = array_pop($contentStreamIdsStack); + if (!array_key_exists($currentStreamId->value, $transitiveUsedStreams)) { + $transitiveUsedStreams[$currentStreamId->value] = true; + + // Find source content streams for the current stream + foreach ($allContentStreams as $stream) { + if ($stream->id === $currentStreamId && $stream->sourceContentStreamId !== null) { + $sourceStreamId = $stream->sourceContentStreamId; + if (!array_key_exists($sourceStreamId->value, $transitiveUsedStreams)) { + $contentStreamIdsStack[] = $sourceStreamId; + } + } + } + } + } + + // Step 3: Check for removed content streams which we do not need anymore transitively + $removedContentStreams = []; + foreach ($allContentStreams as $contentStream) { + if ($contentStream->removed && !array_key_exists($contentStream->id->value, $transitiveUsedStreams)) { + $removedContentStreams[] = $contentStream; + } + } + + return ContentStreams::fromArray($removedContentStreams); + } } diff --git a/Neos.ContentRepository.Core/Classes/Service/ContentStreamPrunerFactory.php b/Neos.ContentRepository.Core/Classes/Service/ContentStreamPrunerFactory.php index 16afae4e370..66acf840a9e 100644 --- a/Neos.ContentRepository.Core/Classes/Service/ContentStreamPrunerFactory.php +++ b/Neos.ContentRepository.Core/Classes/Service/ContentStreamPrunerFactory.php @@ -4,7 +4,6 @@ namespace Neos\ContentRepository\Core\Service; -use Neos\ContentRepository\Core\ContentRepositoryReadModel; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryDependencies; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; @@ -16,11 +15,9 @@ class ContentStreamPrunerFactory implements ContentRepositoryServiceFactoryInter { public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): ContentStreamPruner { - $contentRepositoryReadModel = $serviceFactoryDependencies->contentRepository->projectionState(ContentRepositoryReadModel::class); return new ContentStreamPruner( $serviceFactoryDependencies->contentRepository, $serviceFactoryDependencies->eventStore, - $contentRepositoryReadModel, ); } } diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStream.monopic b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStream.monopic new file mode 100644 index 0000000000000000000000000000000000000000..b69045a3aec2e8fa395cc338bebae614809740ca GIT binary patch literal 2850 zcmV+-3*GepO;1iwP)S1pABzY8000000u$|B+iv5?5&ac`PYECgeJ9(%2AG{VL6F7F z!m~jTBm;t$s7qE76_TDE@4!I5CLgw6vfWK~laxp=FVm(V$q%+flf~)_PgS3)E`D*M zhyR4LG;waf$d5OO$xriW=Evc}xmCxYGj-~opTG()JdQA zp}(GIU-ZBH#U@w^!SH#i#849bL;SMXR5LE_uIYy@<^z zLTb1X*ExlX74^8(YL>3z=yAC+!x^M$Fk8vCXH7R#8CNg?dUYMBZmAF|4L{4yQtitc ztB&j=p}1=Ovle^+HeXFJq1Mm{X4w1nb#@hABtL~=_K2=`LREhgE;4;fs@B~wJ3EcC zYinj|uvss6Tcv~$u#G;2*%xys@w~0f_w_vVAEJ03#`+;RcqUByk*ftfL}?mrl?2&D zq1^m2UhkHZID80_&@mmztr;&!NdxFY2#2Ds%Nk^hn+^r^7Ur|K3d#;x%fWMQ=#=P= z#kijgUWj0YVcAQ>5D)_XK!2lg`tZXY6AHE!pb;r5<$1g|z$+F00RV+qeq z90lXp*aHQ+*jS3}+gM@Sy4YCQ-OHtIQIP<9<`7alk2Xb?d=7%o&dz=x#>qO7O`m)a z4`7&RJXhm>fK+235v(E+D?tyaL!P(6?o*J|V$a8~2rLJPk>P$0Dmed3G|1$CS^n2J z`1xNAMiA%^P=C-Op_U1?P^hIsEf#9IPz#1yGSZTfmW;Gyq$MMwps+o%9b?&xh@}ph zN8jt`a3C~=dWIs`BJ8mmZw3Jmmw(7`@5aN{;FWr|P>}ZZ3&dWj7dP>-JxdV`hJN+-lgP0ytH!Q7a=d+&eu z{@q{h?)|^M^Z)+g&S*A^;+-4{Sn*WVqnynQWhP2Ky0RqMwFgao!OB%fGFPqE7Sy`= zt~NiOHb0&=Kb|%}0sudn8&y|JwTq!@Z*3}Fn604-kHa_(uJV9L*Z1w`*b(i<5$(ni!+B_ag3!xy?1($iv7^Id zFvzoJT{;06WaPvUOh(Dh5W>WW4TLb(55eAU!eIBfZ;e`zp)zbyK1Ja5aVwhz*(bx1 zQbWHo!@n|kS>PodsvhW7rgS}GDVSp^{tH1TL{sGYNg)~p=ZI)zFrq=w57K7QcE1VJ zaPu?~gD*bJCyO}RPQp(zIF_Vw7;M|)T^63vct@s)i~x6~fRc$Q6(OY}rUc1_!JUek z4RO|BDaWG-5aEL#I6Oc^U_Y-=d?rQO6zLspLj`qlO=^)zEu<-_#nk~>myMIua-cw$ zq!!n+pVZ>I-4aLLvttE+w}|3N97X9QABfzl{Jx0(p|fkWY;$Vp&}zdCFY2NOHk}M+ z(}`;<0l6pNNmK~LpMo-MZp?hxB!QgNqP5I8a3 z0plx?T1PwdWF7MKueQ7b_R6$^%7%P(S^;Z2gLSM2>y-!Axv1E^EJgwIp@af~wxG-! zfb+%;Q{K>&p&lE(TIa=DOo1UySwWZ$?GZ@&rPq*7G`HH_a#A!mInk8T{PeyChfnD! z_U7f~_@No1P4qSel`I6|k&a-93;{69b71!H0JDh) zj6@oj)nX+DyYdHs_vQ~!YyJS3@ICN4k;(3La>~hx8)FwbJm{j4Vi#2fI>j!?E5t5v zm+3~3H4l?`XUB=vB|~aH8~1W~-JToNM8_8~Q($QmF}9QwO|Kli!Z_Y&fK6&DB*yHL zvCM8onXbl`yh6BEt~a}wSPbm##s#$B1C3Rhq0!J*DI(zYf+@`G?FH#c{1aAeuZAu3 z`^~~Ku!SbQ>qlU2tpm9|6V7_p>mj(Dj6=@O56~&adIZkT(}_cQpZPcPl7H{M`Qs!% zkmVU_E@l}A`<2P)??m6>Q)BNCy#|ZEB6;`r{_f2WU7|%*mTr`25z@XE()0q-t9Yby z@X>|wco+_h7J0y45m$TZ2&_{mLTxEKsZa!FS2X*Idx(xpPqogj&o&@xU}Ip)M~FU{ zU^m2um&9gU5jNY32)M8)g-Y5S7NuZk=lmU&axsa{S8iJJHmr%a;YE40k*Y-=YS1k( z@(T4LWu=r(x%H@D>N2twJoLJF2JWe)#4-0QQxqT=X_-^4{oFPufqCOEcG!iby)i!2 z(#uQ|J9XT#Jw7@s)lgUbi4IQlLf&xJl{97MAwIa47qPXS##Wjl<<=$wwv@B!c3LQ_ z?Aw!CC@W&;92d%Hl!fw9HL=#~`ce#!q&+ue}0hl z#P(jD#G1BbGC0(RFiYDMxkZx?p*6;pH(6^B5B4S?vGI=EgH1=hCxLSpMB0Py@a zrnf46@?7MrG&sM5-(^GXWkh(;VzGEjc-Dy(f#~Ea6d`h+yvX2yNzby8&GjQ%@7q9? zvP3s{9Eb4I$_*hlI3VISS<2yzL#O@7{M2gGa(Jd?(8$0tXtW>5v`SL?<$wQgy$I!% zg0+0AHiy14LGR2Em%v4HE%83T6_(PqNboI(-WHM3C;9` zW+T;~DQnU(NXlNJ_zde*d&Z^K-i9HA$ktvR0h%HcbhzFa+I!wX(+g6wEs}#2$LCj6 zO(%kC2I-2H5-q*Ptv|&Z@ysjnpm`^rtmQe*CZLKkT@y>xi~DRRmLSw^6OgUwAh1*g z8rynFUJquz^#+nAjK{-YCe19drp(q`obCCbs8fM(c4|pU^gQcyv5|6;DwX@%E`4dW zS{E4OT`i_6BsH(2)jKk%PGF`NmbOIuSL=GtKva(U7w3~OrO(g50u`{%ykmy|0Kue% A^8f$< literal 0 HcmV?d00001 diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStream.php b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStream.php index 1623280473a..def543c48b0 100644 --- a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStream.php +++ b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStream.php @@ -14,11 +14,59 @@ namespace Neos\ContentRepository\Core\SharedModel\Workspace; +use Neos\ContentRepository\Core\Service\ContentStreamPruner; use Neos\EventStore\Model\Event\Version; /** * Content Stream Read Model * + * This model reflects if content streams are currently in use or not. Each content stream + * is first CREATED or FORKED, and then moves to the IN_USE or REBASE_ERROR states; or is removed directly + * in case of temporary content streams. + * + * FORKING: Content streams are forked from a base content stream. It can happen that the base content + * stream is NO_LONGER_IN_USE, but the child content stream is still IN_USE_BY_WORKSPACE. In this case, + * the base content stream can go to removed=true (removed from the content graph), but needs to be retained + * in the event store: If we do a full replay, we need the events of the base content stream before the + * fork happened to rebuild the child content stream. + * This logic is done in {@see ContentStreamPruner::findUnusedAndRemovedContentStreamIds()}. + * + * TEMPORARY content streams: Projections should take care to dispose their temporary content streams, + * by triggering a ContentStreamWasRemoved event after the content stream is no longer used. + * + * The different status a content stream can be in + * + * │ │ + * │(for root │during + * │ content │rebase + * ▼ stream) ▼ + * ┌──────────┐ ┌──────────┐ Temporary + * │ CREATED │ │ FORKED │────┐ status + * └──────────┘ └──────────┘ for + * │ │ temporary + * ├───────────────────────┤ content + * ▼ │ streams + * ┌───────────────────┐ │ │ + * │IN_USE_BY_WORKSPACE│ │ │ + * └───────────────────┘ │ │ Persistent + * │ │ │ status + * ▼ │ │ + * ┌───────────────────┐ │ │ + * │ NO_LONGER_IN_USE │ │ │ + * └───────────────────┘ │ │ + * │ │ │ + * └──────────┬────────────┘ │ + * ▼ │ + * ┌────────────────────────────────────────┐ │ + * │ removed=true │ │ + * │ => removed from content graph │◀─┘ + * └────────────────────────────────────────┘ Cleanup + * │ + * ▼ + * ┌────────────────────────────────────────┐ + * │ completely deleted from event stream │ + * └────────────────────────────────────────┘ + * * @api */ final readonly class ContentStream @@ -31,6 +79,7 @@ public function __construct( public ?ContentStreamId $sourceContentStreamId, public ContentStreamStatus $status, public Version $version, + public bool $removed ) { } } diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStreamStatus.php b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStreamStatus.php index 25fa9947839..ac21aeaa4cc 100644 --- a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStreamStatus.php +++ b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStreamStatus.php @@ -15,40 +15,6 @@ namespace Neos\ContentRepository\Core\SharedModel\Workspace; /** - * The different states a content stream can be in - * - * - * │ │ - * │(for root │during - * │ content │rebase - * ▼ stream) ▼ - * ┌──────────┐ ┌──────────┐ Temporary - * │ CREATED │ │ FORKED │────┐ states - * └──────────┘ └──────────┘ for - * │ │ temporary - * ├───────────────────────┤ content - * ▼ ▼ streams - * ┌───────────────────┐ ┌──────────────┐ │ - * │IN_USE_BY_WORKSPACE│ │ REBASE_ERROR │ │ - * └───────────────────┘ └──────────────┘ │ Persistent - * │ │ │ States - * ▼ │ │ - * ┌───────────────────┐ │ │ - * │ NO_LONGER_IN_USE │ │ │ - * └───────────────────┘ │ │ - * │ │ │ - * └──────────┬────────────┘ │ - * ▼ │ - * ┌────────────────────────────────────────┐ │ - * │ removed=1 │ │ - * │ => removed from all other projections │◀─┘ - * └────────────────────────────────────────┘ Cleanup - * │ - * ▼ - * ┌────────────────────────────────────────┐ - * │ completely deleted from event stream │ - * └────────────────────────────────────────┘ - * * @api */ enum ContentStreamStatus: string implements \JsonSerializable @@ -76,6 +42,8 @@ enum ContentStreamStatus: string implements \JsonSerializable /** * a workspace was tried to be rebased, and during the rebase an error occured. This is the content stream * which contains the errored state - so that we can recover content from it (probably manually) + * + * @deprecated legacy status, FIXME clean up! https://github.com/neos/neos-development-collection/issues/5101 */ case REBASE_ERROR = 'REBASE_ERROR'; diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStreams.php b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStreams.php index 7bf701ad07a..2789309b62a 100644 --- a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStreams.php +++ b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStreams.php @@ -21,7 +21,6 @@ * * @api */ - final class ContentStreams implements \IteratorAggregate, \Countable { /** diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php index 6dbe16daf40..ab550c6bd35 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php @@ -135,7 +135,8 @@ protected function readPayloadTable(TableNode $payloadTable): array public function iExpectTheContentStreamToNotExist(string $rawContentStreamId): void { $contentStream = $this->currentContentRepository->findContentStreamById(ContentStreamId::fromString($rawContentStreamId)); - Assert::assertNull($contentStream, sprintf('Content stream "%s" was not expected to exist, but it does', $rawContentStreamId)); + $contentStreamExists = $contentStream !== null && !$contentStream->removed; + Assert::assertFalse($contentStreamExists, sprintf('Content stream "%s" was not expected to exist, but it does', $rawContentStreamId)); } /** diff --git a/Neos.ContentRepositoryRegistry/Classes/Command/ContentStreamCommandController.php b/Neos.ContentRepositoryRegistry/Classes/Command/ContentStreamCommandController.php index 7db24c23a9e..40d4330c168 100644 --- a/Neos.ContentRepositoryRegistry/Classes/Command/ContentStreamCommandController.php +++ b/Neos.ContentRepositoryRegistry/Classes/Command/ContentStreamCommandController.php @@ -63,7 +63,7 @@ public function pruneRemovedFromEventStreamCommand(string $contentRepository = ' $unusedContentStreams = $contentStreamPruner->pruneRemovedFromEventStream(); $unusedContentStreamsPresent = false; foreach ($unusedContentStreams as $contentStreamId) { - $this->outputFormatted('Removed events for %s', [$contentStreamId->value]); + $this->outputFormatted('Removed events for %s', [$contentStreamId->id->value]); $unusedContentStreamsPresent = true; } if (!$unusedContentStreamsPresent) { From f6046745c2fd615ce63299468e70a976f6e822c6 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 16 Oct 2024 22:48:45 +0200 Subject: [PATCH 18/22] TASK: Use `Workspace->isRootWorkspace()` --- .../Classes/SharedModel/Workspace/Workspace.php | 3 +++ .../Classes/Domain/Service/WorkspacePublishingService.php | 6 +++--- Neos.Neos/Classes/Domain/Service/WorkspaceService.php | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/Workspace.php b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/Workspace.php index 22209528932..115569d32ff 100644 --- a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/Workspace.php +++ b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/Workspace.php @@ -56,6 +56,9 @@ public function __construct( $this->status = $status; } + /** + * @phpstan-assert-if-false WorkspaceName $this->baseWorkspaceName + */ public function isRootWorkspace(): bool { return $this->baseWorkspaceName !== null; diff --git a/Neos.Neos/Classes/Domain/Service/WorkspacePublishingService.php b/Neos.Neos/Classes/Domain/Service/WorkspacePublishingService.php index 05d4aa9face..31836d383d2 100644 --- a/Neos.Neos/Classes/Domain/Service/WorkspacePublishingService.php +++ b/Neos.Neos/Classes/Domain/Service/WorkspacePublishingService.php @@ -88,7 +88,7 @@ public function publishWorkspace(ContentRepositoryId $contentRepositoryId, Works { $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); $crWorkspace = $this->requireContentRepositoryWorkspace($contentRepository, $workspaceName); - if ($crWorkspace->baseWorkspaceName === null) { + if ($crWorkspace->isRootWorkspace()) { throw new \InvalidArgumentException(sprintf('Failed to publish workspace "%s" because it has no base workspace', $workspaceName->value), 1717517124); } $numberOfPendingChanges = $this->countPendingWorkspaceChangesInternal($contentRepository, $workspaceName); @@ -100,7 +100,7 @@ public function publishChangesInSite(ContentRepositoryId $contentRepositoryId, W { $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); $crWorkspace = $this->requireContentRepositoryWorkspace($contentRepository, $workspaceName); - if ($crWorkspace->baseWorkspaceName === null) { + if ($crWorkspace->isRootWorkspace()) { throw new \InvalidArgumentException(sprintf('Failed to publish workspace "%s" because it has no base workspace', $workspaceName->value), 1717517240); } $ancestorNodeTypeName = NodeTypeNameFactory::forSite(); @@ -130,7 +130,7 @@ public function publishChangesInDocument(ContentRepositoryId $contentRepositoryI { $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); $crWorkspace = $this->requireContentRepositoryWorkspace($contentRepository, $workspaceName); - if ($crWorkspace->baseWorkspaceName === null) { + if ($crWorkspace->isRootWorkspace()) { throw new \InvalidArgumentException(sprintf('Failed to publish workspace "%s" because it has no base workspace', $workspaceName->value), 1717517467); } $ancestorNodeTypeName = NodeTypeNameFactory::forDocument(); diff --git a/Neos.Neos/Classes/Domain/Service/WorkspaceService.php b/Neos.Neos/Classes/Domain/Service/WorkspaceService.php index 0ada7877998..89620205c28 100644 --- a/Neos.Neos/Classes/Domain/Service/WorkspaceService.php +++ b/Neos.Neos/Classes/Domain/Service/WorkspaceService.php @@ -73,7 +73,7 @@ public function getWorkspaceMetadata(ContentRepositoryId $contentRepositoryId, W return $metadata ?? new WorkspaceMetadata( WorkspaceTitle::fromString($workspaceName->value), WorkspaceDescription::fromString(''), - $workspace->baseWorkspaceName === null ? WorkspaceClassification::ROOT : WorkspaceClassification::UNKNOWN, + $workspace->isRootWorkspace() ? WorkspaceClassification::ROOT : WorkspaceClassification::UNKNOWN, null, ); } @@ -377,7 +377,7 @@ private function updateWorkspaceMetadata(ContentRepositoryId $contentRepositoryI 'workspace_name' => $workspaceName->value, 'description' => '', 'title' => $workspaceName->value, - 'classification' => $workspace->baseWorkspaceName === null ? WorkspaceClassification::ROOT->value : WorkspaceClassification::UNKNOWN->value, + 'classification' => $workspace->isRootWorkspace() ? WorkspaceClassification::ROOT->value : WorkspaceClassification::UNKNOWN->value, ...$data, ]); } From acd3833fd11e482a121e6cdda7e0e28646661f38 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 16 Oct 2024 22:50:57 +0200 Subject: [PATCH 19/22] TASK: Prevent `default` arm to throw as we can handle possibly all events with `EmbedsContentStreamId` --- .../src/DoctrineDbalContentGraphProjection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index 20669f82c65..5037f6be25a 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -252,7 +252,7 @@ public function apply(EventInterface $event, EventEnvelope $eventEnvelope): void WorkspaceWasPublished::class => $this->whenWorkspaceWasPublished($event), WorkspaceWasRebased::class => $this->whenWorkspaceWasRebased($event), WorkspaceWasRemoved::class => $this->whenWorkspaceWasRemoved($event), - default => throw new \InvalidArgumentException(sprintf('Unsupported event %s', get_debug_type($event))), + default => $event instanceof EmbedsContentStreamId || throw new \InvalidArgumentException(sprintf('Unsupported event %s', get_debug_type($event))), }; if ($event instanceof EmbedsContentStreamId && ContentStreamEventStreamName::isContentStreamStreamName($eventEnvelope->streamName)) { $this->updateContentStreamVersion($event->getContentStreamId(), $eventEnvelope->version); From c91f24f04fd335869850f4ab6bc85317de152518 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 16 Oct 2024 22:53:00 +0200 Subject: [PATCH 20/22] TASK: Move `getContentGraph` again upwards --- .../Classes/ContentRepository.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/ContentRepository.php b/Neos.ContentRepository.Core/Classes/ContentRepository.php index 54e81fb63a3..2acb37f73e2 100644 --- a/Neos.ContentRepository.Core/Classes/ContentRepository.php +++ b/Neos.ContentRepository.Core/Classes/ContentRepository.php @@ -237,6 +237,14 @@ public function resetProjectionState(string $projectionClassName): void $projection->reset(); } + /** + * @throws WorkspaceDoesNotExist if the workspace does not exist + */ + public function getContentGraph(WorkspaceName $workspaceName): ContentGraphInterface + { + return $this->getContentRepositoryReadModel()->getContentGraphByWorkspaceName($workspaceName); + } + /** * Returns the workspace with the given name, or NULL if it does not exist in this content repository */ @@ -264,14 +272,6 @@ public function findContentStreams(): ContentStreams return $this->getContentRepositoryReadModel()->findContentStreams(); } - /** - * @throws WorkspaceDoesNotExist if the workspace does not exist - */ - public function getContentGraph(WorkspaceName $workspaceName): ContentGraphInterface - { - return $this->getContentRepositoryReadModel()->getContentGraphByWorkspaceName($workspaceName); - } - public function getNodeTypeManager(): NodeTypeManager { return $this->nodeTypeManager; From 4a652d23e1809b8d7081faef8de66e5b1b2a9935 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 16 Oct 2024 23:06:14 +0200 Subject: [PATCH 21/22] BUGFIX: Fix logic of `Workspace::isRootWorkspace` LOL --- .../Classes/SharedModel/Workspace/Workspace.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/Workspace.php b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/Workspace.php index 115569d32ff..7f51c161123 100644 --- a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/Workspace.php +++ b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/Workspace.php @@ -61,6 +61,6 @@ public function __construct( */ public function isRootWorkspace(): bool { - return $this->baseWorkspaceName !== null; + return $this->baseWorkspaceName === null; } } From a18801bfbb845815f4746e2de80aa40e66024dd9 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Thu, 17 Oct 2024 12:07:04 +0200 Subject: [PATCH 22/22] TASK: Adjust casings of columns Just for cosmetics: https://github.com/neos/neos-development-collection/pull/5096#discussion_r1803529082 --- .../src/DoctrineDbalContentGraphProjection.php | 3 ++- .../src/Domain/Projection/Feature/Workspace.php | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index 5037f6be25a..ecd410b0a07 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -117,7 +117,8 @@ public function setUp(): void $this->dbal->getSchemaManager()->tablesExist([$legacyWorkspaceTableName]) && !$this->dbal->getSchemaManager()->tablesExist([$this->tableNames->workspace()]) ) { - $statements[] = 'INSERT INTO ' . $this->tableNames->workspace() . ' (name, baseWorkspaceName, currentContentStreamId, status) SELECT workspaceName AS name, baseWorkspaceName, currentContentStreamId, status FROM ' . $legacyWorkspaceTableName; + // we ignore the legacy fields workspacetitle, workspacedescription and workspaceowner + $statements[] = 'INSERT INTO ' . $this->tableNames->workspace() . ' (name, baseWorkspaceName, currentContentStreamId, status) SELECT workspacename AS name, baseworkspacename, currentcontentstreamid, status FROM ' . $legacyWorkspaceTableName; } $legacyContentStreamTableName = str_replace('_p_graph_contentstream', '_p_contentstream', $this->tableNames->contentStream()); if ( diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/Workspace.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/Workspace.php index a9e528277fb..5f1e744ef96 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/Workspace.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/Workspace.php @@ -75,7 +75,7 @@ private function markDependentWorkspacesAsOutdated(WorkspaceName $baseWorkspaceN UPDATE ' . $this->tableNames->workspace() . ' SET status = :outdated WHERE - baseworkspacename = :baseWorkspaceName + baseWorkspaceName = :baseWorkspaceName ', [ 'outdated' => WorkspaceStatus::OUTDATED->value, 'baseWorkspaceName' => $baseWorkspaceName->value