Skip to content

Commit

Permalink
feature #1019 Add make:twig-component maker (kbond)
Browse files Browse the repository at this point in the history
This PR was merged into the 1.0-dev branch.

Discussion
----------

Add `make:twig-component` maker

Adds a maker for `symfony/ux-twig-component`/`symfony/ux-live-component`.

**Creating a _standard_ twig component:**

```bash
bin/console make:twig-component

 The name of your twig component (ie NotificationComponent):
 > Notification

 Make this a live component? (yes/no) [no]: # (defaults to yes if symfony/ux-live-component installed)
 > no

 created: src/Twig/Components/NotificationComponent.php
 created: templates/components/notification.html.twig

  Success!
```

```php
// src/Twig/Components/NotificationComponent.php

<?php

namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent('notification')]
final class NotificationComponent
{
}
```

```twig
{# templates/components/notification.html.twig #}

<div{{ attributes }}>
    <!-- component html -->
</div>
```

---

**Creating a _live_ twig component:**

```bash
bin/console make:twig-component --live # fails if symfony/ux-live-component is not installed

 The name of your twig component (ie NotificationComponent):
 > Notification

 created: src/Twig/Components/NotificationComponent.php
 created: templates/components/notification.html.twig

  Success!
```

```php
// src/Twig/Components/NotificationComponent.php

<?php

namespace App\Twig\Components;

use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\DefaultActionTrait;

#[AsLiveComponent('notification')]
final class NotificationComponent
{
    use DefaultActionTrait;
}
```

```twig
{# templates/components/notification.html.twig #}

<div{{ attributes }}>
    <!-- component html -->
</div>
```

Ref: symfony/ux#108

Commits
-------

acde364 add make:twig-component maker
  • Loading branch information
weaverryan committed Sep 19, 2022
2 parents c113946 + acde364 commit 2fa4e95
Show file tree
Hide file tree
Showing 8 changed files with 258 additions and 0 deletions.
99 changes: 99 additions & 0 deletions src/Maker/MakeTwigComponent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

/*
* This file is part of the Symfony MakerBundle 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\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 <kevinbond@gmail.com>
*/
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 <fg=yellow>NotificationComponent</>)')
->addOption('live', null, InputOption::VALUE_NONE, 'Whether to create a live twig component (requires <fg=yellow>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)));
}
}
}
4 changes: 4 additions & 0 deletions src/Resources/config/makers.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
<tag name="maker.command" />
</service>

<service id="maker.maker.make_twig_component" class="Symfony\Bundle\MakerBundle\Maker\MakeTwigComponent">
<tag name="maker.command" />
</service>

<service id="maker.maker.make_controller" class="Symfony\Bundle\MakerBundle\Maker\MakeController">
<argument type="service" id="maker.php_compat_util" />
<tag name="maker.command" />
Expand Down
10 changes: 10 additions & 0 deletions src/Resources/skeleton/twig/Component.tpl.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?= "<?php\n" ?>

namespace <?= $namespace; ?>;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent('<?= $short_name; ?>')]
final class <?= $class_name."\n" ?>
{
}
12 changes: 12 additions & 0 deletions src/Resources/skeleton/twig/LiveComponent.tpl.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?= "<?php\n" ?>

namespace <?= $namespace; ?>;

use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\DefaultActionTrait;

#[AsLiveComponent('<?= $short_name; ?>')]
final class <?= $class_name."\n" ?>
{
use DefaultActionTrait;
}
3 changes: 3 additions & 0 deletions src/Resources/skeleton/twig/component_template.tpl.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div{{ attributes }}>
<!-- component html -->
</div>
99 changes: 99 additions & 0 deletions tests/Maker/MakeTwigComponentTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

/*
* This file is part of the Symfony MakerBundle 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\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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace App\Tests;

use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

class GeneratedTwigComponentTest extends KernelTestCase
{
public function testController()
{
$output = self::getContainer()->get('twig')->createTemplate("{{ component('{name}') }}")->render();

$this->assertStringContainsString('<div data-controller="live" data-live-url-value=', $output);
$this->assertStringContainsString('<!-- component html -->', $output);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace App\Tests;

use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

class GeneratedTwigComponentTest extends KernelTestCase
{
public function testController()
{
$output = self::getContainer()->get('twig')->createTemplate("{{ component('{name}') }}")->render();

$this->assertSame("<div>\n <!-- component html -->\n</div>\n", $output);
}
}

0 comments on commit 2fa4e95

Please sign in to comment.