From 5eda8331fa7a63eda339ee0e4276b44f9e680ed4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Buliard?= Date: Thu, 11 Apr 2024 16:06:20 +0200 Subject: [PATCH] Debug LiveComponent EventListener --- .../src/Command/TwigComponentDebugCommand.php | 42 ++++++++++++++++--- .../TwigComponentDataCollector.php | 14 +++++++ .../Collector/twig_component.html.twig | 27 ++++++------ 3 files changed, 66 insertions(+), 17 deletions(-) diff --git a/src/TwigComponent/src/Command/TwigComponentDebugCommand.php b/src/TwigComponent/src/Command/TwigComponentDebugCommand.php index d89ed2822c..bcbe199a2d 100644 --- a/src/TwigComponent/src/Command/TwigComponentDebugCommand.php +++ b/src/TwigComponent/src/Command/TwigComponentDebugCommand.php @@ -18,9 +18,11 @@ use Symfony\Component\Console\Helper\TableSeparator; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Finder\Finder; +use Symfony\UX\LiveComponent\Attribute\LiveListener; use Symfony\UX\TwigComponent\Attribute\AsTwigComponent; use Symfony\UX\TwigComponent\Attribute\ExposeInTemplate; use Symfony\UX\TwigComponent\ComponentFactory; @@ -49,6 +51,11 @@ protected function configure(): void $this ->setDefinition([ new InputArgument('name', InputArgument::OPTIONAL, 'A component name or part of the component name'), + new InputOption( + name: 'listening', + mode: InputOption::VALUE_REQUIRED, + description: 'Filter components list to display only those listening to the given action' + ), ]) ->setHelp( <<<'EOF' @@ -83,7 +90,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int return Command::SUCCESS; } - $components = $this->findComponents(); + $components = $this->findComponents($input->getOption('listening')); $this->displayComponentsTable($io, $components); return Command::SUCCESS; @@ -128,14 +135,19 @@ private function findComponentName(SymfonyStyle $io, string $name, bool $interac /** * @return array */ - private function findComponents(): array + private function findComponents(?string $listeningFilter): array { $components = []; foreach ($this->componentClassMap as $class => $name) { - $components[$name] ??= $this->componentFactory->metadataFor($name); + if (null === $listeningFilter || \in_array($listeningFilter, $this->resolveEventsListening($class))) { + $components[$name] ??= $this->componentFactory->metadataFor($name); + } } - foreach ($this->findAnonymousComponents() as $name => $template) { - $components[$name] ??= $this->componentFactory->metadataFor($name); + + if (null === $listeningFilter) { + foreach ($this->findAnonymousComponents() as $name => $template) { + $components[$name] ??= $this->componentFactory->metadataFor($name); + } } return $components; @@ -196,6 +208,14 @@ private function displayComponentDetails(SymfonyStyle $io, string $name): void ['Properties', implode("\n", $this->getComponentProperties($metadata))], ]); + $eventsListened = $this->resolveEventsListening($metadata->get('class')); + if ($eventsListened) { + $table->addRows([ + new TableSeparator(), + ['Listening to', implode("\n", $eventsListened)], + ]); + } + $logMethod = function (\ReflectionMethod $m) { $params = array_map( fn (\ReflectionParameter $p) => '$'.$p->getName(), @@ -314,4 +334,16 @@ private function getAnonymousComponentProperties(ComponentMetadata $metadata): a return $properties; } + + private function resolveEventsListening(string $class): array + { + $events = []; + foreach ((new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { + foreach ($method->getAttributes(LiveListener::class) as $listenAttribute) { + $events[] = $listenAttribute->getArguments()[0]; + } + } + + return $events; + } } diff --git a/src/TwigComponent/src/DataCollector/TwigComponentDataCollector.php b/src/TwigComponent/src/DataCollector/TwigComponentDataCollector.php index f0c1e05f2d..bcd77b8d84 100644 --- a/src/TwigComponent/src/DataCollector/TwigComponentDataCollector.php +++ b/src/TwigComponent/src/DataCollector/TwigComponentDataCollector.php @@ -17,6 +17,7 @@ use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; use Symfony\Component\VarDumper\Caster\ClassStub; use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\UX\LiveComponent\Attribute\LiveListener; use Symfony\UX\TwigComponent\Event\PostRenderEvent; use Symfony\UX\TwigComponent\Event\PreRenderEvent; use Symfony\UX\TwigComponent\EventListener\TwigComponentLoggerListener; @@ -115,6 +116,7 @@ private function collectDataFromLogger(): void 'template_path' => $this->resolveTemplatePath($metadata->getTemplate()), // defer ? lazy ? 'render_count' => 0, 'render_time' => 0, + 'listening' => $this->resolveEventsListening($componentClass), ]; $renderId = spl_object_id($mountedComponent); @@ -182,4 +184,16 @@ private function resolveTemplatePath(string $logicalName): ?string return $source->getPath(); } + + private function resolveEventsListening(string $class): array + { + $events = []; + foreach ((new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { + foreach ($method->getAttributes(LiveListener::class) as $listenAttribute) { + $events[] = $listenAttribute->getArguments()[0]; + } + } + + return $events; + } } diff --git a/src/TwigComponent/templates/Collector/twig_component.html.twig b/src/TwigComponent/templates/Collector/twig_component.html.twig index fe6ded6a41..d41f0c54ee 100644 --- a/src/TwigComponent/templates/Collector/twig_component.html.twig +++ b/src/TwigComponent/templates/Collector/twig_component.html.twig @@ -234,6 +234,9 @@ {% else %} {{ component.template }} {% endif %} + {% if component.listening_to is not empty %} +
Listening to {{ component.listening_to|join(', ') }}
+ {% endif %} {{ component.render_count }} @@ -285,18 +288,18 @@ - - Input props - {{ profiler_dump(render.input_props) }} - - - Attributes - {{ profiler_dump(render.attributes) }} - - - Component - {{ profiler_dump(render.component) }} - + + Input props + {{ profiler_dump(render.input_props) }} + + + Attributes + {{ profiler_dump(render.attributes) }} + + + Component + {{ profiler_dump(render.component) }} + {% endfor %}