Skip to content

Commit 89dc843

Browse files
committed
feature #189 [TwigComponent] add PreMount hook (kbond)
This PR was merged into the 2.x branch. Discussion ---------- [TwigComponent] add `PreMount` hook | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | Tickets | Fix #142 | License | MIT Commits ------- 9913151 [TwigComponent] add PreMount attribute/hook (closes #142)
2 parents df267cb + 9913151 commit 89dc843

File tree

9 files changed

+106
-15
lines changed

9 files changed

+106
-15
lines changed

src/LiveComponent/src/Attribute/AsLiveComponent.php

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -99,20 +99,6 @@ public static function preDehydrateMethods(object $component): \Traversable
9999
yield from self::attributeMethodsFor(PreDehydrate::class, $component);
100100
}
101101

102-
/**
103-
* @param string|object $classOrObject
104-
*
105-
* @return \ReflectionMethod[]
106-
*/
107-
private static function attributeMethodsFor(string $attribute, object $component): \Traversable
108-
{
109-
foreach ((new \ReflectionClass($component))->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
110-
if ($method->getAttributes($attribute)[0] ?? null) {
111-
yield $method;
112-
}
113-
}
114-
}
115-
116102
/**
117103
* @return \ReflectionProperty[]
118104
*/

src/TwigComponent/README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,40 @@ If an option name matches an argument name in `mount()`, the
217217
option is passed as that argument and the component system
218218
will _not_ try to set it directly on a property.
219219

220+
### PreMount Hook
221+
222+
If you need to modify/validate data before it's _mounted_ on the
223+
component use a `PreMount` hook:
224+
225+
```php
226+
// src/Components/AlertComponent.php
227+
228+
use Symfony\UX\TwigComponent\Attribute\PreMount;
229+
// ...
230+
231+
#[AsTwigComponent('alert')]
232+
class AlertComponent
233+
{
234+
public string $message;
235+
public string $type = 'success';
236+
237+
#[PreMount]
238+
public function preMount(array $data): array
239+
{
240+
// validate data
241+
$resolver = new OptionsResolver();
242+
$resolver->setDefaults(['type' => 'success']);
243+
$resolver->setAllowedValues('type', ['success', 'danger']);
244+
$resolver->setRequired('message');
245+
$resolver->setAllowedTypes('message', 'string');
246+
247+
return $resolver->resolve($data)
248+
}
249+
250+
// ...
251+
}
252+
```
253+
220254
## Fetching Services
221255

222256
Let's create a more complex example: a "featured products" component.

src/TwigComponent/src/Attribute/AsTwigComponent.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,28 @@ public function __construct(string $name, ?string $template = null)
2727
$this->name = $name;
2828
$this->template = $template;
2929
}
30+
31+
/**
32+
* @internal
33+
*
34+
* @return \ReflectionMethod[]
35+
*/
36+
public static function preMountMethods(object $component): \Traversable
37+
{
38+
yield from self::attributeMethodsFor(PreMount::class, $component);
39+
}
40+
41+
/**
42+
* @internal
43+
*
44+
* @return \ReflectionMethod[]
45+
*/
46+
protected static function attributeMethodsFor(string $attribute, object $component): \Traversable
47+
{
48+
foreach ((new \ReflectionClass($component))->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
49+
if ($method->getAttributes($attribute)[0] ?? null) {
50+
yield $method;
51+
}
52+
}
53+
}
3054
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace Symfony\UX\TwigComponent\Attribute;
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+
/**
15+
* @author Kevin Bond <[email protected]>
16+
*
17+
* @experimental
18+
*/
19+
#[\Attribute(\Attribute::TARGET_METHOD)]
20+
final class PreMount
21+
{
22+
}

src/TwigComponent/src/ComponentFactory.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\DependencyInjection\ServiceLocator;
1515
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
16+
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
1617

1718
/**
1819
* @author Kevin Bond <[email protected]>
@@ -80,6 +81,7 @@ public function configFor($component, string $name = null): array
8081
public function create(string $name, array $data = []): object
8182
{
8283
$component = $this->getComponent($name);
84+
$data = $this->preMount($component, $data);
8385

8486
$this->mount($component, $data);
8587

@@ -140,4 +142,13 @@ private function getComponent(string $name): object
140142

141143
return $this->components->get($name);
142144
}
145+
146+
private function preMount(object $component, array $data): array
147+
{
148+
foreach (AsTwigComponent::preMountMethods($component) as $method) {
149+
$data = $component->{$method->name}($data);
150+
}
151+
152+
return $data;
153+
}
143154
}

src/TwigComponent/tests/Fixture/Component/ComponentB.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,23 @@
1212
namespace Symfony\UX\TwigComponent\Tests\Fixture\Component;
1313

1414
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
15+
use Symfony\UX\TwigComponent\Attribute\PreMount;
1516

1617
/**
1718
* @author Kevin Bond <[email protected]>
1819
*/
1920
#[AsTwigComponent('component_b', template: 'components/custom1.html.twig')]
2021
final class ComponentB
2122
{
23+
public string $value;
24+
25+
#[PreMount]
26+
public function preMount(array $data): array
27+
{
28+
if (isset($data['value'])) {
29+
$data['value'] = 'pre-mount '.$data['value'];
30+
}
31+
32+
return $data;
33+
}
2234
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
Custom template 1
2+
b value: {{ this.value }}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{{ component('component_a', { propA: 'prop a value 1', propB: 'prop b value 1' }) }}
22
{{ component('component_a', { propA: 'prop a value 2', propB: 'prop b value 2' }) }}
3-
{{ component('component_b') }}
3+
{{ component('component_b', { value: 'b value 1' }) }}

src/TwigComponent/tests/Integration/ComponentExtensionTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public function testCanRenderTheSameComponentMultipleTimes(): void
3838
$this->assertStringContainsString('propB: prop b value 1', $output);
3939
$this->assertStringContainsString('propA: prop a value 2', $output);
4040
$this->assertStringContainsString('propB: prop b value 2', $output);
41+
$this->assertStringContainsString('b value: pre-mount b value 1', $output);
4142
$this->assertStringContainsString('service: service a value', $output);
4243
}
4344

0 commit comments

Comments
 (0)