diff --git a/src/TwigComponent/CHANGELOG.md b/src/TwigComponent/CHANGELOG.md index 2a42f9d5897..a4578a1c545 100644 --- a/src/TwigComponent/CHANGELOG.md +++ b/src/TwigComponent/CHANGELOG.md @@ -1,5 +1,10 @@ # CHANGELOG +## 2.32 + +- Add option `profiler.collect_components` to control component data collection + in the profiler (enabled in debug mode by default) + ## 2.30 - Ensure compatibility with PHP 8.5 diff --git a/src/TwigComponent/config/debug.php b/src/TwigComponent/config/debug.php index 6afabb82245..4d23c7a7bb5 100644 --- a/src/TwigComponent/config/debug.php +++ b/src/TwigComponent/config/debug.php @@ -15,6 +15,7 @@ use Symfony\UX\TwigComponent\DataCollector\TwigComponentDataCollector; use Symfony\UX\TwigComponent\EventListener\TwigComponentLoggerListener; +use function Symfony\Component\DependencyInjection\Loader\Configurator\abstract_arg; use function Symfony\Component\DependencyInjection\Loader\Configurator\service; return static function (ContainerConfigurator $container) { @@ -27,6 +28,7 @@ ->args([ service('ux.twig_component.component_logger_listener'), service('twig'), + abstract_arg('profiler collect components'), ]) ->tag('data_collector', [ 'template' => '@TwigComponent/Collector/twig_component.html.twig', diff --git a/src/TwigComponent/src/DataCollector/TwigComponentDataCollector.php b/src/TwigComponent/src/DataCollector/TwigComponentDataCollector.php index 1ee94c71bdb..cda4ad385b5 100644 --- a/src/TwigComponent/src/DataCollector/TwigComponentDataCollector.php +++ b/src/TwigComponent/src/DataCollector/TwigComponentDataCollector.php @@ -35,6 +35,7 @@ final class TwigComponentDataCollector extends AbstractDataCollector implements public function __construct( private readonly TwigComponentLoggerListener $logger, private readonly Environment $twig, + private readonly bool $collectComponents = true, ) { $this->hasStub = class_exists(ClassStub::class); } @@ -130,12 +131,15 @@ private function collectDataFromLogger(): void 'input_props' => $mountedComponent->getInputProps(), 'attributes' => $mountedComponent->getAttributes()->all(), 'template_index' => $event->getTemplateIndex(), - 'component' => $mountedComponent->getComponent(), 'depth' => \count($ongoingRenders), 'children' => [], 'render_start' => $profile[0], ]; + if ($this->collectComponents) { + $renders[$renderId]['component'] = $mountedComponent->getComponent(); + } + if ($parentId = end($ongoingRenders)) { $renders[$parentId]['children'][] = $renderId; } diff --git a/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php b/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php index dc71c610931..9aee30bccc4 100644 --- a/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php +++ b/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php @@ -150,8 +150,11 @@ static function (ChildDefinition $definition, AsTwigComponent $attribute) { $container->setAlias('console.command.stimulus_component_debug', 'ux.twig_component.command.debug') ->setDeprecated('symfony/ux-twig-component', '2.13', '%alias_id%'); - if ($container->getParameter('kernel.debug') && $config['profiler']) { + if ($config['profiler']['enabled']) { $loader->load('debug.php'); + + $container->getDefinition('ux.twig_component.data_collector') + ->setArgument(2, $config['profiler']['collect_components']); } $loader->load('cache.php'); @@ -215,9 +218,13 @@ public function getConfigTreeBuilder(): TreeBuilder ->scalarNode('anonymous_template_directory') ->info('Defaults to `components`') ->end() - ->booleanNode('profiler') - ->info('Enables the profiler for Twig Component (in debug mode)') - ->defaultValue('%kernel.debug%') + ->arrayNode('profiler') + ->info('Enables the profiler for Twig Component') + ->canBeEnabled() + ->children() + ->booleanNode('enabled')->defaultValue('%kernel.debug%')->end() + ->booleanNode('collect_components')->info('Collect components instances')->defaultTrue()->end() + ->end() ->end() ->scalarNode('controllers_json') ->setDeprecated('symfony/ux-twig-component', '2.18', 'The "twig_component.controllers_json" config option is deprecated, and will be removed in 3.0.') diff --git a/src/TwigComponent/templates/Collector/twig_component.html.twig b/src/TwigComponent/templates/Collector/twig_component.html.twig index c2098156f22..7bd88387fbc 100644 --- a/src/TwigComponent/templates/Collector/twig_component.html.twig +++ b/src/TwigComponent/templates/Collector/twig_component.html.twig @@ -293,10 +293,12 @@ Attributes {{ profiler_dump(render.attributes) }} + {% if render.component is defined %} Component {{ profiler_dump(render.component) }} + {% endif %} {% endfor %} diff --git a/src/TwigComponent/tests/Unit/DataCollector/TwigComponentDataCollectorTest.php b/src/TwigComponent/tests/Unit/DataCollector/TwigComponentDataCollectorTest.php index a547c64e04d..e0cca8e8f47 100644 --- a/src/TwigComponent/tests/Unit/DataCollector/TwigComponentDataCollectorTest.php +++ b/src/TwigComponent/tests/Unit/DataCollector/TwigComponentDataCollectorTest.php @@ -14,9 +14,16 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\UX\TwigComponent\ComponentAttributes; +use Symfony\UX\TwigComponent\ComponentMetadata; use Symfony\UX\TwigComponent\DataCollector\TwigComponentDataCollector; +use Symfony\UX\TwigComponent\Event\PostRenderEvent; +use Symfony\UX\TwigComponent\Event\PreRenderEvent; use Symfony\UX\TwigComponent\EventListener\TwigComponentLoggerListener; +use Symfony\UX\TwigComponent\MountedComponent; use Twig\Environment; +use Twig\Loader\ArrayLoader; +use Twig\Runtime\EscaperRuntime; /** * @author Simon André @@ -35,7 +42,7 @@ public function testCollectDoesNothing() $this->assertSame([], $dataCollector->getData()); } - public function testLateCollect() + public function testLateCollectWithNoCollectedData() { $logger = new TwigComponentLoggerListener(); $twig = $this->createMock(Environment::class); @@ -54,6 +61,44 @@ public function testLateCollect() $this->assertEquals(0.0, $dataCollector->getRenderTime()); } + /** + * @testWith [true] + * [false] + */ + public function testLateCollectWithCollectedData(bool $collectComponents) + { + $logger = new TwigComponentLoggerListener(); + $twig = new Environment(new ArrayLoader()); + $dataCollector = new TwigComponentDataCollector($logger, $twig, $collectComponents); + + // Trigger some events to be logged + $mounted = new MountedComponent('foo', new \stdClass(), new ComponentAttributes([], new EscaperRuntime())); + $eventA = new PreRenderEvent($mounted, new ComponentMetadata(['key' => 'foo', 'template' => 'bar']), []); + $logger->onPreRender($eventA); + $eventB = new PostRenderEvent($mounted); + $logger->onPostRender($eventB); + + $dataCollector->lateCollect(); + + $this->assertSame(1, $dataCollector->getComponentCount()); + $this->assertIsIterable($dataCollector->getComponents()); + $this->assertNotEmpty($dataCollector->getComponents()); + + $this->assertSame(1, $dataCollector->getRenderCount()); + $this->assertIsIterable($dataCollector->getRenders()); + $this->assertNotEmpty($dataCollector->getRenders()); + + foreach ($dataCollector->getRenders() as $render) { + if ($collectComponents) { + $this->assertNotNull($render['component']); + } else { + $this->assertNull($render['component']); + } + } + + $this->assertGreaterThan(0.0, $dataCollector->getRenderTime()); + } + public function testReset() { $logger = new TwigComponentLoggerListener(); diff --git a/src/TwigComponent/tests/Unit/DependencyInjection/TwigComponentExtensionTest.php b/src/TwigComponent/tests/Unit/DependencyInjection/TwigComponentExtensionTest.php index 6695e064caf..f2edd97aa72 100644 --- a/src/TwigComponent/tests/Unit/DependencyInjection/TwigComponentExtensionTest.php +++ b/src/TwigComponent/tests/Unit/DependencyInjection/TwigComponentExtensionTest.php @@ -37,9 +37,10 @@ public function testDataCollectorWithDebugMode() $this->compileContainer($container); $this->assertTrue($container->hasDefinition('ux.twig_component.data_collector')); + $this->assertTrue($container->getDefinition('ux.twig_component.data_collector')->getArgument(2)); } - public function testDataCollectorWithDebugModeCanBeDisabled() + public function testDataCollectorWithCollectComponentsDisabled() { $container = $this->createContainer(); $container->setParameter('kernel.debug', true); @@ -47,22 +48,25 @@ public function testDataCollectorWithDebugModeCanBeDisabled() $container->loadFromExtension('twig_component', [ 'defaults' => [], 'anonymous_template_directory' => 'components/', - 'profiler' => false, + 'profiler' => [ + 'collect_components' => false, + ], ]); $this->compileContainer($container); - $this->assertFalse($container->hasDefinition('ux.twig_component.data_collector')); + $this->assertTrue($container->hasDefinition('ux.twig_component.data_collector')); + $this->assertFalse($container->getDefinition('ux.twig_component.data_collector')->getArgument(2)); } - public function testDataCollectorWithoutDebugMode() + public function testDataCollectorWithDebugModeCanBeDisabled() { $container = $this->createContainer(); - $container->setParameter('kernel.debug', false); + $container->setParameter('kernel.debug', true); $container->registerExtension(new TwigComponentExtension()); $container->loadFromExtension('twig_component', [ 'defaults' => [], 'anonymous_template_directory' => 'components/', - 'profiler' => true, + 'profiler' => false, ]); $this->compileContainer($container);