Skip to content

Commit 65e8a96

Browse files
committed
feature #1652 [TwigComponent] merge props from template with class props (WebMamba)
This PR was squashed before being merged into the 2.x branch. Discussion ---------- [TwigComponent] merge props from template with class props | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | Issues | Fix #1387 | License | MIT This PR gives you the ability to have props in your component template and in your component PHP class. Commits ------- 345833d [TwigComponent] merge props from template with class props
2 parents 8af2726 + 345833d commit 65e8a96

9 files changed

+102
-8
lines changed

src/TwigComponent/src/ComponentRenderer.php

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,25 +107,29 @@ private function preRender(MountedComponent $mounted, array $context = []): PreR
107107
{
108108
$component = $mounted->getComponent();
109109
$metadata = $this->factory->metadataFor($mounted->getName());
110+
$isAnonymous = $mounted->getComponent() instanceof AnonymousComponent;
111+
112+
$classProps = $isAnonymous ? [] : iterator_to_array($this->exposedVariables($component, $metadata->isPublicPropsExposed()));
113+
110114
// expose public properties and properties marked with ExposeInTemplate attribute
111-
$props = iterator_to_array($this->exposedVariables($component, $metadata->isPublicPropsExposed()));
115+
$props = array_merge($mounted->getInputProps(), $classProps);
112116
$variables = array_merge(
113117
// first so values can be overridden
114118
$context,
115-
119+
// add the context in a separate variable to keep track
120+
// of what is coming from outside the component
121+
['__context' => $context],
116122
// keep reference to old context
117123
['outerScope' => $context],
118-
119124
// add the component as "this"
120125
['this' => $component],
121-
122126
// add computed properties proxy
123127
['computed' => new ComputedPropertiesProxy($component)],
124-
128+
$props,
129+
// keep this line for BC break reasons
130+
['__props' => $classProps],
125131
// add attributes
126132
[$metadata->getAttributesVar() => $mounted->getAttributes()],
127-
$props,
128-
['__props' => $props]
129133
);
130134
$event = new PreRenderEvent($mounted, $metadata, $variables);
131135

src/TwigComponent/src/Twig/PropsNode.php

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,21 @@ public function compile(Compiler $compiler): void
3434
;
3535

3636
foreach ($this->getAttribute('names') as $name) {
37+
$compiler
38+
->write('if (isset($context[\'__props\'][\''.$name.'\'])) {')
39+
->raw("\n")
40+
->write('$componentClass = isset($context[\'this\']) ? get_debug_type($context[\'this\']) : "";')
41+
->raw("\n")
42+
->write('throw new \Twig\Error\RuntimeError(\'Cannot define prop "'.$name.'" in template "'.$this->getTemplateName().'". Property already defined in component class "\'.$componentClass.\'".\');')
43+
->raw("\n")
44+
->write('}')
45+
->raw("\n")
46+
;
47+
3748
$compiler
3849
->write('$propsNames[] = \''.$name.'\';')
3950
->write('$context[\'attributes\'] = $context[\'attributes\']->remove(\''.$name.'\');')
40-
->write('if (!isset($context[\'__props\'][\''.$name.'\'])) {');
51+
->write('if (!isset($context[\''.$name.'\'])) {');
4152

4253
if (!$this->hasNode($name)) {
4354
$compiler
@@ -66,5 +77,25 @@ public function compile(Compiler $compiler): void
6677
->write('}')
6778
->write('}')
6879
;
80+
81+
// overwrite the context value if a props with a similar name and a default value exist
82+
if ($this->hasNode($name)) {
83+
$compiler
84+
->write('if (isset($context[\'__context\'][\''.$name.'\'])) {')
85+
->raw("\n")
86+
->write('$contextValue = $context[\'__context\'][\''.$name.'\'];')
87+
->raw("\n")
88+
->write('$propsValue = $context[\''.$name.'\'];')
89+
->raw("\n")
90+
->write('if ($contextValue === $propsValue) {')
91+
->raw("\n")
92+
->write('$context[\''.$name.'\'] = ')
93+
->subcompile($this->getNode($name))
94+
->raw(";\n")
95+
->write('}')
96+
->raw("\n")
97+
->write('}')
98+
;
99+
}
69100
}
70101
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Symfony\UX\TwigComponent\Tests\Fixtures\Component;
4+
5+
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
6+
7+
#[AsTwigComponent]
8+
class Conflict
9+
{
10+
public string $name;
11+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Tests\Fixtures\Component;
13+
14+
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
15+
16+
#[AsTwigComponent]
17+
class FlashMessage
18+
{
19+
public string $message = '';
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<twig:Conflict name='foo'/>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<twig:FlashMessage message='Congrats !' color='success' size='lg'/>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{% props name %}
2+
3+
<p>{{ name }}</p>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{% props size = 'md', color = 'success' %}
2+
3+
<div data-color='{{ color }}' data-size='{{ size }}'>
4+
{{ message }}
5+
</div>

src/TwigComponent/tests/Integration/ComponentExtensionTest.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
1515
use Symfony\UX\TwigComponent\Tests\Fixtures\User;
1616
use Twig\Environment;
17+
use Twig\Error\RuntimeError;
1718

1819
/**
1920
* @author Kevin Bond <kevinbond@gmail.com>
@@ -341,6 +342,23 @@ public function testRenderingHtmlSyntaxComponentWithNestedAttributes(): void
341342
);
342343
}
343344

345+
public function testComponentWithPropsFromTemplateAndClass(): void
346+
{
347+
$output = self::getContainer()->get(Environment::class)->render('component_with_props_from_template_and_class.html.twig');
348+
349+
$this->assertStringContainsString('data-color=\'success\'', $output);
350+
$this->assertStringContainsString('data-size=\'lg\'', $output);
351+
$this->assertStringContainsString('Congrats !', $output);
352+
}
353+
354+
public function testComponentWithConflictBetweenPropsFromTemplateAndClass(): void
355+
{
356+
$this->expectException(RuntimeError::class);
357+
$this->expectExceptionMessage('Cannot define prop "name" in template "components/Conflict.html.twig". Property already defined in component class "Symfony\UX\TwigComponent\Tests\Fixtures\Component\Conflict".');
358+
359+
self::getContainer()->get(Environment::class)->render('component_with_conflict_between_props_from_template_and_class.html.twig');
360+
}
361+
344362
private function renderComponent(string $name, array $data = []): string
345363
{
346364
return self::getContainer()->get(Environment::class)->render('render_component.html.twig', [

0 commit comments

Comments
 (0)