Skip to content

Commit 867ce06

Browse files
committed
feature #771 Introduce a new syntax for twig component (WebMamba)
This PR was merged into the 2.x branch. Discussion ---------- Introduce a new syntax for twig component | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | Tickets | | License | MIT Hey all! This PR introduces a new syntax that is more component-oriented 😁 🚨 Just updated this description to feat with the last modification The problem is: I use components with a lot of parameters, and I use a lot the embedded component feature. So I quickly end up with a hard-to-read template. So here introduce a new syntax: ``` php <t:myComponent parameter1 parameter2='foo'> ``` instead of: ```php {{ component('myComponent', { parameter1: true, parameter2: 'foo' }) }} ``` The old syntax is not removed. This PR gives the ability to choose. Here is a more realistic example: ```php {% extends 'admin.html.twig' %} {% block dashboard %} <t:userDashboard :user="user.id" isAdmin graph="linear" primary> <t:block name="actions"> <t:userActions isAdmin/> </t:block> <t:block name="footer"> <t:userDashBoardFooter/> </t:block> </t:userDashboard> {% endblock %} ``` instead of ```php {% extends 'admin.html.twig' %} {% block dashboard %} {% component 'userDashboard' with {user: user.id, isAdmin: true, graph: 'linear', primary: true} %} {% block actions %} {{ component('userActions', {isAdmin: 'true'}) }} {% endblock %} {% block footer %} {{ component('userDashboardFooter') }} {% endblock %} {% endcomponent %} {% endblock %} ``` So here is a quick summary: - you can use <t:component/>, <t:component></t:component> - you also have a special syntax for nested block <t:block name="blockName"></t:block> - you can use ':' just before your attribute for a value that should be compiled by twig <t:component :user="user.id"> equal to <t:component :user="{{ user.id }}"> - you now have a content block by default. For example, if you have: ``` php <t:component :user="user.id"> <div> // my content <div> <t:component/> ``` then in your component template ```php {% block content %} // your comment will be display here {% endblock %} ``` Piouffff!!!! A lot of stuff going on here 😎 This PR is inspired a lot by this library: https://github.com/giorgiopogliani/twig-components Thanks for your time. Cheers! 😁 Commits ------- 371edad introduce a new HTML-like syntax for twig component
2 parents 2e2b82d + 371edad commit 867ce06

File tree

13 files changed

+565
-22
lines changed

13 files changed

+565
-22
lines changed

src/LiveComponent/tests/Fixtures/Kernel.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
use Symfony\UX\TwigComponent\TwigComponentBundle;
2727
use Twig\Environment;
2828
use Zenstruck\Foundry\ZenstruckFoundryBundle;
29+
use function Symfony\Component\DependencyInjection\Loader\Configurator\service;
2930

3031
/**
3132
* @author Kevin Bond <[email protected]>
@@ -112,6 +113,8 @@ protected function configureContainer(ContainerConfigurator $c): void
112113
->set(MoneyNormalizer::class)->autoconfigure()->autowire()
113114
->set(Entity2Normalizer::class)->autoconfigure()->autowire()
114115
->load(__NAMESPACE__.'\\Component\\', __DIR__.'/Component')
116+
->set(TestingDeterministicIdTwigExtension::class)
117+
->args([service('ux.live_component.deterministic_id_calculator')])
115118
;
116119
}
117120

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace Symfony\UX\LiveComponent\Tests\Fixtures;
4+
5+
use Symfony\UX\LiveComponent\Twig\DeterministicTwigIdCalculator;
6+
use Twig\Extension\AbstractExtension;
7+
use Twig\TwigFunction;
8+
9+
class TestingDeterministicIdTwigExtension extends AbstractExtension
10+
{
11+
public function __construct(private DeterministicTwigIdCalculator $deterministicIdCalculator)
12+
{
13+
}
14+
15+
public function getFunctions(): array
16+
{
17+
return [
18+
new TwigFunction('get_id_for_test', [$this, 'getIdForTest']),
19+
];
20+
}
21+
22+
public function getIdForTest(): string
23+
{
24+
return $this->deterministicIdCalculator->calculateDeterministicId();
25+
}
26+
}

src/LiveComponent/tests/Integration/Twig/DeterministicTwigIdCalculatorTest.php

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,35 +14,15 @@
1414
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
1515
use Symfony\UX\LiveComponent\Twig\DeterministicTwigIdCalculator;
1616
use Twig\Environment;
17-
use Twig\Extension\AbstractExtension;
18-
use Twig\TwigFunction;
1917

2018
final class DeterministicTwigIdCalculatorTest extends KernelTestCase
2119
{
2220
public function testReturnsDeterministicId(): void
2321
{
24-
$deterministicIdCalculator = new DeterministicTwigIdCalculator();
25-
$twigExtension = new class($deterministicIdCalculator) extends AbstractExtension {
26-
public function __construct(private DeterministicTwigIdCalculator $deterministicIdCalculator)
27-
{
28-
}
29-
30-
public function getFunctions(): array
31-
{
32-
return [
33-
new TwigFunction('get_id_for_test', [$this, 'getIdForTest']),
34-
];
35-
}
36-
37-
public function getIdForTest(): string
38-
{
39-
return $this->deterministicIdCalculator->calculateDeterministicId();
40-
}
41-
};
42-
4322
/** @var Environment $twig */
4423
$twig = self::getContainer()->get('twig');
45-
$twig->addExtension($twigExtension);
24+
/** @var DeterministicTwigIdCalculator $deterministicIdCalculator */
25+
$deterministicIdCalculator = self::getContainer()->get('ux.live_component.deterministic_id_calculator');
4626

4727
$rendered = $twig->render('deterministic_id.html.twig');
4828
$this->assertStringContainsString('Deterministic Id Line1-1: "live-3860148629-0"', $rendered);

src/TwigComponent/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## 2.8.0
44

5+
- Add new HTML syntax for rendering components: `<twig:ComponentName>`
56
- `true` attribute values now render just the attribute name, `false` excludes it entirely.
67

78
- The first argument to `AsTwigComponent` is now optional and defaults to the class name.

src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
use Symfony\UX\TwigComponent\ComponentStack;
2525
use Symfony\UX\TwigComponent\DependencyInjection\Compiler\TwigComponentPass;
2626
use Symfony\UX\TwigComponent\Twig\ComponentExtension;
27+
use Symfony\UX\TwigComponent\Twig\ComponentLexer;
28+
use Symfony\UX\TwigComponent\Twig\TwigEnvironmentConfigurator;
2729

2830
/**
2931
* @author Kevin Bond <[email protected]>
@@ -73,5 +75,11 @@ class_exists(AbstractArgument::class) ? new AbstractArgument(sprintf('Added in %
7375
->addTag('container.service_subscriber', ['key' => ComponentRenderer::class, 'id' => 'ux.twig_component.component_renderer'])
7476
->addTag('container.service_subscriber', ['key' => ComponentFactory::class, 'id' => 'ux.twig_component.component_factory'])
7577
;
78+
79+
$container->register('ux.twig_component.twig.lexer', ComponentLexer::class);
80+
81+
$container->register('ux.twig_component.twig.environment_configurator', TwigEnvironmentConfigurator::class)
82+
->setDecoratedService(new Reference('twig.configurator.environment'))
83+
->setArguments([new Reference('ux.twig_component.twig.environment_configurator.inner')]);
7684
}
7785
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\UX\TwigComponent\Twig;
13+
14+
use Twig\Lexer;
15+
use Twig\Source;
16+
use Twig\TokenStream;
17+
18+
/**
19+
* @author Mathèo Daninos <[email protected]>
20+
*
21+
* @internal
22+
*
23+
* thanks to @giorgiopogliani for the inspiration on this lexer <3
24+
*
25+
* @see https://github.com/giorgiopogliani/twig-components
26+
*/
27+
class ComponentLexer extends Lexer
28+
{
29+
public function tokenize(Source $source): TokenStream
30+
{
31+
$preLexer = new TwigPreLexer();
32+
$preparsed = $preLexer->preLexComponents($source->getCode());
33+
34+
return parent::tokenize(
35+
new Source(
36+
$preparsed,
37+
$source->getName(),
38+
$source->getPath()
39+
)
40+
);
41+
}
42+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\UX\TwigComponent\Twig;
13+
14+
use Symfony\Bundle\TwigBundle\DependencyInjection\Configurator\EnvironmentConfigurator;
15+
use Twig\Environment;
16+
17+
class TwigEnvironmentConfigurator
18+
{
19+
private EnvironmentConfigurator $decorated;
20+
21+
public function __construct(
22+
EnvironmentConfigurator $decorated
23+
) {
24+
$this->decorated = $decorated;
25+
}
26+
27+
public function configure(Environment $environment): void
28+
{
29+
$this->decorated->configure($environment);
30+
31+
$environment->setLexer(new ComponentLexer($environment));
32+
}
33+
}

0 commit comments

Comments
 (0)