Skip to content

Commit 5be299d

Browse files
committed
[LiveComponent] Add the live.component service tag
1 parent 71412bc commit 5be299d

File tree

4 files changed

+170
-0
lines changed

4 files changed

+170
-0
lines changed

src/LiveComponent/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"doctrine/doctrine-bundle": "^2.4.3",
3838
"doctrine/orm": "^2.9.4",
3939
"doctrine/persistence": "^2.5.2|^3.0",
40+
"matthiasnoback/symfony-dependency-injection-test": "^5.1",
4041
"phpdocumentor/reflection-docblock": "5.x-dev",
4142
"symfony/dependency-injection": "^5.4|^6.0|^7.0",
4243
"symfony/expression-language": "^5.4|^6.0|^7.0",
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the Symfony package.
7+
*
8+
* (c) Fabien Potencier <[email protected]>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Symfony\UX\LiveComponent\DependencyInjection\Compiler;
15+
16+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
17+
use Symfony\Component\DependencyInjection\ContainerBuilder;
18+
use Symfony\Component\Form\Exception\InvalidArgumentException;
19+
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
20+
21+
/**
22+
* @author Jacob Tobiasz <[email protected]>
23+
*
24+
* @internal
25+
*/
26+
final class LiveComponentTagPass implements CompilerPassInterface
27+
{
28+
public function process(ContainerBuilder $container): void
29+
{
30+
foreach ($container->findTaggedServiceIds('live.component') as $id => $tags) {
31+
foreach ($tags as $tag) {
32+
$liveComponentService = $container->getDefinition($id);
33+
$liveComponentService->addTag('twig.component', [
34+
'key' => $tag['key'] ?? throw new InvalidArgumentException('The "key" attribute is required for the "live.component" tag.'),
35+
'template' => $tag['template'] ?? throw new InvalidArgumentException('The "template" attribute is required for the "live.component" tag.'),
36+
'expose_public_props' => $tag['expose_public_props'] ?? true,
37+
'attributes_var' => $tag['attributes_var'] ?? 'attributes',
38+
'default_action' => $tag['default_action'] ?? null,
39+
'live' => true,
40+
'csrf' => $tag['csrf'] ?? true,
41+
'route' => $tag['route'] ?? 'ux_live_component',
42+
'method' => $tag['method'] ?? 'post',
43+
'url_reference_type' => $tag['url_reference_type'] ?? UrlGeneratorInterface::ABSOLUTE_PATH,
44+
]);
45+
$liveComponentService->addTag('controller.service_arguments');
46+
}
47+
}
48+
}
49+
}

src/LiveComponent/src/LiveComponentBundle.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\DependencyInjection\ContainerBuilder;
1616
use Symfony\Component\HttpKernel\Bundle\Bundle;
1717
use Symfony\UX\LiveComponent\DependencyInjection\Compiler\ComponentDefaultActionPass;
18+
use Symfony\UX\LiveComponent\DependencyInjection\Compiler\LiveComponentTagPass;
1819
use Symfony\UX\LiveComponent\DependencyInjection\Compiler\OptionalDependencyPass;
1920

2021
/**
@@ -28,6 +29,7 @@ public function build(ContainerBuilder $container): void
2829
{
2930
// must run before Symfony\Component\Serializer\DependencyInjection\SerializerPass
3031
$container->addCompilerPass(new OptionalDependencyPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 100);
32+
$container->addCompilerPass(new LiveComponentTagPass(), priority: 100);
3133
$container->addCompilerPass(new ComponentDefaultActionPass());
3234
}
3335

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the Symfony package.
7+
*
8+
* (c) Fabien Potencier <[email protected]>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Symfony\UX\LiveComponent\Tests\Integration\DependencyInjection\Compiler;
15+
16+
use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractCompilerPassTestCase;
17+
use Symfony\Component\DependencyInjection\ContainerBuilder;
18+
use Symfony\Component\DependencyInjection\Definition;
19+
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
20+
use Symfony\UX\LiveComponent\DependencyInjection\Compiler\LiveComponentTagPass;
21+
22+
final class LiveComponentTagPassTest extends AbstractCompilerPassTestCase
23+
{
24+
public function testAddingTwigComponentTagToServicesTaggedWithLiveComponentTag(): void
25+
{
26+
$liveComponent = new Definition();
27+
$liveComponent->addTag('live.component', ['key' => 'foo', 'template' => 'bar']);
28+
29+
$this->setDefinition('my_live_component', $liveComponent);
30+
31+
$this->compile();
32+
33+
$this->assertContainerBuilderHasServiceDefinitionWithTag(
34+
'my_live_component',
35+
'twig.component',
36+
[
37+
'key' => 'foo',
38+
'template' => 'bar',
39+
'expose_public_props' => true,
40+
'attributes_var' => 'attributes',
41+
'default_action' => null,
42+
'live' => true,
43+
'csrf' => true,
44+
'route' => 'ux_live_component',
45+
'method' => 'post',
46+
'url_reference_type' => UrlGeneratorInterface::ABSOLUTE_PATH,
47+
]
48+
);
49+
}
50+
51+
public function testOverridingTagAttributesWithLiveComponentTag(): void
52+
{
53+
$liveComponent = new Definition();
54+
$liveComponent->addTag('live.component', [
55+
'key' => 'foo',
56+
'template' => 'bar',
57+
'expose_public_props' => false,
58+
'attributes_var' => 'custom_attributes',
59+
'default_action' => 'customAction',
60+
'csrf' => false,
61+
'route' => 'custom_route',
62+
'method' => 'get',
63+
'url_reference_type' => UrlGeneratorInterface::ABSOLUTE_URL,
64+
]);
65+
66+
$this->setDefinition('my_live_component', $liveComponent);
67+
68+
$this->compile();
69+
70+
$this->assertContainerBuilderHasServiceDefinitionWithTag(
71+
'my_live_component',
72+
'twig.component',
73+
[
74+
'key' => 'foo',
75+
'template' => 'bar',
76+
'expose_public_props' => false,
77+
'attributes_var' => 'custom_attributes',
78+
'default_action' => 'customAction',
79+
'live' => true,
80+
'csrf' => false,
81+
'route' => 'custom_route',
82+
'method' => 'get',
83+
'url_reference_type' => UrlGeneratorInterface::ABSOLUTE_URL,
84+
]
85+
);
86+
}
87+
88+
public function testThrowingExceptionWhenKeyIsNotPresentOnLiveComponentTag(): void
89+
{
90+
$liveComponent = new Definition();
91+
$liveComponent->addTag('live.component', ['template' => 'bar']);
92+
93+
$this->setDefinition('my_live_component', $liveComponent);
94+
95+
$this->expectException(\InvalidArgumentException::class);
96+
$this->expectExceptionMessage('The "key" attribute is required for the "live.component" tag');
97+
98+
$this->compile();
99+
}
100+
101+
public function testThrowingExceptionWhenTemplateIsNotPresentOnLiveComponentTag(): void
102+
{
103+
$liveComponent = new Definition();
104+
$liveComponent->addTag('live.component', ['key' => 'foo']);
105+
106+
$this->setDefinition('my_live_component', $liveComponent);
107+
108+
$this->expectException(\InvalidArgumentException::class);
109+
$this->expectExceptionMessage('The "template" attribute is required for the "live.component" tag');
110+
111+
$this->compile();
112+
}
113+
114+
protected function registerCompilerPass(ContainerBuilder $container): void
115+
{
116+
$container->addCompilerPass(new LiveComponentTagPass());
117+
}
118+
}

0 commit comments

Comments
 (0)