Skip to content

Commit

Permalink
BUGFIX: Neos Ui JSON serializable property values
Browse files Browse the repository at this point in the history
This bugfix will make use of the \JsonSerializable interface instead directly when serializing properties for the neos ui.

With neos/flow-development-collection#2762 native support for vo's in `flow_json_array` was introduced.

That also allows value object support for node properties, as they can be stored directly the node properties flow_json_array.

Unfortunately the property serialisation for the NeosUi does NOT use the expected `\JsonSerializable::jsonSerialize` but the property mapper and the `ArrayFromObjectConverter` for object types to get an array that will be json encoded.

This works mostly fine but in some cases it fails:
- when your `fromArray` fields and property names values dont match
- when a "object" subtypes the object mapper is no convertable like the `GuzzleHttp\Psr7\Uri`:

```
Too few arguments to function GuzzleHttp\Psr7\Uri::isAbsolute(), 0 passed in /core/neos-manufacture-highest/Packages/Framework/Neos.Utility.ObjectHandling/Classes/ObjectAccess.php on line 151 and exactly 1 expected
```

Current workarounds are aop:
https://github.com/sitegeist/Sitegeist.InspectorGadget/blob/78f5f4a206287b1c4bedf5cb88582ed51cb4a311/Classes/Infrastructure/NodeInfo/NodeInfoPostProcessingAspect.php#L17
Or to use a dumb property mapper:
https://github.com/sitegeist/Sitegeist.Archaeopteryx/blob/9322b9cb8e4824bcaf7aaa247c23b1244a2f1167/Classes/LinkToArrayForNeosUiConverter.php#L12C16-L12C78
  • Loading branch information
mhsdesign committed Feb 16, 2024
1 parent 6b73fe1 commit eada1db
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 4 deletions.
4 changes: 4 additions & 0 deletions Neos.ContentRepository/Classes/Domain/Model/Node.php
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,10 @@ public function getProperty($propertyName, bool $returnNodesAsIdentifiers = fals
}

try {
/**
* In case the value is a value object it _will_ already be deserialized due to the feature in flow_json_array
* {@see \Neos\Flow\Persistence\Doctrine\DataTypes\JsonArrayType::deserializeValueObject}
*/
return $this->propertyMapper->convert($value, $expectedPropertyType);
} catch (\Neos\Flow\Property\Exception $exception) {
throw new NodeException(sprintf('Failed to convert property "%s" of node "%s" to the expected type of "%s": %s', $propertyName, $this->getIdentifier(), $expectedPropertyType, $exception->getMessage()), 1630675703, $exception);
Expand Down
21 changes: 17 additions & 4 deletions Neos.Neos/Classes/Service/Mapping/NodePropertyConverterService.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Neos\Flow\Property\PropertyMapper;
use Neos\Flow\Property\PropertyMappingConfiguration;
use Neos\Flow\Property\PropertyMappingConfigurationInterface;
use Neos\Flow\Property\TypeConverter\DenormalizingObjectConverter;
use Neos\Utility\ObjectAccess;
use Neos\Utility\TypeHandling;
use Neos\ContentRepository\Domain\Model\NodeInterface;
Expand Down Expand Up @@ -155,15 +156,27 @@ public function getPropertiesArray(NodeInterface $node)
}

/**
* Convert the given value to a simple type or an array of simple types.
* Convert the given value to a simple type or an array of simple types or to a \JsonSerializable.
*
* @param mixed $propertyValue
* @param string $dataType
* @return mixed
* @param mixed $propertyValue the deserialized node property value
* @param string $dataType the property type from the node type schema
* @return \JsonSerializable|int|float|string|bool|null|array<mixed>
* @throws PropertyException
*/
protected function convertValue($propertyValue, $dataType)
{
if ($propertyValue instanceof \JsonSerializable && DenormalizingObjectConverter::isDenormalizable($propertyValue::class)) {
/**
* Value object support, as they can be stored directly the node properties flow_json_array
*
* If the value is json-serializable and deserializeable via the {@see DenormalizingObjectConverter} (via e.g. fromArray)
* We return the json-serializable directly.
*
* {@see \Neos\Flow\Persistence\Doctrine\DataTypes\JsonArrayType::deserializeValueObject()}
*/
return $propertyValue;
}

$parsedType = TypeHandling::parseType($dataType);

// This hardcoded handling is to circumvent rewriting PropertyMappers that convert objects. Usually they expect the source to be an object already and break if not.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* source code.
*/

use GuzzleHttp\Psr7\Uri;
use Neos\ContentRepository\Domain\Model\Node;
use Neos\ContentRepository\Domain\Model\NodeType;
use Neos\Flow\Tests\FunctionalTestCase;
Expand Down Expand Up @@ -183,4 +184,67 @@ public function complexTypesWithGivenTypeConverterAreConvertedByTypeConverter()

self::assertEquals($expected, $actual);
}

/**
* @test
*/
public function jsonSerializedAbleTypesAreDirectlySerialized()
{
$voClassName = 'Value' . md5(uniqid(mt_rand(), true));
eval('class ' . $voClassName . ' implements \JsonSerializable' . <<<'PHP'
{
public function __construct(
public \Psr\Http\Message\UriInterface $uri
) {
}
public static function fromArray(array $array): self
{
return new self(
new \GuzzleHttp\Psr7\Uri($array['uri'])
);
}
public function jsonSerialize(): array
{
return [
'uri' => $this->uri->__toString()
];
}
}
PHP);

$propertyValue = new $voClassName(new Uri('localhost://foo.html'));
$expected = '{"uri":"localhost:\\/\\/foo.html"}';

$nodeType = $this
->getMockBuilder(NodeType::class)
->setMethods(['getPropertyType'])
->disableOriginalConstructor()
->getMock();
$nodeType
->expects(self::any())
->method('getPropertyType')
->willReturn(ImageInterface::class);

$node = $this
->getMockBuilder(Node::class)
->setMethods(['getProperty', 'getNodeType'])
->disableOriginalConstructor()
->getMock();
$node
->expects(self::any())
->method('getProperty')
->willReturn($propertyValue);
$node
->expects(self::any())
->method('getNodeType')
->willReturn($nodeType);

$nodePropertyConverterService = new NodePropertyConverterService();

$actual = $nodePropertyConverterService->getProperty($node, 'dontcare');

self::assertEquals($expected, json_encode($actual));
}
}

0 comments on commit eada1db

Please sign in to comment.