From af9f8f07dd8fd7907472c904493acd09defb5d14 Mon Sep 17 00:00:00 2001 From: Jacob Tobiasz Date: Tue, 23 Apr 2024 06:52:44 +0200 Subject: [PATCH 1/2] [TwigHooks] Split the BaseHookable into the HookableComponent and the HookableTemplate --- src/TwigHooks/composer.json | 2 + src/TwigHooks/config/services.php | 4 +- .../config/services/hookable_renderer.php | 1 + .../src/DependencyInjection/Configuration.php | 31 +++-- .../TwigHooksExtension.php | 68 ++++++++- .../Debug/HookDebugCommentRenderer.php | 2 +- .../src/Hook/Renderer/HookRenderer.php | 2 +- .../src/Hookable/AbstractHookable.php | 86 ++---------- src/TwigHooks/src/Hookable/BaseHookable.php | 9 -- .../src/Hookable/HookableComponent.php | 62 +++++++++ .../src/Hookable/HookableTemplate.php | 36 +++++ .../Renderer/CompositeHookableRenderer.php | 2 +- .../Debug/HookableDebugCommentRenderer.php | 12 +- .../Renderer/HookableComponentRenderer.php | 23 ++-- .../Renderer/HookableTemplateRenderer.php | 16 ++- .../src/Profiler/Dumper/HtmlDumper.php | 2 +- src/TwigHooks/src/Profiler/Profile.php | 2 +- ...rovider.php => ComponentPropsProvider.php} | 22 +-- .../src/Provider/ContextProviderInterface.php | 4 +- .../Provider/DefaultConfigurationProvider.php | 2 +- .../src/Provider/DefaultContextProvider.php | 4 +- .../src/Provider/PropsProviderInterface.php | 18 +++ .../src/Registry/HookablesRegistry.php | 2 +- .../Twig/Component/HookableComponentTrait.php | 14 ++ src/TwigHooks/src/Twig/HooksExtension.php | 2 +- .../src/Twig/Runtime/HooksRuntime.php | 6 - .../Functional/.application/public/index.php | 2 +- .../DependencyInjection/ConfigurationTest.php | 30 +++- .../TwigHooksExtensionTest.php | 8 +- .../Unit/Hook/Renderer/HookRendererTest.php | 18 ++- .../tests/Unit/Hookable/BaseHookableTest.php | 129 ------------------ .../Unit/Hookable/HookableComponentTest.php | 96 +++++++++++++ .../Unit/Hookable/HookableTemplateTest.php | 86 ++++++++++++ .../HookableDebugCommentRendererTest.php | 4 +- .../HookableComponentRendererTest.php | 24 ++-- .../Renderer/HookableTemplateRendererTest.php | 12 +- .../Provider/ComponentDataProviderTest.php | 65 --------- .../Provider/ComponentPropsProviderTest.php | 70 ++++++++++ .../DefaultConfigurationProviderTest.php | 4 +- .../Provider/DefaultContextProviderTest.php | 28 ++++ .../Unit/Provider/DefaultDataProviderTest.php | 28 ---- .../Unit/Registry/HookablesRegistryTest.php | 29 ++-- .../MotherObject/BaseHookableMotherObject.php | 52 ------- .../HookableComponentMotherObject.php | 71 ++++++++++ .../HookableMetadataMotherObject.php | 18 +-- .../HookableProfileMotherObject.php | 2 +- .../HookableTemplateMotherObject.php | 78 +++++++++++ 47 files changed, 818 insertions(+), 470 deletions(-) delete mode 100644 src/TwigHooks/src/Hookable/BaseHookable.php create mode 100644 src/TwigHooks/src/Hookable/HookableComponent.php create mode 100644 src/TwigHooks/src/Hookable/HookableTemplate.php rename src/TwigHooks/src/Provider/{ComponentContextProvider.php => ComponentPropsProvider.php} (71%) create mode 100644 src/TwigHooks/src/Provider/PropsProviderInterface.php create mode 100644 src/TwigHooks/src/Twig/Component/HookableComponentTrait.php delete mode 100644 src/TwigHooks/tests/Unit/Hookable/BaseHookableTest.php create mode 100644 src/TwigHooks/tests/Unit/Hookable/HookableComponentTest.php create mode 100644 src/TwigHooks/tests/Unit/Hookable/HookableTemplateTest.php delete mode 100644 src/TwigHooks/tests/Unit/Provider/ComponentDataProviderTest.php create mode 100644 src/TwigHooks/tests/Unit/Provider/ComponentPropsProviderTest.php create mode 100644 src/TwigHooks/tests/Unit/Provider/DefaultContextProviderTest.php delete mode 100644 src/TwigHooks/tests/Unit/Provider/DefaultDataProviderTest.php delete mode 100644 src/TwigHooks/tests/Utils/MotherObject/BaseHookableMotherObject.php create mode 100644 src/TwigHooks/tests/Utils/MotherObject/HookableComponentMotherObject.php create mode 100644 src/TwigHooks/tests/Utils/MotherObject/HookableTemplateMotherObject.php diff --git a/src/TwigHooks/composer.json b/src/TwigHooks/composer.json index 9f15f09d..7a0d99a9 100644 --- a/src/TwigHooks/composer.json +++ b/src/TwigHooks/composer.json @@ -18,9 +18,11 @@ "laminas/laminas-stdlib": "^3.18", "symfony/config": "^5.4 || ^6.0", "symfony/dependency-injection": "^5.4 || ^6.0", + "symfony/expression-language": "^5.4 || ^6.0", "symfony/http-kernel": "^5.4 || ^6.0", "symfony/stopwatch": "^5.4 || ^6.0", "symfony/ux-twig-component": "^2.12", + "symfony/twig-bundle": "^5.4 || ^6.0", "twig/twig": "^2.15 || ^3.0" }, "require-dev": { diff --git a/src/TwigHooks/config/services.php b/src/TwigHooks/config/services.php index 18250d5b..e094d3ee 100644 --- a/src/TwigHooks/config/services.php +++ b/src/TwigHooks/config/services.php @@ -3,7 +3,7 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; use Sylius\TwigHooks\Hook\NameGenerator\TemplateNameGenerator; -use Sylius\TwigHooks\Provider\ComponentContextProvider; +use Sylius\TwigHooks\Provider\ComponentPropsProvider; use Sylius\TwigHooks\Provider\DefaultConfigurationProvider; use Sylius\TwigHooks\Provider\DefaultContextProvider; use Sylius\TwigHooks\Registry\HookablesRegistry; @@ -18,7 +18,7 @@ $services->set('twig_hooks.provider.default_context', DefaultContextProvider::class); - $services->set('twig_hooks.provider.component_data', ComponentContextProvider::class) + $services->set('twig_hooks.provider.component_props', ComponentPropsProvider::class) ->args([ inline_service(ExpressionLanguage::class), ]) diff --git a/src/TwigHooks/config/services/hookable_renderer.php b/src/TwigHooks/config/services/hookable_renderer.php index a251b538..30d54550 100644 --- a/src/TwigHooks/config/services/hookable_renderer.php +++ b/src/TwigHooks/config/services/hookable_renderer.php @@ -20,6 +20,7 @@ $services->set('twig_hooks.renderer.hookable.component', HookableComponentRenderer::class) ->args([ + service('twig_hooks.provider.component_props'), service('ux.twig_component.component_renderer'), ]) ->tag('twig_hooks.hookable_renderer') diff --git a/src/TwigHooks/src/DependencyInjection/Configuration.php b/src/TwigHooks/src/DependencyInjection/Configuration.php index 27f46676..0420dcd4 100644 --- a/src/TwigHooks/src/DependencyInjection/Configuration.php +++ b/src/TwigHooks/src/DependencyInjection/Configuration.php @@ -4,7 +4,8 @@ namespace Sylius\TwigHooks\DependencyInjection; -use Sylius\TwigHooks\Hookable\BaseHookable; +use Sylius\TwigHooks\Hookable\HookableComponent; +use Sylius\TwigHooks\Hookable\HookableTemplate; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; @@ -30,8 +31,8 @@ private function addSupportedHookableTypesConfiguration(ArrayNodeDefinition $roo ->arrayNode('supported_hookable_types') ->useAttributeAsKey('type') ->defaultValue([ - 'template' => BaseHookable::class, - 'component' => BaseHookable::class, + 'template' => HookableTemplate::class, + 'component' => HookableComponent::class, ]) ->scalarPrototype()->end() ->end() @@ -44,9 +45,9 @@ private function addHooksConfiguration(ArrayNodeDefinition $rootNode): void $rootNode ->children() ->arrayNode('hooks') - ->useAttributeAsKey('name') + ->useAttributeAsKey('_name') ->arrayPrototype() - ->useAttributeAsKey('name') + ->useAttributeAsKey('_name') ->arrayPrototype() ->beforeNormalization() ->always(function ($v) { @@ -64,13 +65,20 @@ private function addHooksConfiguration(ArrayNodeDefinition $rootNode): void }) ->end() ->validate() - ->ifTrue(function ($v) { + ->always(static function ($v) { $component = $v['component'] ?? null; $template = $v['template'] ?? null; - return null !== $component && null !== $template; + if (null !== $component && null !== $template) { + throw new \InvalidArgumentException('You cannot define both "component" and "template" at the same time.'); + } + + if (null === $component && [] !== $v['props']) { + throw new \InvalidArgumentException('"Props" cannot be defined for non-component hookables.'); + } + + return $v; }) - ->thenInvalid('You cannot define both "component" and "template" at the same time.') ->end() ->canBeDisabled() ->children() @@ -78,7 +86,12 @@ private function addHooksConfiguration(ArrayNodeDefinition $rootNode): void ->scalarNode('target')->isRequired()->cannotBeEmpty()->end() ->scalarNode('component')->defaultNull()->end() ->scalarNode('template')->defaultNull()->end() - ->arrayNode('data') + ->arrayNode('context') + ->defaultValue([]) + ->useAttributeAsKey('name') + ->prototype('variable')->end() + ->end() + ->arrayNode('props') ->defaultValue([]) ->useAttributeAsKey('name') ->prototype('variable')->end() diff --git a/src/TwigHooks/src/DependencyInjection/TwigHooksExtension.php b/src/TwigHooks/src/DependencyInjection/TwigHooksExtension.php index d6c93fee..809101e3 100644 --- a/src/TwigHooks/src/DependencyInjection/TwigHooksExtension.php +++ b/src/TwigHooks/src/DependencyInjection/TwigHooksExtension.php @@ -4,6 +4,8 @@ namespace Sylius\TwigHooks\DependencyInjection; +use Sylius\TwigHooks\Hookable\HookableComponent; +use Sylius\TwigHooks\Hookable\HookableTemplate; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\Extension; @@ -30,7 +32,7 @@ public function load(array $configs, ContainerBuilder $container): void * @param array, + * context: array, * configuration: array, * priority: int, * enabled: bool, @@ -60,7 +62,8 @@ private function registerHooks(ContainerBuilder $container, array $hooks, array * @param array{ * type: string, * target: string, - * data: array, + * props?: array, + * context: array, * configuration: array, * priority: int, * enabled: bool, @@ -72,15 +75,70 @@ private function registerHookable( string $hookName, string $hookableName, array $hookable, + ): void { + match ($class) { + HookableTemplate::class => $this->registerTemplateHookable($container, $hookName, $hookableName, $hookable), + HookableComponent::class => $this->registerComponentHookable($container, $hookName, $hookableName, $hookable), + default => throw new \InvalidArgumentException(sprintf('Unsupported hookable class "%s".', $class)), + }; + } + + /** + * @param array{ + * type: string, + * target: string, + * context: array, + * configuration: array, + * priority: int, + * enabled: bool, + * } $hookable + */ + private function registerTemplateHookable( + ContainerBuilder $container, + string $hookName, + string $hookableName, + array $hookable, + ): void { + $container + ->register(sprintf('twig_hooks.hook.%s.hookable.%s', $hookName, $hookableName), HookableTemplate::class) + ->setArguments([ + $hookName, + $hookableName, + $hookable['target'], + $hookable['context'], + $hookable['configuration'], + $hookable['priority'], + $hookable['enabled'], + ]) + ->addTag('twig_hooks.hookable', ['priority' => $hookable['priority']]) + ; + } + + /** + * @param array{ + * type: string, + * target: string, + * props?: array, + * context: array, + * configuration: array, + * priority: int, + * enabled: bool, + * } $hookable + */ + private function registerComponentHookable( + ContainerBuilder $container, + string $hookName, + string $hookableName, + array $hookable, ): void { $container - ->register(sprintf('twig_hooks.hook.%s.hookable.%s', $hookName, $hookableName), $class) + ->register(sprintf('twig_hooks.hook.%s.hookable.%s', $hookName, $hookableName), HookableComponent::class) ->setArguments([ $hookName, $hookableName, - $hookable['type'], $hookable['target'], - $hookable['data'], + $hookable['props'] ?? [], + $hookable['context'], $hookable['configuration'], $hookable['priority'], $hookable['enabled'], diff --git a/src/TwigHooks/src/Hook/Renderer/Debug/HookDebugCommentRenderer.php b/src/TwigHooks/src/Hook/Renderer/Debug/HookDebugCommentRenderer.php index 6fbafde3..114149c0 100644 --- a/src/TwigHooks/src/Hook/Renderer/Debug/HookDebugCommentRenderer.php +++ b/src/TwigHooks/src/Hook/Renderer/Debug/HookDebugCommentRenderer.php @@ -8,7 +8,7 @@ final class HookDebugCommentRenderer implements HookRendererInterface { - public function __construct(private HookRendererInterface $innerRenderer) + public function __construct(private readonly HookRendererInterface $innerRenderer) { } diff --git a/src/TwigHooks/src/Hook/Renderer/HookRenderer.php b/src/TwigHooks/src/Hook/Renderer/HookRenderer.php index 668fb9ff..767ddb79 100644 --- a/src/TwigHooks/src/Hook/Renderer/HookRenderer.php +++ b/src/TwigHooks/src/Hook/Renderer/HookRenderer.php @@ -32,7 +32,7 @@ public function render(array $hookNames, array $hookContext = []): string $renderedHookables = []; foreach ($hookables as $hookable) { - $hookMetadata = new HookMetadata($hookable->getHookName(), new ParameterBag($hookContext)); + $hookMetadata = new HookMetadata($hookable->hookName, new ParameterBag($hookContext)); $context = $this->contextProvider->provide($hookable, $hookContext); $configuration = $this->configurationProvider->provide($hookable); diff --git a/src/TwigHooks/src/Hookable/AbstractHookable.php b/src/TwigHooks/src/Hookable/AbstractHookable.php index cc098ca4..36c13963 100644 --- a/src/TwigHooks/src/Hookable/AbstractHookable.php +++ b/src/TwigHooks/src/Hookable/AbstractHookable.php @@ -8,78 +8,26 @@ abstract class AbstractHookable { public const DEFAULT_PRIORITY = 0; - public const TYPE_COMPONENT = 'component'; - - public const TYPE_TEMPLATE = 'template'; - /** - * @param array $data + * @param array $context * @param array $configuration */ public function __construct ( - protected string $hookName, - protected string $name, - protected string $type, - protected string $target, - protected array $data = [], - protected array $configuration = [], - protected ?int $priority = null, - protected ?bool $enabled = null, + public readonly string $hookName, + public readonly string $name, + public readonly string $target, + public readonly array $context = [], + public readonly array $configuration = [], + protected readonly ?int $priority = null, + protected readonly ?bool $enabled = null, ) { } - public function getHookName(): string - { - return $this->hookName; - } - - public function getName(): string - { - return $this->name; - } - - public function getType(): string - { - return $this->type; - } - - public function isType(string $type): bool - { - return $this->type === $type; - } - - public function isComponentType(): bool - { - return $this->isType(self::TYPE_COMPONENT); - } - - public function isTemplateType(): bool - { - return $this->isType(self::TYPE_TEMPLATE); - } - public function getId(): string { return sprintf('%s#%s', $this->hookName, $this->name); } - public function getTarget(): string - { - return $this->target; - } - - /** @return array */ - public function getData(): array - { - return $this->data ?? []; - } - - /** @return array */ - public function getConfiguration(): array - { - return $this->configuration ?? []; - } - public function getPriority(): int { return $this->priority ?? self::DEFAULT_PRIORITY; @@ -90,21 +38,7 @@ public function isEnabled(): bool return $this->enabled ?? true; } - public function overwriteWith(self $hookable): self - { - if ($hookable->getName() !== $this->getName()) { - throw new \InvalidArgumentException('Hookable cannot be overwritten with different name.'); - } + abstract public function overwriteWith(self $hookable): self; - return new static( - $hookable->getHookName(), - $hookable->getName(), - $hookable->getType(), - $hookable->getTarget(), - array_merge($this->getData(), $hookable->data), - array_merge($this->getConfiguration(), $hookable->configuration), - $hookable->priority ?? $this->getPriority(), - $hookable->enabled ?? $this->isEnabled(), - ); - } + abstract public function getType(): string; } diff --git a/src/TwigHooks/src/Hookable/BaseHookable.php b/src/TwigHooks/src/Hookable/BaseHookable.php deleted file mode 100644 index 79b2cb38..00000000 --- a/src/TwigHooks/src/Hookable/BaseHookable.php +++ /dev/null @@ -1,9 +0,0 @@ - $props + * @param array $context + * @param array $configuration + */ + public function __construct ( + public readonly string $hookName, + public readonly string $name, + public readonly string $target, + public readonly array $props = [], + public readonly array $context = [], + public readonly array $configuration = [], + protected readonly ?int $priority = null, + protected readonly ?bool $enabled = null, + ) { + } + + public function getType(): string + { + return self::TYPE_NAME; + } + + /** + * @return array + */ + public function getProps(): array + { + return $this->props; + } + + public function overwriteWith(AbstractHookable $hookable): AbstractHookable + { + if ($hookable->name !== $this->name) { + throw new \InvalidArgumentException(sprintf( + 'Cannot overwrite hookable with different name. Expected "%s", got "%s".', + $this->name, + $hookable->name, + )); + } + + return new self( + $hookable->hookName, + $this->name, + $hookable->target, + $hookable instanceof HookableComponent ? array_merge($this->props, $hookable->props) : [], + array_merge($this->context, $hookable->context), + array_merge($this->configuration, $hookable->configuration), + $hookable->getPriority(), + $hookable->isEnabled(), + ); + } +} diff --git a/src/TwigHooks/src/Hookable/HookableTemplate.php b/src/TwigHooks/src/Hookable/HookableTemplate.php new file mode 100644 index 00000000..ed8d612b --- /dev/null +++ b/src/TwigHooks/src/Hookable/HookableTemplate.php @@ -0,0 +1,36 @@ +name !== $this->name) { + throw new \InvalidArgumentException(sprintf( + 'Cannot overwrite hookable with different name. Expected "%s", got "%s".', + $this->name, + $hookable->name, + )); + } + + return new self( + $hookable->hookName, + $this->name, + $hookable->target, + array_merge($this->context, $hookable->context), + array_merge($this->configuration, $hookable->configuration), + $hookable->getPriority(), + $hookable->isEnabled(), + ); + } +} diff --git a/src/TwigHooks/src/Hookable/Renderer/CompositeHookableRenderer.php b/src/TwigHooks/src/Hookable/Renderer/CompositeHookableRenderer.php index 1d834da4..108e1f7e 100644 --- a/src/TwigHooks/src/Hookable/Renderer/CompositeHookableRenderer.php +++ b/src/TwigHooks/src/Hookable/Renderer/CompositeHookableRenderer.php @@ -37,6 +37,6 @@ public function render(AbstractHookable $hookable, HookableMetadata $metadata): } } - throw new NoSupportedRendererException($hookable->getHookName(), $hookable->getName()); + throw new NoSupportedRendererException($hookable->hookName, $hookable->name); } } diff --git a/src/TwigHooks/src/Hookable/Renderer/Debug/HookableDebugCommentRenderer.php b/src/TwigHooks/src/Hookable/Renderer/Debug/HookableDebugCommentRenderer.php index a0ebaaf1..8fd0329d 100644 --- a/src/TwigHooks/src/Hookable/Renderer/Debug/HookableDebugCommentRenderer.php +++ b/src/TwigHooks/src/Hookable/Renderer/Debug/HookableDebugCommentRenderer.php @@ -28,10 +28,10 @@ private function getOpeningDebugComment(AbstractHookable $hookable): string { return sprintf( '', - $hookable->getHookName(), + $hookable->hookName, $hookable->getType(), - $hookable->getName(), - $hookable->getTarget(), + $hookable->name, + $hookable->target, $hookable->getPriority(), ); } @@ -40,10 +40,10 @@ private function getClosingDebugComment(AbstractHookable $hookable): string { return sprintf( '', - $hookable->getHookName(), + $hookable->hookName, $hookable->getType(), - $hookable->getName(), - $hookable->getTarget(), + $hookable->name, + $hookable->target, $hookable->getPriority(), ); } diff --git a/src/TwigHooks/src/Hookable/Renderer/HookableComponentRenderer.php b/src/TwigHooks/src/Hookable/Renderer/HookableComponentRenderer.php index e00eaa11..fe01597d 100644 --- a/src/TwigHooks/src/Hookable/Renderer/HookableComponentRenderer.php +++ b/src/TwigHooks/src/Hookable/Renderer/HookableComponentRenderer.php @@ -5,37 +5,44 @@ namespace Sylius\TwigHooks\Hookable\Renderer; use Sylius\TwigHooks\Hookable\AbstractHookable; +use Sylius\TwigHooks\Hookable\HookableComponent; use Sylius\TwigHooks\Hookable\Metadata\HookableMetadata; +use Sylius\TwigHooks\Provider\Exception\InvalidExpressionException; +use Sylius\TwigHooks\Provider\PropsProviderInterface; use Symfony\UX\TwigComponent\ComponentRendererInterface; final class HookableComponentRenderer implements SupportableHookableRendererInterface { - public const HOOKABLE_CONFIGURATION_PARAMETER = 'hookableConfiguration'; + public const HOOKABLE_METADATA_PARAMETER = 'hookableMetadata'; public function __construct( + private readonly PropsProviderInterface $propsProvider, private readonly ComponentRendererInterface $componentRenderer, ) { } + /** + * @param HookableComponent $hookable + * @throws InvalidExpressionException + */ public function render(AbstractHookable $hookable, HookableMetadata $metadata): string { if (!$this->supports($hookable)) { throw new \InvalidArgumentException( - sprintf('Hookable must be the "%s" type, but "%s" given.', AbstractHookable::TYPE_COMPONENT, $hookable->getType()), + sprintf('Hookable must be the "%s" type, but "%s" given.', HookableComponent::TYPE_NAME, $hookable->getType()), ); } - $context = $metadata->context->all(); - $configuration = $metadata->configuration->all(); + $props = $this->propsProvider->provide($hookable, $metadata); - return $this->componentRenderer->createAndRender($hookable->getTarget(), [ - self::HOOKABLE_CONFIGURATION_PARAMETER => $configuration, - ...$context, + return $this->componentRenderer->createAndRender($hookable->target, [ + self::HOOKABLE_METADATA_PARAMETER => $metadata, + ...$props, ]); } public function supports(AbstractHookable $hookable): bool { - return $hookable->isComponentType(); + return is_a($hookable, HookableComponent::class, true); } } diff --git a/src/TwigHooks/src/Hookable/Renderer/HookableTemplateRenderer.php b/src/TwigHooks/src/Hookable/Renderer/HookableTemplateRenderer.php index 7735810e..3f357094 100644 --- a/src/TwigHooks/src/Hookable/Renderer/HookableTemplateRenderer.php +++ b/src/TwigHooks/src/Hookable/Renderer/HookableTemplateRenderer.php @@ -5,9 +5,13 @@ namespace Sylius\TwigHooks\Hookable\Renderer; use Sylius\TwigHooks\Hookable\AbstractHookable; +use Sylius\TwigHooks\Hookable\HookableTemplate; use Sylius\TwigHooks\Hookable\Metadata\HookableMetadata; use Sylius\TwigHooks\Twig\Runtime\HooksRuntime; use Twig\Environment as Twig; +use Twig\Error\LoaderError; +use Twig\Error\RuntimeError; +use Twig\Error\SyntaxError; final class HookableTemplateRenderer implements SupportableHookableRendererInterface { @@ -16,21 +20,27 @@ public function __construct( ) { } + /** + * @param HookableTemplate $hookable + * @throws LoaderError + * @throws RuntimeError + * @throws SyntaxError + */ public function render(AbstractHookable $hookable, HookableMetadata $metadata): string { if (!$this->supports($hookable)) { throw new \InvalidArgumentException( - sprintf('Hookable must be the "%s" type, but "%s" given.', AbstractHookable::TYPE_TEMPLATE, $hookable->getType()), + sprintf('Hookable must be the "%s" type, but "%s" given.', HookableTemplate::TYPE_NAME, $hookable->getType()), ); } - return $this->twig->render($hookable->getTarget(), [ + return $this->twig->render($hookable->target, [ HooksRuntime::HOOKABLE_METADATA => $metadata, ]); } public function supports(AbstractHookable $hookable): bool { - return $hookable->isTemplateType(); + return is_a($hookable, HookableTemplate::class, true); } } diff --git a/src/TwigHooks/src/Profiler/Dumper/HtmlDumper.php b/src/TwigHooks/src/Profiler/Dumper/HtmlDumper.php index 3b0b207d..8235efa0 100644 --- a/src/TwigHooks/src/Profiler/Dumper/HtmlDumper.php +++ b/src/TwigHooks/src/Profiler/Dumper/HtmlDumper.php @@ -55,7 +55,7 @@ private function dumpHookableProfile(HookableProfile $hookableProfile, string $p $hookableProfile->getHookable()->getPriority(), $hookableProfile->getDuration(), $hookableProfile->getName(), - $hookableProfile->getHookable()->getTarget(), + $hookableProfile->getHookable()->target, ); $str .= PHP_EOL; $prefix .= $sibling ? '│ ' : ' '; diff --git a/src/TwigHooks/src/Profiler/Profile.php b/src/TwigHooks/src/Profiler/Profile.php index fecbb599..e1fe8153 100644 --- a/src/TwigHooks/src/Profiler/Profile.php +++ b/src/TwigHooks/src/Profiler/Profile.php @@ -55,7 +55,7 @@ public function registerHookableRenderStart(AbstractHookable $hookable): void throw new \RuntimeException('Cannot register hookable render without hook profile'); } - $hookableProfile = new HookableProfile($this->currentHookProfile, $hookable->getName(), $hookable, []); + $hookableProfile = new HookableProfile($this->currentHookProfile, $hookable->name, $hookable, []); $this->currentHookableProfile = $hookableProfile; $this->currentHookProfile->addHookableProfile($this->currentHookableProfile); diff --git a/src/TwigHooks/src/Provider/ComponentContextProvider.php b/src/TwigHooks/src/Provider/ComponentPropsProvider.php similarity index 71% rename from src/TwigHooks/src/Provider/ComponentContextProvider.php rename to src/TwigHooks/src/Provider/ComponentPropsProvider.php index a8f6407b..d5b92764 100644 --- a/src/TwigHooks/src/Provider/ComponentContextProvider.php +++ b/src/TwigHooks/src/Provider/ComponentPropsProvider.php @@ -4,30 +4,36 @@ namespace Sylius\TwigHooks\Provider; -use Sylius\TwigHooks\Hookable\AbstractHookable; +use Sylius\TwigHooks\Hookable\HookableComponent; +use Sylius\TwigHooks\Hookable\Metadata\HookableMetadata; use Sylius\TwigHooks\Provider\Exception\InvalidExpressionException; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; -final class ComponentContextProvider implements ContextProviderInterface +final class ComponentPropsProvider implements PropsProviderInterface { public function __construct ( private ExpressionLanguage $expressionLanguage, ) { } - public function provide(AbstractHookable $hookable, array $hookData): array + public function provide(HookableComponent $hookable, HookableMetadata $metadata): array { - return $this->mapArrayRecursively(function (mixed $value) use ($hookData, $hookable): mixed { + $values = [ + '_context' => $metadata->context, + '_configuration' => $metadata->configuration, + ]; + + return $this->mapArrayRecursively(function (mixed $value) use ($values, $hookable): mixed { if (is_string($value) && str_starts_with($value, '@=')) { try { - return $this->expressionLanguage->evaluate(substr($value, 2), $hookData); + return $this->expressionLanguage->evaluate(substr($value, 2), $values); } catch (\Throwable $e) { throw new InvalidExpressionException( sprintf( 'Failed to evaluate the "%s" expression while rendering the "%s" hookable in the "%s" hook. Error: %s".', $value, - $hookable->getName(), - $hookable->getHookName(), + $hookable->name, + $hookable->hookName, $e->getMessage(), ), previous: $e, @@ -36,7 +42,7 @@ public function provide(AbstractHookable $hookable, array $hookData): array } return $value; - }, $hookable->getData()); + }, $hookable->getProps()); } /** diff --git a/src/TwigHooks/src/Provider/ContextProviderInterface.php b/src/TwigHooks/src/Provider/ContextProviderInterface.php index 8b695965..9522dbe6 100644 --- a/src/TwigHooks/src/Provider/ContextProviderInterface.php +++ b/src/TwigHooks/src/Provider/ContextProviderInterface.php @@ -9,8 +9,8 @@ interface ContextProviderInterface { /** - * @param array $hookData + * @param array $hookContext * @return array */ - public function provide(AbstractHookable $hookable, array $hookData): array; + public function provide(AbstractHookable $hookable, array $hookContext): array; } diff --git a/src/TwigHooks/src/Provider/DefaultConfigurationProvider.php b/src/TwigHooks/src/Provider/DefaultConfigurationProvider.php index 3c3615f1..3816987c 100644 --- a/src/TwigHooks/src/Provider/DefaultConfigurationProvider.php +++ b/src/TwigHooks/src/Provider/DefaultConfigurationProvider.php @@ -10,6 +10,6 @@ final class DefaultConfigurationProvider implements ConfigurationProviderInterfa { public function provide(AbstractHookable $hookable): array { - return $hookable->getConfiguration(); + return $hookable->configuration; } } diff --git a/src/TwigHooks/src/Provider/DefaultContextProvider.php b/src/TwigHooks/src/Provider/DefaultContextProvider.php index 72c845d1..794a2f12 100644 --- a/src/TwigHooks/src/Provider/DefaultContextProvider.php +++ b/src/TwigHooks/src/Provider/DefaultContextProvider.php @@ -8,8 +8,8 @@ final class DefaultContextProvider implements ContextProviderInterface { - public function provide(AbstractHookable $hookable, array $hookData): array + public function provide(AbstractHookable $hookable, array $hookContext): array { - return array_replace($hookable->getData(), $hookData); + return array_merge($hookContext, $hookable->context); } } diff --git a/src/TwigHooks/src/Provider/PropsProviderInterface.php b/src/TwigHooks/src/Provider/PropsProviderInterface.php new file mode 100644 index 00000000..7c6ebb87 --- /dev/null +++ b/src/TwigHooks/src/Provider/PropsProviderInterface.php @@ -0,0 +1,18 @@ + + */ + public function provide(HookableComponent $hookable, HookableMetadata $metadata): array; +} diff --git a/src/TwigHooks/src/Registry/HookablesRegistry.php b/src/TwigHooks/src/Registry/HookablesRegistry.php index 66e03f1c..fc55f461 100644 --- a/src/TwigHooks/src/Registry/HookablesRegistry.php +++ b/src/TwigHooks/src/Registry/HookablesRegistry.php @@ -26,7 +26,7 @@ public function __construct(iterable $hookables) ); } - $this->hookables[$hookable->getHookName()][$hookable->getName()] = $hookable; + $this->hookables[$hookable->hookName][$hookable->name] = $hookable; } } diff --git a/src/TwigHooks/src/Twig/Component/HookableComponentTrait.php b/src/TwigHooks/src/Twig/Component/HookableComponentTrait.php new file mode 100644 index 00000000..a9511698 --- /dev/null +++ b/src/TwigHooks/src/Twig/Component/HookableComponentTrait.php @@ -0,0 +1,14 @@ + true]), + new TwigFunction('get_hookable_metadata', [HooksRuntime::class, 'getHookableMetadata'], ['needs_context' => true]), new TwigFunction('get_hookable_context', [HooksRuntime::class, 'getHookableContext'], ['needs_context' => true]), new TwigFunction('get_hookable_configuration', [HooksRuntime::class, 'getHookableContext'], ['needs_context' => true]), ]; diff --git a/src/TwigHooks/src/Twig/Runtime/HooksRuntime.php b/src/TwigHooks/src/Twig/Runtime/HooksRuntime.php index 63d74174..1227df09 100644 --- a/src/TwigHooks/src/Twig/Runtime/HooksRuntime.php +++ b/src/TwigHooks/src/Twig/Runtime/HooksRuntime.php @@ -15,12 +15,6 @@ final class HooksRuntime implements RuntimeExtensionInterface { public const HOOKABLE_METADATA = 'hookable_metadata'; - /** @deprecated */ - public const HOOKABLE_CONFIGURATION_PARAMETER = 'hookable_configuration'; - - /** @deprecated */ - public const HOOKABLE_DATA_PARAMETER = 'hookable_data'; - public function __construct ( private readonly HookRendererInterface $hookRenderer, private readonly NameGeneratorInterface $nameGenerator, diff --git a/src/TwigHooks/tests/Functional/.application/public/index.php b/src/TwigHooks/tests/Functional/.application/public/index.php index f124b0b1..3fe37c30 100644 --- a/src/TwigHooks/tests/Functional/.application/public/index.php +++ b/src/TwigHooks/tests/Functional/.application/public/index.php @@ -1,6 +1,6 @@ [ 'type' => 'template', 'target' => 'some_target.html.twig', - 'data' => [], + 'context' => [], 'configuration' => [], 'priority' => null, 'enabled' => true, 'component' => null, 'template' => null, + 'props' => [], ], ], ], @@ -98,12 +101,13 @@ public function testItAllowsToUseComponentShortcut(): void 'some_hookable' => [ 'type' => 'component', 'target' => 'MyAwesomeComponent', - 'data' => [], + 'context' => [], 'configuration' => [], 'priority' => null, 'enabled' => true, 'component' => 'MyAwesomeComponent', 'template' => null, + 'props' => [], ], ], ], @@ -132,12 +136,13 @@ public function testItAllowsToUseTemplateShortcut(): void 'some_hookable' => [ 'type' => 'template', 'target' => 'some_target.html.twig', - 'data' => [], + 'context' => [], 'configuration' => [], 'priority' => null, 'enabled' => true, 'component' => null, 'template' => 'some_target.html.twig', + 'props' => [], ], ], ], @@ -165,6 +170,25 @@ public function testItThrowsExceptionWhenBothTemplateAndComponentShortcutsAreDef ); } + public function testItThrowsExceptionWhenPropsAreDefinedForNonComponentHookable(): void + { + $this->assertConfigurationIsInvalid( + [ + [ + 'hooks' => [ + 'some_hook' => [ + 'some_hookable' => [ + 'template' => 'some_target.html.twig', + 'props' => ['key' => 'value'], + ], + ], + ], + ], + ], + '"Props" cannot be defined for non-component hookables.' + ); + } + protected function getConfiguration(): Configuration { return new Configuration(); diff --git a/src/TwigHooks/tests/Integration/DependencyInjection/TwigHooksExtensionTest.php b/src/TwigHooks/tests/Integration/DependencyInjection/TwigHooksExtensionTest.php index 5378bc8a..75743497 100644 --- a/src/TwigHooks/tests/Integration/DependencyInjection/TwigHooksExtensionTest.php +++ b/src/TwigHooks/tests/Integration/DependencyInjection/TwigHooksExtensionTest.php @@ -6,6 +6,8 @@ use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractExtensionTestCase; use Sylius\TwigHooks\DependencyInjection\TwigHooksExtension; +use Sylius\TwigHooks\Hookable\HookableComponent; +use Sylius\TwigHooks\Hookable\HookableTemplate; final class TwigHooksExtensionTest extends AbstractExtensionTestCase { @@ -21,7 +23,7 @@ public function testItRegistersHookablesAsServices(): void 'some_hookable' => [ 'type' => 'template', 'target' => '@SomeBundle/some_template.html.twig', - 'data' => ['some' => 'data'], + 'context' => ['some' => 'context'], 'configuration' => [], 'priority' => 16, 'enabled' => false, @@ -29,7 +31,7 @@ public function testItRegistersHookablesAsServices(): void 'another_hookable' => [ 'type' => 'component', 'target' => 'MyComponent', - 'data' => ['some' => 'data'], + 'context' => ['some' => 'context'], 'configuration' => [], 'priority' => 16, 'enabled' => false, @@ -39,7 +41,7 @@ public function testItRegistersHookablesAsServices(): void 'yet_another_hookable' => [ 'type' => 'template', 'target' => '@SomeBundle/another_template.html.twig', - 'data' => ['some' => 'data'], + 'context' => ['some' => 'context'], 'enabled' => false, ], ] diff --git a/src/TwigHooks/tests/Unit/Hook/Renderer/HookRendererTest.php b/src/TwigHooks/tests/Unit/Hook/Renderer/HookRendererTest.php index 0c19a913..cb5a2939 100644 --- a/src/TwigHooks/tests/Unit/Hook/Renderer/HookRendererTest.php +++ b/src/TwigHooks/tests/Unit/Hook/Renderer/HookRendererTest.php @@ -12,6 +12,7 @@ use Sylius\TwigHooks\Provider\ConfigurationProviderInterface; use Sylius\TwigHooks\Provider\ContextProviderInterface; use Sylius\TwigHooks\Registry\HookablesRegistry; +use Tests\Sylius\TwigHooks\Utils\MotherObject\HookableTemplateMotherObject; final class HookRendererTest extends TestCase { @@ -35,10 +36,10 @@ protected function setUp(): void $this->configurationProvider = $this->createMock(ConfigurationProviderInterface::class); } - public function testItRendersHookablesForGivenHookName(): void + public function testItReturnsRenderedHookables(): void { - $hookableOne = $this->createMock(AbstractHookable::class); - $hookableTwo = $this->createMock(AbstractHookable::class); + $hookableOne = HookableTemplateMotherObject::withName('first_hook'); + $hookableTwo = HookableTemplateMotherObject::withName('second_hook'); $this->hookablesRegistry->method('getEnabledFor')->willReturn([$hookableOne, $hookableTwo]); $this->contextProvider->method('provide')->willReturn([]); @@ -60,6 +61,17 @@ public function testItRendersHookablesForGivenHookName(): void $this->assertSame($expected, $result); } + public function testItReturnsEmptyStringWhenNoHookablesAreFound(): void + { + $this->hookablesRegistry->method('getEnabledFor')->willReturn([]); + $this->contextProvider->method('provide')->willReturn([]); + $this->configurationProvider->method('provide')->willReturn([]); + + $result = $this->getTestSubject()->render(['hook_name']); + + $this->assertSame('', $result); + } + private function getTestSubject(): HookRenderer { return new HookRenderer( diff --git a/src/TwigHooks/tests/Unit/Hookable/BaseHookableTest.php b/src/TwigHooks/tests/Unit/Hookable/BaseHookableTest.php deleted file mode 100644 index 352f1756..00000000 --- a/src/TwigHooks/tests/Unit/Hookable/BaseHookableTest.php +++ /dev/null @@ -1,129 +0,0 @@ -getTestSubject(); - - $this->assertSame('some_hook', $testSubject->getHookName()); - } - - public function testItReturnsName(): void - { - $testSubject = $this->getTestSubject(); - - $this->assertSame('some_name', $testSubject->getName()); - } - - public function testItReturnsTarget(): void - { - $testSubject = $this->getTestSubject(); - - $this->assertSame('some_target', $testSubject->getTarget()); - } - - public function testItReturnsData(): void - { - $testSubject = $this->getTestSubject(); - - $this->assertSame([], $testSubject->getData()); - } - - public function testItReturnsConfiguration(): void - { - $testSubject = $this->getTestSubject(); - - $this->assertSame([], $testSubject->getConfiguration()); - } - - public function testItReturnsPriority(): void - { - $testSubject = $this->getTestSubject(); - - $this->assertSame(0, $testSubject->getPriority()); - } - - public function testItReturnsEnabled(): void - { - $testSubject = $this->getTestSubject(); - - $this->assertTrue($testSubject->isEnabled()); - } - - public function testItThrowsAnExceptionWhenTryingToOverrideHookableWithDifferentName(): void - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Hookable cannot be overwritten with different name.'); - - $testSubject = $this->getTestSubject(); - - $testSubject->overwriteWith(BaseHookableMotherObject::withName('some_other_name')); - } - - public function testItOverwritesHookableWithGivenHookable(): void - { - $hookableToBeOverwritten = new BaseHookable( - 'some_hook', - 'some_name', - 'template', - 'some_target', - ['some_data' => 'yes', 'another_data' => 'no'], - ['title' => 'King', 'name' => 'Arthur'], - 50, - true, - ); - $hookableToOverwrite = new BaseHookable( - 'some_hook', - 'some_name', - 'component', - 'some_other_target', - ['another_data' => 'yes', 'another_other_data' => 'no'], - ['title' => 'Queen'], - 100, - false, - ); - - $overwrittenHookable = $hookableToBeOverwritten->overwriteWith($hookableToOverwrite); - - $this->assertSame('some_hook', $overwrittenHookable->getHookName()); - $this->assertSame('some_name', $overwrittenHookable->getName()); - $this->assertSame('component', $overwrittenHookable->getType()); - $this->assertSame('some_other_target', $overwrittenHookable->getTarget()); - $this->assertSame(['some_data' => 'yes', 'another_data' => 'yes', 'another_other_data' => 'no'], $overwrittenHookable->getData()); - $this->assertSame(['title' => 'Queen', 'name' => 'Arthur'], $overwrittenHookable->getConfiguration()); - $this->assertSame(100, $overwrittenHookable->getPriority()); - $this->assertFalse($overwrittenHookable->isEnabled()); - } - - public function testItAllowsToOverwriteHookableWithAnotherType(): void - { - $testSubject = $this->getTestSubject(); - - $this->assertSame('template', $testSubject->getType()); - - $overwrittenTestSubject = $testSubject->overwriteWith(BaseHookableMotherObject::withType('component')); - - $this->assertSame('component', $overwrittenTestSubject->getType()); - } - - public function testItReturnsItsTypeName(): void - { - $testSubject = $this->getTestSubject(); - - $this->assertSame('template', $testSubject->getType()); - } - - private function getTestSubject(): BaseHookable - { - return BaseHookableMotherObject::some(); - } -} diff --git a/src/TwigHooks/tests/Unit/Hookable/HookableComponentTest.php b/src/TwigHooks/tests/Unit/Hookable/HookableComponentTest.php new file mode 100644 index 00000000..a3c72776 --- /dev/null +++ b/src/TwigHooks/tests/Unit/Hookable/HookableComponentTest.php @@ -0,0 +1,96 @@ +assertSame(10, $hookable->getPriority()); + } + + public function testItReturnsZeroWhenThePriorityIsNull(): void + { + $hookable = HookableComponentMotherObject::withPriority(null); + + $this->assertSame(0, $hookable->getPriority()); + } + + public function testItThrowsAnExceptionWhenOverwritingWithDifferentName(): void + { + $hookable = HookableComponentMotherObject::withName('name'); + $otherHookable = HookableComponentMotherObject::withName('other-name'); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Cannot overwrite hookable with different name. Expected "name", got "other-name".'); + + $hookable->overwriteWith($otherHookable); + } + + public function testItOverwritesTarget(): void + { + $hookable = HookableComponentMotherObject::withTarget('target'); + $otherHookable = HookableComponentMotherObject::withTarget('other-target'); + + $overwrittenHookable = $hookable->overwriteWith($otherHookable); + + $this->assertSame('other-target', $overwrittenHookable->target); + } + + public function testItMergesProps(): void + { + $hookable = HookableComponentMotherObject::withProps(['key' => 'value', 'other-key' => 'value']); + $otherHookable = HookableComponentMotherObject::withProps(['other-key' => 'other-value', 'another-key' => 'another-value']); + + $overwrittenHookable = $hookable->overwriteWith($otherHookable); + + $this->assertSame(['key' => 'value', 'other-key' => 'other-value', 'another-key' => 'another-value'], $overwrittenHookable->props); + } + + public function testItMergesContexts(): void + { + $hookable = HookableComponentMotherObject::withContext(['key' => 'value', 'other-key' => 'value']); + $otherHookable = HookableComponentMotherObject::withContext(['other-key' => 'other-value', 'another-key' => 'another-value']); + + $overwrittenHookable = $hookable->overwriteWith($otherHookable); + + $this->assertSame(['key' => 'value', 'other-key' => 'other-value', 'another-key' => 'another-value'], $overwrittenHookable->context); + } + + public function testItMergesConfigurations(): void + { + $hookable = HookableComponentMotherObject::withConfiguration(['key' => 'value', 'other-key' => 'value']); + $otherHookable = HookableComponentMotherObject::withConfiguration(['other-key' => 'other-value', 'another-key' => 'another-value']); + + $overwrittenHookable = $hookable->overwriteWith($otherHookable); + + $this->assertSame(['key' => 'value', 'other-key' => 'other-value', 'another-key' => 'another-value'], $overwrittenHookable->configuration); + } + + public function testItOverwritesPriority(): void + { + $hookable = HookableComponentMotherObject::withPriority(10); + $otherHookable = HookableComponentMotherObject::withPriority(20); + + $overwrittenHookable = $hookable->overwriteWith($otherHookable); + + $this->assertSame(20, $overwrittenHookable->getPriority()); + } + + public function testItOverwritesEnabled(): void + { + $hookable = HookableComponentMotherObject::enabled(); + $otherHookable = HookableComponentMotherObject::disabled(); + + $overwrittenHookable = $hookable->overwriteWith($otherHookable); + + $this->assertFalse($overwrittenHookable->isEnabled()); + } +} diff --git a/src/TwigHooks/tests/Unit/Hookable/HookableTemplateTest.php b/src/TwigHooks/tests/Unit/Hookable/HookableTemplateTest.php new file mode 100644 index 00000000..85a18a8d --- /dev/null +++ b/src/TwigHooks/tests/Unit/Hookable/HookableTemplateTest.php @@ -0,0 +1,86 @@ +assertSame(10, $hookable->getPriority()); + } + + public function testItReturnsZeroWhenThePriorityIsNull(): void + { + $hookable = HookableTemplateMotherObject::withPriority(null); + + $this->assertSame(0, $hookable->getPriority()); + } + + public function testItThrowsAnExceptionWhenOverwritingWithDifferentName(): void + { + $hookable = HookableTemplateMotherObject::withName('name'); + $otherHookable = HookableTemplateMotherObject::withName('other-name'); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Cannot overwrite hookable with different name. Expected "name", got "other-name".'); + + $hookable->overwriteWith($otherHookable); + } + + public function testItOverwritesTarget(): void + { + $hookable = HookableTemplateMotherObject::withTarget('target'); + $otherHookable = HookableTemplateMotherObject::withTarget('other-target'); + + $overwrittenHookable = $hookable->overwriteWith($otherHookable); + + $this->assertSame('other-target', $overwrittenHookable->target); + } + + public function testItMergesContexts(): void + { + $hookable = HookableTemplateMotherObject::withContext(['key' => 'value', 'other-key' => 'value']); + $otherHookable = HookableTemplateMotherObject::withContext(['other-key' => 'other-value', 'another-key' => 'another-value']); + + $overwrittenHookable = $hookable->overwriteWith($otherHookable); + + $this->assertSame(['key' => 'value', 'other-key' => 'other-value', 'another-key' => 'another-value'], $overwrittenHookable->context); + } + + public function testItMergesConfigurations(): void + { + $hookable = HookableTemplateMotherObject::withConfiguration(['key' => 'value', 'other-key' => 'value']); + $otherHookable = HookableTemplateMotherObject::withConfiguration(['other-key' => 'other-value', 'another-key' => 'another-value']); + + $overwrittenHookable = $hookable->overwriteWith($otherHookable); + + $this->assertSame(['key' => 'value', 'other-key' => 'other-value', 'another-key' => 'another-value'], $overwrittenHookable->configuration); + } + + public function testItOverwritesPriority(): void + { + $hookable = HookableTemplateMotherObject::withPriority(10); + $otherHookable = HookableTemplateMotherObject::withPriority(20); + + $overwrittenHookable = $hookable->overwriteWith($otherHookable); + + $this->assertSame(20, $overwrittenHookable->getPriority()); + } + + public function testItOverwritesEnabled(): void + { + $hookable = HookableTemplateMotherObject::enabled(); + $otherHookable = HookableTemplateMotherObject::disabled(); + + $overwrittenHookable = $hookable->overwriteWith($otherHookable); + + $this->assertFalse($overwrittenHookable->isEnabled()); + } +} diff --git a/src/TwigHooks/tests/Unit/Hookable/Renderer/Debug/HookableDebugCommentRendererTest.php b/src/TwigHooks/tests/Unit/Hookable/Renderer/Debug/HookableDebugCommentRendererTest.php index 5c6b88e1..6d4bf009 100644 --- a/src/TwigHooks/tests/Unit/Hookable/Renderer/Debug/HookableDebugCommentRendererTest.php +++ b/src/TwigHooks/tests/Unit/Hookable/Renderer/Debug/HookableDebugCommentRendererTest.php @@ -8,8 +8,8 @@ use PHPUnit\Framework\TestCase; use Sylius\TwigHooks\Hookable\Renderer\Debug\HookableDebugCommentRenderer; use Sylius\TwigHooks\Hookable\Renderer\HookableRendererInterface; -use Tests\Sylius\TwigHooks\Utils\MotherObject\BaseHookableMotherObject; use Tests\Sylius\TwigHooks\Utils\MotherObject\HookableMetadataMotherObject; +use Tests\Sylius\TwigHooks\Utils\MotherObject\HookableTemplateMotherObject; final class HookableDebugCommentRendererTest extends TestCase { @@ -23,7 +23,7 @@ protected function setUp(): void public function testItAddsDebugCommentsToRenderedHookable(): void { - $hookable = BaseHookableMotherObject::some(); + $hookable = HookableTemplateMotherObject::some(); $metadata = HookableMetadataMotherObject::some(); $this->innerRenderer diff --git a/src/TwigHooks/tests/Unit/Hookable/Renderer/HookableComponentRendererTest.php b/src/TwigHooks/tests/Unit/Hookable/Renderer/HookableComponentRendererTest.php index 5162ef15..8946b0e9 100644 --- a/src/TwigHooks/tests/Unit/Hookable/Renderer/HookableComponentRendererTest.php +++ b/src/TwigHooks/tests/Unit/Hookable/Renderer/HookableComponentRendererTest.php @@ -6,28 +6,33 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Sylius\TwigHooks\Hookable\AbstractHookable; use Sylius\TwigHooks\Hookable\Metadata\HookableMetadata; use Sylius\TwigHooks\Hookable\Renderer\HookableComponentRenderer; +use Sylius\TwigHooks\Provider\PropsProviderInterface; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\UX\TwigComponent\ComponentRendererInterface; -use Tests\Sylius\TwigHooks\Utils\MotherObject\BaseHookableMotherObject; +use Tests\Sylius\TwigHooks\Utils\MotherObject\HookableComponentMotherObject; use Tests\Sylius\TwigHooks\Utils\MotherObject\HookableMetadataMotherObject; +use Tests\Sylius\TwigHooks\Utils\MotherObject\HookableTemplateMotherObject; final class HookableComponentRendererTest extends TestCase { + /** @var PropsProviderInterface&MockObject */ + private PropsProviderInterface $propsProvider; + /** @var ComponentRendererInterface&MockObject */ private ComponentRendererInterface $componentRenderer; protected function setUp(): void { + $this->propsProvider = $this->createMock(PropsProviderInterface::class); $this->componentRenderer = $this->createMock(ComponentRendererInterface::class); } public function testItSupportsOnlyHookableComponents(): void { - $hookableTemplate = BaseHookableMotherObject::withType(AbstractHookable::TYPE_TEMPLATE); - $hookableComponent = BaseHookableMotherObject::withType(AbstractHookable::TYPE_COMPONENT); + $hookableTemplate = HookableTemplateMotherObject::some(); + $hookableComponent = HookableComponentMotherObject::some(); $this->assertTrue($this->getTestSubject()->supports($hookableComponent)); $this->assertFalse($this->getTestSubject()->supports($hookableTemplate)); @@ -35,7 +40,7 @@ public function testItSupportsOnlyHookableComponents(): void public function testItThrowsAnExceptionWhenTryingToRenderUnsupportedHookable(): void { - $hookableTemplate = BaseHookableMotherObject::withType(AbstractHookable::TYPE_TEMPLATE); + $hookableTemplate = HookableTemplateMotherObject::some(); $metadata = $this->createMock(HookableMetadata::class); $this->expectException(\InvalidArgumentException::class); @@ -46,20 +51,21 @@ public function testItThrowsAnExceptionWhenTryingToRenderUnsupportedHookable(): public function testItRendersHookableComponent(): void { + $hookable = HookableComponentMotherObject::withTargetAndProps('some-component', ['some' => 'data']); $metadata = HookableMetadataMotherObject::withContextAndConfiguration( context: new ParameterBag(['some' => 'data']), configuration: new ParameterBag(['some' => 'configuration']), ); + $this->propsProvider->expects($this->once())->method('provide')->with($hookable, $metadata)->willReturn(['some' => 'props']); $this->componentRenderer->expects($this->once())->method('createAndRender')->with( 'some-component', [ - HookableComponentRenderer::HOOKABLE_CONFIGURATION_PARAMETER => ['some' => 'configuration'], - 'some' => 'data', + 'hookableMetadata' => $metadata, + 'some' => 'props', ] )->willReturn('some-rendered-component'); - $hookable = BaseHookableMotherObject::withTypeAndTarget(AbstractHookable::TYPE_COMPONENT, 'some-component'); $renderedComponent = $this->getTestSubject()->render($hookable, $metadata); $this->assertSame('some-rendered-component', $renderedComponent); @@ -67,6 +73,6 @@ public function testItRendersHookableComponent(): void private function getTestSubject(): HookableComponentRenderer { - return new HookableComponentRenderer($this->componentRenderer); + return new HookableComponentRenderer($this->propsProvider, $this->componentRenderer); } } diff --git a/src/TwigHooks/tests/Unit/Hookable/Renderer/HookableTemplateRendererTest.php b/src/TwigHooks/tests/Unit/Hookable/Renderer/HookableTemplateRendererTest.php index f70ceb84..4da51980 100644 --- a/src/TwigHooks/tests/Unit/Hookable/Renderer/HookableTemplateRendererTest.php +++ b/src/TwigHooks/tests/Unit/Hookable/Renderer/HookableTemplateRendererTest.php @@ -6,11 +6,9 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Sylius\TwigHooks\Hookable\AbstractHookable; -use Sylius\TwigHooks\Hookable\HookableTemplate; use Sylius\TwigHooks\Hookable\Metadata\HookableMetadata; use Sylius\TwigHooks\Hookable\Renderer\HookableTemplateRenderer; -use Tests\Sylius\TwigHooks\Utils\MotherObject\BaseHookableMotherObject; +use Tests\Sylius\TwigHooks\Utils\MotherObject\HookableComponentMotherObject; use Tests\Sylius\TwigHooks\Utils\MotherObject\HookableTemplateMotherObject; use Twig\Environment as Twig; @@ -26,8 +24,8 @@ protected function setUp(): void public function testItSupportsOnlyHookableTemplates(): void { - $hookableTemplate = BaseHookableMotherObject::withType(AbstractHookable::TYPE_TEMPLATE); - $hookableComponent = BaseHookableMotherObject::withType(AbstractHookable::TYPE_COMPONENT); + $hookableTemplate = HookableTemplateMotherObject::some(); + $hookableComponent = HookableComponentMotherObject::some(); $this->assertTrue($this->getTestSubject()->supports($hookableTemplate)); $this->assertFalse($this->getTestSubject()->supports($hookableComponent)); @@ -35,7 +33,7 @@ public function testItSupportsOnlyHookableTemplates(): void public function testItThrowsAnExceptionWhenTryingToRenderUnsupportedHookable(): void { - $hookableComponent = BaseHookableMotherObject::withType(AbstractHookable::TYPE_COMPONENT); + $hookableComponent = HookableComponentMotherObject::some(); $metadata = $this->createMock(HookableMetadata::class); $this->expectException(\InvalidArgumentException::class); @@ -52,7 +50,7 @@ public function testItRendersHookableTemplate(): void 'hookable_metadata' => $metadata, ])->willReturn('some-rendered-template'); - $hookable = BaseHookableMotherObject::withTarget('some-template'); + $hookable = HookableTemplateMotherObject::withTarget('some-template'); $renderedTemplate = $this->getTestSubject()->render($hookable, $metadata); $this->assertSame('some-rendered-template', $renderedTemplate); diff --git a/src/TwigHooks/tests/Unit/Provider/ComponentDataProviderTest.php b/src/TwigHooks/tests/Unit/Provider/ComponentDataProviderTest.php deleted file mode 100644 index daae27ef..00000000 --- a/src/TwigHooks/tests/Unit/Provider/ComponentDataProviderTest.php +++ /dev/null @@ -1,65 +0,0 @@ -createTestSubject(); - - $this->assertSame([], $dataProvider->provide($hookable, [])); - $this->assertSame([], $dataProvider->provide($hookable, ['some' => 'data'])); - } - - public function testItReturnsDataFromHookable(): void - { - $hookable = BaseHookableMotherObject::withData(['some' => 'data']); - - $dataProvider = $this->createTestSubject(); - - $this->assertSame(['some' => 'data'], $dataProvider->provide($hookable, [])); - $this->assertSame(['some' => 'data'], $dataProvider->provide($hookable, ['more' => 'data'])); - } - - public function testItPassesTemplateLevelDataToExpressionLanguage(): void - { - $hookable = BaseHookableMotherObject::withData(['some_key' => '@=some']); - - $dataProvider = $this->createTestSubject(); - - $this->assertSame(['some_key' => 'data'], $dataProvider->provide($hookable, ['some' => 'data', 'another' => 'data'])); - } - - public function testItThrowsCustomExceptionWhenEvaluatingAnExpressionFails(): void - { - $hookable = BaseHookableMotherObject::withData(['some_key' => '@=some.nonExistingMethod()']); - - $dataProvider = $this->createTestSubject(); - - $this->expectException(InvalidExpressionException::class); - $this->expectExceptionMessage( - 'Failed to evaluate the "@=some.nonExistingMethod()" expression while rendering the "some_name" hookable in the "some_hook" hook. Error: Unable to call method "nonExistingMethod" of non-object "some".".', - ); - - $dataProvider->provide($hookable, ['some' => 'data', 'another' => 'data']); - } - - private function createTestSubject(): ContextProviderInterface - { - return new ComponentContextProvider( - new ExpressionLanguage(), - ); - } -} diff --git a/src/TwigHooks/tests/Unit/Provider/ComponentPropsProviderTest.php b/src/TwigHooks/tests/Unit/Provider/ComponentPropsProviderTest.php new file mode 100644 index 00000000..467a256e --- /dev/null +++ b/src/TwigHooks/tests/Unit/Provider/ComponentPropsProviderTest.php @@ -0,0 +1,70 @@ +createTestSubject(); + + $this->assertSame([], $propsProvider->provide($hookable, $metadata)); + } + + public function testItReturnsProps(): void + { + $hookable = HookableComponentMotherObject::withProps(['message' => 'Hello, World!']); + $metadata = HookableMetadataMotherObject::some(); + + $propsProvider = $this->createTestSubject(); + + $this->assertSame(['message' => 'Hello, World!'], $propsProvider->provide($hookable, $metadata)); + } + + public function testItEvaluatesExpressions(): void + { + $hookable = HookableComponentMotherObject::withProps([ + 'name' => '@=_configuration.get("anonymize") ? "Anon" : _context.get("username")' + ]); + $anonymizeMetadata = HookableMetadataMotherObject::withContextAndConfiguration( + context: [ + 'username' => 'Jacob', + ], + configuration: [ + 'anonymize' => true, + ], + ); + + $propsProvider = $this->createTestSubject(); + + $this->assertSame(['name' => 'Anon'], $propsProvider->provide($hookable, $anonymizeMetadata)); + + $notAnonymizeMetadata = HookableMetadataMotherObject::withContextAndConfiguration( + context: [ + 'username' => 'Jacob', + ], + configuration: [ + 'anonymize' => false, + ], + ); + + $this->assertSame(['name' => 'Jacob'], $propsProvider->provide($hookable, $notAnonymizeMetadata)); + } + + private function createTestSubject(): PropsProviderInterface + { + return new ComponentPropsProvider(new ExpressionLanguage()); + } +} diff --git a/src/TwigHooks/tests/Unit/Provider/DefaultConfigurationProviderTest.php b/src/TwigHooks/tests/Unit/Provider/DefaultConfigurationProviderTest.php index 6400360d..6d93804d 100644 --- a/src/TwigHooks/tests/Unit/Provider/DefaultConfigurationProviderTest.php +++ b/src/TwigHooks/tests/Unit/Provider/DefaultConfigurationProviderTest.php @@ -6,13 +6,13 @@ use PHPUnit\Framework\TestCase; use Sylius\TwigHooks\Provider\DefaultConfigurationProvider; -use Tests\Sylius\TwigHooks\Utils\MotherObject\BaseHookableMotherObject; +use Tests\Sylius\TwigHooks\Utils\MotherObject\HookableTemplateMotherObject; final class DefaultConfigurationProviderTest extends TestCase { public function testItProvidesConfiguration(): void { - $hookable = BaseHookableMotherObject::withConfiguration(['some' => 'configuration']); + $hookable = HookableTemplateMotherObject::withConfiguration(['some' => 'configuration']); $defaultConfigurationProvider = $this->getTestSubject(); $configuration = $defaultConfigurationProvider->provide($hookable); diff --git a/src/TwigHooks/tests/Unit/Provider/DefaultContextProviderTest.php b/src/TwigHooks/tests/Unit/Provider/DefaultContextProviderTest.php new file mode 100644 index 00000000..2a9b1077 --- /dev/null +++ b/src/TwigHooks/tests/Unit/Provider/DefaultContextProviderTest.php @@ -0,0 +1,28 @@ + 'data', 'another' => 'data']); + $hookContext = ['some' => 'datum', 'yet_another' => 'data']; + + $defaultContextProvider = $this->getTestSubject(); + $context = $defaultContextProvider->provide($hookable, $hookContext); + + $this->assertSame(['some' => 'data', 'yet_another' => 'data', 'another' => 'data'], $context); + } + + private function getTestSubject(): DefaultContextProvider + { + return new DefaultContextProvider(); + } +} diff --git a/src/TwigHooks/tests/Unit/Provider/DefaultDataProviderTest.php b/src/TwigHooks/tests/Unit/Provider/DefaultDataProviderTest.php deleted file mode 100644 index 0c8c2a0d..00000000 --- a/src/TwigHooks/tests/Unit/Provider/DefaultDataProviderTest.php +++ /dev/null @@ -1,28 +0,0 @@ - 'data', 'another' => 'data']); - $hookData = ['some' => 'datum', 'yet_another' => 'data']; - - $defaultDataProvider = $this->getTestSubject(); - $data = $defaultDataProvider->provide($hookable, $hookData); - - $this->assertSame(['some' => 'datum', 'another' => 'data', 'yet_another' => 'data'], $data); - } - - private function getTestSubject(): DefaultContextProvider - { - return new DefaultContextProvider(); - } -} diff --git a/src/TwigHooks/tests/Unit/Registry/HookablesRegistryTest.php b/src/TwigHooks/tests/Unit/Registry/HookablesRegistryTest.php index d58e6dac..57397484 100644 --- a/src/TwigHooks/tests/Unit/Registry/HookablesRegistryTest.php +++ b/src/TwigHooks/tests/Unit/Registry/HookablesRegistryTest.php @@ -8,6 +8,7 @@ use PHPUnit\Framework\TestCase; use Sylius\TwigHooks\Hookable\AbstractHookable; use Sylius\TwigHooks\Registry\HookablesRegistry; +use Tests\Sylius\TwigHooks\Utils\MotherObject\HookableTemplateMotherObject; final class HookablesRegistryTest extends TestCase { @@ -39,17 +40,20 @@ public function testItMergesHookablesWithTheSameName(): void { $hookableOne = $this->createHookable('some_hook', 'hookable_one'); $hookableTwo = $this->createHookable('another_hook', 'hookable_one'); - $hookableThree = $this->createHookable('yet_another_hook', 'hookable_one', false); - $hookableThree->expects($this->exactly(2))->method('overwriteWith')->willReturnCallback( - fn (AbstractHookable $hookable) => match ($hookable) { - $hookableTwo, $hookableOne => $hookableThree, - } - ); - $this->getTestSubject([$hookableOne, $hookableTwo, $hookableThree]) + $hookable = $this->getTestSubject([$hookableOne, $hookableTwo, $hookableThree]) ->getEnabledFor(['some_hook', 'another_hook', 'yet_another_hook']) ; + + $this->assertCount(1, $hookable); + $this->assertSame('some_hook', $hookable[0]->hookName); + $this->assertSame('hookable_one', $hookable[0]->name); + $this->assertSame('some_target', $hookable[0]->target); + $this->assertSame([], $hookable[0]->context); + $this->assertSame([], $hookable[0]->configuration); + $this->assertSame(0, $hookable[0]->getPriority()); + $this->assertTrue($hookable[0]->isEnabled()); } /** @@ -65,11 +69,10 @@ private function getTestSubject(iterable $hookables): HookablesRegistry */ private function createHookable(string $hookName, string $name, bool $enabled = true): AbstractHookable { - $hookable = $this->createMock(AbstractHookable::class); - $hookable->method('getHookName')->willReturn($hookName); - $hookable->method('getName')->willReturn($name); - $hookable->method('isEnabled')->willReturn($enabled); - - return $hookable; + return HookableTemplateMotherObject::with([ + 'hookName' => $hookName, + 'name' => $name, + 'enabled' => $enabled, + ]); } } diff --git a/src/TwigHooks/tests/Utils/MotherObject/BaseHookableMotherObject.php b/src/TwigHooks/tests/Utils/MotherObject/BaseHookableMotherObject.php deleted file mode 100644 index c4b9148e..00000000 --- a/src/TwigHooks/tests/Utils/MotherObject/BaseHookableMotherObject.php +++ /dev/null @@ -1,52 +0,0 @@ - $configuration - */ - public static function withConfiguration(array $configuration): BaseHookable - { - return new BaseHookable('some_hook', 'some_name', AbstractHookable::TYPE_TEMPLATE, 'some_target', configuration: $configuration); - } - - /** - * @param array $data - */ - public static function withData(array $data): BaseHookable - { - return new BaseHookable('some_hook', 'some_name', AbstractHookable::TYPE_TEMPLATE, 'some_target', data: $data); - } -} diff --git a/src/TwigHooks/tests/Utils/MotherObject/HookableComponentMotherObject.php b/src/TwigHooks/tests/Utils/MotherObject/HookableComponentMotherObject.php new file mode 100644 index 00000000..225e9c41 --- /dev/null +++ b/src/TwigHooks/tests/Utils/MotherObject/HookableComponentMotherObject.php @@ -0,0 +1,71 @@ + $configuration + */ + public static function withConfiguration(array $configuration): HookableComponent + { + return new HookableComponent('some_hook', 'some_name', 'some_target', configuration: $configuration); + } + + /** + * @param array $context + */ + public static function withContext(array $context): HookableComponent + { + return new HookableComponent('some_hook', 'some_name', 'some_target', context: $context); + } + + public static function withPriority(?int $priority): HookableComponent + { + return new HookableComponent('some_hook', 'some_name', 'some_target', priority: $priority); + } + + public static function enabled(): HookableComponent + { + return new HookableComponent('some_hook', 'some_name', 'some_target', enabled: true); + } + + public static function disabled(): HookableComponent + { + return new HookableComponent('some_hook', 'some_name', 'some_target', enabled: false); + } +} diff --git a/src/TwigHooks/tests/Utils/MotherObject/HookableMetadataMotherObject.php b/src/TwigHooks/tests/Utils/MotherObject/HookableMetadataMotherObject.php index d7a2c56f..f0d85798 100644 --- a/src/TwigHooks/tests/Utils/MotherObject/HookableMetadataMotherObject.php +++ b/src/TwigHooks/tests/Utils/MotherObject/HookableMetadataMotherObject.php @@ -20,32 +20,34 @@ public static function some(): HookableMetadata ); } - public static function withContext(ParameterBagInterface $context): HookableMetadata + public static function withContext(ParameterBagInterface|array $context): HookableMetadata { return new HookableMetadata( HookMetadataMotherObject::some(), - $context, + is_array($context) ? new ParameterBag($context) : $context, new ParameterBag([]), [] ); } - public static function withConfiguration(ParameterBagInterface $configuration): HookableMetadata + public static function withConfiguration(ParameterBagInterface|array $configuration): HookableMetadata { return new HookableMetadata( HookMetadataMotherObject::some(), new ParameterBag([]), - $configuration, + is_array($configuration) ? new ParameterBag($configuration) : $configuration, [] ); } - public static function withContextAndConfiguration(ParameterBagInterface $context, ParameterBagInterface $configuration): HookableMetadata - { + public static function withContextAndConfiguration( + ParameterBagInterface|array $context, + ParameterBagInterface|array $configuration, + ): HookableMetadata { return new HookableMetadata( HookMetadataMotherObject::some(), - $context, - $configuration, + is_array($context) ? new ParameterBag($context) : $context, + is_array($configuration) ? new ParameterBag($configuration) : $configuration, [] ); } diff --git a/src/TwigHooks/tests/Utils/MotherObject/HookableProfileMotherObject.php b/src/TwigHooks/tests/Utils/MotherObject/HookableProfileMotherObject.php index 2a33cc4c..8e368549 100644 --- a/src/TwigHooks/tests/Utils/MotherObject/HookableProfileMotherObject.php +++ b/src/TwigHooks/tests/Utils/MotherObject/HookableProfileMotherObject.php @@ -13,7 +13,7 @@ public static function some(): HookableProfile return new HookableProfile( HookProfileMotherObject::some(), 'some_name', - BaseHookableMotherObject::some(), + HookableTemplateMotherObject::some(), [], ); } diff --git a/src/TwigHooks/tests/Utils/MotherObject/HookableTemplateMotherObject.php b/src/TwigHooks/tests/Utils/MotherObject/HookableTemplateMotherObject.php new file mode 100644 index 00000000..ecbfe1c0 --- /dev/null +++ b/src/TwigHooks/tests/Utils/MotherObject/HookableTemplateMotherObject.php @@ -0,0 +1,78 @@ + $configuration + */ + public static function withConfiguration(array $configuration): HookableTemplate + { + return new HookableTemplate('some_hook', 'some_name', 'some_target', configuration: $configuration); + } + + /** + * @param array $context + */ + public static function withContext(array $context): HookableTemplate + { + return new HookableTemplate('some_hook', 'some_name', 'some_target', context: $context); + } + + public static function withPriority(?int $priority): HookableTemplate + { + return new HookableTemplate('some_hook', 'some_name', 'some_target', priority: $priority); + } + + public static function enabled(): HookableTemplate + { + return new HookableTemplate('some_hook', 'some_name', 'some_target', enabled: true); + } + + public static function disabled(): HookableTemplate + { + return new HookableTemplate('some_hook', 'some_name', 'some_target', enabled: false); + } +} From 58304abd0858c085bfe893ff495e3887e868426e Mon Sep 17 00:00:00 2001 From: Jacob Tobiasz Date: Tue, 23 Apr 2024 07:09:08 +0200 Subject: [PATCH 2/2] [Maintenance] Fix direct and indirect deprecations --- config/packages/twig.yaml | 3 +++ .../.application/config/packages/framework.php | 13 +++++++++++++ .../config/packages/twig_component.yaml | 4 ++++ 3 files changed, 20 insertions(+) create mode 100644 src/TwigHooks/tests/Functional/.application/config/packages/framework.php create mode 100644 src/TwigHooks/tests/Functional/.application/config/packages/twig_component.yaml diff --git a/config/packages/twig.yaml b/config/packages/twig.yaml index f9f4cc53..f299b5cb 100644 --- a/config/packages/twig.yaml +++ b/config/packages/twig.yaml @@ -1,6 +1,9 @@ twig: default_path: '%kernel.project_dir%/templates' +twig_component: + anonymous_template_directory: 'components/' + when@test: twig: strict_variables: true diff --git a/src/TwigHooks/tests/Functional/.application/config/packages/framework.php b/src/TwigHooks/tests/Functional/.application/config/packages/framework.php new file mode 100644 index 00000000..cb7eff64 --- /dev/null +++ b/src/TwigHooks/tests/Functional/.application/config/packages/framework.php @@ -0,0 +1,13 @@ += 6) { + $container->extension('framework', [ + 'handle_all_throwables' => true, + ]); + } +}; diff --git a/src/TwigHooks/tests/Functional/.application/config/packages/twig_component.yaml b/src/TwigHooks/tests/Functional/.application/config/packages/twig_component.yaml new file mode 100644 index 00000000..810c72cb --- /dev/null +++ b/src/TwigHooks/tests/Functional/.application/config/packages/twig_component.yaml @@ -0,0 +1,4 @@ +twig_component: + anonymous_template_directory: 'components/' + defaults: + App\Twig\Component\: 'components/'