Commit 34c57726 authored by Nikita Hovratov's avatar Nikita Hovratov Committed by Anja Leichsenring
Browse files

[FEATURE] Enable recursive transformation of properties in JsonView

The new property '_recursive' allows to define recursive properties
of objects. These properties are transformed automatically, without
the need to define every entry level manually.

Objects can contain a list of the same type like themselves.
Examples could be folders containing folders or comments having
comments as replies.

Resolves: #90347
Releases: master
Change-Id: I29b34cf3666c5ad142e51371240501cb85941b5a
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/63181

Tested-by: Andreas Fernandez's avatarAndreas Fernandez <a.fernandez@scripting-base.de>
Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Anja Leichsenring's avatarAnja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Andreas Fernandez's avatarAndreas Fernandez <a.fernandez@scripting-base.de>
Reviewed-by: Anja Leichsenring's avatarAnja Leichsenring <aleichsenring@ab-softlab.de>
parent 060b8fa9
.. 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
......@@ -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) {
......
......@@ -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()
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment