diff --git a/src/Maker/MakeTwigComponent.php b/src/Maker/MakeTwigComponent.php new file mode 100644 index 000000000..4be28208b --- /dev/null +++ b/src/Maker/MakeTwigComponent.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Str; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\UX\LiveComponent\Attribute\AsLiveComponent; +use Symfony\UX\TwigComponent\Attribute\AsTwigComponent; + +/** + * @author Kevin Bond + */ +final class MakeTwigComponent extends AbstractMaker +{ + public static function getCommandName(): string + { + return 'make:twig-component'; + } + + public static function getCommandDescription(): string + { + return 'Creates a twig (or live) component'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig): void + { + $command + ->setDescription(self::getCommandDescription()) + ->addArgument('name', InputArgument::OPTIONAL, 'The name of your twig component (ie NotificationComponent)') + ->addOption('live', null, InputOption::VALUE_NONE, 'Whether to create a live twig component (requires symfony/ux-live-component)') + ; + } + + public function configureDependencies(DependencyBuilder $dependencies): void + { + $dependencies->addClassDependency(AsTwigComponent::class, 'symfony/ux-twig-component'); + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void + { + $name = $input->getArgument('name'); + $live = $input->getOption('live'); + + if ($live && !class_exists(AsLiveComponent::class)) { + throw new \RuntimeException('You must install symfony/ux-live-component to create a live component (composer require symfony/ux-live-component)'); + } + + $factory = $generator->createClassNameDetails( + $name, + 'Twig\\Components', + 'Component' + ); + + $shortName = Str::asSnakeCase(Str::removeSuffix($factory->getShortName(), 'Component')); + + $generator->generateClass( + $factory->getFullName(), + sprintf('%s/../Resources/skeleton/twig/%s', __DIR__, $live ? 'LiveComponent.tpl.php' : 'Component.tpl.php'), + [ + 'live' => $live, + 'short_name' => $shortName, + ] + ); + $generator->generateTemplate( + "components/{$shortName}.html.twig", + sprintf('%s/../Resources/skeleton/twig/%s', __DIR__, 'component_template.tpl.php') + ); + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + $io->newLine(); + $io->writeln(" To render the component, use {{ component('{$shortName}') }}."); + $io->newLine(); + } + + public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void + { + if (!$input->getOption('live')) { + $input->setOption('live', $io->confirm('Make this a live component?', class_exists(AsLiveComponent::class))); + } + } +} diff --git a/src/Resources/config/makers.xml b/src/Resources/config/makers.xml index ebcc7ab33..271bba21a 100644 --- a/src/Resources/config/makers.xml +++ b/src/Resources/config/makers.xml @@ -21,6 +21,10 @@ + + + + diff --git a/src/Resources/skeleton/twig/Component.tpl.php b/src/Resources/skeleton/twig/Component.tpl.php new file mode 100644 index 000000000..c25d2b0cc --- /dev/null +++ b/src/Resources/skeleton/twig/Component.tpl.php @@ -0,0 +1,10 @@ + + +namespace ; + +use Symfony\UX\TwigComponent\Attribute\AsTwigComponent; + +#[AsTwigComponent('')] +final class +{ +} diff --git a/src/Resources/skeleton/twig/LiveComponent.tpl.php b/src/Resources/skeleton/twig/LiveComponent.tpl.php new file mode 100644 index 000000000..896922fa7 --- /dev/null +++ b/src/Resources/skeleton/twig/LiveComponent.tpl.php @@ -0,0 +1,12 @@ + + +namespace ; + +use Symfony\UX\LiveComponent\Attribute\AsLiveComponent; +use Symfony\UX\LiveComponent\DefaultActionTrait; + +#[AsLiveComponent('')] +final class +{ + use DefaultActionTrait; +} diff --git a/src/Resources/skeleton/twig/component_template.tpl.php b/src/Resources/skeleton/twig/component_template.tpl.php new file mode 100644 index 000000000..75ebedd3f --- /dev/null +++ b/src/Resources/skeleton/twig/component_template.tpl.php @@ -0,0 +1,3 @@ + + + diff --git a/tests/Maker/MakeTwigComponentTest.php b/tests/Maker/MakeTwigComponentTest.php new file mode 100644 index 000000000..7c62e3609 --- /dev/null +++ b/tests/Maker/MakeTwigComponentTest.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Tests\Maker; + +use Symfony\Bundle\MakerBundle\Maker\MakeTwigComponent; +use Symfony\Bundle\MakerBundle\Test\MakerTestCase; +use Symfony\Bundle\MakerBundle\Test\MakerTestRunner; + +class MakeTwigComponentTest extends MakerTestCase +{ + public function getTestDetails(): \Generator + { + yield 'it_generates_twig_component' => [$this->createMakerTest() + ->addExtraDependencies('symfony/ux-twig-component', 'symfony/twig-bundle') + ->run(function (MakerTestRunner $runner) { + $output = $runner->runMaker(['Alert']); + + $this->assertStringContainsString('created: src/Twig/Components/AlertComponent.php', $output); + $this->assertStringContainsString('created: templates/components/alert.html.twig', $output); + $this->assertStringContainsString("To render the component, use {{ component('alert') }}.", $output); + + $runner->copy( + 'make-twig-component/tests/it_generates_twig_component.php', + 'tests/GeneratedTwigComponentTest.php' + ); + $runner->replaceInFile('tests/GeneratedTwigComponentTest.php', '{name}', 'alert'); + $runner->runTests(); + }), + ]; + + yield 'it_generates_pascal_case_twig_component' => [$this->createMakerTest() + ->addExtraDependencies('symfony/ux-twig-component', 'symfony/twig-bundle') + ->run(function (MakerTestRunner $runner) { + $output = $runner->runMaker(['FormInput']); + + $this->assertStringContainsString('created: src/Twig/Components/FormInputComponent.php', $output); + $this->assertStringContainsString('created: templates/components/form_input.html.twig', $output); + $this->assertStringContainsString("To render the component, use {{ component('form_input') }}.", $output); + + $runner->copy( + 'make-twig-component/tests/it_generates_twig_component.php', + 'tests/GeneratedTwigComponentTest.php' + ); + $runner->replaceInFile('tests/GeneratedTwigComponentTest.php', '{name}', 'form_input'); + $runner->runTests(); + }), + ]; + + yield 'it_generates_live_component' => [$this->createMakerTest() + ->addExtraDependencies('symfony/ux-live-component', 'symfony/twig-bundle') + ->run(function (MakerTestRunner $runner) { + $output = $runner->runMaker(['Alert']); + + $this->assertStringContainsString('created: src/Twig/Components/AlertComponent.php', $output); + $this->assertStringContainsString('created: templates/components/alert.html.twig', $output); + $this->assertStringContainsString("To render the component, use {{ component('alert') }}.", $output); + + $runner->copy( + 'make-twig-component/tests/it_generates_live_component.php', + 'tests/GeneratedLiveComponentTest.php' + ); + $runner->replaceInFile('tests/GeneratedLiveComponentTest.php', '{name}', 'alert'); + $runner->runTests(); + }), + ]; + + yield 'it_generates_pascal_case_live_component' => [$this->createMakerTest() + ->addExtraDependencies('symfony/ux-live-component', 'symfony/twig-bundle') + ->run(function (MakerTestRunner $runner) { + $output = $runner->runMaker(['FormInput']); + + $this->assertStringContainsString('created: src/Twig/Components/FormInputComponent.php', $output); + $this->assertStringContainsString('created: templates/components/form_input.html.twig', $output); + $this->assertStringContainsString("To render the component, use {{ component('form_input') }}.", $output); + + $runner->copy( + 'make-twig-component/tests/it_generates_live_component.php', + 'tests/GeneratedLiveComponentTest.php' + ); + $runner->replaceInFile('tests/GeneratedLiveComponentTest.php', '{name}', 'form_input'); + $runner->runTests(); + }), + ]; + } + + protected function getMakerClass(): string + { + return MakeTwigComponent::class; + } +} diff --git a/tests/fixtures/make-twig-component/tests/it_generates_live_component.php b/tests/fixtures/make-twig-component/tests/it_generates_live_component.php new file mode 100644 index 000000000..15867c1cd --- /dev/null +++ b/tests/fixtures/make-twig-component/tests/it_generates_live_component.php @@ -0,0 +1,16 @@ +get('twig')->createTemplate("{{ component('{name}') }}")->render(); + + $this->assertStringContainsString('
', $output); + } +} diff --git a/tests/fixtures/make-twig-component/tests/it_generates_twig_component.php b/tests/fixtures/make-twig-component/tests/it_generates_twig_component.php new file mode 100644 index 000000000..24dcbfd9b --- /dev/null +++ b/tests/fixtures/make-twig-component/tests/it_generates_twig_component.php @@ -0,0 +1,15 @@ +get('twig')->createTemplate("{{ component('{name}') }}")->render(); + + $this->assertSame("
\n \n
\n", $output); + } +}