Skip to content

Commit

Permalink
FEATURE: Add Fusion prototypes Join, Loop, Map, Reduce and `D…
Browse files Browse the repository at this point in the history
…ataStructure`

The difference between ``RawArray``, ``Array``, ``RawCollection`` and ``Collection`` was often hard to get for new developers. To overcome this the old confusing names are deprecated and new prototypes are introduced wich are easier to get and emphasize the declarative nature of Fusion.

``Neos.Fusion:Join``

The prototype concatenates the fusion values of all fusion-properties and returns the result as a string. This prototype replaces ``Neos.Fusion:Array`` wich is deprecated.

In addition to the behavior of the ``Array`` ``Join`` allows to define the ``@glue`` used for concatenating the parts.

``Neos.Fusion:Loop``

The ``Loop`` prototype iterates over the given items with the itemRenderer and returns the concatenated result as a string. The prototype replaces ``Neos.Fusion:Collection`` wich is deprecated.

Other than in ``Collection`` the items are passed with the key ``items`` instead of ``collection``.

In addition to the behavior of the classic ``Collection`` ``Loop ``  allows to define the ``@glue`` used for concatenating the items.

``Neos.Fusion:DataStructure``

The ``DataStructure`` prototype returns an associative array with all fusion keys evaluated. The prototype replaces ``Neos.Fusion:RawArray`` wich is deprecated.

``Neos.Fusion:Map``

The ``Map`` iterates over the given ``items`` and returns the result as array.  The prototype replaces ``Neos.Fusion:RawCollection`` wich is deprecated.

Other than ``RawCollection`` the items are passed with the key ``items`` instead of ``collection`` and the keys of the given ``items`` are preserved.

``Neos.Fusion:Reduce``

The `Neos.Fusion:Reduce` prototype is added wich reduces the given items to a single value by using ``itemRenderer`` with the following properties.

- `items` (array/Iterable, **required**) The array or iterable to iterate over
- `itemName`: (string, defaults to `item`) Context variable name for each item
- `itemKey`: (string, defaults to `itemKey`) Context variable name for each item key, when working with array
- `carryName`: (string, defaults to `carry`) Context variable that contains the result of the last iteration
- `iterationName`: (string, defaults to `iterator`) A context variable with iteration information will be available under the given name: ``index`` (zero-based), ``cycle`` (1-based), ``isFirst``, ``isLast``
- `itemReducer`: (mixed, **required**) The reducer definition (simple value, expression or object) that will be applied for every item.
- `initialValue`: (mixed, defaults to `null`) The value that is passed to the first iteration or returned if the items are empty
  • Loading branch information
mficzel committed Nov 5, 2018
1 parent 985c4d8 commit a7bc229
Show file tree
Hide file tree
Showing 23 changed files with 1,417 additions and 177 deletions.
Expand Up @@ -15,16 +15,10 @@

/**
* Abstract implementation of a collection renderer for Fusion.
* @deprecated since Neos 4.2 in favor of MapImplementation
*/
abstract class AbstractCollectionImplementation extends AbstractFusionObject
abstract class AbstractCollectionImplementation extends MapImplementation
{
/**
* The number of rendered nodes, filled only after evaluate() was called.
*
* @var integer
*/
protected $numberOfRenderedNodes;

/**
* Render the array collection by triggering the itemRenderer for every element
*
Expand All @@ -36,29 +30,11 @@ public function getCollection()
}

/**
* @return string
*/
public function getItemName()
{
return $this->fusionValue('itemName');
}

/**
* @return string
*/
public function getItemKey()
{
return $this->fusionValue('itemKey');
}

/**
* If set iteration data (index, cycle, isFirst, isLast) is available in context with the name given.
*
* @return string
* @return array
*/
public function getIterationName()
public function getItems()
{
return $this->fusionValue('iterationName');
return $this->getCollection();
}

/**
Expand All @@ -69,7 +45,7 @@ public function getIterationName()
*/
public function evaluate()
{
return implode('', $this->evaluateAsArray());
return implode('', parent::evaluate());
}

/**
Expand All @@ -80,66 +56,6 @@ public function evaluate()
*/
public function evaluateAsArray()
{
$collection = $this->getCollection();

$result = [];
if ($collection === null) {
return $result;
}
$this->numberOfRenderedNodes = 0;
$itemName = $this->getItemName();
if ($itemName === null) {
throw new FusionException('The Collection needs an itemName to be set.', 1344325771);
}
$itemKey = $this->getItemKey();
$iterationName = $this->getIterationName();
$collectionTotalCount = count($collection);
foreach ($collection as $collectionKey => $collectionElement) {
$context = $this->runtime->getCurrentContext();
$context[$itemName] = $collectionElement;
if ($itemKey !== null) {
$context[$itemKey] = $collectionKey;
}
if ($iterationName !== null) {
$context[$iterationName] = $this->prepareIterationInformation($collectionTotalCount);
}

$this->runtime->pushContextArray($context);
$result[] = $this->runtime->render($this->path . '/itemRenderer');
$this->runtime->popContext();
$this->numberOfRenderedNodes++;
}

return $result;
}

/**
* @param integer $collectionCount
* @return array
*/
protected function prepareIterationInformation($collectionCount)
{
$iteration = [
'index' => $this->numberOfRenderedNodes,
'cycle' => ($this->numberOfRenderedNodes + 1),
'isFirst' => false,
'isLast' => false,
'isEven' => false,
'isOdd' => false
];

if ($this->numberOfRenderedNodes === 0) {
$iteration['isFirst'] = true;
}
if (($this->numberOfRenderedNodes + 1) === $collectionCount) {
$iteration['isLast'] = true;
}
if (($this->numberOfRenderedNodes + 1) % 2 === 0) {
$iteration['isEven'] = true;
} else {
$iteration['isOdd'] = true;
}

return $iteration;
return parent::evaluate();
}
}
57 changes: 5 additions & 52 deletions Neos.Fusion/Classes/FusionObjects/ArrayImplementation.php
Expand Up @@ -11,67 +11,20 @@
* source code.
*/

use Neos\Utility\Exception\InvalidPositionException;
use Neos\Utility\PositionalArraySorter;
use Neos\Fusion;

/**
* The old "COA" object
* @deprecated since Neos 4.2 in favor of JoinImplementation
*/
class ArrayImplementation extends AbstractArrayFusionObject
class ArrayImplementation extends JoinImplementation
{
/**
* {@inheritdoc}
* Arrays are always concatenated with an empty string
*
* @return string
*/
public function evaluate()
public function getGlue()
{
$sortedChildFusionKeys = $this->sortNestedFusionKeys();

if (count($sortedChildFusionKeys) === 0) {
return null;
}

$output = '';
foreach ($sortedChildFusionKeys as $key) {
try {
$output .= $this->fusionValue($key);
} catch (\Exception $e) {
$output .= $this->runtime->handleRenderingException($this->path . '/' . $key, $e);
}
}

return $output;
}

/**
* Sort the Fusion objects inside $this->properties depending on:
* - numerical ordering
* - position meta-property
*
* This will ignore all properties defined in "@ignoreProperties" in Fusion
*
* @see PositionalArraySorter
*
* @return array an ordered list of keys
* @throws Fusion\Exception if the positional string has an unsupported format
*/
protected function sortNestedFusionKeys()
{
$arraySorter = new PositionalArraySorter($this->properties, '__meta.position');
try {
$sortedFusionKeys = $arraySorter->getSortedKeys();
} catch (InvalidPositionException $exception) {
throw new Fusion\Exception('Invalid position string', 1345126502, $exception);
}

foreach ($this->ignoreProperties as $ignoredPropertyName) {
$key = array_search($ignoredPropertyName, $sortedFusionKeys);
if ($key !== false) {
unset($sortedFusionKeys[$key]);
}
}
return $sortedFusionKeys;
return '';
}
}
11 changes: 11 additions & 0 deletions Neos.Fusion/Classes/FusionObjects/CollectionImplementation.php
Expand Up @@ -17,9 +17,20 @@
*
* //tsPath collection *Collection
* //tsPath itemRenderer the TS object which is triggered for each element in the node collection
* @deprecated since Neos 4.2 in favor of LoopImplementation
*/
class CollectionImplementation extends AbstractCollectionImplementation
{
/**
* Collections are always concatenated with an empty string
*
* @return string
*/
public function getGlue()
{
return '';
}

/**
* Evaluate the collection nodes
*
Expand Down
82 changes: 82 additions & 0 deletions Neos.Fusion/Classes/FusionObjects/DataStructureImplementation.php
@@ -0,0 +1,82 @@
<?php
namespace Neos\Fusion\FusionObjects;

/*
* This file is part of the Neos.Fusion package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

use Neos\Fusion\Core\Runtime;
use Neos\Utility\Exception\InvalidPositionException;
use Neos\Utility\PositionalArraySorter;
use Neos\Fusion;

/**
* Fusion object to render and array of key value pairs by evaluating all properties
*/
class DataStructureImplementation extends AbstractArrayFusionObject
{
/**
* {@inheritdoc}
*
* @return array
*/
public function evaluate()
{
$sortedChildFusionKeys = $this->sortNestedFusionKeys();

if (count($sortedChildFusionKeys) === 0) {
return [];
}

$result = [];
foreach ($sortedChildFusionKeys as $key) {
try {
$value = $this->fusionValue($key);
} catch (\Exception $e) {
$value = $this->runtime->handleRenderingException($this->path . '/' . $key, $e);
}
if ($value === null && $this->runtime->getLastEvaluationStatus() === Runtime::EVALUATION_SKIPPED) {
continue;
}
$result[$key] = $value;
}

return $result;
}

/**
* Sort the Fusion objects inside $this->properties depending on:
* - numerical ordering
* - position meta-property
*
* This will ignore all properties defined in "@ignoreProperties" in Fusion
*
* @see PositionalArraySorter
*
* @return array an ordered list of key value pairs
* @throws Fusion\Exception if the positional string has an unsupported format
*/
protected function sortNestedFusionKeys()
{
$arraySorter = new PositionalArraySorter($this->properties, '__meta.position');
try {
$sortedFusionKeys = $arraySorter->getSortedKeys();
} catch (InvalidPositionException $exception) {
throw new Fusion\Exception('Invalid position string', 1345126502, $exception);
}

foreach ($this->ignoreProperties as $ignoredPropertyName) {
$key = array_search($ignoredPropertyName, $sortedFusionKeys);
if ($key !== false) {
unset($sortedFusionKeys[$key]);
}
}
return $sortedFusionKeys;
}
}
45 changes: 45 additions & 0 deletions Neos.Fusion/Classes/FusionObjects/JoinImplementation.php
@@ -0,0 +1,45 @@
<?php
namespace Neos\Fusion\FusionObjects;

/*
* This file is part of the Neos.Fusion package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/


/**
* Fusion object to render a list of items as single concatenated string
*/
class JoinImplementation extends DataStructureImplementation
{

/**
* Get the glue to insert between items
*
* @return string
*/
public function getGlue()
{
return $this->fusionValue('__meta/glue') ?? '';
}

/**
* {@inheritdoc}
*
* @return string
*/
public function evaluate()
{
$glue = $this->getGlue();
$parentResult = parent::evaluate();
if ($parentResult !== []) {
return implode($glue, $parentResult);
}
return null;
}
}
44 changes: 44 additions & 0 deletions Neos.Fusion/Classes/FusionObjects/LoopImplementation.php
@@ -0,0 +1,44 @@
<?php
namespace Neos\Fusion\FusionObjects;

/*
* This file is part of the Neos.Fusion package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/


/**
* Render a Fusion collection of using the itemRenderer
*
* //fusionPath items *Collection
* //fusionPath itemRenderer the Fusion object which is triggered for each element in the node collection
*/
class LoopImplementation extends MapImplementation
{

/**
* Get the glue to insert between items
*
* @return string
*/
public function getGlue()
{
return $this->fusionValue('__meta/glue') ?? '';
}

/**
* Evaluate the collection nodes
*
* @return string
*/
public function evaluate()
{
$glue = $this->getGlue();
return implode($glue, parent::evaluate());
}
}

0 comments on commit a7bc229

Please sign in to comment.