diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php deleted file mode 100644 index 5f1b45b09a5..00000000000 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php +++ /dev/null @@ -1,80 +0,0 @@ -contentRepositoryId->value, - 'workspace' - )); - - $currentContentStreamIdStatement = <<dbal->fetchOne($currentContentStreamIdStatement, [ - 'workspaceName' => $workspaceName->value, - ]); - } catch (Exception $e) { - throw new \RuntimeException(sprintf('Failed to load workspace content stream id from database: %s', $e->getMessage()), 1716486077, $e); - } - - if ($currentContentStreamId === false) { - throw WorkspaceDoesNotExist::butWasSupposedTo($workspaceName); - } - - return $this->buildForWorkspaceAndContentStream($workspaceName, ContentStreamId::fromString($currentContentStreamId)); - } - - public function buildForWorkspaceAndContentStream(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraph - { - return new ContentGraph($this->dbal, $this->nodeFactory, $this->contentRepositoryId, $this->nodeTypeManager, $this->tableNames, $workspaceName, $contentStreamId); - } -} diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php index 82ec0d0b389..787b5d24665 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php @@ -42,6 +42,16 @@ public function referenceRelation(): string return $this->tableNamePrefix . '_referencerelation'; } + 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/ContentRepositoryReadModelAdapter.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentRepositoryReadModelAdapter.php new file mode 100644 index 00000000000..56ad85a9d63 --- /dev/null +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentRepositoryReadModelAdapter.php @@ -0,0 +1,160 @@ +dbal, $this->nodeFactory, $this->contentRepositoryId, $this->nodeTypeManager, $this->tableNames, $workspaceName, $contentStreamId); + } + + public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace + { + $workspaceByNameStatement = <<tableNames->workspace()} + WHERE + name = :workspaceName + LIMIT 1 + SQL; + try { + $row = $this->dbal->fetchAssociative($workspaceByNameStatement, [ + 'workspaceName' => $workspaceName->value, + ]); + } catch (Exception $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); + } + + public function findWorkspaces(): 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); + } + + public function findContentStreams(): 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)); + } + + /** + * @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']), + (bool)$row['removed'] + ); + } +} diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index 2cd030ee3bc..ecd410b0a07 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -6,21 +6,28 @@ 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; 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; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\DimensionSpacePointsRepository; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ProjectionContentGraph; -use Neos\ContentRepository\Core\ContentGraphFinder; +use Neos\ContentRepository\Core\ContentRepositoryReadModel; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; 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; +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; @@ -42,8 +49,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; @@ -57,19 +71,22 @@ 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\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 { + use ContentStream; + use NodeMove; + use NodeRemoval; use NodeVariation; use SubtreeTagging; - use NodeRemoval; - use NodeMove; + use Workspace; public const RELATION_DEFAULT_OFFSET = 128; @@ -81,7 +98,7 @@ public function __construct( private readonly ProjectionContentGraph $projectionContentGraph, private readonly ContentGraphTableNames $tableNames, private readonly DimensionSpacePointsRepository $dimensionSpacePointsRepository, - private readonly ContentGraphFinder $contentGraphFinder + private readonly ContentRepositoryReadModel $contentRepositoryReadModel ) { $this->checkpointStorage = new DbalCheckpointStorage( $this->dbal, @@ -92,7 +109,27 @@ public function __construct( public function setUp(): void { - foreach ($this->determineRequiredSqlStatements() as $statement) { + $statements = $this->determineRequiredSqlStatements(); + + // 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]) + && !$this->dbal->getSchemaManager()->tablesExist([$this->tableNames->workspace()]) + ) { + // 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 ( + $this->dbal->getSchemaManager()->tablesExist([$legacyContentStreamTableName]) + && !$this->dbal->getSchemaManager()->tablesExist([$this->tableNames->contentStream()]) + ) { + $statements[] = 'INSERT INTO ' . $this->tableNames->contentStream() . ' (id, version, sourceContentStreamId, status, removed) SELECT contentStreamId AS id, version, sourceContentStreamId, state AS status, removed FROM ' . $legacyContentStreamTableName; + } + // /MIGRATION + + foreach ($statements as $statement) { try { $this->dbal->executeStatement($statement); } catch (DBALException $e) { @@ -140,16 +177,19 @@ public function getCheckpointStorage(): DbalCheckpointStorage return $this->checkpointStorage; } - public function getState(): ContentGraphFinder + public function getState(): ContentRepositoryReadModel { - return $this->contentGraphFinder; + return $this->contentRepositoryReadModel; } 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, @@ -164,19 +204,29 @@ 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, + ]) || $event instanceof EmbedsContentStreamId; } 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), @@ -191,16 +241,33 @@ 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, - default => throw new \InvalidArgumentException(sprintf('Unsupported event %s', get_debug_type($event))), + 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 => $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); + } + } + + private function whenContentStreamWasClosed(ContentStreamWasClosed $event): void + { + $this->updateContentStreamStatus($event->contentStreamId, ContentStreamStatus::CLOSED); + } + + private function whenContentStreamWasCreated(ContentStreamWasCreated $event): void + { + $this->createContentStream($event->contentStreamId, ContentStreamStatus::CREATED); } private function whenContentStreamWasForked(ContentStreamWasForked $event): void @@ -238,6 +305,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, ContentStreamStatus::FORKED, $event->sourceContentStreamId); } private function whenContentStreamWasRemoved(ContentStreamWasRemoved $event): void @@ -281,6 +350,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->updateContentStreamStatus($event->contentStreamId, $event->previousState); } private function whenDimensionShineThroughWasAdded(DimensionShineThroughWasAdded $event): void @@ -632,6 +708,14 @@ 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->updateContentStreamStatus($event->newContentStreamId, ContentStreamStatus::IN_USE_BY_WORKSPACE); + } + private function whenSubtreeWasTagged(SubtreeWasTagged $event): void { $this->addSubtreeTag($event->contentStreamId, $event->nodeAggregateId, $event->affectedDimensionSpacePoints, $event->tag); @@ -642,6 +726,104 @@ 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); + $this->updateContentStreamStatus($event->candidateContentStreamId, ContentStreamStatus::REBASE_ERROR); + } + + private function whenWorkspaceWasCreated(WorkspaceWasCreated $event): void + { + $this->createWorkspace($event->workspaceName, $event->baseWorkspaceName, $event->newContentStreamId); + + // the content stream is in use now + $this->updateContentStreamStatus($event->newContentStreamId, ContentStreamStatus::IN_USE_BY_WORKSPACE); + } + + 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->updateContentStreamStatus($event->newContentStreamId, ContentStreamStatus::IN_USE_BY_WORKSPACE); + // the previous content stream is no longer in use + $this->updateContentStreamStatus($event->previousContentStreamId, ContentStreamStatus::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->updateContentStreamStatus($event->newContentStreamId, ContentStreamStatus::IN_USE_BY_WORKSPACE); + + // the previous content stream is no longer in use + $this->updateContentStreamStatus($event->previousContentStreamId, ContentStreamStatus::NO_LONGER_IN_USE); + } + + 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); + + // the new content stream is in use now + $this->updateContentStreamStatus($event->newSourceContentStreamId, ContentStreamStatus::IN_USE_BY_WORKSPACE); + + // the previous content stream is no longer in use + $this->updateContentStreamStatus($event->previousSourceContentStreamId, ContentStreamStatus::NO_LONGER_IN_USE); + } + + 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); + + // the new content stream is in use now + $this->updateContentStreamStatus($event->newSourceContentStreamId, ContentStreamStatus::IN_USE_BY_WORKSPACE); + + // the previous content stream is no longer in use + $this->updateContentStreamStatus($event->previousSourceContentStreamId, ContentStreamStatus::NO_LONGER_IN_USE); + } + + 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); + + // the new content stream is in use now + $this->updateContentStreamStatus($event->newContentStreamId, ContentStreamStatus::IN_USE_BY_WORKSPACE); + + // the previous content stream is no longer in use + $this->updateContentStreamStatus($event->previousContentStreamId, ContentStreamStatus::NO_LONGER_IN_USE); + } + + private function whenWorkspaceWasRemoved(WorkspaceWasRemoved $event): void + { + $this->removeWorkspace($event->workspaceName); + } /** --------------------------------- */ @@ -662,6 +844,8 @@ 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()); + $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..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\ContentGraphFinder; +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 ContentGraphFinder($contentGraphFactory) + new ContentRepositoryReadModel($contentRepositoryReadModelAdapter) ); } } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php index adbf2add9b8..e10b4f16c62 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,15 @@ public function buildSchema(AbstractSchemaManager $schemaManager): Schema $this->createNodeTable(), $this->createHierarchyRelationTable(), $this->createReferenceRelationTable(), - $this->createDimensionSpacePointsTable() + $this->createDimensionSpacePointsTable(), + $this->createWorkspaceTable(), + $this->createContentStreamTable(), ]); } 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 +66,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 +86,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 +97,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 +109,30 @@ private function createReferenceRelationTable(): Table ->setPrimaryKey(['name', 'position', 'nodeanchorpoint']); } + private function createWorkspaceTable(): Table + { + $workspaceTable = self::createTable($this->tableNames->workspace(), [ + 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(['name']); + } + + private function createContentStreamTable(): Table + { + return self::createTable($this->tableNames->contentStream(), [ + 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('status', 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..dfe1f7f46d5 --- /dev/null +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/ContentStream.php @@ -0,0 +1,54 @@ +dbal->insert($this->tableNames->contentStream(), [ + 'id' => $contentStreamId->value, + 'sourceContentStreamId' => $sourceContentStreamId?->value, + 'version' => 0, + 'status' => $status->value, + ]); + } + + private function updateContentStreamStatus(ContentStreamId $contentStreamId, ContentStreamStatus $status): void + { + $this->dbal->update($this->tableNames->contentStream(), [ + 'status' => $status->value, + ], [ + 'id' => $contentStreamId->value + ]); + } + + private function removeContentStream(ContentStreamId $contentStreamId): void + { + $this->dbal->update($this->tableNames->contentStream(), [ + 'removed' => true, + ], [ + 'id' => $contentStreamId->value + ]); + } + + private function updateContentStreamVersion(ContentStreamId $contentStreamId, Version $version): void + { + $this->dbal->update($this->tableNames->contentStream(), [ + 'version' => $version->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 new file mode 100644 index 00000000000..5f1e744ef96 --- /dev/null +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/Workspace.php @@ -0,0 +1,112 @@ +dbal->insert($this->tableNames->workspace(), [ + 'name' => $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(), + ['name' => $workspaceName->value] + ); + } + + private function updateBaseWorkspace(WorkspaceName $workspaceName, WorkspaceName $baseWorkspaceName, ContentStreamId $newContentStreamId): void + { + $this->dbal->update( + $this->tableNames->workspace(), + [ + 'baseWorkspaceName' => $baseWorkspaceName->value, + 'currentContentStreamId' => $newContentStreamId->value, + ], + ['name' => $workspaceName->value] + ); + } + + private function updateWorkspaceContentStreamId( + WorkspaceName $workspaceName, + ContentStreamId $contentStreamId, + ): void { + $this->dbal->update($this->tableNames->workspace(), [ + 'currentContentStreamId' => $contentStreamId->value, + ], [ + 'name' => $workspaceName->value + ]); + } + + private function markWorkspaceAsUpToDate(WorkspaceName $workspaceName): void + { + $this->dbal->executeStatement(' + UPDATE ' . $this->tableNames->workspace() . ' + SET status = :upToDate + WHERE + name = :workspaceName + ', [ + 'upToDate' => WorkspaceStatus::UP_TO_DATE->value, + 'workspaceName' => $workspaceName->value + ]); + } + + private function markDependentWorkspacesAsOutdated(WorkspaceName $baseWorkspaceName): void + { + $this->dbal->executeStatement(' + 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->executeStatement(' + UPDATE ' . $this->tableNames->workspace() . ' + SET + status = :outdated + WHERE + name = :workspaceName + ', [ + 'outdated' => WorkspaceStatus::OUTDATED->value, + 'workspaceName' => $workspaceName->value + ]); + } + + private function markWorkspaceAsOutdatedConflict(WorkspaceName $workspaceName): void + { + $this->dbal->executeStatement(' + UPDATE ' . $this->tableNames->workspace() . ' + SET + status = :outdatedConflict + WHERE + 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 deleted file mode 100644 index 974c84d17ca..00000000000 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperGraphFactory.php +++ /dev/null @@ -1,64 +0,0 @@ -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 - { - return new ContentHyperGraph($this->dbal, $this->nodeFactory, $this->contentRepositoryId, $this->nodeTypeManager, $this->tableNamePrefix, $workspaceName, $contentStreamId); - } -} diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperRepositoryReadModelAdapter.php b/Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperRepositoryReadModelAdapter.php new file mode 100644 index 00000000000..245ddd82d1f --- /dev/null +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperRepositoryReadModelAdapter.php @@ -0,0 +1,65 @@ +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 findWorkspaces(): Workspaces + { + // TODO: Implement getWorkspaces() method. + return Workspaces::createEmpty(); + } + + public function findContentStreamById(ContentStreamId $contentStreamId): ?ContentStream + { + // TODO: Implement findContentStreamById() method. + return null; + } + + public function findContentStreams(): ContentStreams + { + // TODO: Implement getContentStreams() method. + return ContentStreams::createEmpty(); + } +} diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Projection/HypergraphProjection.php b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Projection/HypergraphProjection.php index 6b683bbd08d..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\ContentGraphFinder; +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 ContentGraphFinder $contentGraphFinder + 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(): ContentGraphFinder + public function getState(): ContentRepositoryReadModel { - return $this->contentGraphFinder; + return $this->contentRepositoryReadModel; } protected function getProjectionHypergraph(): ProjectionHypergraph diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/HypergraphProjectionFactory.php b/Neos.ContentGraph.PostgreSQLAdapter/src/HypergraphProjectionFactory.php index edbb84fbb8e..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\ContentGraphFinder; +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 ContentGraphFinder(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.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..67fa06bbd93 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 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: @@ -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 c0b5270cc37..1c778159fcd 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\SharedModel\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamStatus; 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 @@ -35,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) { @@ -46,14 +47,32 @@ 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(); + $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 + { + return $this->contentRepository->findContentStreamById($contentStreamId) !== null; + } + + public function getContentStreamStatus(ContentStreamId $contentStreamId): ContentStreamStatus + { + $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 getContentStreamFinder(): ContentStreamFinder + public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace { - return $this->contentRepository->getContentStreamFinder(); + return $this->contentRepository->findWorkspaceByName($workspaceName); } /** @@ -61,8 +80,8 @@ public function getContentStreamFinder(): ContentStreamFinder */ 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); @@ -79,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(ContentRepositoryReadModel::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/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 d1262979d40..2acb37f73e2 100644 --- a/Neos.ContentRepository.Core/Classes/ContentRepository.php +++ b/Neos.ContentRepository.Core/Classes/ContentRepository.php @@ -30,20 +30,21 @@ 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\Workspace; -use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceFinder; -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\Workspace; 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,7 +242,7 @@ public function resetProjectionState(string $projectionClassName): void */ public function getContentGraph(WorkspaceName $workspaceName): ContentGraphInterface { - return $this->projectionState(ContentGraphFinder::class)->getByWorkspaceName($workspaceName); + return $this->getContentRepositoryReadModel()->getContentGraphByWorkspaceName($workspaceName); } /** @@ -249,7 +250,7 @@ public function getContentGraph(WorkspaceName $workspaceName): ContentGraphInter */ public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace { - return $this->getWorkspaceFinder()->findOneByName($workspaceName); + return $this->getContentRepositoryReadModel()->findWorkspaceByName($workspaceName); } /** @@ -258,20 +259,17 @@ public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace */ public function findWorkspaces(): Workspaces { - return $this->getWorkspaceFinder()->findAll(); + return $this->getContentRepositoryReadModel()->findWorkspaces(); } - /** - * @deprecated with 9.0.0-beta14 please use {@see ContentRepository::findWorkspaces()} and {@see ContentRepository::findWorkspaceByName()} instead. - */ - public function getWorkspaceFinder(): WorkspaceFinder + public function findContentStreamById(ContentStreamId $contentStreamId): ?ContentStream { - return $this->projectionState(WorkspaceFinder::class); + return $this->getContentRepositoryReadModel()->findContentStreamById($contentStreamId); } - public function getContentStreamFinder(): ContentStreamFinder + public function findContentStreams(): ContentStreams { - return $this->projectionState(ContentStreamFinder::class); + return $this->getContentRepositoryReadModel()->findContentStreams(); } public function getNodeTypeManager(): NodeTypeManager @@ -288,4 +286,9 @@ public function getContentDimensionSource(): ContentDimensionSourceInterface { return $this->contentDimensionSource; } + + private function getContentRepositoryReadModel(): ContentRepositoryReadModel + { + return $this->projectionState(ContentRepositoryReadModel::class); + } } diff --git a/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModel.php b/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModel.php new file mode 100644 index 00000000000..38733eaf7a7 --- /dev/null +++ b/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModel.php @@ -0,0 +1,89 @@ +adapter->findWorkspaceByName($workspaceName); + } + + public function findWorkspaces(): Workspaces + { + return $this->adapter->findWorkspaces(); + } + + public function findContentStreamById(ContentStreamId $contentStreamId): ?ContentStream + { + return $this->adapter->findContentStreamById($contentStreamId); + } + + public function findContentStreams(): ContentStreams + { + return $this->adapter->findContentStreams(); + } + + /** + * The default way to get a content graph to operate on. + * The currently assigned ContentStreamId for the given Workspace is resolved internally. + * + * @throws WorkspaceDoesNotExist if the provided workspace does not resolve to an existing content stream + * @see ContentRepository::getContentGraph() + */ + public function getContentGraphByWorkspaceName(WorkspaceName $workspaceName): ContentGraphInterface + { + $workspace = $this->findWorkspaceByName($workspaceName); + if ($workspace === null) { + throw WorkspaceDoesNotExist::butWasSupposedTo($workspaceName); + } + return $this->adapter->buildContentGraph($workspace->workspaceName, $workspace->currentContentStreamId); + } + + /** + * For testing we allow getting an instance set by both parameters, effectively overriding the relationship at will + * + * @param WorkspaceName $workspaceName + * @param ContentStreamId $contentStreamId + * @internal Only for testing + */ + public function getContentGraphByWorkspaceNameAndContentStreamId(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphInterface + { + return $this->adapter->buildContentGraph($workspaceName, $contentStreamId); + } +} diff --git a/Neos.ContentRepository.Core/Classes/ContentGraphFactoryInterface.php b/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModelAdapterInterface.php similarity index 54% rename from Neos.ContentRepository.Core/Classes/ContentGraphFactoryInterface.php rename to Neos.ContentRepository.Core/Classes/ContentRepositoryReadModelAdapterInterface.php index 2a766579751..7d266fdec9d 100644 --- a/Neos.ContentRepository.Core/Classes/ContentGraphFactoryInterface.php +++ b/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModelAdapterInterface.php @@ -16,20 +16,27 @@ 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 * * @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 buildContentGraph(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphInterface; - public function buildForWorkspaceAndContentStream(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphInterface; + public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace; + + public function findWorkspaces(): Workspaces; + + public function findContentStreamById(ContentStreamId $contentStreamId): ?ContentStream; + + public function findContentStreams(): ContentStreams; } diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php index 9cb894f5d76..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; @@ -81,15 +81,14 @@ 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 ); } - - if ($state === ContentStreamState::STATE_CLOSED) { + $state = $commandHandlingDependencies->getContentStreamStatus($contentStreamId); + if ($state === ContentStreamStatus::CLOSED) { throw new ContentStreamIsClosed( 'Content stream "' . $contentStreamId->value . '" is closed.', 1710260081 @@ -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/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 2c9d9160ccd..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; /** @@ -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->getContentStreamStatus($contentStreamId) === ContentStreamStatus::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->getContentStreamStatus($contentStreamId) !== ContentStreamStatus::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..5e3a7acad57 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,11 @@ 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 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..ea4f29c6232 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php @@ -41,13 +41,9 @@ 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; @@ -67,8 +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\Projection\Workspace\WorkspaceFinder; +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; @@ -103,7 +98,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 +105,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 +120,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, @@ -150,10 +141,7 @@ private function handleCreateWorkspace( new WorkspaceWasCreated( $command->workspaceName, $command->baseWorkspaceName, - $command->workspaceTitle, - $command->workspaceDescription, $command->newContentStreamId, - $command->workspaceOwner ) ); @@ -164,30 +152,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 @@ -210,8 +174,6 @@ private function handleCreateRootWorkspace( $events = Events::with( new RootWorkspaceWasCreated( $command->workspaceName, - $command->workspaceTitle, - $command->workspaceDescription, $newContentStreamId ) ); @@ -236,8 +198,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 +315,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->getContentStreamStatus($oldWorkspaceContentStreamId); // 0) close old content stream $commandHandlingDependencies->handle( @@ -488,13 +449,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->getContentStreamStatus($oldWorkspaceContentStreamId); + $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies); // 1) close old content stream $commandHandlingDependencies->handle( @@ -629,13 +590,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->getContentStreamStatus($contentGraph->getContentStreamId()); + $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies); // 1) close old content stream $commandHandlingDependencies->handle( @@ -773,8 +734,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 +776,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 +813,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 +835,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 +853,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 +867,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 +883,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 +893,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/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..0e3c4d832f1 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCreation/Event/RootWorkspaceWasCreated.php +++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCreation/Event/RootWorkspaceWasCreated.php @@ -15,13 +15,10 @@ 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; -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 +32,6 @@ { public function __construct( public WorkspaceName $workspaceName, - public WorkspaceTitle $workspaceTitle, - public WorkspaceDescription $workspaceDescription, public ContentStreamId $newContentStreamId ) { } @@ -50,8 +45,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/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 @@ - - */ - 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 a689d3f7cde..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) { - ContentStreamWasCreated::class => $this->whenContentStreamWasCreated($event, $eventEnvelope), - RootWorkspaceWasCreated::class => $this->whenRootWorkspaceWasCreated($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))), - }; - } - - 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, - ContentStreamEventStreamName::EVENT_STREAM_NAME_PREFIX - ) - ) { - 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 - ); - } - 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 ff339ca5acc..00000000000 --- a/Neos.ContentRepository.Core/Classes/Projection/Workspace/Workspace.php +++ /dev/null @@ -1,137 +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; - } - - public function isRootWorkspace(): bool - { - return $this->baseWorkspaceName !== 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 61e18df5fc7..00000000000 --- a/Neos.ContentRepository.Core/Classes/Projection/Workspace/WorkspaceFinder.php +++ /dev/null @@ -1,223 +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; - } - - /** - * @deprecated with 9.0.0-beta14 discouraged. You should just operate on workspace names instead. - * To still archive the functionality please use {@see ContentRepository::findWorkspaces()} instead and filter the result: - * - * $this->contentRepository->getWorkspaces()->find( - * fn (Workspace $workspace) => $workspace->currentContentStreamId->equals($contentStreamId) - * ) - * - */ - 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 - * @deprecated with 9.0.0-beta14 please use {@see ContentRepository::findWorkspaces()} and {@see Workspaces::getDependantWorkspaces()} instead. - */ - 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; - } - - /** - * @deprecated with 9.0.0-beta14 owners/collaborators should be assigned to workspaces outside the Content Repository core - * For Neos.Neos please use {@see \Neos\Neos\Domain\Service\WorkspaceService::getPersonalWorkspaceForUser()} - */ - 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); - } - - /** - * @deprecated with 9.0.0-beta14 please use {@see ContentRepository::findWorkspaces()} instead - */ - 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); - } - - /** - * @deprecated with 9.0.0-beta14 please use {@see ContentRepository::findWorkspaces()} instead and filter the result: - * - * $this->contentRepository->getWorkspaces()->filter( - * fn (Workspace $workspace) => $workspace->status === WorkspaceStatus::OUTDATED - * ) - * - * @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/ContentStreamPruner.php b/Neos.ContentRepository.Core/Classes/Service/ContentStreamPruner.php index eb5f5540917..15d7667ae5e 100644 --- a/Neos.ContentRepository.Core/Classes/Service/ContentStreamPruner.php +++ b/Neos.ContentRepository.Core/Classes/Service/ContentStreamPruner.php @@ -4,24 +4,23 @@ namespace Neos\ContentRepository\Core\Service; -use Neos\ContentRepository\Core\CommandHandler\CommandResult; 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\ContentStreams; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamStatus; use Neos\EventStore\EventStoreInterface; /** - * For internal implementation details, see {@see ContentStreamFinder}. + * For implementation details of the content stream states and removed state, see {@see ContentStream}. * * @api */ class ContentStreamPruner implements ContentRepositoryServiceInterface { - private ?CommandResult $lastCommandResult; - public function __construct( private readonly ContentRepository $contentRepository, private readonly EventStoreInterface $eventStore, @@ -46,17 +45,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->findContentStreams()->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 +73,69 @@ 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 { - $removedContentStreams = $this->contentRepository->getContentStreamFinder() - ->findUnusedAndRemovedContentStreams(); - + $removedContentStreams = $this->findUnusedAndRemovedContentStreams(); foreach ($removedContentStreams as $removedContentStream) { - $streamName = ContentStreamEventStreamName::fromContentStreamId($removedContentStream) + $streamName = ContentStreamEventStreamName::fromContentStreamId($removedContentStream->id) ->getEventStreamName(); $this->eventStore->deleteStream($streamName); } - return $removedContentStreams; } public function pruneAll(): void { - $contentStreamIds = $this->contentRepository->getContentStreamFinder()->findAllIds(); - - foreach ($contentStreamIds as $contentStreamId) { - $streamName = ContentStreamEventStreamName::fromContentStreamId($contentStreamId)->getEventStreamName(); + foreach ($this->contentRepository->findContentStreams() as $contentStream) { + $streamName = ContentStreamEventStreamName::fromContentStreamId($contentStream->id)->getEventStreamName(); $this->eventStore->deleteStream($streamName); } } - public function getLastCommandResult(): ?CommandResult + private function findUnusedAndRemovedContentStreams(): ContentStreams { - return $this->lastCommandResult; + $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 42c4b2c48a7..66acf840a9e 100644 --- a/Neos.ContentRepository.Core/Classes/Service/ContentStreamPrunerFactory.php +++ b/Neos.ContentRepository.Core/Classes/Service/ContentStreamPrunerFactory.php @@ -17,7 +17,7 @@ public function build(ContentRepositoryServiceFactoryDependencies $serviceFactor { return new ContentStreamPruner( $serviceFactoryDependencies->contentRepository, - $serviceFactoryDependencies->eventStore + $serviceFactoryDependencies->eventStore, ); } } diff --git a/Neos.ContentRepository.Core/Classes/Service/WorkspaceMaintenanceService.php b/Neos.ContentRepository.Core/Classes/Service/WorkspaceMaintenanceService.php index 053d0dc3565..0c2ee14feab 100644 --- a/Neos.ContentRepository.Core/Classes/Service/WorkspaceMaintenanceService.php +++ b/Neos.ContentRepository.Core/Classes/Service/WorkspaceMaintenanceService.php @@ -9,7 +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\SharedModel\Workspace\Workspace; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspaces; +use Neos\ContentRepository\Core\SharedModel\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->findWorkspaces()->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->findWorkspaces() as $workspace) { $streamName = WorkspaceEventStreamName::fromWorkspaceName($workspace->workspaceName)->getEventStreamName(); $this->eventStore->deleteStream($streamName); } 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 00000000000..b69045a3aec Binary files /dev/null and b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStream.monopic differ diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStreamState.php b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStream.php similarity index 53% rename from Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStreamState.php rename to Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStream.php index 79f1dd3ef10..def543c48b0 100644 --- a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStreamState.php +++ b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStream.php @@ -1,7 +1,7 @@ removed from all other projections │◀─┘ + * │ removed=true │ │ + * │ => removed from content graph │◀─┘ * └────────────────────────────────────────┘ Cleanup * │ * ▼ @@ -51,51 +69,17 @@ * * @api */ -enum ContentStreamState: string implements \JsonSerializable +final readonly class ContentStream { /** - * the content stream was created, but not yet assigned to a workspace. - * - * **temporary state** which should not appear if the system is idle (for content streams which are used with workspaces). - */ - case STATE_CREATED = 'CREATED'; - - /** - * STATE_FORKED means the content stream was forked from an existing content stream, but not yet assigned - * to a workspace. - * - * **temporary state** which should not appear if the system is idle (for content streams which are used with workspaces). - */ - case STATE_FORKED = 'FORKED'; - - /** - * the content stream is currently referenced as the "active" content stream by a workspace. + * @internal */ - case STATE_IN_USE_BY_WORKSPACE = 'IN_USE_BY_WORKSPACE'; - - /** - * 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) - */ - case STATE_REBASE_ERROR = 'REBASE_ERROR'; - - /** - * the content stream was closed and must no longer accept new events - */ - case STATE_CLOSED = 'CLOSED'; - - /** - * the content stream is not used anymore, and can be removed. - */ - case STATE_NO_LONGER_IN_USE = 'NO_LONGER_IN_USE'; - - public static function fromString(string $value): self - { - return self::from($value); - } - - public function jsonSerialize(): string - { - return $this->value; + public function __construct( + public ContentStreamId $id, + 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 new file mode 100644 index 00000000000..ac21aeaa4cc --- /dev/null +++ b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStreamStatus.php @@ -0,0 +1,69 @@ +value; + } +} diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStreams.php b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStreams.php new file mode 100644 index 00000000000..2789309b62a --- /dev/null +++ b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/ContentStreams.php @@ -0,0 +1,98 @@ + + * + * @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..7f51c161123 --- /dev/null +++ b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/Workspace.php @@ -0,0 +1,66 @@ +workspaceName = $workspaceName; + $this->baseWorkspaceName = $baseWorkspaceName; + $this->currentContentStreamId = $currentContentStreamId; + $this->status = $status; + } + + /** + * @phpstan-assert-if-false WorkspaceName $this->baseWorkspaceName + */ + public function isRootWorkspace(): 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/WorkspaceName.php b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/WorkspaceName.php index 85752a9ee63..1a5b65999d4 100644 --- a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/WorkspaceName.php +++ b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/WorkspaceName.php @@ -18,6 +18,7 @@ /** * Name of a workspace. + * The name uniquely identifies a workspace in its Content Repository instance * * @api */ diff --git a/Neos.ContentRepository.Core/Classes/Projection/Workspace/WorkspaceStatus.php b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/WorkspaceStatus.php similarity index 97% rename from Neos.ContentRepository.Core/Classes/Projection/Workspace/WorkspaceStatus.php rename to Neos.ContentRepository.Core/Classes/SharedModel/Workspace/WorkspaceStatus.php index 8b0bb6590ae..7c8d25067ff 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/Workspace/WorkspaceStatus.php +++ b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/WorkspaceStatus.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Neos\ContentRepository\Core\Projection\Workspace; +namespace Neos\ContentRepository\Core\SharedModel\Workspace; /** * @api diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/WorkspaceTitle.php b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/WorkspaceTitle.php deleted file mode 100644 index e00ee41690e..00000000000 --- a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/WorkspaceTitle.php +++ /dev/null @@ -1,52 +0,0 @@ -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 83% rename from Neos.ContentRepository.Core/Classes/Projection/Workspace/Workspaces.php rename to Neos.ContentRepository.Core/Classes/SharedModel/Workspace/Workspaces.php index af32c2634d0..236b2170f83 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,7 +84,7 @@ public function getBaseWorkspaces(WorkspaceName $workspaceName): Workspaces $baseWorkspaceName = null; } } - return Workspaces::fromArray($baseWorkspaces); + return self::fromArray($baseWorkspaces); } /** @@ -103,7 +98,7 @@ public function getDependantWorkspaces(WorkspaceName $workspaceName): Workspaces } /** - * @return \Traversable + * @return \Traversable */ public function getIterator(): \Traversable { diff --git a/Neos.ContentRepository.Export/Tests/Behavior/Features/Bootstrap/CrImportExportTrait.php b/Neos.ContentRepository.Export/Tests/Behavior/Features/Bootstrap/CrImportExportTrait.php index 2b1ce7fd096..0aa571b20ea 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()), $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 d93b47c2d40..e36d50be983 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\WorkspaceFinder; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; use Neos\Media\Domain\Repository\AssetRepository; use Neos\Neos\AssetUsage\AssetUsageService; @@ -19,7 +19,7 @@ class ExportServiceFactory implements ContentRepositoryServiceFactoryInterface public function __construct( private readonly Filesystem $filesystem, - private readonly WorkspaceFinder $workspaceFinder, + private readonly Workspace $targetWorkspace, private readonly AssetRepository $assetRepository, private readonly AssetUsageService $assetUsageService, ) { @@ -30,7 +30,7 @@ public function build(ContentRepositoryServiceFactoryDependencies $serviceFactor return new ExportService( $serviceFactoryDependencies->contentRepositoryId, $this->filesystem, - $this->workspaceFinder, + $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 5bd6b142dec..b7d0b486188 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\Workspace; 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 Workspace $targetWorkspace, 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->targetWorkspace->currentContentStreamId)->getEventStreamName(); $eventStream = $this->eventStore->load($streamName); $eventFileResource = fopen('php://temp/maxmemory:5242880', 'rb+'); 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..00421a6bea4 100644 --- a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Bootstrap/FeatureContext.php +++ b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Bootstrap/FeatureContext.php @@ -12,9 +12,12 @@ use Neos\ContentRepository\BehavioralTests\TestSuite\Behavior\GherkinPyStringNodeBasedNodeTypeManagerFactory; use Neos\ContentRepository\BehavioralTests\TestSuite\Behavior\GherkinTableNodeBasedContentDimensionSourceFactory; use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\ContentRepositoryReadModel; use Neos\ContentRepository\Core\EventStore\EventNormalizer; +use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryDependencies; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; +use Neos\ContentRepository\Core\Infrastructure\Property\PropertyConverter; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; @@ -123,26 +126,26 @@ 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); - // FIXME: Dirty - $contentGraphFactory = (new \ReflectionClass($contentGraphFinder)) - ->getProperty('contentGraphFactory') - ->getValue($contentGraphFinder); - $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.NodeMigration/src/NodeMigrationService.php b/Neos.ContentRepository.NodeMigration/src/NodeMigrationService.php index ba7279ce0c2..63669814819 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; @@ -61,7 +58,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', @@ -70,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,12 +77,10 @@ public function executeMigration(ExecuteMigration $command): void CreateWorkspace::create( $command->targetWorkspaceName, $sourceWorkspace->workspaceName, - WorkspaceTitle::fromString($command->targetWorkspaceName->value), - WorkspaceDescription::fromString(''), $command->contentStreamId, ) ); - $targetWorkspace = $this->contentRepository->getWorkspaceFinder()->findOneByName($command->targetWorkspaceName); + $targetWorkspace = $this->contentRepository->findWorkspaceByName($command->targetWorkspaceName); $targetWorkspaceWasCreated = true; } diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php index a9520df0c93..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\ContentGraphFinder; +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 { - $contentGraphFinder = $this->currentContentRepository->projectionState(ContentGraphFinder::class); + $contentRepositoryReadModel = $this->currentContentRepository->projectionState(ContentRepositoryReadModel::class); - return $contentGraphFinder->getByWorkspaceName($this->currentWorkspaceName)->getSubgraph( + return $contentRepositoryReadModel->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 1dbe5f71716..ab550c6bd35 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,10 +134,9 @@ protected function readPayloadTable(TableNode $payloadTable): array */ public function iExpectTheContentStreamToNotExist(string $rawContentStreamId): void { - Assert::assertTrue( - $this->currentContentRepository->getContentStreamFinder()->hasContentStream(ContentStreamId::fromString($rawContentStreamId)), - sprintf('The content stream "%s" does exist.', $rawContentStreamId) - ); + $contentStream = $this->currentContentRepository->findContentStreamById(ContentStreamId::fromString($rawContentStreamId)); + $contentStreamExists = $contentStream !== null && !$contentStream->removed; + Assert::assertFalse($contentStreamExists, sprintf('Content stream "%s" was not expected to exist, but it does', $rawContentStreamId)); } /** @@ -144,9 +144,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); } /** @@ -227,28 +227,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 - ->getWorkspaceFinder() - ->findOneByName($this->currentWorkspaceName) + ->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 fcf91a05526..d5fde970ec4 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\ContentRepositoryReadModel; use Neos\ContentRepository\Core\Feature\SubtreeTagging\Dto\SubtreeTag; use Neos\ContentRepository\Core\NodeType\NodeTypeName; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface; @@ -32,6 +32,7 @@ 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\Tests\Behavior\Fixtures\DayOfWeek; use Neos\ContentRepository\Core\Tests\Behavior\Fixtures\PostalAddress; use Neos\ContentRepository\Core\Tests\Behavior\Fixtures\PriceSpecification; @@ -80,11 +81,14 @@ public function iGetTheNodeAtPath(string $serializedNodePath): void public function iExpectANodeIdentifiedByXToExistInTheContentGraph(string $serializedNodeDiscriminator): void { $nodeDiscriminator = NodeDiscriminator::fromShorthand($serializedNodeDiscriminator); - $workspaceName = $this->currentContentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId( - $nodeDiscriminator->contentStreamId - )->workspaceName; - $contentGraph = $this->currentContentRepository->getContentGraph($workspaceName); - $currentNodeAggregate = $contentGraph->findNodeAggregateById( + $matchingWorkspace = $this->currentContentRepository->findWorkspaces()->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/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) { 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 1cf5cda4df5..4e75aa55aa1 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 +98,31 @@ public function relatedNodesAction(AssetInterface $asset) $contentRepository = $this->contentRepositoryRegistry->get($usage->getContentRepositoryId()); - $workspace = $contentRepository->getWorkspaceFinder()->findOneByName($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 +143,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/Neos.Media.Browser/Resources/Private/Templates/Usage/RelatedNodes.html b/Neos.Media.Browser/Resources/Private/Templates/Usage/RelatedNodes.html index 2a1b2c27af1..0a2ef2524f8 100644 --- a/Neos.Media.Browser/Resources/Private/Templates/Usage/RelatedNodes.html +++ b/Neos.Media.Browser/Resources/Private/Templates/Usage/RelatedNodes.html @@ -140,7 +140,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/AssetUsageIndexingProcessor.php b/Neos.Neos/Classes/AssetUsage/AssetUsageIndexingProcessor.php index 9a05c7260c8..17c8482dabb 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->findWorkspaces(); + $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..a6c42ef5523 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->findWorkspaces()->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 54250a5dc43..a6c2f0606ad 100644 --- a/Neos.Neos/Classes/Command/CrCommandController.php +++ b/Neos.Neos/Classes/Command/CrCommandController.php @@ -16,7 +16,6 @@ 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; @@ -31,12 +30,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, @@ -61,16 +54,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 b55b5833658..2fdd87931c2 100644 --- a/Neos.Neos/Classes/Command/WorkspaceCommandController.php +++ b/Neos.Neos/Classes/Command/WorkspaceCommandController.php @@ -14,15 +14,14 @@ namespace Neos\Neos\Command; -use Doctrine\DBAL\Exception as DbalException; use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Exception\WorkspaceAlreadyExists; 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\Service\WorkspaceMaintenanceServiceFactory; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; @@ -365,7 +364,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); @@ -377,13 +376,8 @@ public function deleteCommand(string $workspace, bool $force = false, string $co $this->quit(2); } - try { - $dependentWorkspaces = $contentRepositoryInstance->getWorkspaceFinder()->findByBaseWorkspace($workspaceName); - } catch (DbalException $e) { - $this->outputLine('Failed to determine dependant workspaces: %s', [$e->getMessage()]); - $this->quit(1); - } - if (count($dependentWorkspaces) > 0) { + $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]); $this->outputLine(); @@ -447,12 +441,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]); } } @@ -465,11 +459,10 @@ public function rebaseOutdatedCommand(string $contentRepository = 'default', boo 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)->findWorkspaces(); - if (count($workspaces) === 0) { + if ($workspaces->isEmpty()) { $this->outputLine('No workspaces found.'); $this->quit(0); } @@ -507,7 +500,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/Controller/Module/Administration/SitesController.php b/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php index 3fe98412645..e124b5cd0a1 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; @@ -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->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 9fa6e4a8201..ac9269e7789 100644 --- a/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php +++ b/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php @@ -57,7 +57,7 @@ public function removeSiteNode(SiteNodeName $siteNodeName): void ); } - foreach ($this->contentRepository->getWorkspaceFinder()->findAll() 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/Domain/Service/UserService.php b/Neos.Neos/Classes/Domain/Service/UserService.php index 8ad74db35ec..dfa1616d003 100644 --- a/Neos.Neos/Classes/Domain/Service/UserService.php +++ b/Neos.Neos/Classes/Domain/Service/UserService.php @@ -14,7 +14,6 @@ namespace Neos\Neos\Domain\Service; -use Neos\ContentRepository\Core\Projection\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/Domain/Service/WorkspacePublishingService.php b/Neos.Neos/Classes/Domain/Service/WorkspacePublishingService.php index fd7cb7e1453..31836d383d2 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; @@ -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(); @@ -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 e828ed9b86c..c34636a77a6 100644 --- a/Neos.Neos/Classes/Domain/Service/WorkspaceService.php +++ b/Neos.Neos/Classes/Domain/Service/WorkspaceService.php @@ -21,12 +21,10 @@ use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Command\CreateRootWorkspace; use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Command\CreateWorkspace; use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Exception\WorkspaceAlreadyExists; -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; @@ -75,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, ); } @@ -125,8 +123,6 @@ public function createRootWorkspace(ContentRepositoryId $contentRepositoryId, Wo $contentRepository->handle( CreateRootWorkspace::create( $workspaceName, - DeprecatedWorkspaceTitle::fromString($title->value), - DeprecatedWorkspaceDescription::fromString($description->value), ContentStreamId::create() ) ); @@ -306,7 +302,7 @@ public function getUniqueWorkspaceName(ContentRepositoryId $contentRepositoryId, $workspaceName = $workspaceNameCandidate; $attempt = 1; do { - if ($contentRepository->getWorkspaceFinder()->findOneByName($workspaceName) === null) { + if ($contentRepository->findWorkspaceByName($workspaceName) === null) { return $workspaceName; } if ($attempt === 1) { @@ -377,7 +373,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, ]); } @@ -393,8 +389,6 @@ private function createWorkspace(ContentRepositoryId $contentRepositoryId, Works CreateWorkspace::create( $workspaceName, $baseWorkspaceName, - DeprecatedWorkspaceTitle::fromString($title->value), - DeprecatedWorkspaceDescription::fromString($description->value), ContentStreamId::create() ) ); @@ -485,8 +479,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 da3a7c05c55..13a240c39d7 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->findWorkspaces()->getDependantWorkspaces($workspace->workspaceName))); } } $this->workspaceRuntimeCache[$contentRepository->id->value][$workspaceName->value] = $workspaceNames; diff --git a/Neos.Neos/Classes/Fusion/Helper/CachingHelper.php b/Neos.Neos/Classes/Fusion/Helper/CachingHelper.php index f921df9b4ef..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; @@ -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/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) 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 aeb7cb30ee4..3de04edb6bb 100644 --- a/Neos.Neos/Classes/Service/LinkingService.php +++ b/Neos.Neos/Classes/Service/LinkingService.php @@ -285,12 +285,12 @@ public function createNodeUri( $this->lastLinkedNode = $resolvedNode; $contentRepository = $this->contentRepositoryRegistry->get($nodeAddress->contentRepositoryId); - $workspace = $contentRepository->getWorkspaceFinder()->findOneByName($nodeAddress->workspaceName); + $workspace = $contentRepository->findWorkspaceByName($nodeAddress->workspaceName); $mainRequest = $controllerContext->getRequest()->getMainRequest(); $uriBuilder = clone $controllerContext->getUriBuilder(); $uriBuilder->setRequest($mainRequest); - $createLiveUri = $workspace && $workspace->isPublicWorkspace() && !$resolvedNode->tags->contain(SubtreeTag::disabled()); + $createLiveUri = $workspace && $nodeAddress->workspaceName->isLive() && !$resolvedNode->tags->contain(SubtreeTag::disabled()); if ($addQueryString === true) { // legacy feature see https://github.com/neos/neos-development-collection/issues/5076 diff --git a/Neos.Neos/Tests/Behavior/Features/Bootstrap/RoutingTrait.php b/Neos.Neos/Tests/Behavior/Features/Bootstrap/RoutingTrait.php index b3a8a1b399e..3eb2dc1a93a 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->findWorkspaces()->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->findWorkspaces()->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->findWorkspaces()->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 84e9ae5c84c..06916378f00 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; @@ -111,8 +109,6 @@ public function aRootWorkspaceExistsWithoutMetadata(string $workspaceName): void { $this->currentContentRepository->handle(CreateRootWorkspace::create( WorkspaceName::fromString($workspaceName), - DeprecatedWorkspaceTitle::fromString($workspaceName), - DeprecatedWorkspaceDescription::fromString(''), ContentStreamId::create(), )); } @@ -125,8 +121,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.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; diff --git a/Neos.Workspace.Ui/Classes/Controller/WorkspaceController.php b/Neos.Workspace.Ui/Classes/Controller/WorkspaceController.php index 598da056b9d..dac01978f21 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; @@ -132,7 +132,8 @@ public function indexAction(): void $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); $items = []; - foreach ($contentRepository->getWorkspaceFinder()->findAll() as $workspace) { + $allWorkspaces = $contentRepository->findWorkspaces(); + foreach ($allWorkspaces as $workspace) { $workspaceMetadata = $this->workspaceService->getWorkspaceMetadata($contentRepositoryId, $workspace->workspaceName); $permissions = $this->workspaceService->getWorkspacePermissionsForUser($contentRepositoryId, $workspace->workspaceName, $currentUser); if (!$permissions->read) { @@ -145,7 +146,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: !$allWorkspaces->getDependantWorkspaces($workspace->workspaceName)->isEmpty(), permissions: $permissions, ); } @@ -161,7 +162,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'); @@ -170,7 +171,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); @@ -250,7 +251,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'); @@ -287,7 +288,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'), @@ -331,7 +332,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'), @@ -347,9 +348,10 @@ public function deleteAction(WorkspaceName $workspaceName): void $this->redirect('index'); } - $dependentWorkspaces = $contentRepository->getWorkspaceFinder()->findByBaseWorkspace($workspace->workspaceName); - if (count($dependentWorkspaces) > 0) { + $dependentWorkspaces = $contentRepository->findWorkspaces()->getDependantWorkspaces($workspaceName); + if (!$dependentWorkspaces->isEmpty()) { $dependentWorkspaceTitles = []; + /** @var Workspace $dependentWorkspace */ foreach ($dependentWorkspaces as $dependentWorkspace) { $dependentWorkspaceMetadata = $this->workspaceService->getWorkspaceMetadata($contentRepositoryId, $dependentWorkspace->workspaceName); $dependentWorkspaceTitles[] = $dependentWorkspaceMetadata->title->value; @@ -827,8 +829,8 @@ protected function renderContentChanges( ContentStreamId $contentStreamIdOfOriginalNode, ContentRepository $contentRepository, ): array { - $currentWorkspace = $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId( - $contentStreamIdOfOriginalNode + $currentWorkspace = $contentRepository->findWorkspaces()->find( + fn (Workspace $potentialWorkspace) => $potentialWorkspace->currentContentStreamId->equals($contentStreamIdOfOriginalNode) ); $originalNode = null; if ($currentWorkspace !== null) { @@ -1011,7 +1013,7 @@ protected function prepareBaseWorkspaceOptions( ): array { $user = $this->userService->getCurrentUser(); $baseWorkspaceOptions = []; - $workspaces = $contentRepository->getWorkspaceFinder()->findAll(); + $workspaces = $contentRepository->findWorkspaces(); foreach ($workspaces as $workspace) { if ($excludedWorkspace !== null) { if ($workspace->workspaceName->equals($excludedWorkspace)) { @@ -1032,7 +1034,7 @@ protected function prepareBaseWorkspaceOptions( if (!$permissions->manage) { continue; } - $baseWorkspaceOptions[$workspace->workspaceName->value] = $workspace->workspaceTitle->value; + $baseWorkspaceOptions[$workspace->workspaceName->value] = $workspaceMetadata->title->value; } return $baseWorkspaceOptions; @@ -1061,7 +1063,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; } 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: