From 0624ab852cfeae17c4254c8fb2ab4997d306ff84 Mon Sep 17 00:00:00 2001 From: Jan Kiesewetter <jan@t3easy.de> Date: Sun, 16 Feb 2014 15:38:12 +0100 Subject: [PATCH] [TASK] Backport Flow JsonView Change-Id: Ia750e9997bb69b00652a6cc30dd3442574c0b97b Resolves: #56007 Releases: 6.2 Reviewed-on: https://review.typo3.org/27642 Reviewed-by: Stefan Neufeind Tested-by: Stefan Neufeind --- .../extbase/Classes/Mvc/View/JsonView.php | 313 +++++++++++++ .../Tests/Unit/Mvc/View/JsonViewTest.php | 434 ++++++++++++++++++ 2 files changed, 747 insertions(+) create mode 100644 typo3/sysext/extbase/Classes/Mvc/View/JsonView.php create mode 100644 typo3/sysext/extbase/Tests/Unit/Mvc/View/JsonViewTest.php diff --git a/typo3/sysext/extbase/Classes/Mvc/View/JsonView.php b/typo3/sysext/extbase/Classes/Mvc/View/JsonView.php new file mode 100644 index 000000000000..afc580e66b60 --- /dev/null +++ b/typo3/sysext/extbase/Classes/Mvc/View/JsonView.php @@ -0,0 +1,313 @@ +<?php +namespace TYPO3\CMS\Extbase\Mvc\View; + +/*************************************************************** + * Copyright notice + * + * (c) 2010-2014 Extbase Team (http://forge.typo3.org/projects/typo3v4-mvc) + * Extbase is a backport of TYPO3 Flow. All credits go to the TYPO3 Flow team. + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * A copy is found in the text file GPL.txt and important notices to the license + * from the author is found in LICENSE.txt distributed with these scripts. + * + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ +/** + * A JSON view + * + * @api + */ +class JsonView extends \TYPO3\CMS\Extbase\Mvc\View\AbstractView { + + /** + * Definition for the class name exposure configuration, + * that is, if the class name of an object should also be + * part of the output JSON, if configured. + * + * Setting this value, the object's class name is fully + * put out, including the namespace. + */ + const EXPOSE_CLASSNAME_FULLY_QUALIFIED = 1; + + /** + * Puts out only the actual class name without namespace. + * See EXPOSE_CLASSNAME_FULL for the meaning of the constant at all. + */ + const EXPOSE_CLASSNAME_UNQUALIFIED = 2; + + /** + * @var \TYPO3\CMS\Extbase\Reflection\ReflectionService + * @inject + */ + protected $reflectionService; + + /** + * @var \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext + */ + protected $controllerContext; + + /** + * Only variables whose name is contained in this array will be rendered + * + * @var array + */ + protected $variablesToRender = array('value'); + + /** + * The rendering configuration for this JSON view which + * determines which properties of each variable to render. + * + * The configuration array must have the following structure: + * + * Example 1: + * + * array( + * 'variable1' => array( + * '_only' => array('property1', 'property2', ...) + * ), + * 'variable2' => array( + * '_exclude' => array('property3', 'property4, ...) + * ), + * 'variable3' => array( + * '_exclude' => array('secretTitle'), + * '_descend' => array( + * 'customer' => array( + * '_only' => array('firstName', 'lastName') + * ) + * ) + * ), + * 'somearrayvalue' => array( + * '_descendAll' => array( + * '_only' => array('property1') + * ) + * ) + * ) + * + * Of variable1 only property1 and property2 will be included. + * Of variable2 all properties except property3 and property4 + * are used. + * Of variable3 all properties except secretTitle are included. + * + * If a property value is an array or object, it is not included + * by default. If, however, such a property is listed in a "_descend" + * section, the renderer will descend into this sub structure and + * include all its properties (of the next level). + * + * The configuration of each property in "_descend" has the same syntax + * like at the top level. Therefore - theoretically - infinitely nested + * structures can be configured. + * + * To export indexed arrays the "_descendAll" section can be used to + * include all array keys for the output. The configuration inside a + * "_descendAll" will be applied to each array element. + * + * + * Example 2: exposing object identifier + * + * array( + * 'variableFoo' => array( + * '_exclude' => array('secretTitle'), + * '_descend' => array( + * 'customer' => array( // consider 'customer' being a persisted entity + * '_only' => array('firstName'), + * '_exposeObjectIdentifier' => TRUE, + * '_exposedObjectIdentifierKey' => 'guid' + * ) + * ) + * ) + * ) + * + * Note for entity objects you are able to expose the object's identifier + * also, just add an "_exposeObjectIdentifier" directive set to TRUE and + * an additional property '__identity' will appear keeping the persistence + * identifier. Renaming that property name instead of '__identity' is also + * possible with the directive "_exposedObjectIdentifierKey". + * Example 2 above would output (summarized): + * {"customer":{"firstName":"John","guid":"892693e4-b570-46fe-af71-1ad32918fb64"}} + * + * + * Example 3: exposing object's class name + * + * array( + * 'variableFoo' => array( + * '_exclude' => array('secretTitle'), + * '_descend' => array( + * 'customer' => array( // consider 'customer' being an object + * '_only' => array('firstName'), + * '_exposeClassName' => TYPO3\Flow\Mvc\View\JsonView::EXPOSE_CLASSNAME_FULLY_QUALIFIED + * ) + * ) + * ) + * ) + * + * The ``_exposeClassName`` is similar to the objectIdentifier one, but the class name is added to the + * JSON object output, for example (summarized): + * {"customer":{"firstName":"John","__class":"Acme\Foo\Domain\Model\Customer"}} + * + * The other option is EXPOSE_CLASSNAME_UNQUALIFIED which only will give the last part of the class + * without the namespace, for example (summarized): + * {"customer":{"firstName":"John","__class":"Customer"}} + * This might be of interest to not provide information about the package or domain structure behind. + * + * @var array + */ + protected $configuration = array(); + + /** + * @var \TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface + * @inject + */ + protected $persistenceManager; + + /** + * Specifies which variables this JsonView should render + * By default only the variable 'value' will be rendered + * + * @param array $variablesToRender + * @return void + * @api + */ + public function setVariablesToRender(array $variablesToRender) { + $this->variablesToRender = $variablesToRender; + } + + /** + * @param array $configuration The rendering configuration for this JSON view + * @return void + */ + public function setConfiguration(array $configuration) { + $this->configuration = $configuration; + } + + /** + * Transforms the value view variable to a serializable + * array represantion using a YAML view configuration and JSON encodes + * the result. + * + * @return string The JSON encoded variables + * @api + */ + public function render() { + $this->controllerContext->getResponse()->setHeader('Content-Type', 'application/json'); + $propertiesToRender = $this->renderArray(); + return json_encode($propertiesToRender); + } + + /** + * Loads the configuration and transforms the value to a serializable + * array. + * + * @return array An array containing the values, ready to be JSON encoded + * @api + */ + protected function renderArray() { + if (count($this->variablesToRender) === 1) { + $variableName = current($this->variablesToRender); + $valueToRender = isset($this->variables[$variableName]) ? $this->variables[$variableName] : NULL; + $configuration = isset($this->configuration[$variableName]) ? $this->configuration[$variableName] : array(); + } else { + $valueToRender = array(); + foreach ($this->variablesToRender as $variableName) { + $valueToRender[$variableName] = isset($this->variables[$variableName]) ? $this->variables[$variableName] : NULL; + } + $configuration = $this->configuration; + } + return $this->transformValue($valueToRender, $configuration); + } + + /** + * Transforms a value depending on type recursively using the + * supplied configuration. + * + * @param mixed $value The value to transform + * @param array $configuration Configuration for transforming the value + * @return array The transformed value + */ + protected function transformValue($value, array $configuration) { + if (is_array($value) || $value instanceof \ArrayAccess) { + $array = array(); + foreach ($value as $key => $element) { + if (isset($configuration['_descendAll']) && is_array($configuration['_descendAll'])) { + $array[$key] = $this->transformValue($element, $configuration['_descendAll']); + } else { + if (isset($configuration['_only']) && is_array($configuration['_only']) && !in_array($key, $configuration['_only'])) { + continue; + } + if (isset($configuration['_exclude']) && is_array($configuration['_exclude']) && in_array($key, $configuration['_exclude'])) { + continue; + } + $array[$key] = $this->transformValue($element, isset($configuration[$key]) ? $configuration[$key] : array()); + } + } + return $array; + } elseif (is_object($value)) { + return $this->transformObject($value, $configuration); + } else { + return $value; + } + } + + /** + * Traverses the given object structure in order to transform it into an + * array structure. + * + * @param object $object Object to traverse + * @param array $configuration Configuration for transforming the given object or NULL + * @return array Object structure as an array + */ + protected function transformObject($object, array $configuration) { + if ($object instanceof \DateTime) { + return $object->format(\DateTime::ISO8601); + } else { + $propertyNames = \TYPO3\CMS\Extbase\Reflection\ObjectAccess::getGettablePropertyNames($object); + + $propertiesToRender = array(); + foreach ($propertyNames as $propertyName) { + if (isset($configuration['_only']) && is_array($configuration['_only']) && !in_array($propertyName, $configuration['_only'])) { + continue; + } + if (isset($configuration['_exclude']) && is_array($configuration['_exclude']) && in_array($propertyName, $configuration['_exclude'])) { + continue; + } + + $propertyValue = \TYPO3\CMS\Extbase\Reflection\ObjectAccess::getProperty($object, $propertyName); + + if (!is_array($propertyValue) && !is_object($propertyValue)) { + $propertiesToRender[$propertyName] = $propertyValue; + } elseif (isset($configuration['_descend']) && array_key_exists($propertyName, $configuration['_descend'])) { + $propertiesToRender[$propertyName] = $this->transformValue($propertyValue, $configuration['_descend'][$propertyName]); + } + } + if (isset($configuration['_exposeObjectIdentifier']) && $configuration['_exposeObjectIdentifier'] === TRUE) { + if (isset($configuration['_exposedObjectIdentifierKey']) && strlen($configuration['_exposedObjectIdentifierKey']) > 0) { + $identityKey = $configuration['_exposedObjectIdentifierKey']; + } else { + $identityKey = '__identity'; + } + $propertiesToRender[$identityKey] = $this->persistenceManager->getIdentifierByObject($object); + } + if (isset($configuration['_exposeClassName']) && ($configuration['_exposeClassName'] === self::EXPOSE_CLASSNAME_FULLY_QUALIFIED || $configuration['_exposeClassName'] === self::EXPOSE_CLASSNAME_UNQUALIFIED)) { + $className = get_class($object); + $classNameParts = explode('\\', $className); + $propertiesToRender['__class'] = ($configuration['_exposeClassName'] === self::EXPOSE_CLASSNAME_FULLY_QUALIFIED ? $className : array_pop($classNameParts)); + } + + return $propertiesToRender; + } + } +} diff --git a/typo3/sysext/extbase/Tests/Unit/Mvc/View/JsonViewTest.php b/typo3/sysext/extbase/Tests/Unit/Mvc/View/JsonViewTest.php new file mode 100644 index 000000000000..5e490066f2ce --- /dev/null +++ b/typo3/sysext/extbase/Tests/Unit/Mvc/View/JsonViewTest.php @@ -0,0 +1,434 @@ +<?php +namespace TYPO3\CMS\Extbase\Tests\Unit\Mvc\View; + +/*************************************************************** + * Copyright notice + * + * (c) 2010-2014 Extbase Team (http://forge.typo3.org/projects/typo3v4-mvc) + * Extbase is a backport of TYPO3 Flow. All credits go to the TYPO3 Flow team. + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * A copy is found in the text file GPL.txt and important notices to the license + * from the author is found in LICENSE.txt distributed with these scripts. + * + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Extbase\Mvc\View\JsonView; + +/** + * Testcase for the JSON view + * + */ +class JsonViewTest extends \TYPO3\CMS\Core\Tests\UnitTestCase { + + /** + * @var \TYPO3\CMS\Extbase\Mvc\View\JsonView + */ + protected $view; + + /** + * @var \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext + */ + protected $controllerContext; + + /** + * @var \TYPO3\CMS\Extbase\Mvc\Web\Response + */ + protected $response; + + /** + * Sets up this test case + * @return void + */ + public function setUp() { + $this->view = $this->getMock('TYPO3\CMS\Extbase\Mvc\View\JsonView', array('loadConfigurationFromYamlFile')); + $this->controllerContext = $this->getMock('TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext', array(), array(), '', FALSE); + $this->response = $this->getMock('TYPO3\CMS\Extbase\Mvc\Web\Response', array()); + $this->controllerContext->expects($this->any())->method('getResponse')->will($this->returnValue($this->response)); + $this->view->setControllerContext($this->controllerContext); + } + + /** + * data provider for testTransformValue() + * @return array + */ + public function jsonViewTestData() { + $output = array(); + + $object = new \stdClass(); + $object->value1 = 'foo'; + $object->value2 = 1; + $configuration = array(); + $expected = array('value1' => 'foo', 'value2' => 1); + $output[] = array($object, $configuration, $expected, 'all direct child properties should be serialized'); + + $configuration = array('_only' => array('value1')); + $expected = array('value1' => 'foo'); + $output[] = array($object, $configuration, $expected, 'if "only" properties are specified, only these should be serialized'); + + $configuration = array('_exclude' => array('value1')); + $expected = array('value2' => 1); + $output[] = array($object, $configuration, $expected, 'if "exclude" properties are specified, they should not be serialized'); + + $object = new \stdClass(); + $object->value1 = new \stdClass(); + $object->value1->subvalue1 = 'Foo'; + $object->value2 = 1; + $configuration = array(); + $expected = array('value2' => 1); + $output[] = array($object, $configuration, $expected, 'by default, sub objects of objects should not be serialized.'); + + $object = new \stdClass(); + $object->value1 = array('subarray' => 'value'); + $object->value2 = 1; + $configuration = array(); + $expected = array('value2' => 1); + $output[] = array($object, $configuration, $expected, 'by default, sub arrays of objects should not be serialized.'); + + $object = array('foo' => 'bar', 1 => 'baz', 'deep' => array('test' => 'value')); + $configuration = array(); + $expected = array('foo' => 'bar', 1 => 'baz', 'deep' => array('test' => 'value')); + $output[] = array($object, $configuration, $expected, 'associative arrays should be serialized deeply'); + + $object = array('foo', 'bar'); + $configuration = array(); + $expected = array('foo', 'bar'); + $output[] = array($object, $configuration, $expected, 'numeric arrays should be serialized'); + + $nestedObject = new \stdClass(); + $nestedObject->value1 = 'foo'; + $object = array($nestedObject); + $configuration = array(); + $expected = array(array('value1' => 'foo')); + $output[] = array($object, $configuration, $expected, 'array of objects should be serialized'); + + $properties = array('foo' => 'bar', 'prohibited' => 'xxx'); + $nestedObject = $this->getMock('Test' . md5(uniqid(mt_rand(), TRUE)), array('getName', 'getPath', 'getProperties', 'getOther')); + $nestedObject->expects($this->any())->method('getName')->will($this->returnValue('name')); + $nestedObject->expects($this->any())->method('getPath')->will($this->returnValue('path')); + $nestedObject->expects($this->any())->method('getProperties')->will($this->returnValue($properties)); + $nestedObject->expects($this->never())->method('getOther'); + $object = $nestedObject; + $configuration = array( + '_only' => array('name', 'path', 'properties'), + '_descend' => array( + 'properties' => array( + '_exclude' => array('prohibited') + ) + ) + ); + $expected = array( + 'name' => 'name', + 'path' => 'path', + 'properties' => array('foo' => 'bar') + ); + $output[] = array($object, $configuration, $expected, 'descending into arrays should be possible'); + + $nestedObject = new \stdClass(); + $nestedObject->value1 = 'foo'; + $value = new \SplObjectStorage(); + $value->attach($nestedObject); + $configuration = array(); + $expected = array(array('value1' => 'foo')); + $output[] = array($value, $configuration, $expected, 'SplObjectStorage with objects should be serialized'); + + $dateTimeObject = new \DateTime('2011-02-03T03:15:23', new \DateTimeZone('UTC')); + $configuration = array(); + $expected = '2011-02-03T03:15:23+0000'; + $output[] = array($dateTimeObject, $configuration, $expected, 'DateTime object in UTC time zone could not be serialized.'); + + $dateTimeObject = new \DateTime('2013-08-15T15:25:30', new \DateTimeZone('America/Los_Angeles')); + $configuration = array(); + $expected = '2013-08-15T15:25:30-0700'; + $output[] = array($dateTimeObject, $configuration, $expected, 'DateTime object in America/Los_Angeles time zone could not be serialized.'); + return $output; + } + + /** + * @test + * @dataProvider jsonViewTestData + */ + public function testTransformValue($object, $configuration, $expected, $description) { + $jsonView = $this->getAccessibleMock('TYPO3\CMS\Extbase\Mvc\View\JsonView', array('dummy'), array(), '', FALSE); + + $actual = $jsonView->_call('transformValue', $object, $configuration); + + $this->assertEquals($expected, $actual, $description); + } + + /** + * data provider for testTransformValueWithObjectIdentifierExposure() + * @return array + */ + public function objectIdentifierExposureTestData() { + $output = array(); + + $dummyIdentifier = 'e4f40dfc-8c6e-4414-a5b1-6fd3c5cf7a53'; + + $object = new \stdClass(); + $object->value1 = new \stdClass(); + $configuration = array( + '_descend' => array( + 'value1' => array( + '_exposeObjectIdentifier' => TRUE + ) + ) + ); + + $expected = array('value1' => array('__identity' => $dummyIdentifier)); + $output[] = array($object, $configuration, $expected, $dummyIdentifier, 'boolean TRUE should result in __identity key'); + + $configuration['_descend']['value1']['_exposedObjectIdentifierKey'] = 'guid'; + $expected = array('value1' => array('guid' => $dummyIdentifier)); + $output[] = array($object, $configuration, $expected, $dummyIdentifier, 'string value should result in string-equal key'); + + return $output; + } + + /** + * @test + * @dataProvider objectIdentifierExposureTestData + */ + public function testTransformValueWithObjectIdentifierExposure($object, $configuration, $expected, $dummyIdentifier, $description) { + $persistenceManagerMock = $this->getMock('TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager', array('getIdentifierByObject')); + $jsonView = $this->getAccessibleMock('TYPO3\CMS\Extbase\Mvc\View\JsonView', array('dummy'), array(), '', FALSE); + $jsonView->_set('persistenceManager', $persistenceManagerMock); + + $persistenceManagerMock->expects($this->once())->method('getIdentifierByObject')->with($object->value1)->will($this->returnValue($dummyIdentifier)); + + $actual = $jsonView->_call('transformValue', $object, $configuration); + + $this->assertEquals($expected, $actual, $description); + } + + /** + * A data provider + */ + public function exposeClassNameSettingsAndResults() { + $className = 'DummyClass' . md5(uniqid(mt_rand(), TRUE)); + $namespace = 'TYPO3\CMS\Extbase\Tests\Unit\Mvc\View\\' . $className; + return array( + array( + JsonView::EXPOSE_CLASSNAME_FULLY_QUALIFIED, + $className, + $namespace, + array('value1' => array('__class' => $namespace . '\\' . $className)) + ), + array( + JsonView::EXPOSE_CLASSNAME_UNQUALIFIED, + $className, + $namespace, + array('value1' => array('__class' => $className)) + ), + array( + NULL, + $className, + $namespace, + array('value1' => array()) + ) + ); + } + + /** + * @test + * @dataProvider exposeClassNameSettingsAndResults + */ + public function viewExposesClassNameFullyIfConfiguredSo($exposeClassNameSetting, $className, $namespace, $expected) { + $fullyQualifiedClassName = $namespace . '\\' . $className; + if (class_exists($fullyQualifiedClassName) === FALSE) { + eval('namespace ' . $namespace . '; class ' . $className . ' {}'); + } + + $object = new \stdClass(); + $object->value1 = new $fullyQualifiedClassName(); + $configuration = array( + '_descend' => array( + 'value1' => array( + '_exposeClassName' => $exposeClassNameSetting + ) + ) + ); + $reflectionService = $this->getMock('TYPO3\CMS\Extbase\Reflection\ReflectionService'); + $reflectionService->expects($this->any())->method('getClassNameByObject')->will($this->returnCallback(function($object) { + return get_class($object); + })); + + $jsonView = $this->getAccessibleMock('TYPO3\CMS\Extbase\Mvc\View\JsonView', array('dummy'), array(), '', FALSE); + $this->inject($jsonView, 'reflectionService', $reflectionService); + $actual = $jsonView->_call('transformValue', $object, $configuration); + $this->assertEquals($expected, $actual); + } + + /** + * @test + */ + public function renderSetsContentTypeHeader() { + $this->response->expects($this->once())->method('setHeader')->with('Content-Type', 'application/json'); + + $this->view->render(); + } + + /** + * @test + */ + public function renderReturnsJsonRepresentationOfAssignedObject() { + $object = new \stdClass(); + $object->foo = 'Foo'; + $this->view->assign('value', $object); + + $expectedResult = '{"foo":"Foo"}'; + $actualResult = $this->view->render(); + $this->assertEquals($expectedResult, $actualResult); + } + + /** + * @test + */ + public function renderReturnsJsonRepresentationOfAssignedArray() { + $array = array('foo' => 'Foo', 'bar' => 'Bar'); + $this->view->assign('value', $array); + + $expectedResult = '{"foo":"Foo","bar":"Bar"}'; + $actualResult = $this->view->render(); + $this->assertEquals($expectedResult, $actualResult); + } + + /** + * @test + */ + public function renderReturnsJsonRepresentationOfAssignedSimpleValue() { + $value = 'Foo'; + $this->view->assign('value', $value); + + $expectedResult = '"Foo"'; + $actualResult = $this->view->render(); + $this->assertEquals($expectedResult, $actualResult); + } + + /** + * @test + */ + public function renderReturnsNullIfNameOfAssignedVariableIsNotEqualToValue() { + $value = 'Foo'; + $this->view->assign('foo', $value); + + $expectedResult = 'null'; + $actualResult = $this->view->render(); + $this->assertEquals($expectedResult, $actualResult); + } + + /** + * @test + */ + public function renderOnlyRendersVariableWithTheNameValue() { + $this->view + ->assign('value', 'Value') + ->assign('someOtherVariable', 'Foo'); + + $expectedResult = '"Value"'; + $actualResult = $this->view->render(); + $this->assertEquals($expectedResult, $actualResult); + } + + /** + * @test + */ + public function setVariablesToRenderOverridesValueToRender() { + $value = 'Foo'; + $this->view->assign('foo', $value); + $this->view->setVariablesToRender(array('foo')); + + $expectedResult = '"Foo"'; + $actualResult = $this->view->render(); + $this->assertEquals($expectedResult, $actualResult); + } + + /** + * @test + */ + public function renderRendersMultipleValuesIfTheyAreSpecifiedAsVariablesToRender() { + $this->view + ->assign('value', 'Value1') + ->assign('secondValue', 'Value2') + ->assign('someOtherVariable', 'Value3'); + $this->view->setVariablesToRender(array('value', 'secondValue')); + + $expectedResult = '{"value":"Value1","secondValue":"Value2"}'; + $actualResult = $this->view->render(); + $this->assertEquals($expectedResult, $actualResult); + } + + /** + * @test + */ + public function renderCanRenderMultipleComplexObjects() { + $array = array('foo' => array('bar' => 'Baz')); + $object = new \stdClass(); + $object->foo = 'Foo'; + + $this->view + ->assign('array', $array) + ->assign('object', $object) + ->assign('someOtherVariable', 'Value3'); + $this->view->setVariablesToRender(array('array', 'object')); + + $expectedResult = '{"array":{"foo":{"bar":"Baz"}},"object":{"foo":"Foo"}}'; + $actualResult = $this->view->render(); + $this->assertEquals($expectedResult, $actualResult); + } + + /** + * @test + */ + public function renderCanRenderPlainArray() { + $array = array(array('name' => 'Foo', 'secret' => TRUE), array('name' => 'Bar', 'secret' => TRUE)); + + $this->view->assign('value', $array); + $this->view->setConfiguration(array( + 'value' => array( + '_descendAll' => array( + '_only' => array('name') + ) + ) + )); + + $expectedResult = '[{"name":"Foo"},{"name":"Bar"}]'; + $actualResult = $this->view->render(); + $this->assertEquals($expectedResult, $actualResult); + } + + /** + * @test + */ + public function descendAllKeepsArrayIndexes() { + $array = array(array('name' => 'Foo', 'secret' => TRUE), array('name' => 'Bar', 'secret' => TRUE)); + + $this->view->assign('value', $array); + $this->view->setConfiguration(array( + 'value' => array( + '_descendAll' => array( + '_descendAll' => array() + ) + ) + )); + + $expectedResult = '[{"name":"Foo","secret":true},{"name":"Bar","secret":true}]'; + $actualResult = $this->view->render(); + $this->assertEquals($expectedResult, $actualResult); + } +} -- GitLab