diff --git a/Classes/Attribute/AsHelper.php b/Classes/Attribute/AsHelper.php new file mode 100644 index 00000000..ba36ce68 --- /dev/null +++ b/Classes/Attribute/AsHelper.php @@ -0,0 +1,41 @@ + + * + * 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\Attribute; + +/** + * AsHelper + * + * @author Elias Häußler + * @license GPL-2.0-or-later + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)] +final readonly class AsHelper +{ + public const TAG_NAME = 'handlebars.helper'; + + public function __construct( + public string $identifier, + public ?string $method = null, + ) {} +} diff --git a/Classes/Cache/HandlebarsCache.php b/Classes/Cache/HandlebarsCache.php index c1fa5615..f599aae9 100644 --- a/Classes/Cache/HandlebarsCache.php +++ b/Classes/Cache/HandlebarsCache.php @@ -23,6 +23,8 @@ namespace Fr\Typo3Handlebars\Cache; +use Symfony\Component\DependencyInjection\Attribute\AsAlias; +use Symfony\Component\DependencyInjection\Attribute\Autowire; use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; /** @@ -31,9 +33,11 @@ * @author Elias Häußler * @license GPL-2.0-or-later */ +#[AsAlias('handlebars.cache')] class HandlebarsCache implements CacheInterface { public function __construct( + #[Autowire('@cache.handlebars')] protected readonly FrontendInterface $cache, ) {} diff --git a/Classes/Compatibility/View/HandlebarsViewResolver.php b/Classes/Compatibility/View/HandlebarsViewResolver.php index 1ca84d24..cc91c12d 100644 --- a/Classes/Compatibility/View/HandlebarsViewResolver.php +++ b/Classes/Compatibility/View/HandlebarsViewResolver.php @@ -25,6 +25,7 @@ use Fr\Typo3Handlebars\DataProcessing\DataProcessorInterface; use Psr\Http\Message\ServerRequestInterface; +use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Mvc\View\GenericViewResolver; use TYPO3Fluid\Fluid\View\ViewInterface; @@ -35,6 +36,7 @@ * @author Elias Häußler * @license GPL-2.0-or-later */ +#[Autoconfigure(public: true)] class HandlebarsViewResolver extends GenericViewResolver { /** diff --git a/Classes/DataProcessing/SimpleProcessor.php b/Classes/DataProcessing/SimpleProcessor.php index 4de8ad1b..90c3aba1 100644 --- a/Classes/DataProcessing/SimpleProcessor.php +++ b/Classes/DataProcessing/SimpleProcessor.php @@ -27,6 +27,7 @@ use Fr\Typo3Handlebars\Renderer\RendererInterface; use Fr\Typo3Handlebars\Traits\ErrorHandlingTrait; use Psr\Log\LoggerInterface; +use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; /** @@ -35,6 +36,7 @@ * @author Elias Häußler * @license GPL-2.0-or-later */ +#[Autoconfigure(public: true)] class SimpleProcessor implements DataProcessorInterface { use ErrorHandlingTrait; diff --git a/Classes/Renderer/HandlebarsRenderer.php b/Classes/Renderer/HandlebarsRenderer.php index 41038fc8..58fd49dd 100644 --- a/Classes/Renderer/HandlebarsRenderer.php +++ b/Classes/Renderer/HandlebarsRenderer.php @@ -38,6 +38,9 @@ use LightnCandy\Runtime; use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Log\LoggerInterface; +use Symfony\Component\DependencyInjection\Attribute\AsAlias; +use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; +use Symfony\Component\DependencyInjection\Attribute\Autowire; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController; @@ -47,6 +50,8 @@ * @author Elias Häußler * @license GPL-2.0-or-later */ +#[AsAlias('handlebars.renderer')] +#[Autoconfigure(tags: ['handlebars.renderer'])] class HandlebarsRenderer implements RendererInterface, HelperAwareInterface { use HandlebarsHelperTrait; @@ -57,11 +62,15 @@ class HandlebarsRenderer implements RendererInterface, HelperAwareInterface * @param array $defaultData */ public function __construct( + #[Autowire('@handlebars.cache')] protected readonly CacheInterface $cache, protected readonly EventDispatcherInterface $eventDispatcher, protected readonly LoggerInterface $logger, + #[Autowire('@handlebars.template_resolver')] protected readonly TemplateResolverInterface $templateResolver, + #[Autowire('@handlebars.partial_resolver')] protected readonly ?TemplateResolverInterface $partialResolver = null, + #[Autowire('%handlebars.default_data%')] protected array $defaultData = [], ) { $this->debugMode = $this->isDebugModeEnabled(); diff --git a/Classes/Renderer/Helper/BlockHelper.php b/Classes/Renderer/Helper/BlockHelper.php index 09655919..b3a56798 100644 --- a/Classes/Renderer/Helper/BlockHelper.php +++ b/Classes/Renderer/Helper/BlockHelper.php @@ -23,6 +23,7 @@ namespace Fr\Typo3Handlebars\Renderer\Helper; +use Fr\Typo3Handlebars\Attribute; use Fr\Typo3Handlebars\Exception; use Fr\Typo3Handlebars\Renderer; @@ -33,12 +34,13 @@ * @license GPL-2.0-or-later * @see https://github.com/shannonmoeller/handlebars-layouts#block-name */ -class BlockHelper implements HelperInterface +final readonly class BlockHelper implements HelperInterface { /** * @param array $options * @throws Exception\UnsupportedTypeException */ + #[Attribute\AsHelper('block')] public function evaluate(string $name, array $options): string { $data = $options['_this']; diff --git a/Classes/Renderer/Helper/ContentHelper.php b/Classes/Renderer/Helper/ContentHelper.php index 9aef4a8e..971bea46 100644 --- a/Classes/Renderer/Helper/ContentHelper.php +++ b/Classes/Renderer/Helper/ContentHelper.php @@ -23,6 +23,7 @@ namespace Fr\Typo3Handlebars\Renderer\Helper; +use Fr\Typo3Handlebars\Attribute; use Fr\Typo3Handlebars\Renderer; use Psr\Log; @@ -33,16 +34,17 @@ * @license GPL-2.0-or-later * @see https://github.com/shannonmoeller/handlebars-layouts#content-name-modeappendprependreplace */ -class ContentHelper implements HelperInterface +final readonly class ContentHelper implements HelperInterface { public function __construct( - protected readonly Log\LoggerInterface $logger, + private Log\LoggerInterface $logger, ) {} /** * @param array $options * @return string|bool */ + #[Attribute\AsHelper('content')] public function evaluate(string $name, array $options) { $data = $options['_this']; diff --git a/Classes/Renderer/Helper/ExtendHelper.php b/Classes/Renderer/Helper/ExtendHelper.php index 5d869885..0b9de3e0 100644 --- a/Classes/Renderer/Helper/ExtendHelper.php +++ b/Classes/Renderer/Helper/ExtendHelper.php @@ -23,6 +23,7 @@ namespace Fr\Typo3Handlebars\Renderer\Helper; +use Fr\Typo3Handlebars\Attribute; use Fr\Typo3Handlebars\Renderer; /** @@ -32,12 +33,13 @@ * @license GPL-2.0-or-later * @see https://github.com/shannonmoeller/handlebars-layouts#extend-partial-context-keyvalue- */ -class ExtendHelper implements HelperInterface +final readonly class ExtendHelper implements HelperInterface { public function __construct( - protected readonly Renderer\RendererInterface $renderer, + private Renderer\RendererInterface $renderer, ) {} + #[Attribute\AsHelper('extend')] public function evaluate(string $name): string { // Get helper options diff --git a/Classes/Renderer/Helper/RenderHelper.php b/Classes/Renderer/Helper/RenderHelper.php index 1ebfccd4..d4d5c164 100644 --- a/Classes/Renderer/Helper/RenderHelper.php +++ b/Classes/Renderer/Helper/RenderHelper.php @@ -23,6 +23,7 @@ namespace Fr\Typo3Handlebars\Renderer\Helper; +use Fr\Typo3Handlebars\Attribute; use Fr\Typo3Handlebars\DataProcessing; use Fr\Typo3Handlebars\Exception; use Fr\Typo3Handlebars\Renderer; @@ -37,17 +38,18 @@ * @license GPL-2.0-or-later * @see https://github.com/frctl/fractal/blob/main/packages/handlebars/src/helpers/render.js */ -class RenderHelper implements HelperInterface +final readonly class RenderHelper implements HelperInterface { public function __construct( - protected readonly Renderer\RendererInterface $renderer, - protected readonly Core\TypoScript\TypoScriptService $typoScriptService, - protected readonly Frontend\ContentObject\ContentObjectRenderer $contentObjectRenderer, + private Renderer\RendererInterface $renderer, + private Core\TypoScript\TypoScriptService $typoScriptService, + private Frontend\ContentObject\ContentObjectRenderer $contentObjectRenderer, ) {} /** * @throws Exception\InvalidConfigurationException */ + #[Attribute\AsHelper('render')] public function evaluate(string $name): SafeString { // Get helper options diff --git a/Classes/Renderer/Helper/VarDumpHelper.php b/Classes/Renderer/Helper/VarDumpHelper.php index a1f57645..abd60f41 100644 --- a/Classes/Renderer/Helper/VarDumpHelper.php +++ b/Classes/Renderer/Helper/VarDumpHelper.php @@ -23,7 +23,8 @@ namespace Fr\Typo3Handlebars\Renderer\Helper; -use TYPO3\CMS\Core\Utility\DebugUtility; +use Fr\Typo3Handlebars\Attribute; +use TYPO3\CMS\Core; /** * VarDumpHelper @@ -31,16 +32,17 @@ * @author Elias Häußler * @license GPL-2.0-or-later */ -class VarDumpHelper implements HelperInterface +final readonly class VarDumpHelper implements HelperInterface { /** * @param array $context */ + #[Attribute\AsHelper('varDump')] public static function evaluate(array $context): string { \ob_start(); - DebugUtility::debug($context['_this']); + Core\Utility\DebugUtility::debug($context['_this']); return (string)\ob_get_clean(); } diff --git a/Classes/Renderer/Template/TemplatePaths.php b/Classes/Renderer/Template/TemplatePaths.php index 1524c758..e9285aab 100644 --- a/Classes/Renderer/Template/TemplatePaths.php +++ b/Classes/Renderer/Template/TemplatePaths.php @@ -24,6 +24,7 @@ namespace Fr\Typo3Handlebars\Renderer\Template; use Fr\Typo3Handlebars\Configuration\Extension; +use Symfony\Component\DependencyInjection\Attribute\Autowire; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; @@ -51,6 +52,10 @@ class TemplatePaths */ public function __construct( protected readonly ConfigurationManagerInterface $configurationManager, + #[Autowire([ + self::TEMPLATES => '%handlebars.template_root_paths%', + self::PARTIALS => '%handlebars.partial_root_paths%', + ])] protected readonly array $viewConfiguration = [], protected readonly string $type = self::TEMPLATES, ) {} diff --git a/Configuration/Services.php b/Configuration/Services.php index 2cbbe088..f55ab794 100644 --- a/Configuration/Services.php +++ b/Configuration/Services.php @@ -23,13 +23,28 @@ namespace Fr\Typo3Handlebars\DependencyInjection; +use Fr\Typo3Handlebars\Attribute\AsHelper; use Fr\Typo3Handlebars\DependencyInjection\Extension\HandlebarsExtension; +use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; 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('handlebars.helper', 'handlebars.renderer')); + $container->addCompilerPass(new HandlebarsHelperPass(AsHelper::TAG_NAME, 'handlebars.renderer')); $container->addCompilerPass(new FeatureRegistrationPass(), priority: 30); + + $container->registerAttributeForAutoconfiguration( + AsHelper::class, + static function (ChildDefinition $definition, AsHelper $attribute, \Reflector $reflector): void { + $definition->addTag( + AsHelper::TAG_NAME, + [ + 'identifier' => $attribute->identifier, + 'method' => $attribute->method ?? ($reflector instanceof \ReflectionMethod ? $reflector->getName() : '__invoke'), + ], + ); + }, + ); }; diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index fc4f9d8c..5ce43453 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -9,18 +9,12 @@ services: exclude: - '../Classes/DependencyInjection/*' - # Renderer - handlebars.renderer: - class: 'Fr\Typo3Handlebars\Renderer\HandlebarsRenderer' - arguments: - $templateResolver: '@handlebars.template_resolver' - $partialResolver: '@handlebars.partial_resolver' - $cache: '@handlebars.cache' - $defaultData: '%handlebars.default_data%' - tags: ['handlebars.renderer'] - + Fr\Typo3Handlebars\Cache\CacheInterface: + alias: 'handlebars.cache' Fr\Typo3Handlebars\Renderer\RendererInterface: alias: 'handlebars.renderer' + Fr\Typo3Handlebars\Renderer\Template\TemplateResolverInterface: + alias: 'handlebars.template_resolver' # Template handlebars.template_resolver: @@ -33,15 +27,8 @@ services: arguments: $templateRootPaths: '@handlebars.template_paths.partial_root_paths' - Fr\Typo3Handlebars\Renderer\Template\TemplateResolverInterface: - alias: 'handlebars.template_resolver' - handlebars.template_paths: class: 'Fr\Typo3Handlebars\Renderer\Template\TemplatePaths' - arguments: - $viewConfiguration: - template_root_paths: '%handlebars.template_root_paths%' - partial_root_paths: '%handlebars.partial_root_paths%' handlebars.template_paths.template_root_paths: parent: 'handlebars.template_paths' arguments: @@ -51,35 +38,12 @@ services: arguments: $type: 'partial_root_paths' - # Data processor - Fr\Typo3Handlebars\DataProcessing\SimpleProcessor: - public: true - - # Handlebars Helper - Fr\Typo3Handlebars\Renderer\Helper\VarDumpHelper: - tags: - - name: handlebars.helper - identifier: 'varDump' - method: 'evaluate' - # Cache - handlebars.cache: - class: 'Fr\Typo3Handlebars\Cache\HandlebarsCache' - arguments: - $cache: '@cache.handlebars' - - Fr\Typo3Handlebars\Cache\CacheInterface: - alias: 'handlebars.cache' - cache.handlebars: class: TYPO3\CMS\Core\Cache\Frontend\FrontendInterface factory: ['@TYPO3\CMS\Core\Cache\CacheManager', 'getCache'] arguments: ['handlebars'] - # Compatibility - Fr\Typo3Handlebars\Compatibility\View\HandlebarsViewResolver: - public: true - handlebars: default_data: [] template: diff --git a/Tests/Functional/Fixtures/test_extension/Classes/JsonHelper.php b/Tests/Functional/Fixtures/test_extension/Classes/JsonHelper.php index 75794046..4aabe29f 100644 --- a/Tests/Functional/Fixtures/test_extension/Classes/JsonHelper.php +++ b/Tests/Functional/Fixtures/test_extension/Classes/JsonHelper.php @@ -23,6 +23,7 @@ namespace Fr\Typo3Handlebars\TestExtension; +use Fr\Typo3Handlebars\Attribute; use Fr\Typo3Handlebars\Renderer; use LightnCandy\SafeString; @@ -37,6 +38,7 @@ final class JsonHelper implements Renderer\Helper\HelperInterface /** * @param array $context */ + #[Attribute\AsHelper('jsonEncode')] public function encode(array $context): SafeString { return new SafeString(json_encode($context['_this'], JSON_THROW_ON_ERROR)); diff --git a/Tests/Functional/Fixtures/test_extension/Configuration/Services.yaml b/Tests/Functional/Fixtures/test_extension/Configuration/Services.yaml new file mode 100644 index 00000000..8c54789c --- /dev/null +++ b/Tests/Functional/Fixtures/test_extension/Configuration/Services.yaml @@ -0,0 +1,7 @@ +services: + _defaults: + autowire: true + autoconfigure: true + + Fr\Typo3Handlebars\TestExtension\: + resource: '../Classes/*' diff --git a/Tests/Unit/DependencyInjection/FeatureRegistrationPassTest.php b/Tests/Unit/DependencyInjection/FeatureRegistrationPassTest.php index 8359f6ea..90a8951c 100644 --- a/Tests/Unit/DependencyInjection/FeatureRegistrationPassTest.php +++ b/Tests/Unit/DependencyInjection/FeatureRegistrationPassTest.php @@ -26,6 +26,7 @@ use Fr\Typo3Handlebars as Src; use Fr\Typo3Handlebars\Tests; use PHPUnit\Framework; +use Psr\Container; use Psr\Log; use Symfony\Component\Config; use Symfony\Component\DependencyInjection; @@ -136,6 +137,9 @@ private function buildContainer(): DependencyInjection\ContainerBuilder $container->register(Frontend\ContentObject\ContentObjectRenderer::class); $container->register(Log\LoggerInterface::class, Log\NullLogger::class); + // Aliases + $container->setAlias(Container\ContainerInterface::class, 'service_container'); + // Provide dummy extension configuration class $dummyExtensionConfiguration = new Tests\Unit\Fixtures\Classes\DummyExtensionConfiguration($this->activatedFeatures); $container->set(Core\Configuration\ExtensionConfiguration::class, $dummyExtensionConfiguration);