diff --git a/src/Maker/MakeSchedule.php b/src/Maker/MakeSchedule.php new file mode 100644 index 000000000..6bf1c3d99 --- /dev/null +++ b/src/Maker/MakeSchedule.php @@ -0,0 +1,140 @@ + + * + * 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\FileManager; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Str; +use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Finder\Finder; +use Symfony\Component\Process\Process; +use Symfony\Component\Scheduler\Attribute\AsSchedule; +use Symfony\Component\Scheduler\RecurringMessage; +use Symfony\Component\Scheduler\Schedule; +use Symfony\Component\Scheduler\ScheduleProviderInterface; +use Symfony\Contracts\Cache\CacheInterface; + +/** + * @author Jesse Rushlow + * + * @internal + */ +final class MakeSchedule extends AbstractMaker +{ + private string $scheduleName; + private ?string $message = null; + + public function __construct( + private FileManager $fileManager, + private Finder $finder = new Finder(), + ) { + } + + public static function getCommandName(): string + { + return 'make:schedule'; + } + + public static function getCommandDescription(): string + { + return 'Create a scheduler component'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig): void + { + $command + ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeScheduler.txt')) + ; + } + + public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void + { + if (!class_exists(AsSchedule::class)) { + $io->writeln('Running composer require symfony/scheduler'); + $process = Process::fromShellCommandline('composer require symfony/scheduler'); + $process->run(); + $io->writeln('Scheduler successfully installed!'); + } + + // Loop over existing src/Message/* and ask which message the user would like to schedule + $availableMessages = ['Empty Schedule']; + $messageDir = $this->fileManager->getRootDirectory().'/src/Message'; + + if ($this->fileManager->fileExists($messageDir)) { + $finder = $this->finder->in($this->fileManager->getRootDirectory().'/src/Message'); + + foreach ($finder->files() as $file) { + $availableMessages[] = $file->getFilenameWithoutExtension(); + } + } + + $scheduleNameHint = 'MainSchedule'; + + // If the count is 1, no other messages were found - don't ask to create a message + if (1 !== \count($availableMessages)) { + $selectedMessage = $io->choice('Select which message', $availableMessages); + + if ('Empty Schedule' !== $selectedMessage) { + $this->message = $selectedMessage; + + // We don't want SomeMessageSchedule, so remove the "Message" suffix to give us SomeSchedule + $scheduleNameHint = sprintf('%sSchedule', Str::removeSuffix($selectedMessage, 'Message')); + } + } + + // Ask the name of the new schedule + $this->scheduleName = $io->ask(question: 'What should we call the new schedule?', default: $scheduleNameHint); + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void + { + $scheduleClassDetails = $generator->createClassNameDetails( + $this->scheduleName, + 'Scheduler\\', + ); + + $useStatements = new UseStatementGenerator([ + AsSchedule::class, + RecurringMessage::class, + Schedule::class, + ScheduleProviderInterface::class, + CacheInterface::class, + ]); + + if (null !== $this->message) { + $useStatements->addUseStatement('App\\Message\\'.$this->message); + } + + $generator->generateClass( + $scheduleClassDetails->getFullName(), + 'scheduler/Schedule.tpl.php', + [ + 'use_statements' => $useStatements, + 'has_custom_message' => null !== $this->message, + 'message_class_name' => $this->message, + ], + ); + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + } + + public function configureDependencies(DependencyBuilder $dependencies): void + { + } +} diff --git a/src/Resources/config/makers.xml b/src/Resources/config/makers.xml index b3eee7745..6575767af 100644 --- a/src/Resources/config/makers.xml +++ b/src/Resources/config/makers.xml @@ -92,6 +92,11 @@ + + + + + diff --git a/src/Resources/help/MakeScheduler.txt b/src/Resources/help/MakeScheduler.txt new file mode 100644 index 000000000..fe006d9ef --- /dev/null +++ b/src/Resources/help/MakeScheduler.txt @@ -0,0 +1,8 @@ +The %command.name% command generates a schedule to automate repeated +tasks using Symfony's Scheduler Component. + +If the Scheduler Component is not installed, %command.name% will +install it automatically using composer. You can of course do this manually by +running composer require symfony/scheduler. + +php %command.full_name% diff --git a/src/Resources/skeleton/scheduler/Schedule.tpl.php b/src/Resources/skeleton/scheduler/Schedule.tpl.php new file mode 100644 index 000000000..e22be4f2e --- /dev/null +++ b/src/Resources/skeleton/scheduler/Schedule.tpl.php @@ -0,0 +1,30 @@ + + +namespace ; + + + +#[AsSchedule] +final class implements ScheduleProviderInterface +{ + public function __construct( + private CacheInterface $cache, + ) { + } + + public function getSchedule(): Schedule + { + return (new Schedule()) + ->add( + + // @TODO - Modify the frequency to suite your needs + RecurringMessage::every('1 hour', new ()), + + // @TODO - Create a Message to schedule + // RecurringMessage::every('1 hour', new App\Message\Message()), + + ) + ->stateful($this->cache) + ; + } +} diff --git a/tests/Maker/MakeScheduleTest.php b/tests/Maker/MakeScheduleTest.php new file mode 100644 index 000000000..126d3ca5c --- /dev/null +++ b/tests/Maker/MakeScheduleTest.php @@ -0,0 +1,86 @@ + + * + * 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\MakeSchedule; +use Symfony\Bundle\MakerBundle\Test\MakerTestCase; +use Symfony\Bundle\MakerBundle\Test\MakerTestRunner; + +class MakeScheduleTest extends MakerTestCase +{ + protected function getMakerClass(): string + { + return MakeSchedule::class; + } + + public function getTestDetails(): \Generator + { + yield 'it_generates_a_schedule' => [$this->createMakerTest() + ->run(function (MakerTestRunner $runner) { + $output = $runner->runMaker([ + '', // use default schedule name "MainSchedule" + ]); + + $this->assertStringContainsString('Success', $output); + + self::assertFileEquals( + \dirname(__DIR__).'/fixtures/make-schedule/expected/DefaultScheduleEmpty.php', + $runner->getPath('src/Scheduler/MainSchedule.php') + ); + }), + ]; + + yield 'it_generates_a_schedule_select_empty' => [$this->createMakerTest() + ->preRun(function (MakerTestRunner $runner) { + $runner->copy( + 'make-schedule/standard_setup', + '' + ); + }) + ->run(function (MakerTestRunner $runner) { + $output = $runner->runMaker([ + 0, // Select "Empty Schedule" + 'MySchedule', // Go with the default name "MainSchedule" + ]); + + $this->assertStringContainsString('Success', $output); + + self::assertFileEquals( + \dirname(__DIR__).'/fixtures/make-schedule/expected/MyScheduleEmpty.php', + $runner->getPath('src/Scheduler/MySchedule.php') + ); + }), + ]; + + yield 'it_generates_a_schedule_select_existing_message' => [$this->createMakerTest() + ->preRun(function (MakerTestRunner $runner) { + $runner->copy( + 'make-schedule/standard_setup', + '' + ); + }) + ->run(function (MakerTestRunner $runner) { + $output = $runner->runMaker([ + 1, // Select "MyMessage" from choice + '', // Go with the default name "MessageFixtureSchedule" + ]); + + $this->assertStringContainsString('Success', $output); + + self::assertFileEquals( + \dirname(__DIR__).'/fixtures/make-schedule/expected/MyScheduleWithMessage.php', + $runner->getPath('src/Scheduler/MessageFixtureSchedule.php') + ); + }), + ]; + } +} diff --git a/tests/fixtures/make-schedule/expected/DefaultScheduleEmpty.php b/tests/fixtures/make-schedule/expected/DefaultScheduleEmpty.php new file mode 100644 index 000000000..5d3e4ccbf --- /dev/null +++ b/tests/fixtures/make-schedule/expected/DefaultScheduleEmpty.php @@ -0,0 +1,29 @@ +add( + // @TODO - Create a Message to schedule + // RecurringMessage::every('1 hour', new App\Message\Message()), + ) + ->stateful($this->cache) + ; + } +} diff --git a/tests/fixtures/make-schedule/expected/MyScheduleEmpty.php b/tests/fixtures/make-schedule/expected/MyScheduleEmpty.php new file mode 100644 index 000000000..f1ab56c56 --- /dev/null +++ b/tests/fixtures/make-schedule/expected/MyScheduleEmpty.php @@ -0,0 +1,29 @@ +add( + // @TODO - Create a Message to schedule + // RecurringMessage::every('1 hour', new App\Message\Message()), + ) + ->stateful($this->cache) + ; + } +} diff --git a/tests/fixtures/make-schedule/expected/MyScheduleWithMessage.php b/tests/fixtures/make-schedule/expected/MyScheduleWithMessage.php new file mode 100644 index 000000000..8db72057d --- /dev/null +++ b/tests/fixtures/make-schedule/expected/MyScheduleWithMessage.php @@ -0,0 +1,30 @@ +add( + // @TODO - Modify the frequency to suite your needs + RecurringMessage::every('1 hour', new MessageFixture()), + ) + ->stateful($this->cache) + ; + } +} diff --git a/tests/fixtures/make-schedule/standard_setup/src/Message/MessageFixture.php b/tests/fixtures/make-schedule/standard_setup/src/Message/MessageFixture.php new file mode 100644 index 000000000..b9bf67d28 --- /dev/null +++ b/tests/fixtures/make-schedule/standard_setup/src/Message/MessageFixture.php @@ -0,0 +1,11 @@ +