diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-90347-EnableRecursiveTransformationOfPropertiesInJsonView.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-90347-EnableRecursiveTransformationOfPropertiesInJsonView.rst new file mode 100644 index 0000000000000000000000000000000000000000..b8d0a12c4d8abef115d6cfa44a5317d163433e18 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/master/Feature-90347-EnableRecursiveTransformationOfPropertiesInJsonView.rst @@ -0,0 +1,51 @@ +.. include:: ../../Includes.txt + +=========================================================================== +Feature: #90347 - Enable recursive transformation of properties in JsonView +=========================================================================== + +See :issue:`90347` + +Description +=========== + +The Extbase :php:`JsonView` is now able to resolve recursive properties of +objects, e.g. directories containing directories or comments containing comments +as replies. + +Examples: + +1. This is for 1:1 relations, where a comment has at most 1 comment. + +.. code-block:: php + + $configuration = [ + 'comment' => [ + '_recursive' => ['comment'] + ] + ]; + + +2. This is for the more common 1:n relation in which you have lists of sub objects. + +.. code-block:: php + + $configuration = [ + 'directories' => [ + '_descendAll' => [ + '_recursive' => ['directories'] + ], + ] + ]; + +You can put all the other configuration like `_only` or `_exclude` at the same +level as `_recursive` and the view will apply this for all levels. + +Impact +====== + +Developers can now use the `_recursive` property in the :php:`JsonView` +configuration in order to resolve recursive properties instead of defining each +level manually. + +.. index:: ext:extbase diff --git a/typo3/sysext/extbase/Classes/Mvc/View/JsonView.php b/typo3/sysext/extbase/Classes/Mvc/View/JsonView.php index 22d93a04e689ca767467e05135a18d8e501095e6..00d705d04e24767b3928fd5de19a065e35aaec97 100644 --- a/typo3/sysext/extbase/Classes/Mvc/View/JsonView.php +++ b/typo3/sysext/extbase/Classes/Mvc/View/JsonView.php @@ -48,6 +48,11 @@ class JsonView extends AbstractView */ protected $variablesToRender = ['value']; + /** + * @var string + */ + protected $currentVariable = ''; + /** * The rendering configuration for this JSON view which * determines which properties of each variable to render. @@ -67,7 +72,7 @@ class JsonView extends AbstractView * '_exclude' => ['secretTitle'], * '_descend' => [ * 'customer' => [ - * '_only' => [array(]'firstName', 'lastName'] + * '_only' => ['firstName', 'lastName'] * ] * ] * ], @@ -203,17 +208,20 @@ class JsonView extends AbstractView protected function renderArray() { if (count($this->variablesToRender) === 1) { + $firstLevel = false; $variableName = current($this->variablesToRender); + $this->currentVariable = $variableName; $valueToRender = $this->variables[$variableName] ?? null; $configuration = $this->configuration[$variableName] ?? []; } else { + $firstLevel = true; $valueToRender = []; foreach ($this->variablesToRender as $variableName) { $valueToRender[$variableName] = $this->variables[$variableName] ?? null; } $configuration = $this->configuration; } - return $this->transformValue($valueToRender, $configuration); + return $this->transformValue($valueToRender, $configuration, $firstLevel); } /** @@ -222,13 +230,17 @@ class JsonView extends AbstractView * * @param mixed $value The value to transform * @param array $configuration Configuration for transforming the value + * @param bool $firstLevel * @return mixed The transformed value */ - protected function transformValue($value, array $configuration) + protected function transformValue($value, array $configuration, $firstLevel = false) { if (is_array($value) || $value instanceof \ArrayAccess) { $array = []; foreach ($value as $key => $element) { + if ($firstLevel) { + $this->currentVariable = $key; + } if (isset($configuration['_descendAll']) && is_array($configuration['_descendAll'])) { $array[$key] = $this->transformValue($element, $configuration['_descendAll']); } else { @@ -279,6 +291,8 @@ class JsonView extends AbstractView $propertiesToRender[$propertyName] = $propertyValue; } elseif (isset($configuration['_descend']) && array_key_exists($propertyName, $configuration['_descend'])) { $propertiesToRender[$propertyName] = $this->transformValue($propertyValue, $configuration['_descend'][$propertyName]); + } elseif (isset($configuration['_recursive']) && in_array($propertyName, $configuration['_recursive'])) { + $propertiesToRender[$propertyName] = $this->transformValue($propertyValue, $this->configuration[$this->currentVariable]); } } if (isset($configuration['_exposeObjectIdentifier']) && $configuration['_exposeObjectIdentifier'] === true) { diff --git a/typo3/sysext/extbase/Tests/Unit/Mvc/View/JsonViewTest.php b/typo3/sysext/extbase/Tests/Unit/Mvc/View/JsonViewTest.php index 56fdf7bfa11cf9b966c8986accc74745faa2e2e4..bc78fc7ed3cde1e10ffaaef03ff03f1cbbe33985 100644 --- a/typo3/sysext/extbase/Tests/Unit/Mvc/View/JsonViewTest.php +++ b/typo3/sysext/extbase/Tests/Unit/Mvc/View/JsonViewTest.php @@ -150,12 +150,12 @@ class JsonViewTest extends UnitTestCase $dateTimeObject = new \DateTime('2011-02-03T03:15:23', new \DateTimeZone('UTC')); $configuration = []; $expected = '2011-02-03T03:15:23+00:00'; - $output[] = [$dateTimeObject, $configuration, $expected, 'DateTime object in UTC time zone could not be serialized.']; + $output[] = [$dateTimeObject, $configuration, $expected, 'DateTime object in UTC time zone should not be serialized.']; $dateTimeObject = new \DateTime('2013-08-15T15:25:30', new \DateTimeZone('America/Los_Angeles')); $configuration = []; $expected = '2013-08-15T15:25:30-07:00'; - $output[] = [$dateTimeObject, $configuration, $expected, 'DateTime object in America/Los_Angeles time zone could not be serialized.']; + $output[] = [$dateTimeObject, $configuration, $expected, 'DateTime object in America/Los_Angeles time zone should not be serialized.']; return $output; } @@ -176,6 +176,182 @@ class JsonViewTest extends UnitTestCase self::assertSame($expected, $actual, $description); } + /** + * data provider for testRecursive() + * @return array + */ + public function jsonViewTestDataRecursive(): array + { + $object = new class('foo') { + private $value1 = ''; + private $child; + public function __construct($value1) + { + $this->value1 = $value1; + } + public function getValue1() + { + return $this->value1; + } + public function setValue1(string $value1) + { + $this->value1 = $value1; + } + public function getChild() + { + return $this->child; + } + public function setChild($child) + { + $this->child = $child; + } + }; + + $child1 = clone $object; + $child1->setValue1('bar'); + $child2 = clone $object; + $child2->setValue1('baz'); + $child1->setChild($child2); + $object->setChild($child1); + + $configuration = [ + 'testData' => [ + '_recursive' => ['child'] + ] + ]; + + $expected = [ + 'child' => [ + 'child' => [ + 'child' => null, + 'value1' => 'baz' + ], + 'value1' => 'bar', + ], + 'value1' => 'foo', + ]; + + $output[] = [$object, $configuration, $expected, 'testData', 'Recursive rendering of defined property should be possible.']; + + $object = new class('foo') { + private $value1 = ''; + private $children = []; + private $secret = 'secret'; + public function __construct($value1) + { + $this->value1 = $value1; + } + public function getValue1() + { + return $this->value1; + } + public function setValue1(string $value1) + { + $this->value1 = $value1; + } + public function getChildren() + { + return $this->children; + } + public function addChild($child) + { + $this->children[] = $child; + } + public function getSecret() + { + return $this->secret; + } + }; + $child1 = clone $object; + $child1->setValue1('bar'); + $child1->addChild(clone $object); + $child1->addChild(clone $object); + + $child2 = clone $object; + $child2->setValue1('baz'); + $child2->addChild(clone $object); + $child2->addChild(clone $object); + + $object->addChild($child1); + $object->addChild($child2); + $children = [ + clone $object, + clone $object + ]; + + $configuration = [ + 'testData' => [ + '_descendAll' => [ + '_exclude' => ['secret'], + '_recursive' => ['children'] + ], + ] + ]; + + $expected = [ + [ + 'children' => [ + [ + 'children' => [ + ['children' => [], 'value1' => 'foo'], + ['children' => [], 'value1' => 'foo'] + ], + 'value1' => 'bar' + ], + [ + 'children' => [ + ['children' => [], 'value1' => 'foo'], + ['children' => [], 'value1' => 'foo'] + ], + 'value1' => 'baz' + ] + ], + 'value1' => 'foo' + ], + [ + 'children' => [ + [ + 'children' => [ + ['children' => [], 'value1' => 'foo'], + ['children' => [], 'value1' => 'foo'] + ], + 'value1' => 'bar' + ], + [ + 'children' => [ + ['children' => [], 'value1' => 'foo'], + ['children' => [], 'value1' => 'foo'] + ], + 'value1' => 'baz' + ] + ], + 'value1' => 'foo' + ] + ]; + $output[] = [$children, $configuration, $expected, 'testData', 'Recursive rendering of lists of defined property should be possible.']; + + return $output; + } + + /** + * @test + * @param object|array $object + * @param array $configuration + * @param array|string $expected + * @param string $variableToRender + * @param string $description + * @dataProvider jsonViewTestDataRecursive + */ + public function testRecursive($object, array $configuration, $expected, string $variableToRender, string $description): void + { + $jsonView = $this->getAccessibleMock(JsonView::class, ['dummy'], [], '', false); + $jsonView->_set('configuration', $configuration); + $jsonView->_set('variablesToRender', [$variableToRender]); + $jsonView->_call('assign', $variableToRender, $object); + $actual = $jsonView->_call('renderArray'); + + self::assertSame($expected, $actual, $description); + } /** * data provider for testTransformValueWithObjectIdentifierExposure()