From c92fc2fb7fe521b4a802e6cc705b4a18ae46d719 Mon Sep 17 00:00:00 2001 From: "SUMIDA, Ippei" Date: Sat, 1 Jun 2024 14:53:58 +0900 Subject: [PATCH 1/7] [make:event] Add command for event --- src/Maker/MakeEvent.php | 78 ++++++++++++++++++++++ src/Resources/config/makers.xml | 5 ++ src/Resources/help/MakeEvent.txt | 5 ++ src/Resources/skeleton/event/Event.tpl.php | 10 +++ tests/Maker/MakeEventTest.php | 38 +++++++++++ 5 files changed, 136 insertions(+) create mode 100644 src/Maker/MakeEvent.php create mode 100644 src/Resources/help/MakeEvent.txt create mode 100644 src/Resources/skeleton/event/Event.tpl.php create mode 100644 tests/Maker/MakeEventTest.php diff --git a/src/Maker/MakeEvent.php b/src/Maker/MakeEvent.php new file mode 100644 index 000000000..05404a93a --- /dev/null +++ b/src/Maker/MakeEvent.php @@ -0,0 +1,78 @@ + + * + * 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\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Contracts\EventDispatcher\Event; + +/** + * @author Ippei Sumida + */ +class MakeEvent extends AbstractMaker +{ + public static function getCommandName(): string + { + return 'make:event'; + } + + public static function getCommandDescription(): string + { + return 'Create a event class.'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig) + { + $command + ->addArgument('name', InputArgument::OPTIONAL, 'The name of the event class (e.g. OrderPlacedEvent)') + ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeEvent.txt')) + ; + } + + public function configureDependencies(DependencyBuilder $dependencies) + { + $dependencies + ->addClassDependency(Event::class, 'event-dispatcher') + ; + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator) + { + $eventClassNameDetails = $generator->createClassNameDetails( + $input->getArgument('name'), + 'Event\\', + 'Event' + ); + + $generator->generateClass( + $eventClassNameDetails->getFullName(), + 'event/Event.tpl.php', + [ + 'event_class_name' => $eventClassNameDetails->getShortName(), + ] + ); + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + + $io->text([ + 'Next: Open your event and add your logic.', + 'Find the documentation at https://symfony.com/doc/current/event_dispatcher.html', + ]); + } +} diff --git a/src/Resources/config/makers.xml b/src/Resources/config/makers.xml index 3d913d1ac..fa172583d 100644 --- a/src/Resources/config/makers.xml +++ b/src/Resources/config/makers.xml @@ -170,5 +170,10 @@ + + + + + diff --git a/src/Resources/help/MakeEvent.txt b/src/Resources/help/MakeEvent.txt new file mode 100644 index 000000000..5d3520e49 --- /dev/null +++ b/src/Resources/help/MakeEvent.txt @@ -0,0 +1,5 @@ +The %command.name% command generates a new event: + +php %command.full_name% OrderPlacedEvent + +If the argument is missing, the command will ask for the class name interactively. diff --git a/src/Resources/skeleton/event/Event.tpl.php b/src/Resources/skeleton/event/Event.tpl.php new file mode 100644 index 000000000..2e270a089 --- /dev/null +++ b/src/Resources/skeleton/event/Event.tpl.php @@ -0,0 +1,10 @@ + + +namespace ; + +use Symfony\Contracts\EventDispatcher\Event; + +final class extends Event +{ + public function __construct() {} +} \ No newline at end of file diff --git a/tests/Maker/MakeEventTest.php b/tests/Maker/MakeEventTest.php new file mode 100644 index 000000000..92a03e98b --- /dev/null +++ b/tests/Maker/MakeEventTest.php @@ -0,0 +1,38 @@ + + * + * 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\MakeEvent; +use Symfony\Bundle\MakerBundle\Test\MakerTestCase; +use Symfony\Bundle\MakerBundle\Test\MakerTestRunner; + +class MakeEventTest extends MakerTestCase +{ + protected function getMakerClass(): string + { + return MakeEvent::class; + } + + public function getTestDetails(): \Generator + { + yield 'it_makes_event' => [$this->createMakerTest() + ->run(function (MakerTestRunner $runner) { + $runner->runMaker( + [ + // event class name + 'FooEvent', + ] + ); + }), + ]; + } +} From f0752737ff2534963978f5f914ba412fb2afa946 Mon Sep 17 00:00:00 2001 From: "SUMIDA, Ippei" Date: Sun, 2 Jun 2024 18:12:53 +0900 Subject: [PATCH 2/7] [make:event] Add argument --- src/Maker/MakeEvent.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Maker/MakeEvent.php b/src/Maker/MakeEvent.php index 05404a93a..05ce75791 100644 --- a/src/Maker/MakeEvent.php +++ b/src/Maker/MakeEvent.php @@ -15,6 +15,7 @@ use Symfony\Bundle\MakerBundle\DependencyBuilder; use Symfony\Bundle\MakerBundle\Generator; use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Validator; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -52,8 +53,12 @@ public function configureDependencies(DependencyBuilder $dependencies) public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator) { + $name = $input->getArgument('name'); + if (null === $name) { + $name = $io->ask('Event class name (e.g. OrderPlacedEvent)', null, [Validator::class, 'notBlank']); + } $eventClassNameDetails = $generator->createClassNameDetails( - $input->getArgument('name'), + $name, 'Event\\', 'Event' ); From 48d253b4b6b400ab1e2b4bda540b358a9d519821 Mon Sep 17 00:00:00 2001 From: "SUMIDA, Ippei" Date: Sun, 2 Jun 2024 18:25:09 +0900 Subject: [PATCH 3/7] [make:event] fix stan --- src/Maker/MakeEvent.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Maker/MakeEvent.php b/src/Maker/MakeEvent.php index 05ce75791..23b486b88 100644 --- a/src/Maker/MakeEvent.php +++ b/src/Maker/MakeEvent.php @@ -36,7 +36,7 @@ public static function getCommandDescription(): string return 'Create a event class.'; } - public function configureCommand(Command $command, InputConfiguration $inputConfig) + public function configureCommand(Command $command, InputConfiguration $inputConfig): void { $command ->addArgument('name', InputArgument::OPTIONAL, 'The name of the event class (e.g. OrderPlacedEvent)') @@ -44,14 +44,14 @@ public function configureCommand(Command $command, InputConfiguration $inputConf ; } - public function configureDependencies(DependencyBuilder $dependencies) + public function configureDependencies(DependencyBuilder $dependencies): void { $dependencies ->addClassDependency(Event::class, 'event-dispatcher') ; } - public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator) + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void { $name = $input->getArgument('name'); if (null === $name) { From 859da182278a297c8d857ebc8373c365ba797913 Mon Sep 17 00:00:00 2001 From: "SUMIDA, Ippei" Date: Sun, 30 Jun 2024 14:16:21 +0900 Subject: [PATCH 4/7] [make:event] make property interactively. --- src/Maker/MakeEvent.php | 52 +++++++++++++++++++ src/Resources/config/makers.xml | 3 +- src/Resources/skeleton/event/Event.tpl.php | 9 +++- tests/Maker/MakeEventTest.php | 19 +++++++ .../make-event/tests/Event/FooEvent.php | 14 +++++ 5 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 tests/fixtures/make-event/tests/Event/FooEvent.php diff --git a/src/Maker/MakeEvent.php b/src/Maker/MakeEvent.php index 23b486b88..f34af38e1 100644 --- a/src/Maker/MakeEvent.php +++ b/src/Maker/MakeEvent.php @@ -13,12 +13,14 @@ use Symfony\Bundle\MakerBundle\ConsoleStyle; use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Doctrine\DoctrineHelper; use Symfony\Bundle\MakerBundle\Generator; use Symfony\Bundle\MakerBundle\InputConfiguration; use Symfony\Bundle\MakerBundle\Validator; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Question\Question; use Symfony\Contracts\EventDispatcher\Event; /** @@ -26,6 +28,11 @@ */ class MakeEvent extends AbstractMaker { + public function __construct( + private readonly DoctrineHelper $doctrineHelper, + ) { + } + public static function getCommandName(): string { return 'make:event'; @@ -63,11 +70,30 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen 'Event' ); + $fields = []; + $useClasses = []; + while (true) { + $newField = $this->askForNextField($io); + if (null === $newField) { + break; + } + $fields[] = $newField; + if ( + class_exists($this->doctrineHelper->getEntityNamespace().'\\'.$newField['type']) + && !\in_array($this->doctrineHelper->getEntityNamespace().'\\'.$newField['type'], $useClasses, true) + ) { + $useClasses[] = $this->doctrineHelper->getEntityNamespace().'\\'.$newField['type']; + } + } + + asort($useClasses); $generator->generateClass( $eventClassNameDetails->getFullName(), 'event/Event.tpl.php', [ 'event_class_name' => $eventClassNameDetails->getShortName(), + 'fields' => $fields, + 'useClasses' => $useClasses, ] ); @@ -80,4 +106,30 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen 'Find the documentation at https://symfony.com/doc/current/event_dispatcher.html', ]); } + + /** + * @return array{'name': string, 'type': string, 'nullable': bool}|null + */ + public function askForNextField(ConsoleStyle $io): ?array + { + $fieldName = $io->ask('Field name (press enter to stop adding fields)', null); + if (null === $fieldName) { + return null; + } + + $question = new Question('Field type (e.g. string)', 'string'); + $autocompleteValues = ['string', 'int', 'float', 'bool', 'array', 'object', 'callable', 'iterable', 'void']; + $autocompleteValues = array_merge($autocompleteValues, $this->doctrineHelper->getEntitiesForAutocomplete()); + $question->setAutocompleterValues($autocompleteValues); + $question->setValidator([Validator::class, 'notBlank']); + $fieldType = $io->askQuestion($question); + + $nullable = $io->confirm('Can this field be null (nullable)', false); + + return [ + 'name' => $fieldName, + 'type' => $fieldType, + 'nullable' => $nullable, + ]; + } } diff --git a/src/Resources/config/makers.xml b/src/Resources/config/makers.xml index fa172583d..82004d581 100644 --- a/src/Resources/config/makers.xml +++ b/src/Resources/config/makers.xml @@ -171,8 +171,7 @@ - - + diff --git a/src/Resources/skeleton/event/Event.tpl.php b/src/Resources/skeleton/event/Event.tpl.php index 2e270a089..e6635ba28 100644 --- a/src/Resources/skeleton/event/Event.tpl.php +++ b/src/Resources/skeleton/event/Event.tpl.php @@ -3,8 +3,15 @@ namespace ; use Symfony\Contracts\EventDispatcher\Event; + +use ; + final class extends Event { - public function __construct() {} + public function __construct( + + public readonly ? $, + + ) {} } \ No newline at end of file diff --git a/tests/Maker/MakeEventTest.php b/tests/Maker/MakeEventTest.php index 92a03e98b..f7d771c54 100644 --- a/tests/Maker/MakeEventTest.php +++ b/tests/Maker/MakeEventTest.php @@ -17,6 +17,7 @@ class MakeEventTest extends MakerTestCase { + private const EXPECTED_EVENT_PATH = __DIR__.'/../../tests/fixtures/make-event/tests/Event/'; protected function getMakerClass(): string { return MakeEvent::class; @@ -30,8 +31,26 @@ public function getTestDetails(): \Generator [ // event class name 'FooEvent', + // first property name + 'id', + // first property type + 'int', + // first property nullable + 'no', + // second property name + 'name', + // second property type + 'string', + // second property nullable + 'yes', + '', ] ); + + self::assertFileEquals( + self::EXPECTED_EVENT_PATH.'FooEvent.php', + $runner->getPath('src/Event/FooEvent.php') + ); }), ]; } diff --git a/tests/fixtures/make-event/tests/Event/FooEvent.php b/tests/fixtures/make-event/tests/Event/FooEvent.php new file mode 100644 index 000000000..9c6ed76c7 --- /dev/null +++ b/tests/fixtures/make-event/tests/Event/FooEvent.php @@ -0,0 +1,14 @@ + Date: Sun, 30 Jun 2024 14:18:44 +0900 Subject: [PATCH 5/7] [make:event] fix cs-fixer --- tests/Maker/MakeEventTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Maker/MakeEventTest.php b/tests/Maker/MakeEventTest.php index f7d771c54..6f7980d95 100644 --- a/tests/Maker/MakeEventTest.php +++ b/tests/Maker/MakeEventTest.php @@ -18,6 +18,7 @@ class MakeEventTest extends MakerTestCase { private const EXPECTED_EVENT_PATH = __DIR__.'/../../tests/fixtures/make-event/tests/Event/'; + protected function getMakerClass(): string { return MakeEvent::class; From 579a13deba577416878b5a90221130ed7029405b Mon Sep 17 00:00:00 2001 From: "SUMIDA, Ippei" Date: Sun, 30 Jun 2024 14:44:00 +0900 Subject: [PATCH 6/7] [make:event] set visibility --- src/Maker/MakeEvent.php | 19 +++++++++++++++---- src/Resources/skeleton/event/Event.tpl.php | 2 +- tests/Maker/MakeEventTest.php | 12 ++++++++++++ .../make-event/tests/Event/FooEvent.php | 3 ++- 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/Maker/MakeEvent.php b/src/Maker/MakeEvent.php index f34af38e1..cbcb54873 100644 --- a/src/Maker/MakeEvent.php +++ b/src/Maker/MakeEvent.php @@ -11,6 +11,8 @@ namespace Symfony\Bundle\MakerBundle\Maker; +use DateTime; +use DateTimeImmutable; use Symfony\Bundle\MakerBundle\ConsoleStyle; use Symfony\Bundle\MakerBundle\DependencyBuilder; use Symfony\Bundle\MakerBundle\Doctrine\DoctrineHelper; @@ -22,6 +24,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Question\Question; use Symfony\Contracts\EventDispatcher\Event; +use function in_array; /** * @author Ippei Sumida @@ -78,11 +81,17 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen break; } $fields[] = $newField; + $useClass = match (true) { + class_exists($this->doctrineHelper->getEntityNamespace().'\\'.$newField['type']) => $this->doctrineHelper->getEntityNamespace().'\\'.$newField['type'], + class_exists($newField['type']) => $newField['type'], + + default => null, + }; if ( - class_exists($this->doctrineHelper->getEntityNamespace().'\\'.$newField['type']) - && !\in_array($this->doctrineHelper->getEntityNamespace().'\\'.$newField['type'], $useClasses, true) + $useClass + && !in_array($useClass, $useClasses, true) ) { - $useClasses[] = $this->doctrineHelper->getEntityNamespace().'\\'.$newField['type']; + $useClasses[] = $useClass; } } @@ -118,17 +127,19 @@ public function askForNextField(ConsoleStyle $io): ?array } $question = new Question('Field type (e.g. string)', 'string'); - $autocompleteValues = ['string', 'int', 'float', 'bool', 'array', 'object', 'callable', 'iterable', 'void']; + $autocompleteValues = ['string', 'int', 'float', 'bool', 'array', 'object', 'callable', 'iterable', 'void', DateTime::class, DateTimeImmutable::class]; $autocompleteValues = array_merge($autocompleteValues, $this->doctrineHelper->getEntitiesForAutocomplete()); $question->setAutocompleterValues($autocompleteValues); $question->setValidator([Validator::class, 'notBlank']); $fieldType = $io->askQuestion($question); + $visibility = $io->choice('Field visibility (public, protected, private)', ['public', 'private', 'protected'], 'public'); $nullable = $io->confirm('Can this field be null (nullable)', false); return [ 'name' => $fieldName, 'type' => $fieldType, + 'visibility' => $visibility, 'nullable' => $nullable, ]; } diff --git a/src/Resources/skeleton/event/Event.tpl.php b/src/Resources/skeleton/event/Event.tpl.php index e6635ba28..e4484167a 100644 --- a/src/Resources/skeleton/event/Event.tpl.php +++ b/src/Resources/skeleton/event/Event.tpl.php @@ -11,7 +11,7 @@ final class extends Event { public function __construct( - public readonly ? $, + readonly ? $, ) {} } \ No newline at end of file diff --git a/tests/Maker/MakeEventTest.php b/tests/Maker/MakeEventTest.php index 6f7980d95..0b9077ac9 100644 --- a/tests/Maker/MakeEventTest.php +++ b/tests/Maker/MakeEventTest.php @@ -36,14 +36,26 @@ public function getTestDetails(): \Generator 'id', // first property type 'int', + // first property visibility + 'public', // first property nullable 'no', // second property name 'name', // second property type 'string', + // second property visibility + 'private', // second property nullable 'yes', + // third property name + 'createdAt', + // third property type + 'DateTimeInterface', + // third property visibility + 'protected', + // third property nullable + 'no', '', ] ); diff --git a/tests/fixtures/make-event/tests/Event/FooEvent.php b/tests/fixtures/make-event/tests/Event/FooEvent.php index 9c6ed76c7..3c842c10a 100644 --- a/tests/fixtures/make-event/tests/Event/FooEvent.php +++ b/tests/fixtures/make-event/tests/Event/FooEvent.php @@ -8,7 +8,8 @@ final class FooEvent extends Event { public function __construct( public readonly int $id, - public readonly ?string $name, + private readonly ?string $name, + protected readonly DateTimeInterface $createdAt, ) { } } From 9d30849b8a096c15908da5a136ae5fc907d0ebd9 Mon Sep 17 00:00:00 2001 From: "SUMIDA, Ippei" Date: Sun, 30 Jun 2024 14:46:27 +0900 Subject: [PATCH 7/7] [make:event] fix cs-fixer --- src/Maker/MakeEvent.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Maker/MakeEvent.php b/src/Maker/MakeEvent.php index cbcb54873..65d968a98 100644 --- a/src/Maker/MakeEvent.php +++ b/src/Maker/MakeEvent.php @@ -11,8 +11,6 @@ namespace Symfony\Bundle\MakerBundle\Maker; -use DateTime; -use DateTimeImmutable; use Symfony\Bundle\MakerBundle\ConsoleStyle; use Symfony\Bundle\MakerBundle\DependencyBuilder; use Symfony\Bundle\MakerBundle\Doctrine\DoctrineHelper; @@ -24,7 +22,6 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Question\Question; use Symfony\Contracts\EventDispatcher\Event; -use function in_array; /** * @author Ippei Sumida @@ -89,7 +86,7 @@ class_exists($newField['type']) => $newField['type'], }; if ( $useClass - && !in_array($useClass, $useClasses, true) + && !\in_array($useClass, $useClasses, true) ) { $useClasses[] = $useClass; } @@ -127,7 +124,7 @@ public function askForNextField(ConsoleStyle $io): ?array } $question = new Question('Field type (e.g. string)', 'string'); - $autocompleteValues = ['string', 'int', 'float', 'bool', 'array', 'object', 'callable', 'iterable', 'void', DateTime::class, DateTimeImmutable::class]; + $autocompleteValues = ['string', 'int', 'float', 'bool', 'array', 'object', 'callable', 'iterable', 'void', \DateTime::class, \DateTimeImmutable::class]; $autocompleteValues = array_merge($autocompleteValues, $this->doctrineHelper->getEntitiesForAutocomplete()); $question->setAutocompleterValues($autocompleteValues); $question->setValidator([Validator::class, 'notBlank']);