Skip to content

Commit

Permalink
Merge pull request #344 from CPS-IT/0.8/feature-registration
Browse files Browse the repository at this point in the history
[FEATURE] Provide extension configuration to enable extended features
  • Loading branch information
eliashaeussler authored Sep 18, 2024
2 parents 910afa7 + af98952 commit 78b4bd3
Show file tree
Hide file tree
Showing 7 changed files with 349 additions and 11 deletions.
93 changes: 93 additions & 0 deletions Classes/DependencyInjection/FeatureRegistrationPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

declare(strict_types=1);

/*
* This file is part of the TYPO3 CMS extension "handlebars".
*
* Copyright (C) 2024 Elias Häußler <e.haeussler@familie-redlich.de>
*
* 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 <https://www.gnu.org/licenses/>.
*/

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 <e.haeussler@familie-redlich.de>
* @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<Renderer\Helper\HelperInterface> $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;
}
}
}
1 change: 1 addition & 0 deletions Configuration/Services.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
163 changes: 163 additions & 0 deletions Tests/Unit/DependencyInjection/FeatureRegistrationPassTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
<?php

declare(strict_types=1);

/*
* This file is part of the TYPO3 CMS extension "handlebars".
*
* Copyright (C) 2024 Elias Häußler <e.haeussler@familie-redlich.de>
*
* 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 <https://www.gnu.org/licenses/>.
*/

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 <e.haeussler@familie-redlich.de>
* @license GPL-2.0-or-later
*/
#[Framework\Attributes\CoversClass(Src\DependencyInjection\FeatureRegistrationPass::class)]
final class FeatureRegistrationPassTest extends TestingFramework\Core\Unit\UnitTestCase
{
/**
* @var array<string, bool>
*/
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<Src\Renderer\Helper\HelperInterface> $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;
}
}
18 changes: 7 additions & 11 deletions Tests/Unit/Fixtures/Classes/DummyConfigurationManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<string, mixed>
*/
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<string, mixed>
*/
public function getConfiguration(string $configurationType, ?string $extensionName = null, ?string $pluginName = null): array
Expand Down
63 changes: 63 additions & 0 deletions Tests/Unit/Fixtures/Classes/DummyExtensionConfiguration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

declare(strict_types=1);

/*
* This file is part of the TYPO3 CMS extension "handlebars".
*
* Copyright (C) 2024 Elias Häußler <e.haeussler@familie-redlich.de>
*
* 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 <https://www.gnu.org/licenses/>.
*/

namespace Fr\Typo3Handlebars\Tests\Unit\Fixtures\Classes;

use TYPO3\CMS\Core;

/**
* DummyExtensionConfiguration
*
* @author Elias Häußler <e.haeussler@familie-redlich.de>
* @license GPL-2.0-or-later
* @internal
*/
final class DummyExtensionConfiguration extends Core\Configuration\ExtensionConfiguration
{
/**
* @var array<string, bool>
*/
private array $activatedFeatures;

/**
* @param array<string, bool> $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];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
services:
_defaults:
autowire: true
autoconfigure: true
public: false

Fr\Typo3Handlebars\:
resource: '../../../../Classes/*'
14 changes: 14 additions & 0 deletions ext_conf_template.txt
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 78b4bd3

Please sign in to comment.