From ebd5c895c03d648540abde5f498d82751c3172be Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Thu, 16 Nov 2023 17:01:22 +0100 Subject: [PATCH] FEATURE: Add `constraints.maxItems` for references --- ...SetNodeReferences_ConstraintChecks.feature | 12 +++++++++- .../Feature/Common/ConstraintChecks.php | 23 +++++++++++++++++-- .../Dto/NodeReferencesToWrite.php | 7 +++++- .../NodeReferencing/NodeReferencing.php | 6 +++++ .../Classes/NodeType/NodeType.php | 5 ++-- .../Exception/ReferenceCannotBeSet.php | 16 +++++++++++-- 6 files changed, 61 insertions(+), 8 deletions(-) diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/01-SetNodeReferences_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/01-SetNodeReferences_ConstraintChecks.feature index dded08fe2ec..d88b78a9989 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/01-SetNodeReferences_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/01-SetNodeReferences_ConstraintChecks.feature @@ -14,7 +14,9 @@ Feature: Constraint checks on SetNodeReferences nonReferenceProperty: type: string references: - reference: {} + reference: + constraints: + maxItems: 1 references: {} constrainedReference: constraints: @@ -112,6 +114,14 @@ Feature: Constraint checks on SetNodeReferences | references | [{"target":"lady-eleonode-rootford"}] | Then the last command should have thrown an exception of type "NodeAggregateIsRoot" + Scenario: Try to set references exceeding the maxItems count + When the command SetNodeReferences is executed with payload and exceptions are caught: + | Key | Value | + | sourceNodeAggregateId | "source-nodandaise" | + | referenceName | "reference" | + | references | [{"target":"anthony-destinode"}, {"target":"berta-destinode"}] | + Then the last command should have thrown an exception of type "ReferenceCannotBeSet" with code 1700150156 + Scenario: Try to reference a node aggregate of a type not matching the constraints When the command SetNodeReferences is executed with payload and exceptions are caught: | Key | Value | diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php index ac3257b30df..9033081cd9a 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php @@ -18,6 +18,7 @@ use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\Exception\DimensionSpacePointNotFound; +use Neos\ContentRepository\Core\Feature\NodeReferencing\Dto\SerializedNodeReferences; use Neos\ContentRepository\Core\NodeType\ConstraintCheck; use Neos\ContentRepository\Core\SharedModel\Exception\PropertyCannotBeSet; use Neos\ContentRepository\Core\SharedModel\Exception\RootNodeAggregateDoesNotExist; @@ -223,7 +224,7 @@ protected function requireNodeTypeToAllowNodesOfTypeInReference( $constraints = $referenceDeclaration['constraints']['nodeTypes'] ?? []; if (!ConstraintCheck::create($constraints)->isNodeTypeAllowed($nodeType)) { - throw ReferenceCannotBeSet::becauseTheConstraintsAreNotMatched( + throw ReferenceCannotBeSet::becauseTheNodeTypeConstraintsAreNotMatched( $referenceName, $nodeTypeName, $nodeTypeNameInQuestion @@ -231,6 +232,24 @@ protected function requireNodeTypeToAllowNodesOfTypeInReference( } } + protected function requireNodeTypeToAllowCountOfReferencesInReference(SerializedNodeReferences $nodeReferences, ReferenceName $referenceName, NodeTypeName $nodeTypeName): void + { + $nodeType = $this->nodeTypeManager->getNodeType($nodeTypeName->value); + + $maxItems = $nodeType->getReferences()[$referenceName->value]['constraints']['maxItems'] ?? null; + if ($maxItems === null) { + return; + } + + if ($maxItems < count($nodeReferences)) { + throw ReferenceCannotBeSet::becauseTheItemsCountConstraintsAreNotMatched( + $referenceName, + $nodeTypeName, + count($nodeReferences) + ); + } + } + /** * NodeType and NodeName must belong together to the same node, which is the to-be-checked one. * @@ -641,7 +660,7 @@ protected function validateReferenceProperties( ?? null; if (is_null($referencePropertyConfig)) { - throw ReferenceCannotBeSet::becauseTheItDoesNotDeclareAProperty( + throw ReferenceCannotBeSet::becauseTheReferenceDoesNotDeclareTheProperty( $referenceName, $nodeTypeName, PropertyName::fromString($propertyName) diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/Dto/NodeReferencesToWrite.php b/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/Dto/NodeReferencesToWrite.php index fd6f36af88b..8ba90b34b43 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/Dto/NodeReferencesToWrite.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/Dto/NodeReferencesToWrite.php @@ -26,7 +26,7 @@ * @implements \IteratorAggregate * @api used as part of commands */ -final readonly class NodeReferencesToWrite implements \IteratorAggregate, \JsonSerializable +final readonly class NodeReferencesToWrite implements \IteratorAggregate, \Countable, \JsonSerializable { /** * @var array @@ -90,4 +90,9 @@ public function jsonSerialize(): array { return $this->references; } + + public function count(): int + { + return count($this->references); + } } diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/NodeReferencing.php b/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/NodeReferencing.php index 2b3268398fa..3ca9f56b76b 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/NodeReferencing.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/NodeReferencing.php @@ -116,6 +116,12 @@ private function handleSetSerializedNodeReferences( ); $this->requireNodeTypeToDeclareReference($sourceNodeAggregate->nodeTypeName, $command->referenceName); + $this->requireNodeTypeToAllowCountOfReferencesInReference( + $command->references, + $command->referenceName, + $sourceNodeAggregate->nodeTypeName + ); + foreach ($command->references as $reference) { assert($reference instanceof SerializedNodeReference); $destinationNodeAggregate = $this->requireProjectedNodeAggregate( diff --git a/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php b/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php index 46a0b267a1f..ce152f0d25a 100644 --- a/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php +++ b/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php @@ -526,14 +526,15 @@ protected function setFullConfiguration(array $fullConfiguration): void switch ($propertyType) { case 'reference': unset($propertyConfiguration['type']); - $propertyConfiguration['constraints']['valueReference']['maxValue'] = 1; + $propertyConfiguration['constraints']['maxItems'] = 1; $referencesConfiguration[$propertyName] = $propertyConfiguration; unset($fullConfiguration['properties'][$propertyName]); + break; case 'references': unset($propertyConfiguration['type']); $referencesConfiguration[$propertyName] = $propertyConfiguration; unset($fullConfiguration['properties'][$propertyName]); - default: + break; } } $fullConfiguration['references'] = $referencesConfiguration; diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Exception/ReferenceCannotBeSet.php b/Neos.ContentRepository.Core/Classes/SharedModel/Exception/ReferenceCannotBeSet.php index 47929baa74e..cf3e6a638a4 100644 --- a/Neos.ContentRepository.Core/Classes/SharedModel/Exception/ReferenceCannotBeSet.php +++ b/Neos.ContentRepository.Core/Classes/SharedModel/Exception/ReferenceCannotBeSet.php @@ -36,7 +36,7 @@ public static function becauseTheNodeTypeDoesNotDeclareIt( ); } - public static function becauseTheConstraintsAreNotMatched( + public static function becauseTheNodeTypeConstraintsAreNotMatched( ReferenceName $referenceName, NodeTypeName $nodeTypeName, NodeTypeName $nameOfAttemptedType @@ -48,7 +48,19 @@ public static function becauseTheConstraintsAreNotMatched( ); } - public static function becauseTheItDoesNotDeclareAProperty( + public static function becauseTheItemsCountConstraintsAreNotMatched( + ReferenceName $referenceName, + NodeTypeName $nodeTypeName, + int $countOfAttemptedReferencesToWrite + ): self { + return new self( + 'Reference "' . $referenceName->value . '" cannot be set for node type "' + . $nodeTypeName->value . '" because the constraints do not allow to set ' . $countOfAttemptedReferencesToWrite . ' references', + 1700150156 + ); + } + + public static function becauseTheReferenceDoesNotDeclareTheProperty( ReferenceName $referenceName, NodeTypeName $nodeTypeName, PropertyName $propertyName