From 5c916d43d7d3aaa9b91d22140fa308381eb17337 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 9 Jun 2023 23:36:00 +0200 Subject: [PATCH] FEATURE: Add support for setting references --- .../NodeCreation/NodeCreationService.php | 72 ++++--- .../NodeCreation/PropertiesAndReferences.php | 31 +-- Classes/Domain/NodeCreation/ReferenceType.php | 38 +++- .../Domain/TemplateNodeCreationHandler.php | 4 +- Classes/NeosEscrTemplateWriteAdapter.php | 177 ------------------ Configuration/Testing/NodeTypes.yaml | 3 + .../Functional/ContentRepositoryTestTrait.php | 43 +++-- .../Functional/JsonSerializeNodeTreeTrait.php | 34 +++- Tests/Functional/NodeTemplateTest.php | 50 ++--- .../Domain/NodeCreation/ReferenceTypeTest.php | 9 +- 10 files changed, 199 insertions(+), 262 deletions(-) delete mode 100644 Classes/NeosEscrTemplateWriteAdapter.php diff --git a/Classes/Domain/NodeCreation/NodeCreationService.php b/Classes/Domain/NodeCreation/NodeCreationService.php index 60a19a2..56ab722 100644 --- a/Classes/Domain/NodeCreation/NodeCreationService.php +++ b/Classes/Domain/NodeCreation/NodeCreationService.php @@ -7,12 +7,19 @@ use Flowpack\NodeTemplates\Domain\Template\RootTemplate; use Flowpack\NodeTemplates\Domain\Template\Template; use Flowpack\NodeTemplates\Domain\Template\Templates; -use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\Feature\NodeCreation\Command\CreateNodeAggregateWithNode; use Neos\ContentRepository\Core\Feature\NodeModification\Command\SetNodeProperties; +use Neos\ContentRepository\Core\Feature\NodeModification\Dto\PropertyValuesToWrite; +use Neos\ContentRepository\Core\Feature\NodeReferencing\Command\SetNodeReferences; +use Neos\ContentRepository\Core\Feature\NodeReferencing\Dto\NodeReferencesToWrite; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateIds; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; +use Neos\ContentRepository\Core\SharedModel\Node\ReferenceName; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\Flow\Annotations as Flow; use Neos\Neos\Ui\NodeCreationHandler\NodeCreationCommands; use Neos\Neos\Utility\NodeUriPathSegmentGenerator; @@ -26,7 +33,7 @@ class NodeCreationService protected $nodeUriPathSegmentGenerator; public function __construct( - private readonly ContentRepository $contentRepository, + private readonly ContentSubgraphInterface $subgraph, private readonly NodeTypeManager $nodeTypeManager ) { } @@ -45,14 +52,9 @@ public function apply(RootTemplate $template, NodeCreationCommands $commands, Ca $initialProperties = $commands->initialCreateCommand->initialPropertyValues; $initialProperties = $initialProperties->merge( - $propertiesAndReferences->requireValidProperties($nodeType, $caughtExceptions) + PropertyValuesToWrite::fromArray($propertiesAndReferences->requireValidProperties($nodeType, $caughtExceptions)) ); - // todo set references - // foreach ($propertiesAndReferences->requireValidReferences($nodeType, $commands->getContext(), $caughtExceptions) as $key => $value) { - // $commands->setProperty($key, $value); - // } - // $this->ensureNodeHasUriPathSegment($commands, $template); return $this->applyTemplateRecursively( $template->getChildNodes(), @@ -62,7 +64,14 @@ public function apply(RootTemplate $template, NodeCreationCommands $commands, Ca $commands->initialCreateCommand->nodeAggregateId, $nodeType, ), - $commands->withInitialPropertyValues($initialProperties), + $commands->withInitialPropertyValues($initialProperties)->withAdditionalCommands( + ...$this->createReferencesCommands( + $commands->initialCreateCommand->contentStreamId, + $commands->initialCreateCommand->nodeAggregateId, + $commands->initialCreateCommand->originDimensionSpacePoint, + $propertiesAndReferences->requireValidReferences($nodeType, $this->subgraph, $caughtExceptions) + ) + ), $caughtExceptions ); } @@ -89,12 +98,16 @@ private function applyTemplateRecursively(Templates $templates, ToBeCreatedNode $template->getName() ), $parentNode->originDimensionSpacePoint, - $propertiesAndReferences->requireValidProperties($nodeType, $caughtExceptions) + PropertyValuesToWrite::fromArray($propertiesAndReferences->requireValidProperties($nodeType, $caughtExceptions)) + ), + ...$this->createReferencesCommands( + $parentNode->contentStreamId, + $nodeAggregateId, + $parentNode->originDimensionSpacePoint, + $propertiesAndReferences->requireValidReferences($nodeType, $this->subgraph, $caughtExceptions) ) ); - // todo references - $commands = $this->applyTemplateRecursively( $template->getChildNodes(), $parentNode->withNodeTypeAndNodeAggregateId( @@ -104,7 +117,6 @@ private function applyTemplateRecursively(Templates $templates, ToBeCreatedNode $commands, $caughtExceptions ); - continue; } @@ -125,11 +137,8 @@ private function applyTemplateRecursively(Templates $templates, ToBeCreatedNode $nodeType = $this->nodeTypeManager->getNodeType($template->getType()); - $propertiesAndReferences = PropertiesAndReferences::createFromArrayAndTypeDeclarations($template->getProperties(), $nodeType); - // hande references - $commands = $commands->withAdditionalCommands( new CreateNodeAggregateWithNode( $parentNode->contentStreamId, @@ -138,14 +147,16 @@ private function applyTemplateRecursively(Templates $templates, ToBeCreatedNode $parentNode->originDimensionSpacePoint, $parentNode->nodeAggregateId, nodeName: NodeName::fromString(uniqid('node-', false)), - initialPropertyValues: $propertiesAndReferences->requireValidProperties($nodeType, $caughtExceptions) + initialPropertyValues: PropertyValuesToWrite::fromArray($propertiesAndReferences->requireValidProperties($nodeType, $caughtExceptions)) + ), + ...$this->createReferencesCommands( + $parentNode->contentStreamId, + $nodeAggregateId, + $parentNode->originDimensionSpacePoint, + $propertiesAndReferences->requireValidReferences($nodeType, $this->subgraph, $caughtExceptions) ) ); - // set references - // foreach ($propertiesAndReferences->requireValidReferences($nodeType, $node->getContext(), $caughtExceptions) as $key => $value) { - // $node->setProperty($key, $value); - // } // $this->ensureNodeHasUriPathSegment($node, $template); $commands = $this->applyTemplateRecursively( @@ -162,6 +173,25 @@ private function applyTemplateRecursively(Templates $templates, ToBeCreatedNode return $commands; } + /** + * @param array $references + * @return list + */ + private function createReferencesCommands(ContentStreamId $contentStreamId, NodeAggregateId $nodeAggregateId, OriginDimensionSpacePoint $originDimensionSpacePoint, array $references): array + { + $commands = []; + foreach ($references as $name => $nodeAggregateIds) { + $commands[] = new SetNodeReferences( + $contentStreamId, + $nodeAggregateId, + $originDimensionSpacePoint, + ReferenceName::fromString($name), + NodeReferencesToWrite::fromNodeAggregateIds($nodeAggregateIds) + ); + } + return $commands; + } + /** * All document node types get a uri path segment; if it is not explicitly set in the properties, * it should be built based on the title property diff --git a/Classes/Domain/NodeCreation/PropertiesAndReferences.php b/Classes/Domain/NodeCreation/PropertiesAndReferences.php index 263f367..df2a5de 100644 --- a/Classes/Domain/NodeCreation/PropertiesAndReferences.php +++ b/Classes/Domain/NodeCreation/PropertiesAndReferences.php @@ -4,8 +4,9 @@ use Flowpack\NodeTemplates\Domain\ExceptionHandling\CaughtException; use Flowpack\NodeTemplates\Domain\ExceptionHandling\CaughtExceptions; -use Neos\ContentRepository\Core\Feature\NodeModification\Dto\PropertyValuesToWrite; use Neos\ContentRepository\Core\NodeType\NodeType; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateIds; use Neos\Flow\Annotations as Flow; /** @@ -50,7 +51,7 @@ public static function createFromArrayAndTypeDeclarations(array $propertiesAndRe * This is a problem, as setting `null` might not be possible via the Neos UI and the Fusion rendering is most likely not going to handle this edge case. * Related discussion {@link https://github.com/Flowpack/Flowpack.NodeTemplates/issues/41} */ - public function requireValidProperties(NodeType $nodeType, CaughtExceptions $caughtExceptions): PropertyValuesToWrite + public function requireValidProperties(NodeType $nodeType, CaughtExceptions $caughtExceptions): array { $validProperties = []; foreach ($this->properties as $propertyName => $propertyValue) { @@ -83,25 +84,29 @@ public function requireValidProperties(NodeType $nodeType, CaughtExceptions $cau ); } } - return PropertyValuesToWrite::fromArray($validProperties); + return $validProperties; } - public function requireValidReferences(NodeType $nodeType, Context $subgraph, CaughtExceptions $caughtExceptions): array + /** + * @return array + */ + public function requireValidReferences(NodeType $nodeType, ContentSubgraphInterface $subgraph, CaughtExceptions $caughtExceptions): array { $validReferences = []; foreach ($this->references as $referenceName => $referenceValue) { $referenceType = ReferenceType::fromPropertyOfNodeType($referenceName, $nodeType); - if (!$referenceType->isMatchedBy($referenceValue, $subgraph)) { - $caughtExceptions->add(CaughtException::fromException(new \RuntimeException( - sprintf( - 'Reference could not be set, because node reference(s) %s cannot be resolved.', - json_encode($referenceValue) - ), - 1685958176560 - ))->withOrigin(sprintf('Reference "%s" in NodeType "%s"', $referenceName, $nodeType->getName()))); + try { + $nodeAggregateIds = $referenceType->resolveNodeAggregateIds($referenceValue, $subgraph); + } catch (\RuntimeException $runtimeException) { + $caughtExceptions->add( + CaughtException::fromException($runtimeException) + ->withOrigin(sprintf('Reference "%s" in NodeType "%s"', $referenceName, $nodeType->getName())) + ); continue; } - $validReferences[$referenceName] = $referenceValue; + if ($nodeAggregateIds->getIterator()->count()) { + $validReferences[$referenceName] = $nodeAggregateIds; + } } return $validReferences; } diff --git a/Classes/Domain/NodeCreation/ReferenceType.php b/Classes/Domain/NodeCreation/ReferenceType.php index efe8f6f..1235a61 100644 --- a/Classes/Domain/NodeCreation/ReferenceType.php +++ b/Classes/Domain/NodeCreation/ReferenceType.php @@ -8,6 +8,7 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateIds; use Neos\Flow\Annotations as Flow; /** @@ -74,29 +75,48 @@ public function getValue(): string return $this->value; } - public function isMatchedBy($propertyValue, ContentSubgraphInterface $subgraphForResolving): bool + /** + * @param mixed $referenceValue + * @param ContentSubgraphInterface $subgraphForResolving + * @throws \RuntimeException in case the $referenceValue could not be resolved to a node + */ + public function resolveNodeAggregateIds(mixed $referenceValue, ContentSubgraphInterface $subgraphForResolving): NodeAggregateIds { - if ($propertyValue === null) { - return true; + if ($referenceValue === null) { + return NodeAggregateIds::createEmpty(); } - $nodeAggregatesOrIds = $this->isReference() ? [$propertyValue] : $propertyValue; + + $becauseOfInvalidValue = fn () => throw new \RuntimeException( + sprintf( + 'Reference could not be set, because node reference(s) %s cannot be resolved.', + json_encode($referenceValue) + ), + 1685958176560 + ); + + $nodeAggregatesOrIds = $this->isReference() ? [$referenceValue] : $referenceValue; if (is_array($nodeAggregatesOrIds) === false) { - return false; + throw $becauseOfInvalidValue(); } + + $nodeAggregateIds = []; + foreach ($nodeAggregatesOrIds as $singleNodeAggregateOrId) { if ($singleNodeAggregateOrId instanceof Node) { + $nodeAggregateIds[] = $singleNodeAggregateOrId->nodeAggregateId; continue; } try { $singleNodeAggregateId = is_string($singleNodeAggregateOrId) ? NodeAggregateId::fromString($singleNodeAggregateOrId) : $singleNodeAggregateOrId; } catch (\Exception) { - return false; + throw $becauseOfInvalidValue(); } - if ($singleNodeAggregateId instanceof NodeAggregateId && $subgraphForResolving->findNodeById($singleNodeAggregateId) instanceof Node) { + if ($singleNodeAggregateId instanceof NodeAggregateId && $subgraphForResolving->findNodeById($singleNodeAggregateId)) { + $nodeAggregateIds[] = $singleNodeAggregateId; continue; } - return false; + throw $becauseOfInvalidValue(); } - return true; + return NodeAggregateIds::fromArray($nodeAggregateIds); } } diff --git a/Classes/Domain/TemplateNodeCreationHandler.php b/Classes/Domain/TemplateNodeCreationHandler.php index d7b55bb..3ba2791 100644 --- a/Classes/Domain/TemplateNodeCreationHandler.php +++ b/Classes/Domain/TemplateNodeCreationHandler.php @@ -48,7 +48,7 @@ public function handle( $evaluationContext = [ 'data' => $data, // todo evaluate which context variables - 'subgraph' => $contentRepository->getContentGraph()->getSubgraph( + 'subgraph' => $subgraph = $contentRepository->getContentGraph()->getSubgraph( $commands->initialCreateCommand->contentStreamId, $commands->initialCreateCommand->originDimensionSpacePoint->toDimensionSpacePoint(), VisibilityConstraints::frontend() @@ -60,7 +60,7 @@ public function handle( $template = $this->templateConfigurationProcessor->processTemplateConfiguration($templateConfiguration, $evaluationContext, $caughtExceptions); // $this->exceptionHandler->handleAfterTemplateConfigurationProcessing($caughtExceptions, $node); - return (new NodeCreationService($contentRepository, $contentRepository->getNodeTypeManager()))->apply($template, $commands, $caughtExceptions); + return (new NodeCreationService($subgraph, $contentRepository->getNodeTypeManager()))->apply($template, $commands, $caughtExceptions); // $this->exceptionHandler->handleAfterNodeCreation($caughtExceptions, $node); } catch (TemplateNotCreatedException|TemplatePartiallyCreatedException $templateCreationException) { throw $templateCreationException; diff --git a/Classes/NeosEscrTemplateWriteAdapter.php b/Classes/NeosEscrTemplateWriteAdapter.php deleted file mode 100644 index bf7454e..0000000 --- a/Classes/NeosEscrTemplateWriteAdapter.php +++ /dev/null @@ -1,177 +0,0 @@ -writeTemplate($command, $contentRepository);` - */ -class NeosEscrTemplateWriteAdapter -{ - public function writeTemplate(CreateNodeAggregateWithNode $createNodeAggregateWithNode, ContentRepository $contentRepository): void - { - $template = [ - 'childNodes' => [ - 'main' => [ - 'name' => 'main', - 'childNodes' => [ - 'content1' => [ - 'type' => "Neos.Demo:Content.Text", - 'properties' => [ - 'text' => "huhu", - ] - ] - ] - ], - 'foo' => [ - 'type' => 'Neos.Demo:Document.Page', - 'childNodes' => [ - 'main' => [ - 'name' => 'main', - 'childNodes' => [ - 'content1' => [ - 'type' => "Neos.Demo:Content.Text", - 'properties' => [ - 'text' => "textiii" - ] - ], - 'content2' => [ - 'type' => "Neos.Demo:Content.Text", - 'properties' => [ - 'text' => "huijkuihjnihujbn" - ] - ] - ] - ], - ] - ] - ] - ]; - - $createNodeCommand = $this->augmentWithTetheredDescendantNodeAggregateIds($createNodeAggregateWithNode, $contentRepository); - - if (isset($template['properties'])) { - // documents generate uripath blabla - $createNodeCommand = $createNodeCommand->initialPropertyValues->merge( - PropertyValuesToWrite::fromArray($this->requireValidProperties($template['properties'])) - ); - } - - $commands = $this->createCommandsRecursivelyFromTemplateChildNodes($createNodeCommand, $template, $contentRepository); - - $contentRepository->handle($createNodeCommand)->block(); - - foreach ($commands as $command) { - $contentRepository->handle($command)->block(); - } - } - - /** - * In the old CR, it was common practice to set internal or meta properties via this syntax: `_hidden` so it was also allowed in templates but not anymore. - * @throws \InvalidArgumentException - */ - private function requireValidProperties(array $properties): array - { - $legacyInternalProperties = [ - '_accessRoles', - '_contentObject', - '_hidden', - '_hiddenAfterDateTime', - '_hiddenBeforeDateTime', - '_hiddenInIndex', - '_index', - '_name', - '_nodeType', - '_removed', - '_workspace' - ]; - foreach ($properties as $propertyName => $propertyValue) { - if (str_starts_with($propertyName, '_')) { - $lowerPropertyName = strtolower($propertyName); - foreach ($legacyInternalProperties as $legacyInternalProperty) { - if ($lowerPropertyName === strtolower($legacyInternalProperty)) { - throw new \InvalidArgumentException('Internal legacy properties are not implement.' . $propertyName); - } - } - } - } - return $properties; - } - - private function createCommandsRecursivelyFromTemplateChildNodes(CreateNodeAggregateWithNode $createParentNodeCommand, array $template, ContentRepository $contentRepository): array - { - $makeCreateNodeCommand = function (NodeAggregateId $parentNodeAggregateId, array $subTemplate) use ($createParentNodeCommand, $contentRepository) { - return $this->augmentWithTetheredDescendantNodeAggregateIds(new CreateNodeAggregateWithNode( - contentStreamId: $createParentNodeCommand->contentStreamId, - nodeAggregateId: NodeAggregateId::create(), - nodeTypeName: NodeTypeName::fromString($subTemplate['type']), - originDimensionSpacePoint: $createParentNodeCommand->originDimensionSpacePoint, - parentNodeAggregateId: $parentNodeAggregateId, - nodeName: NodeName::fromString(uniqid('node-', false)), - initialPropertyValues: isset($subTemplate['properties']) - ? PropertyValuesToWrite::fromArray($this->requireValidProperties($subTemplate['properties'])) - : null - ), $contentRepository); - }; - - $commands = []; - foreach ($template['childNodes'] ?? [] as $childNode) { - if (isset($childNode['name']) && $autoCreatedNodeId = $createParentNodeCommand->tetheredDescendantNodeAggregateIds->getNodeAggregateId(NodePath::fromString($childNode['name']))) { - if (isset($childNode['type'])) { - throw new \Exception('For auto-created nodes the type cannot be qualified.'); - } - if (isset($childNode['properties'])) { - $commands[] = new SetNodeProperties( - $createParentNodeCommand->contentStreamId, - $autoCreatedNodeId, - $createParentNodeCommand->originDimensionSpacePoint, - PropertyValuesToWrite::fromArray($this->requireValidProperties($childNode['properties'])) - ); - } - foreach ($childNode['childNodes'] ?? [] as $innerChildNode) { - $commands[] = $newParent = $makeCreateNodeCommand($autoCreatedNodeId, $innerChildNode); - $commands = [...$commands, ...$this->createCommandsRecursivelyFromTemplateChildNodes($newParent, $innerChildNode, $contentRepository)]; - } - } else { - // if is document setUriPath based on title - $commands[] = $newParent = $makeCreateNodeCommand($createParentNodeCommand->nodeAggregateId, $childNode); - $commands = [...$commands, ...$this->createCommandsRecursivelyFromTemplateChildNodes($newParent, $childNode, $contentRepository)]; - } - } - return $commands; - } - - /** - * Precalculate the nodeIds for the auto-created childNodes, so that we can determine the id beforehand and use it for succeeding operations. - * - * ``` - * $mainContentCollectionNodeId = $createNodeAggregateWithNode->tetheredDescendantNodeAggregateIds->getNodeAggregateId(NodePath::fromString('main')) - * ``` - */ - private function augmentWithTetheredDescendantNodeAggregateIds(CreateNodeAggregateWithNode $createNodeAggregateWithNode, ContentRepository $contentRepository): CreateNodeAggregateWithNode - { - $nodeType = $contentRepository->getNodeTypeManager()->getNodeType($createNodeAggregateWithNode->nodeTypeName); - if (!isset($nodeType->getFullConfiguration()['childNodes'])) { - return $createNodeAggregateWithNode; - } - $nodeAggregateIdsByNodePaths = NodeAggregateIdsByNodePaths::createEmpty(); - foreach (array_keys($nodeType->getFullConfiguration()['childNodes']) as $autoCreatedNodeName) { - $nodeAggregateIdsByNodePaths = $nodeAggregateIdsByNodePaths->add( - NodePath::fromString($autoCreatedNodeName), - NodeAggregateId::create() - ); - } - return $createNodeAggregateWithNode->withTetheredDescendantNodeAggregateIds( - $nodeAggregateIdsByNodePaths->merge($createNodeAggregateWithNode->tetheredDescendantNodeAggregateIds) - ); - } -} diff --git a/Configuration/Testing/NodeTypes.yaml b/Configuration/Testing/NodeTypes.yaml index cca63f4..805b2f9 100644 --- a/Configuration/Testing/NodeTypes.yaml +++ b/Configuration/Testing/NodeTypes.yaml @@ -1,6 +1,9 @@ 'Flowpack.NodeTemplates:Document.Page': superTypes: 'Neos.Neos:Document': true + constraints: + nodeTypes: + 'unstructured': true childNodes: main: type: 'Neos.Neos:ContentCollection' diff --git a/Tests/Functional/ContentRepositoryTestTrait.php b/Tests/Functional/ContentRepositoryTestTrait.php index 466f467..ddb9b56 100644 --- a/Tests/Functional/ContentRepositoryTestTrait.php +++ b/Tests/Functional/ContentRepositoryTestTrait.php @@ -5,9 +5,11 @@ use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\Factory\ContentRepositoryId; +use Neos\ContentRepository\Core\Infrastructure\DbalClientInterface; use Neos\ContentRepository\Core\Tests\Behavior\Features\Bootstrap\Helpers\FakeClockFactory; use Neos\ContentRepository\Core\Tests\Behavior\Features\Bootstrap\Helpers\FakeUserIdProviderFactory; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; +use Neos\EventStore\Exception\CheckpointException; use Neos\Flow\Configuration\ConfigurationManager; use Neos\Flow\ObjectManagement\ObjectManager; @@ -20,6 +22,8 @@ trait ContentRepositoryTestTrait private ContentRepositoryId $contentRepositoryId; + private static $wasContentRepositorySetupCalled = false; + private function initCleanContentRepository(): void { $this->contentRepositoryId ??= ContentRepositoryId::fromString('default'); @@ -30,14 +34,6 @@ private function initCleanContentRepository(): void 'Neos.ContentRepositoryRegistry' ); - // in case we do not have tests annotated with @adapters=Postgres, we - // REMOVE the Postgres projection from the Registry settings. This way, we won't trigger - // Postgres projection catchup for tests which are not yet postgres-aware. - // - // This is to make the testcases more stable and deterministic. We can remove this workaround - // once the Postgres adapter is fully ready. - unset($registrySettings['presets'][$this->contentRepositoryId->value]['projections']['Neos.ContentGraph.PostgreSQLAdapter:Hypergraph']); - $registrySettings['presets'][$this->contentRepositoryId->value]['userIdProvider']['factoryObjectName'] = FakeUserIdProviderFactory::class; $registrySettings['presets'][$this->contentRepositoryId->value]['clock']['factoryObjectName'] = FakeClockFactory::class; @@ -50,10 +46,31 @@ private function initCleanContentRepository(): void ); $this->contentRepository = $contentRepositoryRegistry->get($this->contentRepositoryId); - // // Big performance optimization: only run the setup once - DRAMATICALLY reduces test time - // if ($this->alwaysRunContentRepositorySetup || !self::$wasContentRepositorySetupCalled) { - $this->contentRepository->setUp(); - // self::$wasContentRepositorySetupCalled = true; - // } + // Performance optimization: only run the setup once + if (!self::$wasContentRepositorySetupCalled) { + $this->contentRepository->setUp(); + self::$wasContentRepositorySetupCalled = true; + } + + $connection = $this->objectManager->get(DbalClientInterface::class)->getConnection(); + + // reset events and projections + $eventTableName = sprintf('cr_%s_events', $this->contentRepositoryId->value); + $connection->executeStatement('TRUNCATE ' . $eventTableName); + // todo Projection Reset may fail because the lock cannot be acquired + try { + $this->contentRepository->resetProjectionStates(); + } catch (CheckpointException $checkpointException) { + if ($checkpointException->getCode() === 1652279016) { + // another process is in the critical section; a.k.a. + // the lock is acquired already by another process. + + // in case this actually happens, we should implement a retry + throw new \RuntimeException('Projection reset failed because the lock cannot be acquired', 1686342087789, $checkpointException); + } else { + // some error error - we re-throw + throw $checkpointException; + } + } } } diff --git a/Tests/Functional/JsonSerializeNodeTreeTrait.php b/Tests/Functional/JsonSerializeNodeTreeTrait.php index 52430d8..21a9704 100644 --- a/Tests/Functional/JsonSerializeNodeTreeTrait.php +++ b/Tests/Functional/JsonSerializeNodeTreeTrait.php @@ -2,25 +2,51 @@ namespace Flowpack\NodeTemplates\Tests\Functional; +use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindReferencesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\Subtree; +use Neos\ContentRepository\Core\Projection\NodeHiddenState\NodeHiddenStateFinder; trait JsonSerializeNodeTreeTrait { + private readonly ContentRepository $contentRepository; + private function jsonSerializeNodeAndDescendents(Subtree $subtree): array { + $hiddenStateFinder = $this->contentRepository->projectionState(NodeHiddenStateFinder::class); + $node = $subtree->node; + $subgraph = $this->contentRepository->getContentGraph()->getSubgraph( + $node->subgraphIdentity->contentStreamId, + $node->subgraphIdentity->dimensionSpacePoint, + $node->subgraphIdentity->visibilityConstraints + ); + + $references = $subgraph->findReferences($node->nodeAggregateId, FindReferencesFilter::create()); + + $referencesArray = []; + foreach ($references as $reference) { + $referencesArray[$reference->name->value] ??= []; + $referencesArray[$reference->name->value][] = array_filter([ + 'node' => sprintf('Node(%s, %s)', $reference->node->nodeAggregateId->value, $reference->node->nodeTypeName->value), + 'properties' => iterator_to_array($reference->properties ?? []) + ]); + } + return array_filter([ 'nodeTypeName' => $node->nodeTypeName, 'nodeName' => $node->classification->isTethered() ? $node->nodeName : null, - // todo - 'isDisabled' => false, + 'isDisabled' => $hiddenStateFinder->findHiddenState( + $node->subgraphIdentity->contentStreamId, + $node->originDimensionSpacePoint->toDimensionSpacePoint(), + $node->nodeAggregateId + )->isHidden, 'properties' => $this->serializeValuesInArray( iterator_to_array($node->properties->getIterator()) ), - // todo - 'references' => [], // $this->serializeValuesInArray($references) + 'references' => $referencesArray, 'childNodes' => array_map( fn ($subtree) => $this->jsonSerializeNodeAndDescendents($subtree), $subtree->children diff --git a/Tests/Functional/NodeTemplateTest.php b/Tests/Functional/NodeTemplateTest.php index 2e959a8..9162344 100644 --- a/Tests/Functional/NodeTemplateTest.php +++ b/Tests/Functional/NodeTemplateTest.php @@ -25,13 +25,15 @@ use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceTitle; use Neos\ContentRepository\Core\Tests\Behavior\Features\Bootstrap\Helpers\FakeUserIdProvider; -use Neos\Flow\Tests\FunctionalTestCase; +use Neos\Flow\Core\Bootstrap; +use Neos\Flow\ObjectManagement\ObjectManagerInterface; use Neos\Neos\FrontendRouting\NodeAddressFactory; use Neos\Neos\Ui\Domain\Model\ChangeCollection; use Neos\Neos\Ui\Domain\Model\FeedbackCollection; use Neos\Neos\Ui\TypeConverter\ChangeCollectionConverter; +use PHPUnit\Framework\TestCase; -class NodeTemplateTest extends FunctionalTestCase +class NodeTemplateTest extends TestCase // we don't use Flows functional test case as it would reset the database afterwards { use SnapshotTrait; use FeedbackCollectionMessagesTrait; @@ -50,11 +52,14 @@ class NodeTemplateTest extends FunctionalTestCase private RootTemplate $lastCreatedRootTemplate; - protected static $testablePersistenceEnabled = true; + protected static $testablePersistenceEnabled = false; + + private ObjectManagerInterface $objectManager; public function setUp(): void { - parent::setUp(); + $this->objectManager = Bootstrap::$staticObjectManager; + $this->setupContentRepository(); $this->nodeTemplateDumper = $this->objectManager->get(NodeTemplateDumper::class); @@ -190,18 +195,15 @@ public function testNodeCreationMatchesSnapshot1(): void /** @test */ public function testNodeCreationMatchesSnapshot2(): void { - $this->createNodeInto( - $targetNode = $this->homePageNode->getNode('main'), - $toBeCreatedNodeTypeName = NodeTypeName::fromString('Flowpack.NodeTemplates:Content.Columns.Two.CreationDialogAndWithItems'), - [ + $createdNode = $this->createNodeInto( + $this->homePageMainContentCollectionNode, + NodeTypeName::fromString('Flowpack.NodeTemplates:Content.Columns.Two.CreationDialogAndWithItems'), [ 'text' => '

bar

' ] ); $this->assertLastCreatedTemplateMatchesSnapshot('TwoColumnPreset'); - $createdNode = $targetNode->getChildNodes($toBeCreatedNodeTypeName->getValue())[0]; - self::assertSame([], $this->getMessagesOfFeedbackCollection()); $this->assertNodeDumpAndTemplateDumpMatchSnapshot('TwoColumnPreset', $createdNode); } @@ -209,16 +211,14 @@ public function testNodeCreationMatchesSnapshot2(): void /** @test */ public function testNodeCreationMatchesSnapshot3(): void { - $this->createNodeInto( - $targetNode = $this->homePageNode->getNode('main'), - $toBeCreatedNodeTypeName = NodeTypeName::fromString('Flowpack.NodeTemplates:Content.Columns.Two.WithContext'), + $createdNode = $this->createNodeInto( + $this->homePageMainContentCollectionNode, + NodeTypeName::fromString('Flowpack.NodeTemplates:Content.Columns.Two.WithContext'), [] ); $this->assertLastCreatedTemplateMatchesSnapshot('TwoColumnPreset'); - $createdNode = $targetNode->getChildNodes($toBeCreatedNodeTypeName->getValue())[0]; - self::assertSame([], $this->getMessagesOfFeedbackCollection()); $this->assertNodeDumpAndTemplateDumpMatchSnapshot('TwoColumnPreset', $createdNode); } @@ -226,18 +226,26 @@ public function testNodeCreationMatchesSnapshot3(): void /** @test */ public function testNodeCreationWithDifferentPropertyTypes(): void { - $this->createNodeInto( - $targetNode = $this->homePageNode->getNode('main'), - $toBeCreatedNodeTypeName = NodeTypeName::fromString('Flowpack.NodeTemplates:Content.DifferentPropertyTypes'), + $this->contentRepository->handle( + new CreateNodeAggregateWithNode( + $this->homePageNode->subgraphIdentity->contentStreamId, + $someNodeId = NodeAggregateId::fromString('7f7bac1c-9400-4db5-bbaa-2b8251d127c5'), + NodeTypeName::fromString('unstructured'), + $this->homePageNode->originDimensionSpacePoint, + $this->homePageNode->nodeAggregateId + ) + )->block(); + + $createdNode = $this->createNodeInto( + $this->homePageMainContentCollectionNode, + NodeTypeName::fromString('Flowpack.NodeTemplates:Content.DifferentPropertyTypes'), [ - 'someNode' => $this->homePageNode->createNode('some-node', null, '7f7bac1c-9400-4db5-bbaa-2b8251d127c5') + 'someNode' => $this->subgraph->findNodeById($someNodeId) ] ); $this->assertLastCreatedTemplateMatchesSnapshot('DifferentPropertyTypes'); - $createdNode = $targetNode->getChildNodes($toBeCreatedNodeTypeName->getValue())[0]; - self::assertSame([], $this->getMessagesOfFeedbackCollection()); $this->assertNodeDumpAndTemplateDumpMatchSnapshot('DifferentPropertyTypes', $createdNode); } diff --git a/Tests/Unit/Domain/NodeCreation/ReferenceTypeTest.php b/Tests/Unit/Domain/NodeCreation/ReferenceTypeTest.php index 13173b4..592eef2 100644 --- a/Tests/Unit/Domain/NodeCreation/ReferenceTypeTest.php +++ b/Tests/Unit/Domain/NodeCreation/ReferenceTypeTest.php @@ -41,10 +41,15 @@ public function testIsMatchedBy(string $declarationType, array $validValues, arr $nodeTypeMock, ); foreach ($validValues as $validValue) { - Assert::assertTrue($subject->isMatchedBy($validValue, $subgraphMock), sprintf('Value %s should match.', get_debug_type($validValue))); + Assert::assertTrue((bool)$subject->resolveNodeAggregateIds($validValue, $subgraphMock), sprintf('Value %s should match.', get_debug_type($validValue))); } foreach ($invalidValues as $invalidValue) { - Assert::assertFalse($subject->isMatchedBy($invalidValue, $subgraphMock), sprintf('Value %s should not match.', get_debug_type($validValue))); + try { + $subject->resolveNodeAggregateIds($invalidValue, $subgraphMock); + self::fail(sprintf('Value %s should not match.', get_debug_type($validValue))); + } catch (\RuntimeException) { + Assert::assertTrue(true); + } } }