Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TwigHooks] Template configuration provider with context #208

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions config/packages/sylius_twig_hooks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ sylius_twig_hooks:
description:
template: 'book/index/content/header/description.html.twig'

'sylius_admin.book.show.content.header.title_block':
title:
template: '@SyliusBootstrapAdminUi/shared/crud/common/content/header/title_block/title.html.twig'
configuration:
title: '@=_context.book.getTitle()'

'sylius_admin.talk.create.content':
form:
component: 'App\Twig\Component\TalkFormComponent'
Expand Down
11 changes: 11 additions & 0 deletions src/TwigHooks/config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
use Sylius\TwigHooks\Provider\ComponentPropsProvider;
use Sylius\TwigHooks\Provider\DefaultConfigurationProvider;
use Sylius\TwigHooks\Provider\DefaultContextProvider;
use Sylius\TwigHooks\Provider\PropsProviderInterface;
use Sylius\TwigHooks\Provider\TemplateConfigurationProvider;
use Sylius\TwigHooks\Provider\TemplateConfigurationProviderInterface;
use Sylius\TwigHooks\Registry\HookablesRegistry;
use Sylius\TwigHooks\Twig\HooksExtension;
use Sylius\TwigHooks\Twig\Runtime\HooksRuntime;
Expand All @@ -39,9 +42,17 @@
inline_service(ExpressionLanguage::class),
])
;
$services->alias(PropsProviderInterface::class, 'sylius_twig_hooks.provider.component_props');

$services->set('sylius_twig_hooks.provider.default_configuration', DefaultConfigurationProvider::class);

$services->set('sylius_twig_hooks.provider.template_configuration', TemplateConfigurationProvider::class)
->args([
inline_service(ExpressionLanguage::class),
])
;
$services->alias(TemplateConfigurationProviderInterface::class, 'sylius_twig_hooks.provider.template_configuration');

$services->set('sylius_twig_hooks.registry.hookables', HookablesRegistry::class)
->args([
tagged_iterator('sylius_twig_hooks.hookable'),
Expand Down
1 change: 1 addition & 0 deletions src/TwigHooks/config/services/hookable_renderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
$services->set('sylius_twig_hooks.renderer.hookable.template', HookableTemplateRenderer::class)
->args([
service('twig'),
service('sylius_twig_hooks.provider.template_configuration'),
])
->tag('sylius_twig_hooks.hookable_renderer')
;
Expand Down
5 changes: 5 additions & 0 deletions src/TwigHooks/src/Hookable/Metadata/HookableMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,9 @@ public function hasPrefixes(): bool
{
return count($this->prefixes) > 0;
}

public function withConfiguration(ScalarDataBagInterface $configuration): self
{
return new self($this->renderedBy, $this->context, $configuration, $this->prefixes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,20 @@

namespace Sylius\TwigHooks\Hookable\Renderer;

use Sylius\TwigHooks\Bag\ScalarDataBag;
use Sylius\TwigHooks\Hookable\AbstractHookable;
use Sylius\TwigHooks\Hookable\HookableTemplate;
use Sylius\TwigHooks\Hookable\Metadata\HookableMetadata;
use Sylius\TwigHooks\Hookable\Renderer\Exception\HookRenderException;
use Sylius\TwigHooks\Provider\TemplateConfigurationProviderInterface;
use Sylius\TwigHooks\Twig\Runtime\HooksRuntime;
use Twig\Environment as Twig;

final class HookableTemplateRenderer implements SupportableHookableRendererInterface
{
public function __construct(
private readonly Twig $twig,
private readonly TemplateConfigurationProviderInterface $configurationProvider,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are in 0.x, I don't think we need a bc layer for that.

) {
}

Expand All @@ -39,6 +42,9 @@ public function render(AbstractHookable $hookable, HookableMetadata $metadata):
}

try {
$configuration = $this->configurationProvider->provide($hookable, $metadata);
$metadata = $metadata->withConfiguration(new ScalarDataBag($configuration));

return $this->twig->render($hookable->template, [
HooksRuntime::HOOKABLE_METADATA => $metadata,
]);
Expand Down
72 changes: 72 additions & 0 deletions src/TwigHooks/src/Provider/TemplateConfigurationProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Sylius\TwigHooks\Provider;

use Sylius\TwigHooks\Hookable\HookableTemplate;
use Sylius\TwigHooks\Hookable\Metadata\HookableMetadata;
use Sylius\TwigHooks\Provider\Exception\InvalidExpressionException;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;

final class TemplateConfigurationProvider implements TemplateConfigurationProviderInterface
{
public function __construct(
private readonly ExpressionLanguage $expressionLanguage,
) {
}

public function provide(HookableTemplate $hookable, HookableMetadata $metadata): array
{
$values = [
'_context' => $metadata->context,
];

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), $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->name,
$hookable->hookName,
$e->getMessage(),
),
previous: $e,
);
}
}

return $value;
}, $hookable->configuration);
}

/**
* @param array<array-key, mixed> $array
*
* @return array<array-key, mixed>
*/
private function mapArrayRecursively(callable $callback, array $array): array
diimpp marked this conversation as resolved.
Show resolved Hide resolved
{
$result = [];
foreach ($array as $key => $value) {
$result[$key] = is_array($value)
? $this->mapArrayRecursively($callback, $value)
: $callback($value);
}

return $result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Sylius\TwigHooks\Provider;

use Sylius\TwigHooks\Hookable\HookableTemplate;
use Sylius\TwigHooks\Hookable\Metadata\HookableMetadata;
use Sylius\TwigHooks\Provider\Exception\InvalidExpressionException;

interface TemplateConfigurationProviderInterface
{
/**
* @throws InvalidExpressionException
*
* @return array<string, mixed>
*/
public function provide(HookableTemplate $hookable, HookableMetadata $metadata): array;
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
use Sylius\TwigHooks\Hookable\Metadata\HookableMetadata;
use Sylius\TwigHooks\Hookable\Renderer\Exception\HookRenderException;
use Sylius\TwigHooks\Hookable\Renderer\HookableTemplateRenderer;
use Sylius\TwigHooks\Provider\TemplateConfigurationProvider;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Tests\Sylius\TwigHooks\Utils\MotherObject\HookableComponentMotherObject;
use Tests\Sylius\TwigHooks\Utils\MotherObject\HookableMetadataMotherObject;
use Tests\Sylius\TwigHooks\Utils\MotherObject\HookableTemplateMotherObject;
use Twig\Environment as Twig;
use Twig\Error\Error;
Expand Down Expand Up @@ -55,7 +58,7 @@ public function testItThrowsAnExceptionWhenTryingToRenderUnsupportedHookable():

public function testItRendersHookableTemplate(): void
{
$metadata = $this->createMock(HookableMetadata::class);
$metadata = HookableMetadataMotherObject::some();

$this->twig->expects($this->once())->method('render')->with('some-template', [
'hookable_metadata' => $metadata,
Expand All @@ -69,7 +72,7 @@ public function testItRendersHookableTemplate(): void

public function testItThrowsAnExceptionWhenTwigThrowsAnError(): void
{
$metadata = $this->createMock(HookableMetadata::class);
$metadata = HookableMetadataMotherObject::some();

$this->twig->expects($this->once())->method('render')->with('some-template', [
'hookable_metadata' => $metadata,
Expand All @@ -78,13 +81,13 @@ public function testItThrowsAnExceptionWhenTwigThrowsAnError(): void
$hookable = HookableTemplateMotherObject::withTarget('some-template');

$this->expectException(HookRenderException::class);
$this->expectExceptionMessage('An error occurred during rendering the "some_name" hook in the "some_hook" hookable. Unable to find the template at line 76.');
$this->expectExceptionMessage('An error occurred during rendering the "some_name" hook in the "some_hook" hookable. Unable to find the template at line 79.');

$this->getTestSubject()->render($hookable, $metadata);
}

private function getTestSubject(): HookableTemplateRenderer
{
return new HookableTemplateRenderer($this->twig);
return new HookableTemplateRenderer($this->twig, new TemplateConfigurationProvider(new ExpressionLanguage()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Tests\Sylius\TwigHooks\Unit\Provider;

use PHPUnit\Framework\TestCase;
use Sylius\TwigHooks\Provider\TemplateConfigurationProvider;
use Sylius\TwigHooks\Provider\TemplateConfigurationProviderInterface;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Tests\Sylius\TwigHooks\Utils\MotherObject\HookableMetadataMotherObject;
use Tests\Sylius\TwigHooks\Utils\MotherObject\HookableTemplateMotherObject;

final class TemplatesConfigurationProviderTest extends TestCase
{
public function testItReturnsEmptyArrayWhenNoConfigurationDefined(): void
{
$hookable = HookableTemplateMotherObject::withConfiguration([]);
$metadata = HookableMetadataMotherObject::some();

$templateConfigurationProvider = $this->createTestSubject();

$this->assertSame([], $templateConfigurationProvider->provide($hookable, $metadata));
}

public function testItReturnsConfiguration(): void
{
$hookable = HookableTemplateMotherObject::withConfiguration(['message' => 'Hello, World!']);
$metadata = HookableMetadataMotherObject::some();

$templateConfigurationProvider = $this->createTestSubject();

$this->assertSame(['message' => 'Hello, World!'], $templateConfigurationProvider->provide($hookable, $metadata));
}

public function testItEvaluatesExpressions(): void
{
$hookable = HookableTemplateMotherObject::withConfiguration([
'username' => '@=_context.username',
]);
$metadata = HookableMetadataMotherObject::withContext(
['username' => 'Jacob'],
);

$templateConfigurationProvider = $this->createTestSubject();

$this->assertSame(['username' => 'Jacob'], $templateConfigurationProvider->provide($hookable, $metadata));
}

private function createTestSubject(): TemplateConfigurationProviderInterface
{
return new TemplateConfigurationProvider(new ExpressionLanguage());
}
}
7 changes: 0 additions & 7 deletions templates/book/show/content/page_body.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,10 @@
<div class="page-body">
<div class="container-xl">
<div class="card mb-3">
<div class="card-header">
<div class="card-title" {{ sylius_test_html_attribute('title') }}>
{{ book.title }}
</div>
</div>
<div class="card-body" {{ sylius_test_html_attribute('author-name') }}>
<strong>{{ 'app.ui.author'|trans }}:</strong>
{{ book.authorName }}
</div>
</div>
</div>
</div>


3 changes: 1 addition & 2 deletions tests/Functional/BookTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,9 @@ public function testShowingBook(): void
self::assertResponseIsSuccessful();

// Validate Header
self::assertSelectorTextContains('h1.page-title', 'Show Book');
self::assertSelectorTextContains('h1.page-title', 'Shinning');

// Validate page body
self::assertSelectorTextContains('[data-test-title]', 'Shinning');
self::assertSelectorTextContains('[data-test-author-name]', 'Stephen King');
}

Expand Down
4 changes: 2 additions & 2 deletions tests/Translations/FrenchTranslatedUiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ public function testShowItem(): void

self::assertResponseIsSuccessful();

// Validate Header
self::assertSelectorTextContains('h1.page-title', 'Afficher Livre');
// Validate Body
self::assertSelectorTextContains('[data-test-author-name] strong', 'Auteur');
}

public function testBrowsingItems(): void
Expand Down
Loading