From 82a9e178b4479d7362e5001a544266aef571b586 Mon Sep 17 00:00:00 2001 From: sneakyvv Date: Fri, 25 Aug 2023 17:09:02 +0200 Subject: [PATCH] [LiveComponent] Add priority to PreDehydrate & PostHydrate hooks --- .../src/Attribute/AsLiveComponent.php | 16 ++---- .../src/Attribute/PostHydrate.php | 7 +++ .../src/Attribute/PreDehydrate.php | 7 +++ .../Unit/Attribute/AsLiveComponentTest.php | 56 ++++++++++++++++--- .../src/Attribute/AsTwigComponent.php | 34 ++++++----- 5 files changed, 87 insertions(+), 33 deletions(-) diff --git a/src/LiveComponent/src/Attribute/AsLiveComponent.php b/src/LiveComponent/src/Attribute/AsLiveComponent.php index 018fd23c3cc..e820315754a 100644 --- a/src/LiveComponent/src/Attribute/AsLiveComponent.php +++ b/src/LiveComponent/src/Attribute/AsLiveComponent.php @@ -67,13 +67,7 @@ public static function isActionAllowed(object $component, string $action): bool */ public static function preReRenderMethods(object $component): iterable { - $methods = iterator_to_array(self::attributeMethodsFor(PreReRender::class, $component)); - - usort($methods, static function (\ReflectionMethod $a, \ReflectionMethod $b) { - return $a->getAttributes(PreReRender::class)[0]->newInstance()->priority <=> $b->getAttributes(PreReRender::class)[0]->newInstance()->priority; - }); - - return array_reverse($methods); + return self::attributeMethodsByPriorityFor($component, PreReRender::class); } /** @@ -81,9 +75,9 @@ public static function preReRenderMethods(object $component): iterable * * @return \ReflectionMethod[] */ - public static function postHydrateMethods(object $component): \Traversable + public static function postHydrateMethods(object $component): iterable { - yield from self::attributeMethodsFor(PostHydrate::class, $component); + return self::attributeMethodsByPriorityFor($component, PostHydrate::class); } /** @@ -91,9 +85,9 @@ public static function postHydrateMethods(object $component): \Traversable * * @return \ReflectionMethod[] */ - public static function preDehydrateMethods(object $component): \Traversable + public static function preDehydrateMethods(object $component): iterable { - yield from self::attributeMethodsFor(PreDehydrate::class, $component); + return self::attributeMethodsByPriorityFor($component, PreDehydrate::class); } public static function liveListeners(object $component): array diff --git a/src/LiveComponent/src/Attribute/PostHydrate.php b/src/LiveComponent/src/Attribute/PostHydrate.php index 254933b10fe..2abac5dfd2b 100644 --- a/src/LiveComponent/src/Attribute/PostHydrate.php +++ b/src/LiveComponent/src/Attribute/PostHydrate.php @@ -17,4 +17,11 @@ #[\Attribute(\Attribute::TARGET_METHOD)] final class PostHydrate { + /** + * @param int $priority If multiple hooks are registered in a component, use to configure + * the order in which they are called (higher called earlier) + */ + public function __construct(public int $priority = 0) + { + } } diff --git a/src/LiveComponent/src/Attribute/PreDehydrate.php b/src/LiveComponent/src/Attribute/PreDehydrate.php index fc779dc984a..d764b83e5a9 100644 --- a/src/LiveComponent/src/Attribute/PreDehydrate.php +++ b/src/LiveComponent/src/Attribute/PreDehydrate.php @@ -17,4 +17,11 @@ #[\Attribute(\Attribute::TARGET_METHOD)] final class PreDehydrate { + /** + * @param int $priority If multiple hooks are registered in a component, use to configure + * the order in which they are called (higher called earlier) + */ + public function __construct(public int $priority = 0) + { + } } diff --git a/src/LiveComponent/tests/Unit/Attribute/AsLiveComponentTest.php b/src/LiveComponent/tests/Unit/Attribute/AsLiveComponentTest.php index 93d4ae23350..07fb5d1a10f 100644 --- a/src/LiveComponent/tests/Unit/Attribute/AsLiveComponentTest.php +++ b/src/LiveComponent/tests/Unit/Attribute/AsLiveComponentTest.php @@ -13,6 +13,8 @@ use PHPUnit\Framework\TestCase; use Symfony\UX\LiveComponent\Attribute\AsLiveComponent; +use Symfony\UX\LiveComponent\Attribute\PostHydrate; +use Symfony\UX\LiveComponent\Attribute\PreDehydrate; use Symfony\UX\LiveComponent\Attribute\PreReRender; use Symfony\UX\LiveComponent\Tests\Fixtures\Component\Component5; @@ -21,20 +23,58 @@ */ final class AsLiveComponentTest extends TestCase { - public function testCanGetPreDehydrateMethods(): void + public function testPreDehydrateMethodsAreOrderedByPriority(): void { - $methods = iterator_to_array(AsLiveComponent::preDehydrateMethods(new Component5())); + $hooks = AsLiveComponent::preDehydrateMethods( + new class() { + #[PreDehydrate(priority: -10)] + public function hook1() + { + } + + #[PreDehydrate(priority: 10)] + public function hook2() + { + } + + #[PreDehydrate] + public function hook3() + { + } + } + ); - $this->assertCount(1, $methods); - $this->assertSame('method4', $methods[0]->getName()); + $this->assertCount(3, $hooks); + $this->assertSame('hook2', $hooks[0]->name); + $this->assertSame('hook3', $hooks[1]->name); + $this->assertSame('hook1', $hooks[2]->name); } - public function testCanGetPostHydrateMethods(): void + public function testPostHydrateMethodsAreOrderedByPriority(): void { - $methods = iterator_to_array(AsLiveComponent::postHydrateMethods(new Component5())); + $hooks = AsLiveComponent::postHydrateMethods( + new class() { + #[PostHydrate(priority: -10)] + public function hook1() + { + } + + #[PostHydrate(priority: 10)] + public function hook2() + { + } + + #[PostHydrate] + public function hook3() + { + } + } + ); - $this->assertCount(1, $methods); - $this->assertSame('method5', $methods[0]->getName()); + $this->assertCount(3, $hooks); + $this->assertSame('hook2', $hooks[0]->name); + $this->assertSame('hook3', $hooks[1]->name); + $this->assertSame('hook1', $hooks[2]->name); } public function testPreMountHooksAreOrderedByPriority(): void diff --git a/src/TwigComponent/src/Attribute/AsTwigComponent.php b/src/TwigComponent/src/Attribute/AsTwigComponent.php index 79268956d65..6667707a632 100644 --- a/src/TwigComponent/src/Attribute/AsTwigComponent.php +++ b/src/TwigComponent/src/Attribute/AsTwigComponent.php @@ -45,13 +45,7 @@ public function serviceConfig(): array */ public static function preMountMethods(object $component): iterable { - $methods = iterator_to_array(self::attributeMethodsFor(PreMount::class, $component)); - - usort($methods, static function (\ReflectionMethod $a, \ReflectionMethod $b) { - return $a->getAttributes(PreMount::class)[0]->newInstance()->priority <=> $b->getAttributes(PreMount::class)[0]->newInstance()->priority; - }); - - return array_reverse($methods); + return self::attributeMethodsByPriorityFor($component, PreMount::class); } /** @@ -61,13 +55,7 @@ public static function preMountMethods(object $component): iterable */ public static function postMountMethods(object $component): iterable { - $methods = iterator_to_array(self::attributeMethodsFor(PostMount::class, $component)); - - usort($methods, static function (\ReflectionMethod $a, \ReflectionMethod $b) { - return $a->getAttributes(PostMount::class)[0]->newInstance()->priority <=> $b->getAttributes(PostMount::class)[0]->newInstance()->priority; - }); - - return array_reverse($methods); + return self::attributeMethodsByPriorityFor($component, PostMount::class); } /** @@ -83,4 +71,22 @@ protected static function attributeMethodsFor(string $attribute, object $compone } } } + + /** + * @param class-string $attributeClass + * + * @return \ReflectionMethod[] + * + * @internal + */ + protected static function attributeMethodsByPriorityFor(object $component, string $attributeClass): array + { + $methods = iterator_to_array(self::attributeMethodsFor($attributeClass, $component)); + + usort($methods, static function (\ReflectionMethod $a, \ReflectionMethod $b) use ($attributeClass) { + return $a->getAttributes($attributeClass)[0]->newInstance()->priority <=> $b->getAttributes($attributeClass)[0]->newInstance()->priority; + }); + + return array_reverse($methods); + } }