diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeModification/Dto/SerializedPropertyValue.php b/Neos.ContentRepository.Core/Classes/Feature/NodeModification/Dto/SerializedPropertyValue.php index 57ba6eaba4a..7e3aa1af39e 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeModification/Dto/SerializedPropertyValue.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeModification/Dto/SerializedPropertyValue.php @@ -33,7 +33,7 @@ public function __construct( } /** - * @param array $valueAndType + * @param array{type:string,value:mixed} $valueAndType */ public static function fromArray(array $valueAndType): self { diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeModification/Dto/SerializedPropertyValues.php b/Neos.ContentRepository.Core/Classes/Feature/NodeModification/Dto/SerializedPropertyValues.php index 9b1f162e6fe..936e941d925 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeModification/Dto/SerializedPropertyValues.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeModification/Dto/SerializedPropertyValues.php @@ -43,7 +43,7 @@ public static function createEmpty(): self } /** - * @param array $propertyValues + * @param array $propertyValues */ public static function fromArray(array $propertyValues): self { @@ -129,6 +129,9 @@ public function splitByScope(NodeType $nodeType): array ); } + /** + * @phpstan-assert-if-true !null $this->getProperty() + */ public function propertyExists(string $propertyName): bool { return isset($this->values[$propertyName]); diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php index d29f4191ec9..446ecc996de 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php @@ -29,21 +29,23 @@ * * The node does not have structure information, i.e. no infos * about its children. To f.e. fetch children, you need to fetch - * the subgraph via $node->subgraphIdentity and then - * call findChildNodes() on the subgraph. + * the subgraph {@see ContentGraphInterface::getSubgraph()} via + * $subgraphIdentity {@see Node::$subgraphIdentity}. and then + * call findChildNodes() {@see ContentSubgraphInterface::findChildNodes()} + * on the subgraph. * * @api Note: The constructor is not part of the public API */ final readonly class Node { /** - * @param ContentSubgraphIdentity $subgraphIdentity This is part of the node's "Read Model" identity which is defined by: {@see self::subgraphIdentity} and {@see self::nodeAggregateId} + * @param ContentSubgraphIdentity $subgraphIdentity This is part of the node's "Read Model" identity which is defined by: {@see self::subgraphIdentity} and {@see self::nodeAggregateId}. With this information, you can fetch a Subgraph using {@see ContentGraphInterface::getSubgraph()}. * @param NodeAggregateId $nodeAggregateId NodeAggregateId (identifier) of this node. This is part of the node's "Read Model" identity which is defined by: {@see self::subgraphIdentity} and {@see self::nodeAggregateId} * @param OriginDimensionSpacePoint $originDimensionSpacePoint The DimensionSpacePoint the node originates in. Usually needed to address a Node in a NodeAggregate in order to update it. * @param NodeAggregateClassification $classification The classification (regular, root, tethered) of this node * @param NodeTypeName $nodeTypeName The node's node type name; always set, even if unknown to the NodeTypeManager * @param NodeType|null $nodeType The node's node type, null if unknown to the NodeTypeManager - @deprecated Don't rely on this too much, as the capabilities of the NodeType here will probably change a lot; Ask the {@see NodeTypeManager} instead - * @param PropertyCollection $properties All properties of this node. References are NOT part of this API; To access references, {@see ContentSubgraphInterface::findReferences()} can be used; To read the serialized properties, call properties->serialized(). + * @param PropertyCollection $properties All properties of this node. References are NOT part of this API; To access references, {@see ContentSubgraphInterface::findReferences()} can be used; To read the serialized properties use {@see PropertyCollection::serialized()}. * @param NodeName|null $nodeName The optional name of this node {@see ContentSubgraphInterface::findChildNodeConnectedThroughEdgeName()} * @param Timestamps $timestamps Creation and modification timestamps of this node */ @@ -69,10 +71,7 @@ public static function create(ContentSubgraphIdentity $subgraphIdentity, NodeAgg } /** - * Returns the specified property. - * - * If the node has a content object attached, the property will be fetched - * there if it is gettable. + * Returns the specified property, or null if it does not exist (or was set to null -> unset) * * @param string $propertyName Name of the property * @return mixed value of the property @@ -80,14 +79,15 @@ public static function create(ContentSubgraphIdentity $subgraphIdentity, NodeAgg */ public function getProperty(string $propertyName): mixed { - return $this->properties[$propertyName]; + return $this->properties->offsetGet($propertyName); } /** - * If this node has a property with the given name. Does NOT check the NodeType; but checks - * for a non-NULL property value. + * If this node has a property with the given name. It does not check if the property exists in the current NodeType schema. + * + * That means that {@see self::getProperty()} will not be null, except for the rare case the property deserializing returns null. * - * @param string $propertyName + * @param string $propertyName Name of the property * @return boolean * @api */ diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/PropertyCollection.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/PropertyCollection.php index f673a3cfc5d..5c736d72a24 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/PropertyCollection.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/PropertyCollection.php @@ -24,7 +24,7 @@ * @implements \IteratorAggregate * @api This object should not be instantiated by 3rd parties, but it is part of the {@see Node} read model */ -final class PropertyCollection implements \ArrayAccess, \IteratorAggregate +final class PropertyCollection implements \ArrayAccess, \IteratorAggregate, \Countable { /** * Properties from Nodes @@ -56,14 +56,16 @@ public function offsetExists($offset): bool public function offsetGet($offset): mixed { - if (!isset($this->deserializedPropertyValuesRuntimeCache[$offset])) { - $serializedProperty = $this->serializedPropertyValues->getProperty($offset); - $this->deserializedPropertyValuesRuntimeCache[$offset] = $serializedProperty === null - ? null - : $this->propertyConverter->deserializePropertyValue($serializedProperty); + if (array_key_exists($offset, $this->deserializedPropertyValuesRuntimeCache)) { + return $this->deserializedPropertyValuesRuntimeCache[$offset]; } - return $this->deserializedPropertyValuesRuntimeCache[$offset]; + $serializedProperty = $this->serializedPropertyValues->getProperty($offset); + if ($serializedProperty === null) { + return null; + } + return $this->deserializedPropertyValuesRuntimeCache[$offset] = + $this->propertyConverter->deserializePropertyValue($serializedProperty); } public function offsetSet($offset, $value): never @@ -90,4 +92,9 @@ public function serialized(): SerializedPropertyValues { return $this->serializedPropertyValues; } + + public function count(): int + { + return count($this->serializedPropertyValues); + } } diff --git a/Neos.ContentRepository.NodeMigration/src/Transformation/StripTagsOnPropertyTransformationFactory.php b/Neos.ContentRepository.NodeMigration/src/Transformation/StripTagsOnPropertyTransformationFactory.php index 1484827576b..405fd46196f 100644 --- a/Neos.ContentRepository.NodeMigration/src/Transformation/StripTagsOnPropertyTransformationFactory.php +++ b/Neos.ContentRepository.NodeMigration/src/Transformation/StripTagsOnPropertyTransformationFactory.php @@ -53,10 +53,9 @@ public function execute( DimensionSpacePointSet $coveredDimensionSpacePoints, ContentStreamId $contentStreamForWriting ): ?CommandResult { - if ($node->hasProperty($this->propertyName)) { - $properties = $node->properties; - /** @var SerializedPropertyValue $serializedPropertyValue safe since Node::hasProperty */ - $serializedPropertyValue = $properties->serialized()->getProperty($this->propertyName); + $properties = $node->properties->serialized(); + if ($properties->propertyExists($this->propertyName)) { + $serializedPropertyValue = $properties->getProperty($this->propertyName); $propertyValue = $serializedPropertyValue->value; if (!is_string($propertyValue)) { throw new \Exception(