Skip to content

Commit

Permalink
Add Anonymous Components support for 3rd-party bundles
Browse files Browse the repository at this point in the history
  • Loading branch information
yceruto committed Aug 13, 2024
1 parent 9e6920d commit c207af6
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 8 deletions.
4 changes: 4 additions & 0 deletions src/TwigComponent/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

## 2.20.0

- Add Anonymous Component support for 3rd-party bundles #2019

## 2.17.0

- Add nested attribute support #1405
Expand Down
48 changes: 43 additions & 5 deletions src/TwigComponent/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1622,6 +1622,42 @@ controls how components are named and where their templates live:
If a component class matches multiple namespaces, the first matched will
be used.

3rd-Party Bundle
~~~~~~~~~~~~~~~~

The flexibility of Twig Components is extended even further when integrated
with third-party bundles, allowing developers to seamlessly include pre-built
components into their projects.

Anonymous Components
--------------------

.. versionadded:: 2.20

The bundle convention for Anonymous components was added in TwigComponents 2.18.

Using a component from a third-party bundle is just as straightforward as using
one from your own application. Once the bundle is installed and configured, you
can reference its components directly within your Twig templates:

.. code-block:: html+twig

<twig:Shadcn:Button type="primary">
Click me
</twig:Shadcn:Button>

Here, the component name is composed of the bundle's Twig namespace ``Shadcn``, followed
by a colon, and then the component path Button.

.. note::

You can discover the Twig namespace of every registered bundle by inspecting the
``bin/console debug:twig`` command.

The component must be located in the bundle's ``templates/components/`` directory. For
example, the component referenced as ``<twig:Shadcn:Button>`` should have its template
file at ``templates/components/Button.html.twig`` within the Shadcn bundle.

Debugging Components
--------------------

Expand All @@ -1635,13 +1671,14 @@ that live in ``templates/components/``:
$ php bin/console debug:twig-component
+---------------+-----------------------------+------------------------------------+------+
| Component | Class | Template | Live |
| Component | Class | Template | Type |
+---------------+-----------------------------+------------------------------------+------+
| Coucou | App\Components\Alert | components/Coucou.html.twig | |
| RandomNumber | App\Components\RandomNumber | components/RandomNumber.html.twig | X |
| RandomNumber | App\Components\RandomNumber | components/RandomNumber.html.twig | Live |
| Test | App\Components\foo\Test | components/foo/Test.html.twig | |
| Button | Anonymous component | components/Button.html.twig | |
| foo:Anonymous | Anonymous component | components/foo/Anonymous.html.twig | |
| Button | | components/Button.html.twig | Anon |
| foo:Anonymous | | components/foo/Anonymous.html.twig | Anon |
| Acme:Button | | @Acme/components/Button.html.twig | Anon |
+---------------+-----------------------------+------------------------------------+------+
Pass the name of some component as an argument to print its details:
Expand All @@ -1654,9 +1691,10 @@ Pass the name of some component as an argument to print its details:
| Property | Value |
+---------------------------------------------------+-----------------------------------+
| Component | RandomNumber |
| Live | X |
| Class | App\Components\RandomNumber |
| Template | components/RandomNumber.html.twig |
| Type | Live |
+---------------------------------------------------+-----------------------------------+
| Properties (type / name / default value if exist) | string $name = toto |
| | string $type = test |
| Live Properties | int $max = 1000 |
Expand Down
40 changes: 37 additions & 3 deletions src/TwigComponent/src/Command/TwigComponentDebugCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
use Symfony\UX\TwigComponent\ComponentMetadata;
use Symfony\UX\TwigComponent\Twig\PropsNode;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;

#[AsCommand(name: 'debug:twig-component', description: 'Display components and them usages for an application')]
class TwigComponentDebugCommand extends Command
Expand Down Expand Up @@ -148,13 +149,46 @@ private function findComponents(): array
*/
private function findAnonymousComponents(): array
{
$componentsDir = $this->twigTemplatesPath.'/'.$this->anonymousDirectory;
$dirs = [$componentsDir => FilesystemLoader::MAIN_NAMESPACE];
$twigLoader = $this->twig->getLoader();
if ($twigLoader instanceof FilesystemLoader) {
foreach ($twigLoader->getNamespaces() as $namespace) {
if (str_starts_with($namespace, '!')) {
continue; // ignore parent convention namespaces
}

foreach ($twigLoader->getPaths($namespace) as $path) {
if (FilesystemLoader::MAIN_NAMESPACE === $namespace) {
$componentsDir = $path.'/'.$this->anonymousDirectory;
} else {
$componentsDir = $path.'/components';
}

if (!is_dir($componentsDir)) {
continue;
}

$dirs[$componentsDir] = $namespace;
}
}
}

$components = [];
$anonymousPath = $this->twigTemplatesPath.'/'.$this->anonymousDirectory;
$finderTemplates = new Finder();
$finderTemplates->files()->in($anonymousPath)->notPath('/_')->name('*.html.twig');
$finderTemplates->files()
->in(array_keys($dirs))
->notPath('/_')
->name('*.html.twig')
;
foreach ($finderTemplates as $template) {
$component = str_replace('/', ':', $template->getRelativePathname());
$component = substr($component, 0, -10);
$component = substr($component, 0, -10); // remove file extension ".html.twig"

if (isset($dirs[$template->getPath()]) && FilesystemLoader::MAIN_NAMESPACE !== $dirs[$template->getPath()]) {
$component = $dirs[$template->getPath()].':'.$component;
}

$components[$component] = $component;
}

Expand Down
10 changes: 10 additions & 0 deletions src/TwigComponent/src/ComponentTemplateFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ public function findAnonymousComponentTemplate(string $name): ?string
return $template;
}

$parts = explode('/', $componentPath, 2);
if (\count($parts) < 2) {
return null;
}

$template = '@'.$parts[0].'/components/'.$parts[1].'.html.twig';
if ($loader->exists($template)) {
return $template;
}

return null;
}
}
22 changes: 22 additions & 0 deletions src/TwigComponent/tests/Fixtures/Bundle/AcmeBundle/AcmeBundle.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

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

namespace Symfony\UX\TwigComponent\Tests\Fixtures\Bundle\AcmeBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;

class AcmeBundle extends Bundle
{
public function getPath(): string
{
return __DIR__;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

2 changes: 2 additions & 0 deletions src/TwigComponent/tests/Fixtures/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Symfony\Bundle\TwigBundle\TwigBundle;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
use Symfony\UX\TwigComponent\Tests\Fixtures\Bundle\AcmeBundle\AcmeBundle;
use Symfony\UX\TwigComponent\Tests\Fixtures\Component\ComponentB;
use Symfony\UX\TwigComponent\TwigComponentBundle;

Expand All @@ -32,6 +33,7 @@ public function registerBundles(): iterable
yield new FrameworkBundle();
yield new TwigBundle();
yield new TwigComponentBundle();
yield new AcmeBundle();
}

protected function configureContainer(ContainerConfigurator $c): void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,21 @@ public function testWithAnonymousComponent(): void
$this->assertStringContainsString('primary = true', $display);
}

public function testWithBundleAnonymousComponent(): void
{
$commandTester = $this->createCommandTester();
$commandTester->execute(['name' => 'Acme:Button']);

$commandTester->assertCommandIsSuccessful();

$display = $commandTester->getDisplay();

$this->tableDisplayCheck($display);
$this->assertStringContainsString('Acme:Button', $display);
$this->assertStringContainsString('@Acme/components/Button.html.twig', $display);
$this->assertStringContainsString('Anonymous', $display);
}

public function testWithoutPublicProps(): void
{
$commandTester = $this->createCommandTester();
Expand Down
9 changes: 9 additions & 0 deletions src/TwigComponent/tests/Integration/ComponentFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,15 @@ public function testAnonymous(): void
$this->factory()->metadataFor('anonymous:AButton');
}

public function testLoadingAnonymousComponentFromBundle(): void
{
$metadata = $this->factory()->metadataFor('Acme:Button');

$this->assertSame('@Acme/components/Button.html.twig', $metadata->getTemplate());
$this->assertSame('Acme:Button', $metadata->getName());
$this->assertNull($metadata->get('class'));
}

public function testAutoNamingInSubDirectory(): void
{
$metadata = $this->factory()->metadataFor('SubDirectory:ComponentInSubDirectory');
Expand Down

0 comments on commit c207af6

Please sign in to comment.