Skip to content

[TwigComponent] add PreMount hook #189

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 0 additions & 14 deletions src/LiveComponent/src/Attribute/AsLiveComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,20 +99,6 @@ public static function preDehydrateMethods(object $component): \Traversable
yield from self::attributeMethodsFor(PreDehydrate::class, $component);
}

/**
* @param string|object $classOrObject
*
* @return \ReflectionMethod[]
*/
private static function attributeMethodsFor(string $attribute, object $component): \Traversable
{
foreach ((new \ReflectionClass($component))->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
if ($method->getAttributes($attribute)[0] ?? null) {
yield $method;
}
}
}

/**
* @return \ReflectionProperty[]
*/
Expand Down
34 changes: 34 additions & 0 deletions src/TwigComponent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,40 @@ If an option name matches an argument name in `mount()`, the
option is passed as that argument and the component system
will _not_ try to set it directly on a property.

### PreMount Hook

If you need to modify/validate data before it's _mounted_ on the
component use a `PreMount` hook:

```php
// src/Components/AlertComponent.php

use Symfony\UX\TwigComponent\Attribute\PreMount;
// ...

#[AsTwigComponent('alert')]
class AlertComponent
{
public string $message;
public string $type = 'success';

#[PreMount]
public function preMount(array $data): array
{
// validate data
$resolver = new OptionsResolver();
$resolver->setDefaults(['type' => 'success']);
$resolver->setAllowedValues('type', ['success', 'danger']);
$resolver->setRequired('message');
$resolver->setAllowedTypes('message', 'string');

return $resolver->resolve($data)
}

// ...
}
```

## Fetching Services

Let's create a more complex example: a "featured products" component.
Expand Down
24 changes: 24 additions & 0 deletions src/TwigComponent/src/Attribute/AsTwigComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,28 @@ public function __construct(string $name, ?string $template = null)
$this->name = $name;
$this->template = $template;
}

/**
* @internal
*
* @return \ReflectionMethod[]
*/
public static function preMountMethods(object $component): \Traversable
{
yield from self::attributeMethodsFor(PreMount::class, $component);
}

/**
* @internal
*
* @return \ReflectionMethod[]
*/
protected static function attributeMethodsFor(string $attribute, object $component): \Traversable
{
foreach ((new \ReflectionClass($component))->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
if ($method->getAttributes($attribute)[0] ?? null) {
yield $method;
}
}
}
}
22 changes: 22 additions & 0 deletions src/TwigComponent/src/Attribute/PreMount.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Symfony\UX\TwigComponent\Attribute;

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

/**
* @author Kevin Bond <[email protected]>
*
* @experimental
*/
#[\Attribute(\Attribute::TARGET_METHOD)]
final class PreMount
{
}
11 changes: 11 additions & 0 deletions src/TwigComponent/src/ComponentFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

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

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

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

return $this->components->get($name);
}

private function preMount(object $component, array $data): array
{
foreach (AsTwigComponent::preMountMethods($component) as $method) {
$data = $component->{$method->name}($data);
}

return $data;
}
}
12 changes: 12 additions & 0 deletions src/TwigComponent/tests/Fixture/Component/ComponentB.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,23 @@
namespace Symfony\UX\TwigComponent\Tests\Fixture\Component;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
use Symfony\UX\TwigComponent\Attribute\PreMount;

/**
* @author Kevin Bond <[email protected]>
*/
#[AsTwigComponent('component_b', template: 'components/custom1.html.twig')]
final class ComponentB
{
public string $value;

#[PreMount]
public function preMount(array $data): array
{
if (isset($data['value'])) {
$data['value'] = 'pre-mount '.$data['value'];
}

return $data;
}
}
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
Custom template 1
b value: {{ this.value }}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{{ component('component_a', { propA: 'prop a value 1', propB: 'prop b value 1' }) }}
{{ component('component_a', { propA: 'prop a value 2', propB: 'prop b value 2' }) }}
{{ component('component_b') }}
{{ component('component_b', { value: 'b value 1' }) }}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public function testCanRenderTheSameComponentMultipleTimes(): void
$this->assertStringContainsString('propB: prop b value 1', $output);
$this->assertStringContainsString('propA: prop a value 2', $output);
$this->assertStringContainsString('propB: prop b value 2', $output);
$this->assertStringContainsString('b value: pre-mount b value 1', $output);
$this->assertStringContainsString('service: service a value', $output);
}

Expand Down