Refactor template events related code - extract registry & renderer

This commit is contained in:
Kamil Kokot 2019-12-27 16:18:07 +01:00
parent 05c9e8fc7d
commit 6566f7227d
No known key found for this signature in database
GPG key ID: 7BD76F7054D93C89
16 changed files with 352 additions and 124 deletions

View file

@ -10,9 +10,15 @@ parameters:
- '**/DependencyInjection/Configuration.php'
# Test dependencies
- '**/Bundle/*/spec/**.php'
- '**/Bundle/*/test/app/**.php'
- '**/Bundle/*/test/src/**.php'
- '**/Bundle/*/Tests/Functional/app/**.php'
- '**/Bundle/*/Tests/Functional/var/**.php'
# Vendor files in subpackages
- '**/Bundle/*/vendor/**.php'
- '**/Component/*/vendor/**.php'
# These packages aren't in require-dev of the global package
- '**MongoDB**'

View file

@ -13,8 +13,11 @@ declare(strict_types=1);
namespace Sylius\Bundle\UiBundle\DependencyInjection;
use Sylius\Bundle\UiBundle\Registry\TemplateBlock;
use Sylius\Bundle\UiBundle\Registry\TemplateBlockRegistryInterface;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Zend\Stdlib\SplPriorityQueue;
@ -36,27 +39,22 @@ final class SyliusUiExtension extends Extension
*/
private function loadEvents(array $eventsConfig, ContainerBuilder $container): void
{
$templateEventExtensionDefinition = $container->getDefinition('sylius.twig.extension.template_event');
$templateBlockRegistryDefinition = $container->findDefinition(TemplateBlockRegistryInterface::class);
$blocksForEvents = [];
foreach ($eventsConfig as $eventName => $eventConfiguration) {
$blocksPriorityQueue = new SplPriorityQueue();
foreach ($eventConfiguration['blocks'] as $blockName => $details) {
if (!$details['enabled']) {
continue;
}
$blocksPriorityQueue->insert($details['template'], $details['priority']);
}
if ($blocksPriorityQueue->count() === 0) {
continue;
$blocksPriorityQueue->insert(
new Definition(TemplateBlock::class, [$blockName, $details['template'], $details['priority'], $details['enabled']]),
$details['priority']
);
}
$blocksForEvents[$eventName] = $blocksPriorityQueue->toArray();
}
$templateEventExtensionDefinition->setArgument(1, $blocksForEvents);
$templateBlockRegistryDefinition->setArgument(0, $blocksForEvents);
}
}

View file

@ -0,0 +1,61 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* 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\Bundle\UiBundle\Registry;
final class TemplateBlock
{
/** @var string */
private $name;
/** @var string */
private $template;
/** @var int */
private $priority;
/** @var bool */
private $enabled;
public function __construct(
string $name,
string $template,
int $priority,
bool $enabled
) {
$this->name = $name;
$this->template = $template;
$this->priority = $priority;
$this->enabled = $enabled;
}
public function name(): string
{
return $this->name;
}
public function template(): string
{
return $this->template;
}
public function priority(): int
{
return $this->priority;
}
public function enabled(): bool
{
return $this->enabled;
}
}

View file

@ -0,0 +1,40 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* 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\Bundle\UiBundle\Registry;
final class TemplateBlockRegistry implements TemplateBlockRegistryInterface
{
/** @psalm-var array<string, list<TemplateBlock>> */
private $eventsToTemplateBlocks;
public function __construct(array $eventsToTemplateBlocks)
{
$this->eventsToTemplateBlocks = $eventsToTemplateBlocks;
}
public function all(): array
{
return $this->eventsToTemplateBlocks;
}
public function findEnabledForEvent(string $eventName): array
{
return array_values(array_filter(
$this->eventsToTemplateBlocks[$eventName] ?? [],
static function (TemplateBlock $templateBlock): bool {
return $templateBlock->enabled();
}
));
}
}

View file

@ -0,0 +1,31 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* 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\Bundle\UiBundle\Registry;
interface TemplateBlockRegistryInterface
{
/**
* @return TemplateBlock[][]
*
* @psalm-return array<string, list<TemplateBlock>>
*/
public function all(): array;
/**
* @return TemplateBlock[]
*
* @psalm-return list<TemplateBlock>
*/
public function findEnabledForEvent(string $eventName): array;
}

View file

@ -0,0 +1,57 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* 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\Bundle\UiBundle\Renderer;
use Sylius\Bundle\UiBundle\Registry\TemplateBlockRegistryInterface;
use Twig\Environment;
final class TemplateEventRenderer implements TemplateEventRendererInterface
{
/** @var TemplateBlockRegistryInterface */
private $templateBlockRegistry;
/** @var Environment */
private $twig;
/** @var bool */
private $debug;
public function __construct(TemplateBlockRegistryInterface $templateBlockRegistry, Environment $twig, bool $debug)
{
$this->templateBlockRegistry = $templateBlockRegistry;
$this->twig = $twig;
$this->debug = $debug;
}
public function render(string $eventName, array $context = []): string
{
$templateBlocks = $this->templateBlockRegistry->findEnabledForEvent($eventName);
$renderedTemplates = [];
foreach ($templateBlocks as $templateBlock) {
if ($this->debug && strrpos($templateBlock->template(), '.html.twig') !== false) {
$renderedTemplates[] = sprintf(
'<!-- event name: "%s", block name: "%s", template: "%s", priority: %d -->',
$eventName,
$templateBlock->name(),
$templateBlock->template(),
$templateBlock->priority()
);
}
$renderedTemplates[] = $this->twig->render($templateBlock->template(), $context);
}
return implode("\n", $renderedTemplates);
}
}

View file

@ -0,0 +1,19 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* 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\Bundle\UiBundle\Renderer;
interface TemplateEventRendererInterface
{
public function render(string $eventName, array $context = []): string;
}

View file

@ -15,12 +15,7 @@
<imports>
<import resource="services/controller.xml" />
<import resource="services/form.xml" />
<import resource="services/template_event.xml" />
<import resource="services/twig.xml" />
</imports>
<services>
<service id="sylius.ui.sonata_multiple_block_event_listener" class="Sylius\Bundle\UiBundle\Block\MultipleBlockEventListener">
<argument type="collection" />
</service>
</services>
</container>

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This file is part of the Sylius package.
(c) Paweł Jędrzejewski
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
-->
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="Sylius\Bundle\UiBundle\Registry\TemplateBlockRegistryInterface" class="Sylius\Bundle\UiBundle\Registry\TemplateBlockRegistry">
<argument type="collection" />
</service>
<service id="Sylius\Bundle\UiBundle\Renderer\TemplateEventRendererInterface" class="Sylius\Bundle\UiBundle\Renderer\TemplateEventRenderer">
<argument type="service" id="Sylius\Bundle\UiBundle\Registry\TemplateBlockRegistryInterface" />
<argument type="service" id="twig" />
<argument>%kernel.debug%</argument>
</service>
</services>
</container>

View file

@ -34,8 +34,7 @@
</service>
<service id="sylius.twig.extension.template_event" class="Sylius\Bundle\UiBundle\Twig\TemplateEventExtension" public="false">
<argument type="service" id="twig" />
<argument type="collection" />
<argument type="service" id="Sylius\Bundle\UiBundle\Renderer\TemplateEventRendererInterface" />
<tag name="twig.extension" />
</service>

View file

@ -15,6 +15,9 @@ namespace Sylius\Bundle\UiBundle\Tests\DependencyInjection;
use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractExtensionTestCase;
use Sylius\Bundle\UiBundle\DependencyInjection\SyliusUiExtension;
use Sylius\Bundle\UiBundle\Registry\TemplateBlock;
use Sylius\Bundle\UiBundle\Registry\TemplateBlockRegistryInterface;
use Symfony\Component\DependencyInjection\Definition;
final class SyliusUiExtensionTest extends AbstractExtensionTestCase
{
@ -32,39 +35,20 @@ final class SyliusUiExtensionTest extends AbstractExtensionTestCase
]]);
$this->assertContainerBuilderHasServiceDefinitionWithArgument(
'sylius.twig.extension.template_event',
1,
TemplateBlockRegistryInterface::class,
0,
[
'first_event' => [
'first.html.twig',
'second.html.twig',
new Definition(TemplateBlock::class, ['first_block', 'first.html.twig', 0, true]),
new Definition(TemplateBlock::class, ['second_block', 'second.html.twig', 0, true]),
],
'second_event' => [
'another.html.twig',
new Definition(TemplateBlock::class, ['another_block', 'another.html.twig', 0, true]),
],
]
);
}
/** @test */
public function it_does_not_register_disabled_blocks(): void
{
$this->load(['events' => [
'event_name' => ['blocks' => [
'first_block' => ['template' => 'first.html.twig', 'enabled' => false, 'priority' => 0],
'second_block' => ['template' => 'second.html.twig', 'enabled' => true, 'priority' => 0],
]],
]]);
$this->assertContainerBuilderHasServiceDefinitionWithArgument(
'sylius.twig.extension.template_event',
1,
['event_name' => [
'second.html.twig',
]]
);
}
/** @test */
public function it_sorts_blocks_by_their_priority_and_uses_fifo_ordering(): void
{
@ -78,13 +62,13 @@ final class SyliusUiExtensionTest extends AbstractExtensionTestCase
]]);
$this->assertContainerBuilderHasServiceDefinitionWithArgument(
'sylius.twig.extension.template_event',
1,
TemplateBlockRegistryInterface::class,
0,
['event_name' => [
'first.html.twig',
'second.html.twig',
'third.html.twig',
'fourth.html.twig',
new Definition(TemplateBlock::class, ['first_block', 'first.html.twig', 5, true]),
new Definition(TemplateBlock::class, ['second_block', 'second.html.twig', 0, true]),
new Definition(TemplateBlock::class, ['third_block', 'third.html.twig', 0, true]),
new Definition(TemplateBlock::class, ['fourth_block', 'fourth.html.twig', -5, true]),
]]
);
}

View file

@ -18,7 +18,7 @@ use Twig\TwigFilter;
final class MergeRecursiveExtension extends AbstractExtension
{
public function getFilters(): array
public function getFilters(): array
{
return [
new TwigFilter(

View file

@ -13,45 +13,24 @@ declare(strict_types=1);
namespace Sylius\Bundle\UiBundle\Twig;
use Twig\Environment;
use Sylius\Bundle\UiBundle\Renderer\TemplateEventRendererInterface;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
final class TemplateEventExtension extends AbstractExtension
{
/** @var Environment */
private $twig;
/** @var TemplateEventRendererInterface */
private $templateEventRenderer;
/**
* @var array
*
* @psalm-var array<string, list<string>>
*/
private $eventsToTemplates;
public function __construct(Environment $twig, array $eventsToTemplates)
public function __construct(TemplateEventRendererInterface $templateEventRenderer)
{
$this->twig = $twig;
$this->eventsToTemplates = $eventsToTemplates;
$this->templateEventRenderer = $templateEventRenderer;
}
public function getFunctions(): array
{
return [
new TwigFunction('sylius_template_event', [$this, 'renderBlocksForEvent'], ['is_safe' => ['html']]),
new TwigFunction('sylius_template_event', [$this->templateEventRenderer, 'render'], ['is_safe' => ['html']]),
];
}
public function renderBlocksForEvent(string $event, array $options = []): string
{
$templates = $this->eventsToTemplates[$event] ?? [];
$renderedTemplates = [];
foreach ($templates as $template) {
$renderedTemplates[] = $this->twig->render($template, $options);
}
return implode("\n", $renderedTemplates);
}
}

View file

@ -0,0 +1,51 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace spec\Sylius\Bundle\UiBundle\Registry;
use PhpSpec\ObjectBehavior;
use Sylius\Bundle\UiBundle\Registry\TemplateBlock;
use Sylius\Bundle\UiBundle\Registry\TemplateBlockRegistryInterface;
final class TemplateBlockRegistrySpec extends ObjectBehavior
{
function let(): void
{
$this->beConstructedWith([]);
}
function it_is_a_template_block_registry(): void
{
$this->shouldImplement(TemplateBlockRegistryInterface::class);
}
function it_returns_all_template_blocks(): void
{
$templateBlock = new TemplateBlock('block_name', 'block.html.twig', 10, true);
$this->beConstructedWith(['event' => [$templateBlock]]);
$this->all()->shouldReturn(['event' => [$templateBlock]]);
}
function it_returns_enabled_template_blocks_for_a_given_event(): void
{
$firstTemplateBlock = new TemplateBlock('first_block', 'first.html.twig', 0, true);
$secondTemplateBlock = new TemplateBlock('second_block', 'second.html.twig', 10, false);
$thirdTemplateBlock = new TemplateBlock('third_block', 'third.html.twig', 50, true);
$this->beConstructedWith(['event' => [$firstTemplateBlock, $secondTemplateBlock], 'another_event' => [$thirdTemplateBlock]]);
$this->findEnabledForEvent('event')->shouldReturn([$firstTemplateBlock]);
}
}

View file

@ -0,0 +1,29 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace spec\Sylius\Bundle\UiBundle\Registry;
use PhpSpec\ObjectBehavior;
final class TemplateBlockSpec extends ObjectBehavior
{
function it_represents_a_template_block(): void
{
$this->beConstructedWith('block_name', 'block.html.twig', 10, false);
$this->name()->shouldReturn('block_name');
$this->template()->shouldReturn('block.html.twig');
$this->priority()->shouldReturn(10);
$this->enabled()->shouldReturn(false);
}
}

View file

@ -1,47 +0,0 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace spec\Sylius\Bundle\UiBundle\Twig;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Twig\Environment;
final class TemplateEventExtensionSpec extends ObjectBehavior
{
function it_renders_blocks_for_given_event(Environment $twig): void
{
$this->beConstructedWith($twig, [
'event_name' => [
'first.html.twig',
'second.html.twig',
],
]);
$twig->render('first.html.twig', ['option' => 'value'])->willReturn('First template');
$twig->render('second.html.twig', ['option' => 'value'])->willReturn('Second template');
$this->renderBlocksForEvent('event_name', ['option' => 'value'])->shouldReturn(
"First template\nSecond template"
);
}
function it_returns_empty_string_if_there_are_no_blocks_registered_for_given_event(Environment $twig): void
{
$this->beConstructedWith($twig, ['event_name' => ['first.html.twig']]);
$twig->render(Argument::cetera())->shouldNotBeCalled();
$this->renderBlocksForEvent('another_event')->shouldReturn('');
}
}