diff --git a/Classes/DependencyInjection/HandlebarsHelperPass.php b/Classes/DependencyInjection/HandlebarsHelperPass.php
index 6e7aa8dc..2700d161 100644
--- a/Classes/DependencyInjection/HandlebarsHelperPass.php
+++ b/Classes/DependencyInjection/HandlebarsHelperPass.php
@@ -23,11 +23,8 @@
namespace Fr\Typo3Handlebars\DependencyInjection;
-use Fr\Typo3Handlebars\Renderer\HelperAwareInterface;
-use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
-use Symfony\Component\DependencyInjection\ContainerBuilder;
-use Symfony\Component\DependencyInjection\Definition;
-use Symfony\Component\DependencyInjection\Reference;
+use Fr\Typo3Handlebars\Renderer;
+use Symfony\Component\DependencyInjection;
/**
* HandlebarsHelperPass
@@ -37,56 +34,28 @@
* @internal
* @codeCoverageIgnore
*/
-final class HandlebarsHelperPass implements CompilerPassInterface
+final readonly class HandlebarsHelperPass implements DependencyInjection\Compiler\CompilerPassInterface
{
- /**
- * @var Definition[]
- */
- private array $rendererDefinitions = [];
-
public function __construct(
- private readonly string $helperTagName,
- private readonly string $rendererTagName,
+ private string $helperTagName,
) {}
- public function process(ContainerBuilder $container): void
+ public function process(DependencyInjection\ContainerBuilder $container): void
{
- $this->fetchRendererDefinitions($container);
+ $registryDefinition = $container->getDefinition(Renderer\Helper\HelperRegistry::class);
// Register tagged Handlebars helper at all Helper-aware renderers
foreach ($container->findTaggedServiceIds($this->helperTagName) as $serviceId => $tags) {
- $container->findDefinition($serviceId)->setPublic(true);
-
foreach (array_filter($tags) as $attributes) {
$this->validateTag($serviceId, $attributes);
- $this->registerHelper(
- $attributes['identifier'],
- [new Reference($serviceId), $attributes['method']]
- );
- }
- }
- }
-
- /**
- * @param array{0: string|Reference, 1: string} $callable
- */
- private function registerHelper(string $name, array $callable): void
- {
- foreach ($this->rendererDefinitions as $rendererDefinition) {
- $rendererDefinition->addMethodCall('registerHelper', [$name, $callable]);
- }
- }
- protected function fetchRendererDefinitions(ContainerBuilder $container): void
- {
- $this->rendererDefinitions = [];
-
- foreach (array_keys($container->findTaggedServiceIds($this->rendererTagName)) as $serviceId) {
- $rendererDefinition = $container->findDefinition($serviceId);
- $rendererClass = $rendererDefinition->getClass();
-
- if ($rendererClass !== null && \in_array(HelperAwareInterface::class, class_implements($rendererClass) ?: [])) {
- $this->rendererDefinitions[] = $rendererDefinition;
+ $registryDefinition->addMethodCall(
+ 'add',
+ [
+ $attributes['identifier'],
+ [new DependencyInjection\Reference($serviceId), $attributes['method']],
+ ],
+ );
}
}
}
@@ -99,13 +68,13 @@ private function validateTag(string $serviceId, array $tagAttributes): void
if (!\array_key_exists('identifier', $tagAttributes) || (string)$tagAttributes['identifier'] === '') {
throw new \InvalidArgumentException(
\sprintf('Service tag "%s" requires an identifier attribute to be defined, missing in: %s', $this->helperTagName, $serviceId),
- 1606236820
+ 1606236820,
);
}
if (!\array_key_exists('method', $tagAttributes) || (string)$tagAttributes['method'] === '') {
throw new \InvalidArgumentException(
\sprintf('Service tag "%s" requires an method attribute to be defined, missing in: %s', $this->helperTagName, $serviceId),
- 1606245140
+ 1606245140,
);
}
}
diff --git a/Classes/Exception/Exception.php b/Classes/Exception/Exception.php
new file mode 100644
index 00000000..a809badd
--- /dev/null
+++ b/Classes/Exception/Exception.php
@@ -0,0 +1,32 @@
+
+ *
+ * This program 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.
+ *
+ * This program 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+namespace Fr\Typo3Handlebars\Exception;
+
+/**
+ * Exception
+ *
+ * @author Elias Häußler
+ * @license GPL-2.0-or-later
+ */
+abstract class Exception extends \Exception {}
diff --git a/Classes/Renderer/HelperAwareInterface.php b/Classes/Exception/HelperIsNotRegistered.php
similarity index 65%
rename from Classes/Renderer/HelperAwareInterface.php
rename to Classes/Exception/HelperIsNotRegistered.php
index 25f16ae4..77126eef 100644
--- a/Classes/Renderer/HelperAwareInterface.php
+++ b/Classes/Exception/HelperIsNotRegistered.php
@@ -5,7 +5,7 @@
/*
* This file is part of the TYPO3 CMS extension "handlebars".
*
- * Copyright (C) 2021 Elias Häußler
+ * Copyright (C) 2025 Elias Häußler
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,25 +21,21 @@
* along with this program. If not, see .
*/
-namespace Fr\Typo3Handlebars\Renderer;
+namespace Fr\Typo3Handlebars\Exception;
/**
- * HelperAwareInterface
+ * HelperIsNotRegistered
*
* @author Elias Häußler
* @license GPL-2.0-or-later
*/
-interface HelperAwareInterface
+final class HelperIsNotRegistered extends Exception
{
- /**
- * Get all registered Handlebars helpers.
- *
- * @return array
- */
- public function getHelpers(): array;
-
- /**
- * Register new Handlebars helper with given function.
- */
- public function registerHelper(string $name, mixed $function): void;
+ public function __construct(string $name)
+ {
+ parent::__construct(
+ \sprintf('Handlebars helper "%s" is not registered.', $name),
+ 1736242470,
+ );
+ }
}
diff --git a/Classes/Renderer/HandlebarsRenderer.php b/Classes/Renderer/HandlebarsRenderer.php
index 58fd49dd..e0a9e287 100644
--- a/Classes/Renderer/HandlebarsRenderer.php
+++ b/Classes/Renderer/HandlebarsRenderer.php
@@ -30,8 +30,8 @@
use Fr\Typo3Handlebars\Exception\InvalidTemplateFileException;
use Fr\Typo3Handlebars\Exception\TemplateCompilationException;
use Fr\Typo3Handlebars\Exception\TemplateNotFoundException;
+use Fr\Typo3Handlebars\Renderer\Helper\HelperRegistry;
use Fr\Typo3Handlebars\Renderer\Template\TemplateResolverInterface;
-use Fr\Typo3Handlebars\Traits\HandlebarsHelperTrait;
use LightnCandy\Context;
use LightnCandy\LightnCandy;
use LightnCandy\Partial;
@@ -52,10 +52,8 @@
*/
#[AsAlias('handlebars.renderer')]
#[Autoconfigure(tags: ['handlebars.renderer'])]
-class HandlebarsRenderer implements RendererInterface, HelperAwareInterface
+class HandlebarsRenderer implements RendererInterface
{
- use HandlebarsHelperTrait;
-
protected readonly bool $debugMode;
/**
@@ -65,6 +63,7 @@ public function __construct(
#[Autowire('@handlebars.cache')]
protected readonly CacheInterface $cache,
protected readonly EventDispatcherInterface $eventDispatcher,
+ protected readonly HelperRegistry $helperRegistry,
protected readonly LoggerInterface $logger,
#[Autowire('@handlebars.template_resolver')]
protected readonly TemplateResolverInterface $templateResolver,
@@ -122,7 +121,7 @@ protected function processRendering(string $templatePath, array $data): string
// Render content
$content = $renderer($beforeRenderingEvent->getData(), [
'debug' => Runtime::DEBUG_TAGS_HTML,
- 'helpers' => $this->helpers,
+ 'helpers' => $this->helperRegistry->getAll(),
]);
// Dispatch after rendering event
@@ -235,7 +234,7 @@ protected function prepareCompileResult(string $compileResult): callable
*/
protected function getHelperStubs(): array
{
- return array_fill_keys(array_keys($this->helpers), true);
+ return array_fill_keys(array_keys($this->helperRegistry->getAll()), true);
}
/**
diff --git a/Classes/Traits/HandlebarsHelperTrait.php b/Classes/Renderer/Helper/HelperRegistry.php
similarity index 66%
rename from Classes/Traits/HandlebarsHelperTrait.php
rename to Classes/Renderer/Helper/HelperRegistry.php
index 9573e962..15743264 100644
--- a/Classes/Traits/HandlebarsHelperTrait.php
+++ b/Classes/Renderer/Helper/HelperRegistry.php
@@ -5,7 +5,7 @@
/*
* This file is part of the TYPO3 CMS extension "handlebars".
*
- * Copyright (C) 2020 Elias Häußler
+ * Copyright (C) 2025 Elias Häußler
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,26 +21,30 @@
* along with this program. If not, see .
*/
-namespace Fr\Typo3Handlebars\Traits;
+namespace Fr\Typo3Handlebars\Renderer\Helper;
use Fr\Typo3Handlebars\Exception;
-use Fr\Typo3Handlebars\Renderer;
+use Psr\Log;
use TYPO3\CMS\Core;
/**
- * HandlebarsHelperTrait
+ * HelperRegistry
*
* @author Elias Häußler
* @license GPL-2.0-or-later
*/
-trait HandlebarsHelperTrait
+final class HelperRegistry implements Core\SingletonInterface
{
/**
- * @var array
+ * @var array
*/
- protected array $helpers = [];
+ private array $helpers = [];
- public function registerHelper(string $name, mixed $function): void
+ public function __construct(
+ private readonly Log\LoggerInterface $logger,
+ ) {}
+
+ public function add(string $name, mixed $function): void
{
try {
$this->helpers[$name] = $this->decorateHelperFunction(
@@ -59,18 +63,35 @@ public function registerHelper(string $name, mixed $function): void
}
/**
- * @return array
+ * @throws Exception\HelperIsNotRegistered
+ */
+ public function get(string $name): callable
+ {
+ if (!isset($this->helpers[$name])) {
+ throw new Exception\HelperIsNotRegistered($name);
+ }
+
+ return $this->helpers[$name];
+ }
+
+ /**
+ * @return array
*/
- public function getHelpers(): array
+ public function getAll(): array
{
return $this->helpers;
}
+ public function has(string $name): bool
+ {
+ return isset($this->helpers[$name]);
+ }
+
/**
* @throws Exception\InvalidHelperException
* @throws \ReflectionException
*/
- protected function resolveHelperFunction(mixed $function): callable
+ private function resolveHelperFunction(mixed $function): callable
{
// Try to resolve the Helper function in this order:
//
@@ -78,8 +99,7 @@ protected function resolveHelperFunction(mixed $function): callable
// ├─ a. as string
// └─ b. as closure or first class callable syntax
// 2. invokable class
- // ├─ a. as string (class-name)
- // └─ b. as object
+ // └─ a. as string (class-name)
// 3. class implementing Helper interface
// ├─ a. as string (class-name)
// └─ b. as object
@@ -103,7 +123,7 @@ protected function resolveHelperFunction(mixed $function): callable
}
// 3a. class implementing Helper interface as string
- if (class_exists($function) && \is_a($function, Renderer\Helper\HelperInterface::class, true)) {
+ if (class_exists($function) && \is_a($function, HelperInterface::class, true)) {
return Core\Utility\GeneralUtility::makeInstance($function)->render(...);
}
}
@@ -113,16 +133,9 @@ protected function resolveHelperFunction(mixed $function): callable
return $function;
}
- if (\is_object($function)) {
- // 2b. invokable class as object
- if (\is_callable($function)) {
- return $function;
- }
-
- // 3b. class implementing Helper interface as object
- if ($function instanceof Renderer\Helper\HelperInterface) {
- return $function->render(...);
- }
+ // 3b. class implementing Helper interface as object
+ if (\is_object($function) && $function instanceof HelperInterface) {
+ return $function->render(...);
}
// 4a. class method as string
@@ -148,12 +161,6 @@ protected function resolveHelperFunction(mixed $function): callable
throw Exception\InvalidHelperException::forFunction($className . '::' . $methodName);
}
- // Check if method can be called statically
- $callable = [$className, $methodName];
- if ($reflectionMethod->isStatic() && \is_callable($callable)) {
- return $callable;
- }
-
// Instantiate class if not done yet
/** @var class-string $className */
if (\is_string($className)) {
@@ -169,38 +176,15 @@ protected function resolveHelperFunction(mixed $function): callable
}
/**
- * @return callable(\Fr\Typo3Handlebars\Renderer\Helper\Context\HelperContext): mixed
+ * @return callable(Context\HelperContext): mixed
*/
- protected function decorateHelperFunction(callable $function): callable
+ private function decorateHelperFunction(callable $function): callable
{
return static function () use ($function) {
$arguments = \func_get_args();
- $context = Renderer\Helper\Context\HelperContext::fromRuntimeCall($arguments);
+ $context = Context\HelperContext::fromRuntimeCall($arguments);
return $function($context);
};
}
-
- /**
- * @codeCoverageIgnore
- * @deprecated use resolveHelperFunction() instead and check for thrown exceptions
- */
- protected function isValidHelper(mixed $helperFunction): bool
- {
- trigger_error(
- \sprintf(
- 'The method "%s" is deprecated and will be removed with 0.9.0. ' .
- 'Use "%s::resolveHelperFunction()" instead and check for thrown exceptions.',
- __METHOD__,
- __TRAIT__,
- ),
- E_USER_DEPRECATED,
- );
-
- try {
- return (bool)$this->resolveHelperFunction($helperFunction);
- } catch (Exception\InvalidHelperException | \ReflectionException) {
- return false;
- }
- }
}
diff --git a/Configuration/Services.php b/Configuration/Services.php
index 0102cfec..3d05c27c 100644
--- a/Configuration/Services.php
+++ b/Configuration/Services.php
@@ -33,7 +33,7 @@
return static function (ContainerConfigurator $containerConfigurator, ContainerBuilder $container): void {
$container->registerExtension(new HandlebarsExtension());
$container->addCompilerPass(new DataProcessorPass('handlebars.processor', 'handlebars.compatibility_layer'));
- $container->addCompilerPass(new HandlebarsHelperPass(AsHelper::TAG_NAME, 'handlebars.renderer'));
+ $container->addCompilerPass(new HandlebarsHelperPass(AsHelper::TAG_NAME));
$container->addCompilerPass(new FeatureRegistrationPass(), priority: 30);
$container->registerAttributeForAutoconfiguration(
diff --git a/Tests/Functional/Renderer/Helper/BlockHelperTest.php b/Tests/Functional/Renderer/Helper/BlockHelperTest.php
index 8c971c6a..100d8f11 100644
--- a/Tests/Functional/Renderer/Helper/BlockHelperTest.php
+++ b/Tests/Functional/Renderer/Helper/BlockHelperTest.php
@@ -54,18 +54,22 @@ protected function setUp(): void
{
parent::setUp();
+ $helperRegistry = new Src\Renderer\Helper\HelperRegistry(new Log\NullLogger());
+
$this->templateRootPath = 'EXT:test_extension/Resources/Templates/';
$this->logger = new Log\Test\TestLogger();
$this->templateResolver = new Src\Renderer\Template\FlatTemplateResolver($this->getTemplatePaths());
$this->renderer = new Src\Renderer\HandlebarsRenderer(
new Src\Cache\NullCache(),
new EventDispatcher\EventDispatcher(),
+ $helperRegistry,
$this->logger,
$this->templateResolver,
);
- $this->renderer->registerHelper('extend', new Src\Renderer\Helper\ExtendHelper($this->renderer));
- $this->renderer->registerHelper('content', new Src\Renderer\Helper\ContentHelper($this->logger));
- $this->renderer->registerHelper('block', new Src\Renderer\Helper\BlockHelper());
+
+ $helperRegistry->add('extend', new Src\Renderer\Helper\ExtendHelper($this->renderer));
+ $helperRegistry->add('content', new Src\Renderer\Helper\ContentHelper($this->logger));
+ $helperRegistry->add('block', new Src\Renderer\Helper\BlockHelper());
}
#[Framework\Attributes\Test]
diff --git a/Tests/Functional/Renderer/Helper/ContentHelperTest.php b/Tests/Functional/Renderer/Helper/ContentHelperTest.php
index 24ad6e89..fb9358d0 100644
--- a/Tests/Functional/Renderer/Helper/ContentHelperTest.php
+++ b/Tests/Functional/Renderer/Helper/ContentHelperTest.php
@@ -54,18 +54,22 @@ protected function setUp(): void
{
parent::setUp();
+ $helperRegistry = new Src\Renderer\Helper\HelperRegistry(new Log\NullLogger());
+
$this->templateRootPath = 'EXT:test_extension/Resources/Templates/';
$this->logger = new Log\Test\TestLogger();
$this->templateResolver = new Src\Renderer\Template\FlatTemplateResolver($this->getTemplatePaths());
$this->renderer = new Src\Renderer\HandlebarsRenderer(
new Src\Cache\NullCache(),
new EventDispatcher\EventDispatcher(),
+ $helperRegistry,
$this->logger,
$this->templateResolver,
);
- $this->renderer->registerHelper('extend', new Src\Renderer\Helper\ExtendHelper($this->renderer));
- $this->renderer->registerHelper('content', new Src\Renderer\Helper\ContentHelper($this->logger));
- $this->renderer->registerHelper('block', new Src\Renderer\Helper\BlockHelper());
+
+ $helperRegistry->add('extend', new Src\Renderer\Helper\ExtendHelper($this->renderer));
+ $helperRegistry->add('content', new Src\Renderer\Helper\ContentHelper($this->logger));
+ $helperRegistry->add('block', new Src\Renderer\Helper\BlockHelper());
}
#[Framework\Attributes\Test]
diff --git a/Tests/Functional/Renderer/Helper/ExtendHelperTest.php b/Tests/Functional/Renderer/Helper/ExtendHelperTest.php
index b5857b9e..c955bf06 100644
--- a/Tests/Functional/Renderer/Helper/ExtendHelperTest.php
+++ b/Tests/Functional/Renderer/Helper/ExtendHelperTest.php
@@ -54,16 +54,20 @@ protected function setUp(): void
{
parent::setUp();
+ $helperRegistry = new Src\Renderer\Helper\HelperRegistry(new Log\NullLogger());
+
$this->templateRootPath = 'EXT:test_extension/Resources/Templates/';
$this->templateResolver = new Src\Renderer\Template\FlatTemplateResolver($this->getTemplatePaths());
$this->renderer = new Src\Renderer\HandlebarsRenderer(
new Src\Cache\NullCache(),
new EventDispatcher\EventDispatcher(),
+ $helperRegistry,
new Log\NullLogger(),
$this->templateResolver,
);
- $this->renderer->registerHelper('extend', new Src\Renderer\Helper\ExtendHelper($this->renderer));
- $this->renderer->registerHelper('jsonEncode', new TestExtension\JsonHelper());
+
+ $helperRegistry->add('extend', new Src\Renderer\Helper\ExtendHelper($this->renderer));
+ $helperRegistry->add('jsonEncode', new TestExtension\JsonHelper());
}
#[Framework\Attributes\Test]
diff --git a/Tests/Functional/Renderer/Helper/RenderHelperTest.php b/Tests/Functional/Renderer/Helper/RenderHelperTest.php
index 6620713c..2f753247 100644
--- a/Tests/Functional/Renderer/Helper/RenderHelperTest.php
+++ b/Tests/Functional/Renderer/Helper/RenderHelperTest.php
@@ -57,11 +57,14 @@ protected function setUp(): void
{
parent::setUp();
+ $helperRegistry = new Src\Renderer\Helper\HelperRegistry(new Log\NullLogger());
+
$this->templateRootPath = 'EXT:test_extension/Resources/Templates/';
$this->templateResolver = new Src\Renderer\Template\FlatTemplateResolver($this->getTemplatePaths());
$this->renderer = new Src\Renderer\HandlebarsRenderer(
new Src\Cache\NullCache(),
new EventDispatcher\EventDispatcher(),
+ $helperRegistry,
new Log\NullLogger(),
$this->templateResolver,
);
@@ -75,7 +78,7 @@ protected function setUp(): void
$this->contentObjectRenderer,
);
- $this->renderer->registerHelper('render', $subject);
+ $helperRegistry->add('render', $subject);
}
#[Framework\Attributes\Test]
diff --git a/Tests/Unit/DataProcessing/AbstractDataProcessorTest.php b/Tests/Unit/DataProcessing/AbstractDataProcessorTest.php
index 539666c0..d17348da 100644
--- a/Tests/Unit/DataProcessing/AbstractDataProcessorTest.php
+++ b/Tests/Unit/DataProcessing/AbstractDataProcessorTest.php
@@ -60,6 +60,7 @@ protected function setUp(): void
new Src\Renderer\HandlebarsRenderer(
$this->getCache(),
new EventDispatcher\EventDispatcher(),
+ new Src\Renderer\Helper\HelperRegistry($this->logger),
$this->logger,
$this->getTemplateResolver(),
),
diff --git a/Tests/Unit/DataProcessing/SimpleProcessorTest.php b/Tests/Unit/DataProcessing/SimpleProcessorTest.php
index b06fc72f..dff95432 100644
--- a/Tests/Unit/DataProcessing/SimpleProcessorTest.php
+++ b/Tests/Unit/DataProcessing/SimpleProcessorTest.php
@@ -46,6 +46,7 @@ final class SimpleProcessorTest extends TestingFramework\Core\Unit\UnitTestCase
private Frontend\ContentObject\ContentObjectRenderer&Framework\MockObject\MockObject $contentObjectRendererMock;
private Log\Test\TestLogger $logger;
+ private Src\Renderer\Helper\HelperRegistry $helperRegistry;
private Src\Renderer\HandlebarsRenderer $renderer;
private Src\DataProcessing\SimpleProcessor $subject;
@@ -55,9 +56,11 @@ protected function setUp(): void
$this->contentObjectRendererMock = $this->createMock(Frontend\ContentObject\ContentObjectRenderer::class);
$this->logger = new Log\Test\TestLogger();
+ $this->helperRegistry = new Src\Renderer\Helper\HelperRegistry($this->logger);
$this->renderer = new Src\Renderer\HandlebarsRenderer(
$this->getCache(),
new EventDispatcher\EventDispatcher(),
+ $this->helperRegistry,
$this->logger,
$this->getTemplateResolver(),
);
@@ -88,7 +91,7 @@ public function processThrowsExceptionIfTemplatePathIsNotConfigured(array $confi
#[Framework\Attributes\Test]
public function processReturnsRenderedTemplate(): void
{
- $this->renderer->registerHelper('varDump', Src\Renderer\Helper\VarDumpHelper::class);
+ $this->helperRegistry->add('varDump', Src\Renderer\Helper\VarDumpHelper::class);
$this->contentObjectRendererMock->data = [
'uid' => 1,
diff --git a/Tests/Unit/Exception/HelperIsNotRegisteredTest.php b/Tests/Unit/Exception/HelperIsNotRegisteredTest.php
new file mode 100644
index 00000000..3651a5f6
--- /dev/null
+++ b/Tests/Unit/Exception/HelperIsNotRegisteredTest.php
@@ -0,0 +1,47 @@
+
+ *
+ * This program 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.
+ *
+ * This program 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+namespace Fr\Typo3Handlebars\Tests\Unit\Exception;
+
+use Fr\Typo3Handlebars as Src;
+use PHPUnit\Framework;
+use TYPO3\TestingFramework;
+
+/**
+ * HelperIsNotRegisteredTest
+ *
+ * @author Elias Häußler
+ * @license GPL-2.0-or-later
+ */
+#[Framework\Attributes\CoversClass(Src\Exception\HelperIsNotRegistered::class)]
+final class HelperIsNotRegisteredTest extends TestingFramework\Core\Unit\UnitTestCase
+{
+ #[Framework\Attributes\Test]
+ public function constructorReturnsExceptionForUnregisteredHelper(): void
+ {
+ $actual = new Src\Exception\HelperIsNotRegistered('foo');
+
+ self::assertSame('Handlebars helper "foo" is not registered.', $actual->getMessage());
+ self::assertSame(1736242470, $actual->getCode());
+ }
+}
diff --git a/Tests/Unit/Fixtures/Classes/Renderer/Helper/DummyHelper.php b/Tests/Unit/Fixtures/Classes/Renderer/Helper/DummyHelper.php
index 2ac29b35..b9455300 100644
--- a/Tests/Unit/Fixtures/Classes/Renderer/Helper/DummyHelper.php
+++ b/Tests/Unit/Fixtures/Classes/Renderer/Helper/DummyHelper.php
@@ -39,11 +39,6 @@ public function render(Renderer\Helper\Context\HelperContext $context): string
return 'foo';
}
- public function __invoke(): string
- {
- return 'foo';
- }
-
public static function staticExecute(): string
{
return 'foo';
diff --git a/Tests/Unit/Fixtures/Classes/Traits/DummyHandlebarsHelperTraitClass.php b/Tests/Unit/Fixtures/Classes/Renderer/Helper/DummyInvokableHelper.php
similarity index 74%
rename from Tests/Unit/Fixtures/Classes/Traits/DummyHandlebarsHelperTraitClass.php
rename to Tests/Unit/Fixtures/Classes/Renderer/Helper/DummyInvokableHelper.php
index 3da75ec6..5041d1c0 100644
--- a/Tests/Unit/Fixtures/Classes/Traits/DummyHandlebarsHelperTraitClass.php
+++ b/Tests/Unit/Fixtures/Classes/Renderer/Helper/DummyInvokableHelper.php
@@ -21,23 +21,19 @@
* along with this program. If not, see .
*/
-namespace Fr\Typo3Handlebars\Tests\Unit\Fixtures\Classes\Traits;
-
-use Fr\Typo3Handlebars\Traits;
-use Psr\Log;
+namespace Fr\Typo3Handlebars\Tests\Unit\Fixtures\Classes\Renderer\Helper;
/**
- * DummyHandlebarsHelperTraitClass
+ * DummyInvokableHelper
*
* @author Elias Häußler
* @license GPL-2.0-or-later
* @internal
*/
-final class DummyHandlebarsHelperTraitClass
+final readonly class DummyInvokableHelper
{
- use Traits\HandlebarsHelperTrait;
-
- public function __construct(
- private readonly Log\LoggerInterface $logger,
- ) {}
+ public function __invoke(): string
+ {
+ return 'foo';
+ }
}
diff --git a/Tests/Unit/Renderer/HandlebarsRendererTest.php b/Tests/Unit/Renderer/HandlebarsRendererTest.php
index dbe0629d..0b083e38 100644
--- a/Tests/Unit/Renderer/HandlebarsRendererTest.php
+++ b/Tests/Unit/Renderer/HandlebarsRendererTest.php
@@ -45,6 +45,7 @@ final class HandlebarsRendererTest extends TestingFramework\Core\Unit\UnitTestCa
use Tests\HandlebarsTemplateResolverTrait;
private Log\Test\TestLogger $logger;
+ private Src\Renderer\Helper\HelperRegistry $helperRegistry;
private Src\Renderer\HandlebarsRenderer $subject;
private Frontend\Controller\TypoScriptFrontendController&Framework\MockObject\MockObject $tsfeMock;
@@ -72,10 +73,7 @@ public function renderLogsCriticalErrorIfGivenTemplateIsNotAvailable(): void
}));
}
- /**
- * @test
- * @
- */
+ #[Framework\Attributes\Test]
public function renderLogsCriticalErrorIfGivenTemplateIsNotReadable(): void
{
$this->templateResolver = new Tests\Unit\Fixtures\Classes\Renderer\Template\DummyTemplateResolver();
@@ -106,7 +104,7 @@ public function renderReturnsEmptyStringIfGivenTemplateIsEmpty(): void
#[Framework\Attributes\Test]
public function renderMergesDefaultDataWithGivenData(): void
{
- $this->subject->registerHelper('varDump', Src\Renderer\Helper\VarDumpHelper::class);
+ $this->helperRegistry->add('varDump', Src\Renderer\Helper\VarDumpHelper::class);
$this->subject->setDefaultData([
'foo' => 'baz',
]);
@@ -229,6 +227,7 @@ public function resolvePartialReturnsNullIfNoPartialResolverIsRegistered(): void
$subject = new Src\Renderer\HandlebarsRenderer(
$this->getCache(),
new EventDispatcher\EventDispatcher(),
+ $this->helperRegistry,
$this->logger,
$this->getTemplateResolver(),
);
@@ -287,10 +286,12 @@ protected function tearDown(): void
private function renewSubject(string $rendererClass = Src\Renderer\HandlebarsRenderer::class): Src\Renderer\HandlebarsRenderer
{
$this->logger = new Log\Test\TestLogger();
+ $this->helperRegistry = new Src\Renderer\Helper\HelperRegistry($this->logger);
return $this->subject = new $rendererClass(
$this->getCache(),
new EventDispatcher\EventDispatcher(),
+ $this->helperRegistry,
$this->logger,
$this->getTemplateResolver(),
$this->getPartialResolver(),
diff --git a/Tests/Unit/Traits/HandlebarsHelperTraitTest.php b/Tests/Unit/Renderer/Helper/HelperRegistryTest.php
similarity index 51%
rename from Tests/Unit/Traits/HandlebarsHelperTraitTest.php
rename to Tests/Unit/Renderer/Helper/HelperRegistryTest.php
index fac608f8..9b9afe54 100644
--- a/Tests/Unit/Traits/HandlebarsHelperTraitTest.php
+++ b/Tests/Unit/Renderer/Helper/HelperRegistryTest.php
@@ -21,7 +21,7 @@
* along with this program. If not, see .
*/
-namespace Fr\Typo3Handlebars\Tests\Unit\Traits;
+namespace Fr\Typo3Handlebars\Tests\Unit\Renderer\Helper;
use Fr\Typo3Handlebars as Src;
use Fr\Typo3Handlebars\Tests;
@@ -29,74 +29,127 @@
use Psr\Log;
use TYPO3\TestingFramework;
+use function trim;
+
/**
- * HandlebarsHelperTraitTest
+ * HelperRegistryTest
*
* @author Elias Häußler
* @license GPL-2.0-or-later
*/
-#[Framework\Attributes\CoversClass(Src\Traits\HandlebarsHelperTrait::class)]
-final class HandlebarsHelperTraitTest extends TestingFramework\Core\Unit\UnitTestCase
+#[Framework\Attributes\CoversClass(Src\Renderer\Helper\HelperRegistry::class)]
+final class HelperRegistryTest extends TestingFramework\Core\Unit\UnitTestCase
{
private Log\Test\TestLogger $logger;
- private Tests\Unit\Fixtures\Classes\Traits\DummyHandlebarsHelperTraitClass $subject;
+ private Src\Renderer\Helper\HelperRegistry $subject;
protected function setUp(): void
{
parent::setUp();
$this->logger = new Log\Test\TestLogger();
- $this->subject = new Tests\Unit\Fixtures\Classes\Traits\DummyHandlebarsHelperTraitClass($this->logger);
+ $this->subject = new Src\Renderer\Helper\HelperRegistry($this->logger);
}
#[Framework\Attributes\Test]
- #[Framework\Attributes\DataProvider('registerHelperLogsCriticalErrorIfGivenHelperIsInvalidDataProvider')]
- public function registerHelperLogsCriticalErrorIfGivenHelperIsInvalid(mixed $function): void
+ #[Framework\Attributes\DataProvider('addLogsCriticalErrorIfGivenHelperIsInvalidDataProvider')]
+ public function addLogsCriticalErrorIfGivenHelperIsInvalid(mixed $function): void
{
- $this->subject->registerHelper('foo', $function);
+ $this->subject->add('foo', $function);
+
self::assertTrue($this->logger->hasCriticalThatPasses(function ($logRecord) use ($function) {
self::assertSame('Error while registering Handlebars helper "foo".', $logRecord['message']);
self::assertSame('foo', $logRecord['context']['name']);
self::assertSame($function, $logRecord['context']['function']);
return true;
}));
- self::assertSame([], $this->subject->getHelpers());
+ self::assertSame([], $this->subject->getAll());
}
#[Framework\Attributes\Test]
- #[Framework\Attributes\DataProvider('registerHelperRegistersHelperCorrectlyDataProvider')]
- public function registerHelperRegistersHelperCorrectly(mixed $function, callable $expectedCallable): void
+ #[Framework\Attributes\DataProvider('addRegistersHelperCorrectlyDataProvider')]
+ public function addRegistersHelperCorrectly(mixed $function, callable $expectedCallable): void
{
- $this->subject->registerHelper('foo', $function);
+ $this->subject->add('foo', $function);
$expected = $this->mapExpectedCallable($expectedCallable);
- self::assertEquals(['foo' => $expected], $this->subject->getHelpers());
+ self::assertEquals($expected, $this->subject->get('foo'));
+ }
+
+ #[Framework\Attributes\Test]
+ public function addDecoratesHelperFunction(): void
+ {
+ $function = static fn(Src\Renderer\Helper\Context\HelperContext $context) => $context[0] . $context['foo'];
+
+ $options = [
+ 'hash' => [
+ 'foo' => 'baz',
+ ],
+ 'contexts' => [null],
+ '_this' => [],
+ 'data' => [],
+ ];
+
+ $this->subject->add('foo', $function);
+
+ self::assertSame('foobaz', $this->subject->get('foo')('foo', $options));
+ }
+
+ #[Framework\Attributes\Test]
+ public function addOverridesAvailableHelper(): void
+ {
+ $this->subject->add('foo', 'trim');
+
+ self::assertEquals($this->mapExpectedCallable(trim(...)), $this->subject->get('foo'));
+
+ $this->subject->add('foo', 'strtolower');
+
+ self::assertEquals($this->mapExpectedCallable(strtolower(...)), $this->subject->get('foo'));
+ }
+
+ #[Framework\Attributes\Test]
+ public function getThrowsExceptionIfGivenHelperIsNotRegistered(): void
+ {
+ $this->expectExceptionObject(
+ new Src\Exception\HelperIsNotRegistered('foo'),
+ );
+
+ $this->subject->get('foo');
}
#[Framework\Attributes\Test]
- public function registerHelperOverridesAvailableHelper(): void
+ public function getReturnsRegisteredHelper(): void
{
- $this->subject->registerHelper('foo', 'trim');
- self::assertEquals(['foo' => $this->mapExpectedCallable(trim(...))], $this->subject->getHelpers());
+ $this->subject->add('foo', 'trim');
+
+ self::assertEquals($this->mapExpectedCallable(trim(...)), $this->subject->get('foo'));
+ }
+
+ #[Framework\Attributes\Test]
+ public function getAllReturnsRegisteredHelpers(): void
+ {
+ self::assertSame([], $this->subject->getAll());
+
+ $this->subject->add('foo', 'strtolower');
- $this->subject->registerHelper('foo', 'strtolower');
- self::assertEquals(['foo' => $this->mapExpectedCallable(strtolower(...))], $this->subject->getHelpers());
+ self::assertEquals(['foo' => $this->mapExpectedCallable(strtolower(...))], $this->subject->getAll());
}
#[Framework\Attributes\Test]
- public function getHelpersReturnsRegisteredHelpers(): void
+ public function hasReturnsTrueIfGivenHelperIsRegistered(): void
{
- self::assertSame([], $this->subject->getHelpers());
+ self::assertFalse($this->subject->has('foo'));
- $this->subject->registerHelper('foo', 'strtolower');
- self::assertEquals(['foo' => $this->mapExpectedCallable(strtolower(...))], $this->subject->getHelpers());
+ $this->subject->add('foo', 'strtolower');
+
+ self::assertTrue($this->subject->has('foo'));
}
/**
* @return \Generator
*/
- public static function registerHelperLogsCriticalErrorIfGivenHelperIsInvalidDataProvider(): \Generator
+ public static function addLogsCriticalErrorIfGivenHelperIsInvalidDataProvider(): \Generator
{
yield 'null value' => [null];
yield 'non-callable function as string' => ['foo_baz'];
@@ -108,37 +161,53 @@ public static function registerHelperLogsCriticalErrorIfGivenHelperIsInvalidData
/**
* @return \Generator
*/
- public static function registerHelperRegistersHelperCorrectlyDataProvider(): \Generator
+ public static function addRegistersHelperCorrectlyDataProvider(): \Generator
{
- yield 'callable function as string' => [
+ yield 'callable as string' => [
'trim',
trim(...),
];
yield 'invokable class as string' => [
+ Tests\Unit\Fixtures\Classes\Renderer\Helper\DummyInvokableHelper::class,
+ new Tests\Unit\Fixtures\Classes\Renderer\Helper\DummyInvokableHelper(),
+ ];
+ yield 'class implementing Helper interface as string' => [
Tests\Unit\Fixtures\Classes\Renderer\Helper\DummyHelper::class,
- new Tests\Unit\Fixtures\Classes\Renderer\Helper\DummyHelper(),
+ (new Tests\Unit\Fixtures\Classes\Renderer\Helper\DummyHelper())->render(...),
+ ];
+ yield 'callable as closure' => [
+ static fn() => 'foo',
+ static fn() => 'foo',
+ ];
+ yield 'callable as first class callable syntax' => [
+ trim(...),
+ trim(...),
];
yield 'invokable class as object' => [
+ new Tests\Unit\Fixtures\Classes\Renderer\Helper\DummyInvokableHelper(),
+ new Tests\Unit\Fixtures\Classes\Renderer\Helper\DummyInvokableHelper(),
+ ];
+ yield 'class implementing Helper interface as object' => [
new Tests\Unit\Fixtures\Classes\Renderer\Helper\DummyHelper(),
- new Tests\Unit\Fixtures\Classes\Renderer\Helper\DummyHelper(),
+ (new Tests\Unit\Fixtures\Classes\Renderer\Helper\DummyHelper())->render(...),
];
- yield 'callable static class method' => [
+ yield 'static class method as string' => [
Tests\Unit\Fixtures\Classes\Renderer\Helper\DummyHelper::class . '::staticExecute',
[Tests\Unit\Fixtures\Classes\Renderer\Helper\DummyHelper::class, 'staticExecute'],
];
- yield 'callable non-static class method' => [
+ yield 'non-static class method as string' => [
Tests\Unit\Fixtures\Classes\Renderer\Helper\DummyHelper::class . '::execute',
[new Tests\Unit\Fixtures\Classes\Renderer\Helper\DummyHelper(), 'execute'],
];
- yield 'callable static class method in array syntax' => [
+ yield 'static class method as array' => [
[Tests\Unit\Fixtures\Classes\Renderer\Helper\DummyHelper::class, 'staticExecute'],
[Tests\Unit\Fixtures\Classes\Renderer\Helper\DummyHelper::class, 'staticExecute'],
];
- yield 'callable non-static class method in array syntax' => [
+ yield 'non-static class method as array' => [
[Tests\Unit\Fixtures\Classes\Renderer\Helper\DummyHelper::class, 'execute'],
[new Tests\Unit\Fixtures\Classes\Renderer\Helper\DummyHelper(), 'execute'],
];
- yield 'callable non-static class method in initialized array syntax' => [
+ yield 'class method as initialized array' => [
[new Tests\Unit\Fixtures\Classes\Renderer\Helper\DummyHelper(), 'execute'],
[new Tests\Unit\Fixtures\Classes\Renderer\Helper\DummyHelper(), 'execute'],
];
diff --git a/rector.php b/rector.php
index c308af5d..b7e879ee 100644
--- a/rector.php
+++ b/rector.php
@@ -46,7 +46,7 @@
$rectorConfig->skip([
FirstClassCallableRector::class => [
__DIR__ . '/Tests/Functional/Renderer/Helper/RenderHelperTest.php',
- __DIR__ . '/Tests/Unit/Traits/HandlebarsHelperTraitTest.php',
+ __DIR__ . '/Tests/Unit/Renderer/Helper/HelperRegistryTest.php',
],
// @todo Remove once code is rewritten