diff --git a/src/Maker/MakeEvent.php b/src/Maker/MakeEvent.php new file mode 100644 index 000000000..65d968a98 --- /dev/null +++ b/src/Maker/MakeEvent.php @@ -0,0 +1,143 @@ + + * + * 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\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; + +/** + * @author Ippei Sumida + */ +class MakeEvent extends AbstractMaker +{ + public function __construct( + private readonly DoctrineHelper $doctrineHelper, + ) { + } + + 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): void + { + $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): void + { + $dependencies + ->addClassDependency(Event::class, 'event-dispatcher') + ; + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void + { + $name = $input->getArgument('name'); + if (null === $name) { + $name = $io->ask('Event class name (e.g. OrderPlacedEvent)', null, [Validator::class, 'notBlank']); + } + $eventClassNameDetails = $generator->createClassNameDetails( + $name, + 'Event\\', + 'Event' + ); + + $fields = []; + $useClasses = []; + while (true) { + $newField = $this->askForNextField($io); + if (null === $newField) { + 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 ( + $useClass + && !\in_array($useClass, $useClasses, true) + ) { + $useClasses[] = $useClass; + } + } + + asort($useClasses); + $generator->generateClass( + $eventClassNameDetails->getFullName(), + 'event/Event.tpl.php', + [ + 'event_class_name' => $eventClassNameDetails->getShortName(), + 'fields' => $fields, + 'useClasses' => $useClasses, + ] + ); + + $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', + ]); + } + + /** + * @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', \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/config/makers.xml b/src/Resources/config/makers.xml index 3d913d1ac..82004d581 100644 --- a/src/Resources/config/makers.xml +++ b/src/Resources/config/makers.xml @@ -170,5 +170,9 @@ + + + + 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..e4484167a --- /dev/null +++ b/src/Resources/skeleton/event/Event.tpl.php @@ -0,0 +1,17 @@ + + +namespace ; + +use Symfony\Contracts\EventDispatcher\Event; + +use ; + + +final class extends Event +{ + public function __construct( + + readonly ? $, + + ) {} +} \ No newline at end of file diff --git a/tests/Maker/MakeEventTest.php b/tests/Maker/MakeEventTest.php new file mode 100644 index 000000000..0b9077ac9 --- /dev/null +++ b/tests/Maker/MakeEventTest.php @@ -0,0 +1,70 @@ + + * + * 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 +{ + private const EXPECTED_EVENT_PATH = __DIR__.'/../../tests/fixtures/make-event/tests/Event/'; + + 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', + // first property name + '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', + '', + ] + ); + + 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..3c842c10a --- /dev/null +++ b/tests/fixtures/make-event/tests/Event/FooEvent.php @@ -0,0 +1,15 @@ +