From 422805cf0279499433490e95f6224f3cdc434e21 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 29 Jan 2025 09:14:46 +0100 Subject: [PATCH 1/3] TASK: Followup #5371 remove legacy `CopyNodesRecursively` command --- .../Factory/ContentRepositoryFactory.php | 6 - .../Command/CopyNodesRecursively.php | 166 ------------ .../Dto/NodeSubtreeSnapshot.php | 135 ---------- .../NodeDuplicationCommandHandler.php | 238 ------------------ .../Classes/Feature/RebaseableCommands.php | 3 - .../Classes/Service/EventMigrationService.php | 9 +- .../NodeAggregateIdMapping.php | 26 -- 7 files changed, 4 insertions(+), 579 deletions(-) delete mode 100644 Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/Command/CopyNodesRecursively.php delete mode 100644 Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/Dto/NodeSubtreeSnapshot.php delete mode 100644 Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php diff --git a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php index fde8bdd29a7..9f7ef31dcb3 100644 --- a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php +++ b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php @@ -24,7 +24,6 @@ use Neos\ContentRepository\Core\EventStore\EventNormalizer; use Neos\ContentRepository\Core\Feature\DimensionSpaceAdjustment\DimensionSpaceCommandHandler; use Neos\ContentRepository\Core\Feature\NodeAggregateCommandHandler; -use Neos\ContentRepository\Core\Feature\NodeDuplication\NodeDuplicationCommandHandler; use Neos\ContentRepository\Core\Feature\WorkspaceCommandHandler; use Neos\ContentRepository\Core\Infrastructure\Property\PropertyConverter; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; @@ -158,11 +157,6 @@ public function getOrBuild(): ContentRepository new DimensionSpaceCommandHandler( $this->contentDimensionZookeeper, $this->interDimensionalVariationGraph, - ), - new NodeDuplicationCommandHandler( - $this->nodeTypeManager, - $this->contentDimensionZookeeper, - $this->interDimensionalVariationGraph, ) ); diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/Command/CopyNodesRecursively.php b/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/Command/CopyNodesRecursively.php deleted file mode 100644 index ef175733c40..00000000000 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/Command/CopyNodesRecursively.php +++ /dev/null @@ -1,166 +0,0 @@ - - */ - public function jsonSerialize(): array - { - return get_object_vars($this); - } - - public function withNodeAggregateIdMapping( - NodeAggregateIdMapping $nodeAggregateIdMapping - ): self { - return new self( - $this->workspaceName, - $this->nodeTreeToInsert, - $this->targetDimensionSpacePoint, - $this->targetParentNodeAggregateId, - $this->targetSucceedingSiblingNodeAggregateId, - $this->targetNodeName, - $nodeAggregateIdMapping - ); - } - - /** - * The target node's optional name. - * - * @deprecated the concept regarding node-names for non-tethered nodes is outdated. - */ - public function withTargetNodeName(NodeName $targetNodeName): self - { - return new self( - $this->workspaceName, - $this->nodeTreeToInsert, - $this->targetDimensionSpacePoint, - $this->targetParentNodeAggregateId, - $this->targetSucceedingSiblingNodeAggregateId, - $targetNodeName, - $this->nodeAggregateIdMapping - ); - } - - public function createCopyForWorkspace( - WorkspaceName $targetWorkspaceName, - ): self { - return new self( - $targetWorkspaceName, - $this->nodeTreeToInsert, - $this->targetDimensionSpacePoint, - $this->targetParentNodeAggregateId, - $this->targetSucceedingSiblingNodeAggregateId, - $this->targetNodeName, - $this->nodeAggregateIdMapping - ); - } -} diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/Dto/NodeSubtreeSnapshot.php b/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/Dto/NodeSubtreeSnapshot.php deleted file mode 100644 index 1a724f5dbef..00000000000 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/Dto/NodeSubtreeSnapshot.php +++ /dev/null @@ -1,135 +0,0 @@ -findChildNodes($sourceNode->aggregateId, FindChildNodesFilter::create()) as $sourceChildNode - ) { - $childNodes[] = self::fromSubgraphAndStartNode($subgraph, $sourceChildNode); - } - $properties = $sourceNode->properties; - - return new self( - $sourceNode->aggregateId, - $sourceNode->nodeTypeName, - $sourceNode->name, - $sourceNode->classification, - $properties->serialized(), - self::serializeProjectedReferences( - $subgraph->findReferences($sourceNode->aggregateId, FindReferencesFilter::create()) - ), - $childNodes - ); - } - - /** - * @return array - */ - public function jsonSerialize(): array - { - return [ - 'nodeAggregateId' => $this->nodeAggregateId, - 'nodeTypeName' => $this->nodeTypeName, - 'nodeName' => $this->nodeName, - 'nodeAggregateClassification' => $this->nodeAggregateClassification, - 'propertyValues' => $this->propertyValues, - 'nodeReferences' => $this->nodeReferences, - 'childNodes' => $this->childNodes, - ]; - } - - public function walk(\Closure $forEachElementFn): void - { - $forEachElementFn($this); - foreach ($this->childNodes as $childNode) { - $childNode->walk($forEachElementFn); - } - } - - /** - * @param array $array - */ - public static function fromArray(array $array): self - { - $childNodes = []; - foreach ($array['childNodes'] as $childNode) { - $childNodes[] = self::fromArray($childNode); - } - - return new self( - NodeAggregateId::fromString($array['nodeAggregateId']), - NodeTypeName::fromString($array['nodeTypeName']), - isset($array['nodeName']) ? NodeName::fromString($array['nodeName']) : null, - NodeAggregateClassification::from($array['nodeAggregateClassification']), - SerializedPropertyValues::fromArray($array['propertyValues']), - SerializedNodeReferences::fromArray($array['nodeReferences']), - $childNodes - ); - } - - private static function serializeProjectedReferences(References $references): SerializedNodeReferences - { - $serializedReferences = []; - $serializedReferencesByName = []; - foreach ($references as $reference) { - if (!isset($serializedReferencesByName[$reference->name->value])) { - $serializedReferencesByName[$reference->name->value] = []; - } - $serializedReferencesByName[$reference->name->value][] = SerializedNodeReference::fromTargetAndProperties($reference->node->aggregateId, $reference->properties ? $reference->properties->serialized() : SerializedPropertyValues::createEmpty()); - } - - foreach ($serializedReferencesByName as $name => $referenceObjects) { - $serializedReferences[] = SerializedNodeReferencesForName::fromSerializedReferences(ReferenceName::fromString($name), $referenceObjects); - } - - return SerializedNodeReferences::fromArray($serializedReferences); - } -} diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php deleted file mode 100644 index d102d58e26a..00000000000 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php +++ /dev/null @@ -1,238 +0,0 @@ -nodeTypeManager; - } - - protected function getAllowedDimensionSubspace(): DimensionSpacePointSet - { - return $this->contentDimensionZookeeper->getAllowedDimensionSubspace(); - } - - public function canHandle(CommandInterface|RebasableToOtherWorkspaceInterface $command): bool - { - return method_exists($this, 'handle' . (new \ReflectionClass($command))->getShortName()); - } - - public function handle(CommandInterface|RebasableToOtherWorkspaceInterface $command, CommandHandlingDependencies $commandHandlingDependencies): EventsToPublish - { - /** @phpstan-ignore-next-line */ - return match ($command::class) { - CopyNodesRecursively::class => $this->handleCopyNodesRecursively($command, $commandHandlingDependencies), - }; - } - - /** - * @throws NodeConstraintException - */ - private function handleCopyNodesRecursively( - CopyNodesRecursively $command, - CommandHandlingDependencies $commandHandlingDependencies - ): EventsToPublish { - // Basic constraints (Content Stream / Dimension Space Point / Node Type of to-be-inserted root node) - $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $commandHandlingDependencies); - $this->requireDimensionSpacePointToExist( - $command->targetDimensionSpacePoint->toDimensionSpacePoint() - ); - $nodeType = $this->requireNodeType($command->nodeTreeToInsert->nodeTypeName); - $this->requireNodeTypeToNotBeOfTypeRoot($nodeType); - - // Constraint: Does the target parent node allow nodes of this type? - // NOTE: we only check this for the *root* node of the to-be-inserted structure; and not for its - // children (as we want to create the structure as-is; assuming it was already valid beforehand). - $this->requireConstraintsImposedByAncestorsAreMet( - $contentGraph, - $nodeType, - [$command->targetParentNodeAggregateId] - ); - - // Constraint: The new nodeAggregateIds are not allowed to exist yet. - $this->requireNewNodeAggregateIdsToNotExist( - $contentGraph, - $command->nodeAggregateIdMapping - ); - - // Constraint: the parent node must exist in the command's DimensionSpacePoint as well - $parentNodeAggregate = $this->requireProjectedNodeAggregate( - $contentGraph, - $command->targetParentNodeAggregateId - ); - if ($command->targetSucceedingSiblingNodeAggregateId) { - $this->requireProjectedNodeAggregate( - $contentGraph, - $command->targetSucceedingSiblingNodeAggregateId - ); - } - $this->requireNodeAggregateToCoverDimensionSpacePoint( - $parentNodeAggregate, - $command->targetDimensionSpacePoint->toDimensionSpacePoint() - ); - - // Calculate Covered Dimension Space Points: All points being specializations of the - // given DSP, where the parent also exists. - $specializations = $this->interDimensionalVariationGraph->getSpecializationSet( - $command->targetDimensionSpacePoint->toDimensionSpacePoint() - ); - $coveredDimensionSpacePoints = $specializations->getIntersection( - $parentNodeAggregate->coveredDimensionSpacePoints - ); - - // Constraint: The node name must be free for a new child of the parent node aggregate - if ($command->targetNodeName) { - $this->requireNodeNameToBeUncovered( - $contentGraph, - $command->targetNodeName, - $command->targetParentNodeAggregateId, - ); - } - - // Now, we can start creating the recursive structure. - $events = []; - $this->createEventsForNodeToInsert( - $contentGraph, - $command->targetDimensionSpacePoint, - $coveredDimensionSpacePoints, - $command->targetParentNodeAggregateId, - $command->targetSucceedingSiblingNodeAggregateId, - $command->targetNodeName, - $command->nodeTreeToInsert, - $command->nodeAggregateIdMapping, - $events - ); - - return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId( - $contentGraph->getContentStreamId() - )->getEventStreamName(), - RebaseableCommand::enrichWithCommand( - $command, - Events::fromArray($events) - ), - $expectedVersion - ); - } - - private function requireNewNodeAggregateIdsToNotExist( - ContentGraphInterface $contentGraph, - \Neos\Neos\Domain\Service\NodeDuplication\NodeAggregateIdMapping $nodeAggregateIdMapping - ): void { - foreach ($nodeAggregateIdMapping->getAllNewNodeAggregateIds() as $nodeAggregateId) { - $this->requireProjectedNodeAggregateToNotExist( - $contentGraph, - $nodeAggregateId - ); - } - } - - /** - * @param array $events - */ - private function createEventsForNodeToInsert( - ContentGraphInterface $contentGraph, - OriginDimensionSpacePoint $originDimensionSpacePoint, - DimensionSpacePointSet $coveredDimensionSpacePoints, - NodeAggregateId $targetParentNodeAggregateId, - ?NodeAggregateId $targetSucceedingSiblingNodeAggregateId, - ?NodeName $targetNodeName, - NodeSubtreeSnapshot $nodeToInsert, - \Neos\Neos\Domain\Service\NodeDuplication\NodeAggregateIdMapping $nodeAggregateIdMapping, - array &$events, - ): void { - $events[] = new NodeAggregateWithNodeWasCreated( - $contentGraph->getWorkspaceName(), - $contentGraph->getContentStreamId(), - $nodeAggregateIdMapping->getNewNodeAggregateId( - $nodeToInsert->nodeAggregateId - ) ?: NodeAggregateId::create(), - $nodeToInsert->nodeTypeName, - $originDimensionSpacePoint, - $targetSucceedingSiblingNodeAggregateId - ? $this->resolveInterdimensionalSiblingsForCreation( - $contentGraph, - $targetSucceedingSiblingNodeAggregateId, - $originDimensionSpacePoint, - $coveredDimensionSpacePoints - ) - : InterdimensionalSiblings::fromDimensionSpacePointSetWithoutSucceedingSiblings($coveredDimensionSpacePoints), - $targetParentNodeAggregateId, - $targetNodeName, - $nodeToInsert->propertyValues, - $nodeToInsert->nodeAggregateClassification, - $nodeToInsert->nodeReferences, - ); - - foreach ($nodeToInsert->childNodes as $childNodeToInsert) { - $this->createEventsForNodeToInsert( - $contentGraph, - $originDimensionSpacePoint, - $coveredDimensionSpacePoints, - // the just-inserted node becomes the new parent node ID - $nodeAggregateIdMapping->getNewNodeAggregateId( - $nodeToInsert->nodeAggregateId - ) ?: NodeAggregateId::create(), - // $childNodesToInsert is already in the correct order; so appending only is fine. - null, - $childNodeToInsert->nodeName, - $childNodeToInsert, - $nodeAggregateIdMapping, - $events - ); - } - } -} diff --git a/Neos.ContentRepository.Core/Classes/Feature/RebaseableCommands.php b/Neos.ContentRepository.Core/Classes/Feature/RebaseableCommands.php index 4d5e6764b45..2c47299af79 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/RebaseableCommands.php +++ b/Neos.ContentRepository.Core/Classes/Feature/RebaseableCommands.php @@ -105,9 +105,6 @@ private static function commandMatchesAtLeastOneNode( UntagSubtree::class, UpdateRootNodeAggregateDimensions::class, => $command->nodeAggregateId->equals($nodeId), - CopyNodesRecursively::class => $command->nodeAggregateIdMapping->getNewNodeAggregateId( - $command->nodeTreeToInsert->nodeAggregateId - )?->equals($nodeId), SetSerializedNodeReferences::class => $command->sourceNodeAggregateId->equals($nodeId), // for non node-aggregate-changes we return false, so they are kept as remainder: AddDimensionShineThrough::class, diff --git a/Neos.ContentRepositoryRegistry/Classes/Service/EventMigrationService.php b/Neos.ContentRepositoryRegistry/Classes/Service/EventMigrationService.php index e1b3b4dae8e..444ef7f7926 100644 --- a/Neos.ContentRepositoryRegistry/Classes/Service/EventMigrationService.php +++ b/Neos.ContentRepositoryRegistry/Classes/Service/EventMigrationService.php @@ -13,7 +13,6 @@ use Neos\ContentRepository\Core\Feature\NodeCreation\Command\CreateNodeAggregateWithNodeAndSerializedProperties; use Neos\ContentRepository\Core\Feature\NodeDisabling\Command\DisableNodeAggregate; use Neos\ContentRepository\Core\Feature\NodeDisabling\Command\EnableNodeAggregate; -use Neos\ContentRepository\Core\Feature\NodeDuplication\Command\CopyNodesRecursively; use Neos\ContentRepository\Core\Feature\NodeModification\Command\SetSerializedNodeProperties; use Neos\ContentRepository\Core\Feature\NodeMove\Command\MoveNodeAggregate; use Neos\ContentRepository\Core\Feature\NodeReferencing\Command\SetSerializedNodeReferences; @@ -223,7 +222,7 @@ public function migratePropertiesToUnset(\Closure $outputFn): void $outputRewriteNotice(sprintf('Metadata: Removed %d $initialPropertyValues', $propertiesWithNullValues)); $this->updateEventMetaData($eventEnvelope->sequenceNumber, $eventMetaData); } - } elseif ($eventMetaData['commandClass'] === CopyNodesRecursively::class) { + } elseif ($eventMetaData['commandClass'] === 'Neos\\ContentRepository\\Core\\Feature\\NodeDuplication\\Command\\CopyNodesRecursively') { // nodes can be also created on copy, and in $nodeTreeToInsert, we have to also omit null values. // NodeDuplicationCommandHandler::createEventsForNodeToInsert @@ -411,7 +410,7 @@ public function migrateMetaDataToWorkspaceName(\Closure $outputFn): void CreateNodeAggregateWithNodeAndSerializedProperties::class, DisableNodeAggregate::class, EnableNodeAggregate::class, - CopyNodesRecursively::class, + 'Neos\\ContentRepository\\Core\\Feature\\NodeDuplication\\Command\\CopyNodesRecursively', SetSerializedNodeProperties::class, MoveNodeAggregate::class, SetSerializedNodeReferences::class, @@ -863,7 +862,7 @@ public function migrateCopyTetheredNode(\Closure $outputFn): void $eventMetaData = $eventEnvelope->event->metadata?->value; // a copy is basically a NodeAggregateWithNodeWasCreated with CopyNodesRecursively command, so we skip others: - if (!$eventMetaData || ($eventMetaData['commandClass'] ?? null) !== CopyNodesRecursively::class) { + if (!$eventMetaData || ($eventMetaData['commandClass'] ?? null) !== 'Neos\\ContentRepository\\Core\\Feature\\NodeDuplication\\Command\\CopyNodesRecursively') { continue; } @@ -900,7 +899,7 @@ public function copyNodesStatus(\Closure $outputFn): void foreach ($eventStream as $eventEnvelope) { $eventMetaData = $eventEnvelope->event->metadata?->value; // a copy is basically a NodeAggregateWithNodeWasCreated with CopyNodesRecursively command, so we skip others: - if (!$eventMetaData || ($eventMetaData['commandClass'] ?? null) !== CopyNodesRecursively::class) { + if (!$eventMetaData || ($eventMetaData['commandClass'] ?? null) !== 'Neos\\ContentRepository\\Core\\Feature\\NodeDuplication\\Command\\CopyNodesRecursively') { continue; } diff --git a/Neos.Neos/Classes/Domain/Service/NodeDuplication/NodeAggregateIdMapping.php b/Neos.Neos/Classes/Domain/Service/NodeDuplication/NodeAggregateIdMapping.php index 88cac7635a1..01eea26a486 100644 --- a/Neos.Neos/Classes/Domain/Service/NodeDuplication/NodeAggregateIdMapping.php +++ b/Neos.Neos/Classes/Domain/Service/NodeDuplication/NodeAggregateIdMapping.php @@ -14,7 +14,6 @@ * source code. */ -use Neos\ContentRepository\Core\Feature\NodeDuplication\Dto\NodeSubtreeSnapshot; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; /** @@ -63,23 +62,6 @@ public function withNewNodeAggregateId(NodeAggregateId $oldNodeAggregateId, Node return new self($nodeAggregateIds); } - /** - * Create a new id mapping, *GENERATING* new ids. - */ - public static function generateForNodeSubtreeSnapshot(NodeSubtreeSnapshot $nodeSubtreeSnapshot): self - { - $nodeAggregateIdMapping = []; - /** @phpstan-ignore neos.cr.internal */ - $nodeSubtreeSnapshot->walk( - function (NodeSubtreeSnapshot $nodeSubtreeSnapshot) use (&$nodeAggregateIdMapping) { - // here, we create new random NodeAggregateIds. - $nodeAggregateIdMapping[$nodeSubtreeSnapshot->nodeAggregateId->value] = NodeAggregateId::create(); - } - ); - - return new self($nodeAggregateIdMapping); - } - /** * @param array $array */ @@ -106,12 +88,4 @@ public function jsonSerialize(): array { return $this->nodeAggregateIds; } - - /** - * @return array - */ - public function getAllNewNodeAggregateIds(): array - { - return array_values($this->nodeAggregateIds); - } } From 6a15b6d2c882e66071cb29ff86600dc5f2b0aab2 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 29 Jan 2025 09:36:29 +0100 Subject: [PATCH 2/3] TASK: Provide transparent conflict when attempting to publish legacy copy nodes --- .../Classes/Feature/RebaseableCommands.php | 10 +++-- .../Feature/WorkspaceCommandHandler.php | 43 ++++++++++++++++--- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/Feature/RebaseableCommands.php b/Neos.ContentRepository.Core/Classes/Feature/RebaseableCommands.php index 2c47299af79..20df892d60a 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/RebaseableCommands.php +++ b/Neos.ContentRepository.Core/Classes/Feature/RebaseableCommands.php @@ -11,7 +11,6 @@ use Neos\ContentRepository\Core\Feature\NodeCreation\Command\CreateNodeAggregateWithNodeAndSerializedProperties; use Neos\ContentRepository\Core\Feature\NodeDisabling\Command\DisableNodeAggregate; use Neos\ContentRepository\Core\Feature\NodeDisabling\Command\EnableNodeAggregate; -use Neos\ContentRepository\Core\Feature\NodeDuplication\Command\CopyNodesRecursively; use Neos\ContentRepository\Core\Feature\NodeModification\Command\SetSerializedNodeProperties; use Neos\ContentRepository\Core\Feature\NodeMove\Command\MoveNodeAggregate; use Neos\ContentRepository\Core\Feature\NodeReferencing\Command\SetSerializedNodeReferences; @@ -24,7 +23,7 @@ use Neos\ContentRepository\Core\Feature\SubtreeTagging\Command\TagSubtree; use Neos\ContentRepository\Core\Feature\SubtreeTagging\Command\UntagSubtree; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateIds; -use Neos\EventStore\Model\EventStream\EventStreamInterface; +use Neos\EventStore\Model\EventEnvelope; /** * @internal @@ -43,11 +42,14 @@ public function __construct( $this->items = $items; } - public static function extractFromEventStream(EventStreamInterface $eventStream): self + /** + * @param iterable $eventStream + */ + public static function extractFromEventStream(iterable $eventStream): self { $commands = []; foreach ($eventStream as $eventEnvelope) { - if ($eventEnvelope->event->metadata && isset($eventEnvelope->event->metadata?->value['commandClass'])) { + if (isset($eventEnvelope->event->metadata?->value['commandClass'])) { $commands[] = RebaseableCommand::extractFromEventEnvelope($eventEnvelope); } } diff --git a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php index f06de074a30..45253622731 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php @@ -50,6 +50,7 @@ use Neos\ContentRepository\Core\Feature\WorkspacePublication\Event\WorkspaceWasPublished; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Command\RebaseWorkspace; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\ConflictingEvent; +use Neos\ContentRepository\Core\Feature\WorkspaceRebase\ConflictingEvents; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Dto\RebaseErrorHandlingStrategy; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Event\WorkspaceWasRebased; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Exception\PartialWorkspaceRebaseFailed; @@ -67,6 +68,7 @@ use Neos\EventStore\Exception\ConcurrencyException; use Neos\EventStore\Model\Event\SequenceNumber; use Neos\EventStore\Model\Event\Version; +use Neos\EventStore\Model\EventEnvelope; use Neos\EventStore\Model\EventStream\EventStreamInterface; use Neos\EventStore\Model\EventStream\ExpectedVersion; @@ -199,10 +201,10 @@ private function handlePublishWorkspace( $baseWorkspaceContentStreamVersion = $this->requireOpenContentStreamAndVersion($baseWorkspace, $commandHandlingDependencies); $rebaseableCommands = RebaseableCommands::extractFromEventStream( - $this->eventStore->load( + $this->triggerConflictForLegacyEvents($this->eventStore->load( ContentStreamEventStreamName::fromContentStreamId($workspace->currentContentStreamId) ->getEventStreamName() - ) + ), dropLegacyEvents: false) ); yield $this->closeContentStream( @@ -369,10 +371,10 @@ private function handleRebaseWorkspace( } $rebaseableCommands = RebaseableCommands::extractFromEventStream( - $this->eventStore->load( + $this->triggerConflictForLegacyEvents($this->eventStore->load( ContentStreamEventStreamName::fromContentStreamId($workspace->currentContentStreamId) ->getEventStreamName() - ) + ), dropLegacyEvents: $command->rebaseErrorHandlingStrategy === RebaseErrorHandlingStrategy::STRATEGY_FORCE) ); yield $this->closeContentStream( @@ -432,6 +434,35 @@ static function ($handle) use ($rebaseableCommands): void { yield $this->removeContentStreamWithoutConstraintChecks($workspace->currentContentStreamId); } + /** + * This layer can be removed if there are no legacy events expected during rebasing + * @return iterable + */ + private function triggerConflictForLegacyEvents(EventStreamInterface $eventStream, bool $dropLegacyEvents): iterable + { + foreach ($eventStream as $eventEnvelope) { + if (($eventEnvelope->event->metadata?->value['commandClass'] ?? '') === 'Neos\\ContentRepository\\Core\\Feature\\NodeDuplication\\Command\\CopyNodesRecursively') { + // The original draft of the CopyNodesRecursively command was removed. In case events that are created via that command are attempted to be + // normally rebased, published, or discarded we manually cause a conflict to ensure that the event is removed. + if ($dropLegacyEvents === true) { + // a forced rebase drops the legacy events + continue; + } + $originalEvent = $this->eventNormalizer->denormalize($eventEnvelope->event); + throw WorkspaceRebaseFailed::duringRebase( + new ConflictingEvents( + new ConflictingEvent( + $originalEvent, + throw new \RuntimeException('The legacy command CopyNodesRecursively was removed. Its not possible to rebase this event and it must be dropped.', 1738139237), + $eventEnvelope->sequenceNumber + ), + ) + ); + } + yield $eventEnvelope; + } + } + /** * This method is like a combined Rebase and Publish! * @@ -452,10 +483,10 @@ private function handlePublishIndividualNodesFromWorkspace( $baseWorkspaceContentStreamVersion = $this->requireOpenContentStreamAndVersion($baseWorkspace, $commandHandlingDependencies); $rebaseableCommands = RebaseableCommands::extractFromEventStream( - $this->eventStore->load( + $this->triggerConflictForLegacyEvents($this->eventStore->load( ContentStreamEventStreamName::fromContentStreamId($workspace->currentContentStreamId) ->getEventStreamName() - ) + ), dropLegacyEvents: false) ); [$matchingCommands, $remainingCommands] = $rebaseableCommands->separateMatchingAndRemainingCommands($command->nodesToPublish); From 1e57565c1b3ea845afc0012e17859e4486bb5821 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 29 Jan 2025 11:18:50 +0100 Subject: [PATCH 3/3] TASK: Silently drop `CopyNodesRecursively` instead of causing a conflict While causing a conflict and letting it be solved via force is clever, it might have more negative user side effects: Because of the force flag, any actual conflicts for later changes will be dropped and the user wouldnt know about them because there was just one conflict communicated. Instead of solving this more delicate by merging this conflict with the other possible conflicts we decide to just ignore the CopyNodesRecursively as this was communicated for 3 betas. --- .../Classes/Feature/RebaseableCommands.php | 12 +++--- .../Feature/WorkspaceCommandHandler.php | 43 +++---------------- 2 files changed, 13 insertions(+), 42 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/Feature/RebaseableCommands.php b/Neos.ContentRepository.Core/Classes/Feature/RebaseableCommands.php index 20df892d60a..7fe372c8690 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/RebaseableCommands.php +++ b/Neos.ContentRepository.Core/Classes/Feature/RebaseableCommands.php @@ -23,7 +23,7 @@ use Neos\ContentRepository\Core\Feature\SubtreeTagging\Command\TagSubtree; use Neos\ContentRepository\Core\Feature\SubtreeTagging\Command\UntagSubtree; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateIds; -use Neos\EventStore\Model\EventEnvelope; +use Neos\EventStore\Model\EventStream\EventStreamInterface; /** * @internal @@ -42,14 +42,16 @@ public function __construct( $this->items = $items; } - /** - * @param iterable $eventStream - */ - public static function extractFromEventStream(iterable $eventStream): self + public static function extractFromEventStream(EventStreamInterface $eventStream): self { $commands = []; foreach ($eventStream as $eventEnvelope) { if (isset($eventEnvelope->event->metadata?->value['commandClass'])) { + if ($eventEnvelope->event->metadata->value['commandClass'] === 'Neos\\ContentRepository\\Core\\Feature\\NodeDuplication\\Command\\CopyNodesRecursively') { + // The original draft of the CopyNodesRecursively command was removed with Beta 16. In case events that are created via that command are attempted to be + // normally rebased, published, or discarded we instead silently drop these events as announced. + continue; + } $commands[] = RebaseableCommand::extractFromEventEnvelope($eventEnvelope); } } diff --git a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php index 45253622731..f06de074a30 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php @@ -50,7 +50,6 @@ use Neos\ContentRepository\Core\Feature\WorkspacePublication\Event\WorkspaceWasPublished; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Command\RebaseWorkspace; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\ConflictingEvent; -use Neos\ContentRepository\Core\Feature\WorkspaceRebase\ConflictingEvents; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Dto\RebaseErrorHandlingStrategy; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Event\WorkspaceWasRebased; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Exception\PartialWorkspaceRebaseFailed; @@ -68,7 +67,6 @@ use Neos\EventStore\Exception\ConcurrencyException; use Neos\EventStore\Model\Event\SequenceNumber; use Neos\EventStore\Model\Event\Version; -use Neos\EventStore\Model\EventEnvelope; use Neos\EventStore\Model\EventStream\EventStreamInterface; use Neos\EventStore\Model\EventStream\ExpectedVersion; @@ -201,10 +199,10 @@ private function handlePublishWorkspace( $baseWorkspaceContentStreamVersion = $this->requireOpenContentStreamAndVersion($baseWorkspace, $commandHandlingDependencies); $rebaseableCommands = RebaseableCommands::extractFromEventStream( - $this->triggerConflictForLegacyEvents($this->eventStore->load( + $this->eventStore->load( ContentStreamEventStreamName::fromContentStreamId($workspace->currentContentStreamId) ->getEventStreamName() - ), dropLegacyEvents: false) + ) ); yield $this->closeContentStream( @@ -371,10 +369,10 @@ private function handleRebaseWorkspace( } $rebaseableCommands = RebaseableCommands::extractFromEventStream( - $this->triggerConflictForLegacyEvents($this->eventStore->load( + $this->eventStore->load( ContentStreamEventStreamName::fromContentStreamId($workspace->currentContentStreamId) ->getEventStreamName() - ), dropLegacyEvents: $command->rebaseErrorHandlingStrategy === RebaseErrorHandlingStrategy::STRATEGY_FORCE) + ) ); yield $this->closeContentStream( @@ -434,35 +432,6 @@ static function ($handle) use ($rebaseableCommands): void { yield $this->removeContentStreamWithoutConstraintChecks($workspace->currentContentStreamId); } - /** - * This layer can be removed if there are no legacy events expected during rebasing - * @return iterable - */ - private function triggerConflictForLegacyEvents(EventStreamInterface $eventStream, bool $dropLegacyEvents): iterable - { - foreach ($eventStream as $eventEnvelope) { - if (($eventEnvelope->event->metadata?->value['commandClass'] ?? '') === 'Neos\\ContentRepository\\Core\\Feature\\NodeDuplication\\Command\\CopyNodesRecursively') { - // The original draft of the CopyNodesRecursively command was removed. In case events that are created via that command are attempted to be - // normally rebased, published, or discarded we manually cause a conflict to ensure that the event is removed. - if ($dropLegacyEvents === true) { - // a forced rebase drops the legacy events - continue; - } - $originalEvent = $this->eventNormalizer->denormalize($eventEnvelope->event); - throw WorkspaceRebaseFailed::duringRebase( - new ConflictingEvents( - new ConflictingEvent( - $originalEvent, - throw new \RuntimeException('The legacy command CopyNodesRecursively was removed. Its not possible to rebase this event and it must be dropped.', 1738139237), - $eventEnvelope->sequenceNumber - ), - ) - ); - } - yield $eventEnvelope; - } - } - /** * This method is like a combined Rebase and Publish! * @@ -483,10 +452,10 @@ private function handlePublishIndividualNodesFromWorkspace( $baseWorkspaceContentStreamVersion = $this->requireOpenContentStreamAndVersion($baseWorkspace, $commandHandlingDependencies); $rebaseableCommands = RebaseableCommands::extractFromEventStream( - $this->triggerConflictForLegacyEvents($this->eventStore->load( + $this->eventStore->load( ContentStreamEventStreamName::fromContentStreamId($workspace->currentContentStreamId) ->getEventStreamName() - ), dropLegacyEvents: false) + ) ); [$matchingCommands, $remainingCommands] = $rebaseableCommands->separateMatchingAndRemainingCommands($command->nodesToPublish);