Skip to content

Commit

Permalink
ENH Looping through arrays in templates
Browse files Browse the repository at this point in the history
  • Loading branch information
GuySartorelli committed May 16, 2024
1 parent c6aee6c commit f7b853e
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 12 deletions.
4 changes: 4 additions & 0 deletions src/View/SSViewer_DataPresenter.php
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ public function getInjectedValue($property, array $params, $cast = true)
// Get source for this value
$result = $this->getValueSource($property);
if (!array_key_exists('source', $result)) {
// $Me is a special property. If nothing is providing an override, return the current item.
if ($property === 'Me') {
return ['obj' => $this->getItem()];
}
return null;
}

Expand Down
12 changes: 12 additions & 0 deletions src/View/SSViewer_Scope.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,16 @@ public function getItem()
if (is_scalar($item)) {
$item = $this->convertScalarToDBField($item);
}
// Wrap arrays
if (is_array($item)) {
if (array_is_list($item)) {
// Wrap in ArrayIterator to respect method signature
$item = new ArrayIterator($item);
} else {
// Wrap in ArrayData so values can be accessed by key in templates
$item = ArrayData::create($item);
}
}
return $item;
}

Expand Down Expand Up @@ -308,6 +318,8 @@ public function next()
// Item may be an array or a regular IteratorAggregate
if (is_array($this->item)) {
$this->itemIterator = new ArrayIterator($this->item);
} elseif ($this->item instanceof Iterator) {
$this->itemIterator = $this->item;
} else {
$this->itemIterator = $this->item->getIterator();

Expand Down
24 changes: 12 additions & 12 deletions src/View/ViewableData.php
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,7 @@ protected function objCacheClear()
* @param array $arguments
* @param bool $cache Cache this object
* @param string $cacheName a custom cache name
* @return Object|DBField
* @return object|DBField
*/
public function obj($fieldName, $arguments = [], $cache = false, $cacheName = null)
{
Expand All @@ -558,6 +558,17 @@ public function obj($fieldName, $arguments = [], $cache = false, $cacheName = nu
$value = $this->$fieldName;
}

// Wrap arrays
if (is_array($value)) {
if (array_is_list($value)) {
// Wrap in ArrayIterator to respect method signature
$value = new ArrayIterator($value);
} else {
// Wrap in ArrayData so values can be accessed by key in templates
$value = ArrayData::create($value);
}
}

// Cast object
if (!is_object($value)) {
// Force cast
Expand Down Expand Up @@ -668,17 +679,6 @@ public function getViewerTemplates($suffix = '')
return SSViewer::get_templates_by_class(static::class, $suffix, self::class);
}

/**
* When rendering some objects it is necessary to iterate over the object being rendered, to do this, you need
* access to itself.
*
* @return ViewableData
*/
public function Me()
{
return $this;
}

/**
* Get part of the current classes ancestry to be used as a CSS class.
*
Expand Down
45 changes: 45 additions & 0 deletions tests/php/View/SSViewerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace SilverStripe\View\Tests;

use ArrayIterator;
use Exception;
use InvalidArgumentException;
use LogicException;
Expand Down Expand Up @@ -1331,6 +1332,50 @@ public function testCastingHelpers()
);
}

public function provideLoop(): array
{
return [
'nested array and iterator' => [
'iterable' => [
[
'value 1',
'value 2',
],
new ArrayIterator([
'value 3',
'value 4',
]),
],
'template' => '<% loop $Iterable %><% loop $Me %>$Me<% end_loop %><% end_loop %>',
'expected' => 'value 1 value 2 value 3 value 4',
],
'nested associative arrays' => [
'iterable' => [
[
'Foo' => 'one',
],
[
'Foo' => 'two',
],
[
'Foo' => 'three',
],
],
'template' => '<% loop $Iterable %>$Foo<% end_loop %>',
'expected' => 'one two three',
],
];
}

/**
* @dataProvider provideLoop
*/
public function testLoop(iterable $iterable, string $template, string $expected): void
{
$data = new ArrayData(['Iterable' => $iterable]);
$this->assertEqualIgnoringWhitespace($expected, $this->render($template, $data));
}

public function testSSViewerBasicIteratorSupport()
{
$data = new ArrayData(
Expand Down
42 changes: 42 additions & 0 deletions tests/php/View/ViewableDataTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace SilverStripe\View\Tests;

use ArrayIterator;
use ReflectionMethod;
use SilverStripe\ORM\FieldType\DBField;
use SilverStripe\Dev\SapphireTest;
Expand Down Expand Up @@ -278,4 +279,45 @@ public function testDynamicData()
$this->assertSame($obj, $viewableData->getDynamicData('abc'));
$this->assertSame($obj, $viewableData->abc);
}

public function provideWrapArrayInObj(): array
{
return [
'empty array' => [
'arr' => [],
'expectedClass' => ArrayIterator::class,
],
'fully indexed array' => [
'arr' => [
'value1',
'value2',
],
'expectedClass' => ArrayIterator::class,
],
'fully associative array' => [
'arr' => [
'v1' => 'value1',
'v2' => 'value2',
],
'expectedClass' => ArrayData::class,
],
'partially associative array' => [
'arr' => [
'value1',
'v2' => 'value2',
],
'expectedClass' => ArrayData::class,
],
];
}

/**
* @dataProvider provideWrapArrayInObj
*/
public function testWrapArrayInObj(array $arr, string $expectedClass): void
{
$viewableData = new ViewableData();
$viewableData->arr = $arr;
$this->assertInstanceOf($expectedClass, $viewableData->obj('arr'));
}
}

0 comments on commit f7b853e

Please sign in to comment.