From 7b0f600fdfc0b108bdf91b93056b19f7771444eb Mon Sep 17 00:00:00 2001 From: andyexeter Date: Fri, 14 Nov 2025 10:02:45 +0000 Subject: [PATCH] Allow using directory name as component name for anonymous components --- src/TwigComponent/CHANGELOG.md | 1 + src/TwigComponent/doc/index.rst | 37 +++++++++++++++++++ .../src/ComponentTemplateFinder.php | 5 +++ .../templates/anonymous/Bar.html.twig | 0 .../templates/anonymous/Bar/index.html.twig | 0 .../templates/anonymous/Menu/Item.html.twig | 0 .../templates/anonymous/Menu/index.html.twig | 0 .../Integration/ComponentFactoryTest.php | 23 ++++++++++++ .../Unit/ComponentTemplateFinderTest.php | 5 +++ 9 files changed, 71 insertions(+) create mode 100644 src/TwigComponent/tests/Fixtures/templates/anonymous/Bar.html.twig create mode 100644 src/TwigComponent/tests/Fixtures/templates/anonymous/Bar/index.html.twig create mode 100644 src/TwigComponent/tests/Fixtures/templates/anonymous/Menu/Item.html.twig create mode 100644 src/TwigComponent/tests/Fixtures/templates/anonymous/Menu/index.html.twig diff --git a/src/TwigComponent/CHANGELOG.md b/src/TwigComponent/CHANGELOG.md index a4578a1c545..c2065acd61a 100644 --- a/src/TwigComponent/CHANGELOG.md +++ b/src/TwigComponent/CHANGELOG.md @@ -4,6 +4,7 @@ - Add option `profiler.collect_components` to control component data collection in the profiler (enabled in debug mode by default) +- Add support for using directory name as component name for anonymous components ## 2.30 diff --git a/src/TwigComponent/doc/index.rst b/src/TwigComponent/doc/index.rst index 6cae99346a3..ed4d37bf2c3 100644 --- a/src/TwigComponent/doc/index.rst +++ b/src/TwigComponent/doc/index.rst @@ -676,6 +676,43 @@ the subdirectory: {# renders as: #} +If your anonymous component lives in a directory with the same name, you can +name the component file ``index.html.twig`` to avoid repetition: + +.. code-block:: html+twig + + {# templates/components/Menu/index.html.twig #} + + +.. code-block:: html+twig + + {# templates/components/Menu/Item.html.twig #} + {% props href, label %} +
  • + {% block content %}{{ label }}{% endblock %} +
  • + +.. code-block:: html+twig + + {# index.html.twig #} + ... + + Home + About + + + {# renders as: #} + + Like normal, you can pass extra attributes that will be rendered on the element: .. code-block:: html+twig diff --git a/src/TwigComponent/src/ComponentTemplateFinder.php b/src/TwigComponent/src/ComponentTemplateFinder.php index 0ceb583e1f3..df63f4b7377 100644 --- a/src/TwigComponent/src/ComponentTemplateFinder.php +++ b/src/TwigComponent/src/ComponentTemplateFinder.php @@ -66,6 +66,11 @@ public function findAnonymousComponentTemplate(string $name): ?string return $template; } + $template = rtrim($this->directory, '/').'/'.$componentPath.'/index.html.twig'; + if ($loader->exists($template)) { + return $template; + } + $parts = explode('/', $componentPath, 2); if (\count($parts) < 2) { return null; diff --git a/src/TwigComponent/tests/Fixtures/templates/anonymous/Bar.html.twig b/src/TwigComponent/tests/Fixtures/templates/anonymous/Bar.html.twig new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/TwigComponent/tests/Fixtures/templates/anonymous/Bar/index.html.twig b/src/TwigComponent/tests/Fixtures/templates/anonymous/Bar/index.html.twig new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/TwigComponent/tests/Fixtures/templates/anonymous/Menu/Item.html.twig b/src/TwigComponent/tests/Fixtures/templates/anonymous/Menu/Item.html.twig new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/TwigComponent/tests/Fixtures/templates/anonymous/Menu/index.html.twig b/src/TwigComponent/tests/Fixtures/templates/anonymous/Menu/index.html.twig new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/TwigComponent/tests/Integration/ComponentFactoryTest.php b/src/TwigComponent/tests/Integration/ComponentFactoryTest.php index c2189c955f4..ab4639ad0de 100644 --- a/src/TwigComponent/tests/Integration/ComponentFactoryTest.php +++ b/src/TwigComponent/tests/Integration/ComponentFactoryTest.php @@ -178,6 +178,29 @@ public function testLoadingAnonymousComponentFromBundle() $this->assertNull($metadata->get('class')); } + public function testLoadingAnonymousComponentWithFallback() + { + self::bootKernel(['environment' => 'anonymous_directory']); + + $metadata = $this->factory()->metadataFor('Menu'); + + $this->assertSame('anonymous/Menu/index.html.twig', $metadata->getTemplate()); + $this->assertSame('Menu', $metadata->getName()); + $this->assertNull($metadata->get('class')); + + $metadata = $this->factory()->metadataFor('Menu:Item'); + + $this->assertSame('anonymous/Menu/Item.html.twig', $metadata->getTemplate()); + $this->assertSame('Menu:Item', $metadata->getName()); + $this->assertNull($metadata->get('class')); + + // Ensure anonymous/Bar.html.twig takes precedence over anonymous/Bar/index.html.twig + $metadata = $this->factory()->metadataFor('Bar'); + $this->assertSame('anonymous/Bar.html.twig', $metadata->getTemplate()); + $this->assertSame('Bar', $metadata->getName()); + $this->assertNull($metadata->get('class')); + } + public function testAutoNamingInSubDirectory() { $metadata = $this->factory()->metadataFor('SubDirectory:ComponentInSubDirectory'); diff --git a/src/TwigComponent/tests/Unit/ComponentTemplateFinderTest.php b/src/TwigComponent/tests/Unit/ComponentTemplateFinderTest.php index 66a19bd0646..685a0db575f 100644 --- a/src/TwigComponent/tests/Unit/ComponentTemplateFinderTest.php +++ b/src/TwigComponent/tests/Unit/ComponentTemplateFinderTest.php @@ -117,6 +117,8 @@ public function testFindTemplateWithinDirectory() 'foo/bar.html.twig', 'bar/foo/bar.html.twig', 'foo/foo/bar.html.twig', + 'foo/qux/index.html.twig', + 'foo/foo/baz/index.html.twig', ]; $loader = $this->createLoader($templates); $finder = new ComponentTemplateFinder($loader, 'foo'); @@ -124,6 +126,9 @@ public function testFindTemplateWithinDirectory() $this->assertEquals('foo/bar.html.twig', $finder->findAnonymousComponentTemplate('bar')); $this->assertEquals('foo/foo/bar.html.twig', $finder->findAnonymousComponentTemplate('foo:bar')); $this->assertEquals('foo/foo/bar.html.twig', $finder->findAnonymousComponentTemplate('foo:bar')); + + $this->assertEquals('foo/qux/index.html.twig', $finder->findAnonymousComponentTemplate('qux')); + $this->assertEquals('foo/foo/baz/index.html.twig', $finder->findAnonymousComponentTemplate('foo:baz')); } private function createLoader(array $templates): LoaderInterface