-
Notifications
You must be signed in to change notification settings - Fork 93
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[FEATURE] ViewHelpers to return first/last item of an array (#866)
The FirstViewHelper and LastViewHelper return the first or last item of a specified array, respectively. ## Examples ```xml <f:first value="{0: 'first', 1: 'second', 2: 'third'}" /> <!-- Outputs "first" --> <f:last value="{0: 'first', 1: 'second', 2: 'third'}" /> <!-- Outputs "third" --> ```
- Loading branch information
Showing
4 changed files
with
362 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
/* | ||
* This file belongs to the package "TYPO3 Fluid". | ||
* See LICENSE.txt that was shipped with this package. | ||
*/ | ||
|
||
namespace TYPO3Fluid\Fluid\ViewHelpers; | ||
|
||
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; | ||
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; | ||
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; | ||
|
||
/** | ||
* The FirstViewHelper returns the first item of an array. | ||
* | ||
* Example | ||
* ======== | ||
* :: | ||
* | ||
* <f:first value="{0: 'first', 1: 'second'}" /> | ||
* | ||
* .. code-block:: text | ||
* | ||
* first | ||
*/ | ||
final class FirstViewHelper extends AbstractViewHelper | ||
{ | ||
use CompileWithRenderStatic; | ||
|
||
public function initializeArguments(): void | ||
{ | ||
$this->registerArgument('value', 'array', '', false); | ||
} | ||
|
||
public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext): mixed | ||
{ | ||
$value = $arguments['value'] ?? $renderChildrenClosure(); | ||
|
||
if ($value === null || !is_iterable($value)) { | ||
$givenType = get_debug_type($value); | ||
throw new \InvalidArgumentException( | ||
'The argument "value" was registered with type "array", but is of type "' . | ||
$givenType . '" in view helper "' . static::class . '".', | ||
1712220569 | ||
); | ||
} | ||
|
||
$value = self::iteratorToArray($value); | ||
|
||
return array_shift($value); | ||
} | ||
|
||
/** | ||
* This ensures compatibility with PHP 8.1 | ||
*/ | ||
private static function iteratorToArray(\Traversable|array $iterator): array | ||
{ | ||
return is_array($iterator) ? $iterator : iterator_to_array($iterator); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
/* | ||
* This file belongs to the package "TYPO3 Fluid". | ||
* See LICENSE.txt that was shipped with this package. | ||
*/ | ||
|
||
namespace TYPO3Fluid\Fluid\ViewHelpers; | ||
|
||
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; | ||
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; | ||
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; | ||
|
||
/** | ||
* The LastViewHelper returns the last item of an array. | ||
* | ||
* Example | ||
* ======== | ||
* :: | ||
* | ||
* <f:last value="{0: 'first', 1: 'second'}" /> | ||
* | ||
* .. code-block:: text | ||
* | ||
* second | ||
*/ | ||
final class LastViewHelper extends AbstractViewHelper | ||
{ | ||
use CompileWithRenderStatic; | ||
|
||
public function initializeArguments(): void | ||
{ | ||
$this->registerArgument('value', 'array', '', false); | ||
} | ||
|
||
public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext): mixed | ||
{ | ||
$value = $arguments['value'] ?? $renderChildrenClosure(); | ||
|
||
if ($value === null || !is_iterable($value)) { | ||
$givenType = get_debug_type($value); | ||
throw new \InvalidArgumentException( | ||
'The argument "value" was registered with type "array", but is of type "' . | ||
$givenType . '" in view helper "' . static::class . '".', | ||
1712221620 | ||
); | ||
} | ||
|
||
$value = self::iteratorToArray($value); | ||
|
||
return array_pop($value); | ||
} | ||
|
||
/** | ||
* This ensures compatibility with PHP 8.1 | ||
*/ | ||
private static function iteratorToArray(\Traversable|array $iterator): array | ||
{ | ||
return is_array($iterator) ? $iterator : iterator_to_array($iterator); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
/* | ||
* This file belongs to the package "TYPO3 Fluid". | ||
* See LICENSE.txt that was shipped with this package. | ||
*/ | ||
|
||
namespace TYPO3\CMS\Fluid\Tests\Functional\ViewHelpers; | ||
|
||
use PHPUnit\Framework\Attributes\DataProvider; | ||
use PHPUnit\Framework\Attributes\Test; | ||
use TYPO3Fluid\Fluid\Tests\Functional\AbstractFunctionalTestCase; | ||
use TYPO3Fluid\Fluid\Tests\Functional\Fixtures\Various\ArrayAccessExample; | ||
use TYPO3Fluid\Fluid\Tests\Functional\Fixtures\Various\IterableExample; | ||
use TYPO3Fluid\Fluid\View\TemplateView; | ||
|
||
final class FirstViewHelperTest extends AbstractFunctionalTestCase | ||
{ | ||
public static function renderValidDataProvider(): iterable | ||
{ | ||
yield 'empty value' => [ | ||
'arguments' => [ | ||
'value' => [], | ||
], | ||
'src' => '<f:first value="{value}" />', | ||
'expectation' => null, | ||
]; | ||
yield 'empty value inline' => [ | ||
'arguments' => [ | ||
'value' => [], | ||
], | ||
'src' => '{value -> f:first()}', | ||
'expectation' => null, | ||
]; | ||
yield 'single item' => [ | ||
'arguments' => [ | ||
'value' => [1], | ||
], | ||
'src' => '<f:first value="{value}" />', | ||
'expectation' => 1, | ||
]; | ||
yield 'multiple items' => [ | ||
'arguments' => [ | ||
'value' => ['first', 'second', 'third'], | ||
], | ||
'src' => '<f:first value="{value}" />', | ||
'expectation' => 'first', | ||
]; | ||
yield 'value inline as iterable' => [ | ||
'arguments' => [ | ||
'value' => new IterableExample(['first', 'second', 'third']), | ||
], | ||
'src' => '{value -> f:first()}', | ||
'expectation' => 'first', | ||
]; | ||
} | ||
|
||
#[DataProvider('renderValidDataProvider')] | ||
#[Test] | ||
public function renderValid(array $arguments, string $src, mixed $expectation): void | ||
{ | ||
$view = new TemplateView(); | ||
$view->getRenderingContext()->setCache(self::$cache); | ||
$view->getRenderingContext()->getTemplatePaths()->setTemplateSource($src); | ||
$view->assignMultiple($arguments); | ||
self::assertSame($expectation, $view->render()); | ||
|
||
$view = new TemplateView(); | ||
$view->getRenderingContext()->setCache(self::$cache); | ||
$view->getRenderingContext()->getTemplatePaths()->setTemplateSource($src); | ||
$view->assignMultiple($arguments); | ||
self::assertSame($expectation, $view->render()); | ||
} | ||
|
||
public static function renderInvalidDataProvider(): iterable | ||
{ | ||
yield 'invalid string content' => [ | ||
'arguments' => [ | ||
], | ||
'src' => '<f:first>SOME TEXT</f:first>', | ||
]; | ||
yield 'invalid string inline' => [ | ||
'arguments' => [ | ||
'value' => 'string', | ||
], | ||
'src' => '{value -> f:first()}', | ||
]; | ||
yield 'arrayaccess inline' => [ | ||
'arguments' => [ | ||
'value' => new ArrayAccessExample(['foo' => 'bar']), | ||
], | ||
'src' => '{value -> f:first()}', | ||
]; | ||
} | ||
|
||
#[DataProvider('renderInvalidDataProvider')] | ||
#[Test] | ||
public function renderInvalid(array $arguments, string $src): void | ||
{ | ||
$this->expectException(\InvalidArgumentException::class); | ||
$this->expectExceptionCode(1712220569); | ||
|
||
$view = new TemplateView(); | ||
$view->getRenderingContext()->setCache(self::$cache); | ||
$view->getRenderingContext()->getTemplatePaths()->setTemplateSource($src); | ||
$view->assignMultiple($arguments); | ||
$view->render(); | ||
|
||
$view = new TemplateView(); | ||
$view->getRenderingContext()->setCache(self::$cache); | ||
$view->getRenderingContext()->getTemplatePaths()->setTemplateSource($src); | ||
$view->assignMultiple($arguments); | ||
$view->render(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
/* | ||
* This file belongs to the package "TYPO3 Fluid". | ||
* See LICENSE.txt that was shipped with this package. | ||
*/ | ||
|
||
namespace TYPO3\CMS\Fluid\Tests\Functional\ViewHelpers; | ||
|
||
use PHPUnit\Framework\Attributes\DataProvider; | ||
use PHPUnit\Framework\Attributes\Test; | ||
use TYPO3Fluid\Fluid\Tests\Functional\AbstractFunctionalTestCase; | ||
use TYPO3Fluid\Fluid\Tests\Functional\Fixtures\Various\ArrayAccessExample; | ||
use TYPO3Fluid\Fluid\Tests\Functional\Fixtures\Various\IterableExample; | ||
use TYPO3Fluid\Fluid\View\TemplateView; | ||
|
||
final class LastViewHelperTest extends AbstractFunctionalTestCase | ||
{ | ||
public static function renderValidDataProvider(): iterable | ||
{ | ||
yield 'empty value' => [ | ||
'arguments' => [ | ||
'value' => [], | ||
], | ||
'src' => '<f:last value="{value}" />', | ||
'expectation' => null, | ||
]; | ||
yield 'empty value inline' => [ | ||
'arguments' => [ | ||
'value' => [], | ||
], | ||
'src' => '{value -> f:last()}', | ||
'expectation' => null, | ||
]; | ||
yield 'single item' => [ | ||
'arguments' => [ | ||
'value' => [1], | ||
], | ||
'src' => '<f:last value="{value}" />', | ||
'expectation' => 1, | ||
]; | ||
yield 'multiple items' => [ | ||
'arguments' => [ | ||
'value' => ['first', 'second', 'third'], | ||
], | ||
'src' => '<f:last value="{value}" />', | ||
'expectation' => 'third', | ||
]; | ||
yield 'value inline as iterable' => [ | ||
'arguments' => [ | ||
'value' => new IterableExample(['first', 'second', 'third']), | ||
], | ||
'src' => '{value -> f:last()}', | ||
'expectation' => 'third', | ||
]; | ||
} | ||
|
||
#[DataProvider('renderValidDataProvider')] | ||
#[Test] | ||
public function renderValid(array $arguments, string $src, mixed $expectation): void | ||
{ | ||
$view = new TemplateView(); | ||
$view->getRenderingContext()->setCache(self::$cache); | ||
$view->getRenderingContext()->getTemplatePaths()->setTemplateSource($src); | ||
$view->assignMultiple($arguments); | ||
self::assertSame($expectation, $view->render()); | ||
|
||
$view = new TemplateView(); | ||
$view->getRenderingContext()->setCache(self::$cache); | ||
$view->getRenderingContext()->getTemplatePaths()->setTemplateSource($src); | ||
$view->assignMultiple($arguments); | ||
self::assertSame($expectation, $view->render()); | ||
} | ||
|
||
public static function renderInvalidDataProvider(): iterable | ||
{ | ||
yield 'invalid string content' => [ | ||
'arguments' => [ | ||
], | ||
'src' => '<f:last>SOME TEXT</f:last>', | ||
]; | ||
yield 'invalid string inline' => [ | ||
'arguments' => [ | ||
'value' => 'string', | ||
], | ||
'src' => '{value -> f:last()}', | ||
]; | ||
yield 'arrayaccess inline' => [ | ||
'arguments' => [ | ||
'value' => new ArrayAccessExample(['foo' => 'bar']), | ||
], | ||
'src' => '{value -> f:last()}', | ||
]; | ||
} | ||
|
||
#[DataProvider('renderInvalidDataProvider')] | ||
#[Test] | ||
public function renderInvalid(array $arguments, string $src): void | ||
{ | ||
$this->expectException(\InvalidArgumentException::class); | ||
$this->expectExceptionCode(1712221620); | ||
|
||
$view = new TemplateView(); | ||
$view->getRenderingContext()->setCache(self::$cache); | ||
$view->getRenderingContext()->getTemplatePaths()->setTemplateSource($src); | ||
$view->assignMultiple($arguments); | ||
$view->render(); | ||
|
||
$view = new TemplateView(); | ||
$view->getRenderingContext()->setCache(self::$cache); | ||
$view->getRenderingContext()->getTemplatePaths()->setTemplateSource($src); | ||
$view->assignMultiple($arguments); | ||
$view->render(); | ||
} | ||
} |