From af98952aecf1f4833797fbcc3ef27754542ddce8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=A4u=C3=9Fler?= Date: Wed, 18 Sep 2024 22:38:19 +0200 Subject: [PATCH] [FEATURE] Provide extension configuration to enable extended features --- .../FeatureRegistrationPass.php | 93 ++++++++++ Configuration/Services.php | 1 + .../FeatureRegistrationPassTest.php | 163 ++++++++++++++++++ .../Classes/DummyConfigurationManager.php | 18 +- .../Classes/DummyExtensionConfiguration.php | 63 +++++++ .../feature-registration-services.yaml | 8 + ext_conf_template.txt | 14 ++ 7 files changed, 349 insertions(+), 11 deletions(-) create mode 100644 Classes/DependencyInjection/FeatureRegistrationPass.php create mode 100644 Tests/Unit/DependencyInjection/FeatureRegistrationPassTest.php create mode 100644 Tests/Unit/Fixtures/Classes/DummyExtensionConfiguration.php create mode 100644 Tests/Unit/Fixtures/Services/feature-registration-services.yaml create mode 100644 ext_conf_template.txt diff --git a/Classes/DependencyInjection/FeatureRegistrationPass.php b/Classes/DependencyInjection/FeatureRegistrationPass.php new file mode 100644 index 00000000..98b25759 --- /dev/null +++ b/Classes/DependencyInjection/FeatureRegistrationPass.php @@ -0,0 +1,93 @@ + + * + * 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\DependencyInjection; + +use Fr\Typo3Handlebars\Configuration; +use Fr\Typo3Handlebars\Renderer; +use Symfony\Component\DependencyInjection; +use TYPO3\CMS\Core; + +/** + * FeatureRegistrationPass + * + * @author Elias Häußler + * @license GPL-2.0-or-later + * @internal + */ +final class FeatureRegistrationPass implements DependencyInjection\Compiler\CompilerPassInterface +{ + private DependencyInjection\ContainerBuilder $container; + private Core\Configuration\ExtensionConfiguration $extensionConfiguration; + + public function process(DependencyInjection\ContainerBuilder $container): void + { + $this->container = $container; + $this->extensionConfiguration = $this->container->get(Core\Configuration\ExtensionConfiguration::class); + + if ($this->isFeatureEnabled('blockHelper')) { + $this->activateHelper('block', Renderer\Helper\BlockHelper::class); + } + if ($this->isFeatureEnabled('contentHelper')) { + $this->activateHelper('content', Renderer\Helper\ContentHelper::class); + } + if ($this->isFeatureEnabled('extendHelper')) { + $this->activateHelper('extend', Renderer\Helper\ExtendHelper::class); + } + if ($this->isFeatureEnabled('renderHelper')) { + $this->activateHelper('render', Renderer\Helper\RenderHelper::class); + } + if ($this->isFeatureEnabled('flatTemplateResolver')) { + $this->activateFlatTemplateResolver(); + } + } + + /** + * @param class-string $className + */ + private function activateHelper(string $name, string $className, string $methodName = 'evaluate'): void + { + $definition = $this->container->getDefinition($className); + $definition->addTag('handlebars.helper', [ + 'identifier' => $name, + 'method' => $methodName, + ]); + } + + private function activateFlatTemplateResolver(): void + { + $this->container->getDefinition('handlebars.template_resolver')->setClass(Renderer\Template\FlatTemplateResolver::class); + $this->container->getDefinition('handlebars.partial_resolver')->setClass(Renderer\Template\FlatTemplateResolver::class); + } + + private function isFeatureEnabled(string $featureName): bool + { + $configurationPath = sprintf('features/%s/enable', $featureName); + + try { + return (bool)$this->extensionConfiguration->get(Configuration\Extension::KEY, $configurationPath); + } catch (Core\Exception) { + return false; + } + } +} diff --git a/Configuration/Services.php b/Configuration/Services.php index cdef0e55..2cbbe088 100644 --- a/Configuration/Services.php +++ b/Configuration/Services.php @@ -31,4 +31,5 @@ $container->registerExtension(new HandlebarsExtension()); $container->addCompilerPass(new DataProcessorPass('handlebars.processor', 'handlebars.compatibility_layer')); $container->addCompilerPass(new HandlebarsHelperPass('handlebars.helper', 'handlebars.renderer')); + $container->addCompilerPass(new FeatureRegistrationPass(), priority: 30); }; diff --git a/Tests/Unit/DependencyInjection/FeatureRegistrationPassTest.php b/Tests/Unit/DependencyInjection/FeatureRegistrationPassTest.php new file mode 100644 index 00000000..67f7f110 --- /dev/null +++ b/Tests/Unit/DependencyInjection/FeatureRegistrationPassTest.php @@ -0,0 +1,163 @@ + + * + * 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\DependencyInjection; + +use Fr\Typo3Handlebars as Src; +use Fr\Typo3Handlebars\Tests; +use PHPUnit\Framework; +use Psr\Log; +use Symfony\Component\Config; +use Symfony\Component\DependencyInjection; +use TYPO3\CMS\Core; +use TYPO3\CMS\Frontend; +use TYPO3\TestingFramework; + +/** + * FeatureRegistrationPassTest + * + * @author Elias Häußler + * @license GPL-2.0-or-later + */ +#[Framework\Attributes\CoversClass(Src\DependencyInjection\FeatureRegistrationPass::class)] +final class FeatureRegistrationPassTest extends TestingFramework\Core\Unit\UnitTestCase +{ + /** + * @var array + */ + protected array $activatedFeatures = [ + 'blockHelper' => false, + 'contentHelper' => false, + 'extendHelper' => false, + 'renderHelper' => false, + 'flatTemplateResolver' => false, + ]; + + #[Framework\Attributes\Test] + public function processDoesNotActivateDisabledFeatures(): void + { + $container = $this->buildContainer(); + + self::assertSame([], $container->findTaggedServiceIds('handlebars.helper')); + self::assertNotInstanceOf(Src\Renderer\Template\FlatTemplateResolver::class, $container->get('handlebars.template_resolver')); + self::assertNotInstanceOf(Src\Renderer\Template\FlatTemplateResolver::class, $container->get('handlebars.partial_resolver')); + } + + #[Framework\Attributes\Test] + public function processActivatesEnabledHelpers(): void + { + $this->activatedFeatures['blockHelper'] = true; + $this->activatedFeatures['contentHelper'] = true; + $this->activatedFeatures['extendHelper'] = true; + $this->activatedFeatures['renderHelper'] = true; + + $container = $this->buildContainer(); + + self::assertCount(4, $container->findTaggedServiceIds('handlebars.helper')); + self::assertHelperIsTagged($container, Src\Renderer\Helper\BlockHelper::class, 'block'); + self::assertHelperIsTagged($container, Src\Renderer\Helper\ContentHelper::class, 'content'); + self::assertHelperIsTagged($container, Src\Renderer\Helper\ExtendHelper::class, 'extend'); + self::assertHelperIsTagged($container, Src\Renderer\Helper\RenderHelper::class, 'render'); + } + + #[Framework\Attributes\Test] + public function processActivatesEnabledTemplateResolvers(): void + { + $this->activatedFeatures['flatTemplateResolver'] = true; + + $container = $this->buildContainer(); + + self::assertSame([], $container->findTaggedServiceIds('handlebars.helper')); + self::assertInstanceOf(Src\Renderer\Template\FlatTemplateResolver::class, $container->get('handlebars.template_resolver')); + self::assertInstanceOf(Src\Renderer\Template\FlatTemplateResolver::class, $container->get('handlebars.partial_resolver')); + } + + #[Framework\Attributes\Test] + public function processDoesNotActivateFeaturesIfExtensionConfigurationIsMissing(): void + { + unset($this->activatedFeatures['blockHelper']); + unset($this->activatedFeatures['contentHelper']); + unset($this->activatedFeatures['extendHelper']); + unset($this->activatedFeatures['renderHelper']); + unset($this->activatedFeatures['flatTemplateResolver']); + + $container = $this->buildContainer(); + + self::assertSame([], $container->findTaggedServiceIds('handlebars.helper')); + self::assertNotInstanceOf(Src\Renderer\Template\FlatTemplateResolver::class, $container->get('handlebars.template_resolver')); + self::assertNotInstanceOf(Src\Renderer\Template\FlatTemplateResolver::class, $container->get('handlebars.partial_resolver')); + } + + /** + * @param class-string $className + */ + private static function assertHelperIsTagged(DependencyInjection\ContainerBuilder $container, string $className, string $name): void + { + $serviceIds = $container->findTaggedServiceIds('handlebars.helper'); + $expectedConfiguration = [ + 'identifier' => $name, + 'method' => 'evaluate', + ]; + + self::assertArrayHasKey($className, $serviceIds); + self::assertSame($expectedConfiguration, $serviceIds[$className][0]); + } + + private function buildContainer(): DependencyInjection\ContainerBuilder + { + $container = new DependencyInjection\ContainerBuilder(); + $container->registerExtension(new Src\DependencyInjection\Extension\HandlebarsExtension()); + + $yamlFileLoader = new DependencyInjection\Loader\YamlFileLoader($container, new Config\FileLocator(\dirname(__DIR__) . '/Fixtures/Services')); + $yamlFileLoader->load('feature-registration-services.yaml'); + + // Constructor arguments of helpers + $container->register(Core\TypoScript\TypoScriptService::class); + $container->register(Frontend\ContentObject\ContentObjectRenderer::class); + $container->register(Log\LoggerInterface::class, Log\NullLogger::class); + + // Provide dummy extension configuration class + $dummyExtensionConfiguration = new Tests\Unit\Fixtures\Classes\DummyExtensionConfiguration($this->activatedFeatures); + $container->set(Core\Configuration\ExtensionConfiguration::class, $dummyExtensionConfiguration); + + // Simulate required services + $dummyTemplatePathsDefinition = new DependencyInjection\Definition(Src\Renderer\Template\TemplatePaths::class); + $dummyTemplatePathsDefinition->addArgument(new Tests\Unit\Fixtures\Classes\DummyConfigurationManager()); + $dummyTemplateResolverDefinition = (new DependencyInjection\Definition('stdClass'))->setPublic(true); + $dummyTemplateResolverDefinition->addArgument(new DependencyInjection\Reference(Src\Renderer\Template\TemplatePaths::class)); + + $container->setDefinition(Src\Renderer\Template\TemplatePaths::class, $dummyTemplatePathsDefinition); + $container->setDefinition('handlebars.template_resolver', $dummyTemplateResolverDefinition); + $container->setDefinition('handlebars.partial_resolver', $dummyTemplateResolverDefinition); + $container->setDefinition(Src\Renderer\RendererInterface::class, $dummyTemplateResolverDefinition); + + $container->setParameter(Src\DependencyInjection\Extension\HandlebarsExtension::PARAMETER_TEMPLATE_ROOT_PATHS, []); + $container->setParameter(Src\DependencyInjection\Extension\HandlebarsExtension::PARAMETER_PARTIAL_ROOT_PATHS, []); + + $container->addCompilerPass(new Src\DependencyInjection\FeatureRegistrationPass()); + $container->addCompilerPass(new Core\DependencyInjection\PublicServicePass('handlebars.helper')); + $container->compile(); + + return $container; + } +} diff --git a/Tests/Unit/Fixtures/Classes/DummyConfigurationManager.php b/Tests/Unit/Fixtures/Classes/DummyConfigurationManager.php index 098aacaa..cc2d960e 100644 --- a/Tests/Unit/Fixtures/Classes/DummyConfigurationManager.php +++ b/Tests/Unit/Fixtures/Classes/DummyConfigurationManager.php @@ -23,8 +23,8 @@ namespace Fr\Typo3Handlebars\Tests\Unit\Fixtures\Classes; -use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; -use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; +use TYPO3\CMS\Extbase; +use TYPO3\CMS\Frontend; /** * DummyConfigurationManager @@ -33,30 +33,26 @@ * @license GPL-2.0-or-later * @internal */ -final class DummyConfigurationManager implements ConfigurationManagerInterface +final class DummyConfigurationManager implements Extbase\Configuration\ConfigurationManagerInterface { /** * @var array */ - public $configuration = []; + public array $configuration = []; - /** - * @var ContentObjectRenderer - */ - private $cObj; + private ?Frontend\ContentObject\ContentObjectRenderer $cObj = null; - public function setContentObject(ContentObjectRenderer $contentObject): void + public function setContentObject(Frontend\ContentObject\ContentObjectRenderer $contentObject): void { $this->cObj = $contentObject; } - public function getContentObject(): ?ContentObjectRenderer + public function getContentObject(): ?Frontend\ContentObject\ContentObjectRenderer { return $this->cObj; } /** - * @inheritDoc * @return array */ public function getConfiguration(string $configurationType, ?string $extensionName = null, ?string $pluginName = null): array diff --git a/Tests/Unit/Fixtures/Classes/DummyExtensionConfiguration.php b/Tests/Unit/Fixtures/Classes/DummyExtensionConfiguration.php new file mode 100644 index 00000000..e00d22cb --- /dev/null +++ b/Tests/Unit/Fixtures/Classes/DummyExtensionConfiguration.php @@ -0,0 +1,63 @@ + + * + * 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\Fixtures\Classes; + +use TYPO3\CMS\Core; + +/** + * DummyExtensionConfiguration + * + * @author Elias Häußler + * @license GPL-2.0-or-later + * @internal + */ +final class DummyExtensionConfiguration extends Core\Configuration\ExtensionConfiguration +{ + /** + * @var array + */ + private array $activatedFeatures; + + /** + * @param array $activatedFeatures + */ + public function __construct(array $activatedFeatures) + { + $this->activatedFeatures = $activatedFeatures; + } + + /** + * @throws Core\Exception + */ + public function get(string $extension, string $path = ''): bool + { + [, $featureName] = explode('/', $path); + + if (!isset($this->activatedFeatures[$featureName])) { + throw new Core\Exception('dummy exception'); + } + + return $this->activatedFeatures[$featureName]; + } +} diff --git a/Tests/Unit/Fixtures/Services/feature-registration-services.yaml b/Tests/Unit/Fixtures/Services/feature-registration-services.yaml new file mode 100644 index 00000000..ad548520 --- /dev/null +++ b/Tests/Unit/Fixtures/Services/feature-registration-services.yaml @@ -0,0 +1,8 @@ +services: + _defaults: + autowire: true + autoconfigure: true + public: false + + Fr\Typo3Handlebars\: + resource: '../../../../Classes/*' diff --git a/ext_conf_template.txt b/ext_conf_template.txt new file mode 100644 index 00000000..651b1a0e --- /dev/null +++ b/ext_conf_template.txt @@ -0,0 +1,14 @@ +# cat=features/blockHelper/enable; type=boolean; label=Block helper: Enable globally (applies to the default Handlebars renderer) +features.blockHelper.enable = 1 + +# cat=features/contentHelper/enable; type=boolean; label=Content helper: Enable globally (applies to the default Handlebars renderer) +features.contentHelper.enable = 1 + +# cat=features/extendHelper/enable; type=boolean; label=Extend helper: Enable globally (applies to the default Handlebars renderer) +features.extendHelper.enable = 1 + +# cat=features/renderHelper/enable; type=boolean; label=Render helper: Enable globally (applies to the default Handlebars renderer) +features.renderHelper.enable = 1 + +# cat=features/flatTemplateResolver/enable; type=boolean; label=Flat template resolver: Enable globally (applies to the default Handlebars renderer) +features.flatTemplateResolver.enable = 0