Skip to content

Commit

Permalink
feature #1487 [make:schedule] a new command for creating recurring Sy…
Browse files Browse the repository at this point in the history
…mfony Schedules
  • Loading branch information
jrushlow authored Mar 27, 2024
1 parent 0a5fcd4 commit 74a8918
Show file tree
Hide file tree
Showing 9 changed files with 368 additions and 0 deletions.
140 changes: 140 additions & 0 deletions src/Maker/MakeSchedule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
<?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\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 <jr@rushlow.dev>
*
* @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
{
}
}
5 changes: 5 additions & 0 deletions src/Resources/config/makers.xml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@
<tag name="maker.command" />
</service>

<service id="maker.maker.make_schedule" class="Symfony\Bundle\MakerBundle\Maker\MakeSchedule">
<argument type="service" id="maker.file_manager" />
<tag name="maker.command" />
</service>

<service id="maker.maker.make_serializer_encoder" class="Symfony\Bundle\MakerBundle\Maker\MakeSerializerEncoder">
<tag name="maker.command" />
</service>
Expand Down
8 changes: 8 additions & 0 deletions src/Resources/help/MakeScheduler.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
The <info>%command.name%</info> command generates a schedule to automate repeated
tasks using Symfony's Scheduler Component.

If the Scheduler Component is not installed, <info>%command.name%</info> will
install it automatically using composer. You can of course do this manually by
running <info>composer require symfony/scheduler</info>.

<info>php %command.full_name%</info>
30 changes: 30 additions & 0 deletions src/Resources/skeleton/scheduler/Schedule.tpl.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?= "<?php\n" ?>

namespace <?= $namespace; ?>;

<?= $use_statements; ?>

#[AsSchedule]
final class <?= $class_name; ?> implements ScheduleProviderInterface
{
public function __construct(
private CacheInterface $cache,
) {
}

public function getSchedule(): Schedule
{
return (new Schedule())
->add(
<?php if ($has_custom_message): ?>
// @TODO - Modify the frequency to suite your needs
RecurringMessage::every('1 hour', new <?= $message_class_name; ?>()),
<?php else: ?>
// @TODO - Create a Message to schedule
// RecurringMessage::every('1 hour', new App\Message\Message()),
<?php endif ?>
)
->stateful($this->cache)
;
}
}
86 changes: 86 additions & 0 deletions tests/Maker/MakeScheduleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?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\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')
);
}),
];
}
}
29 changes: 29 additions & 0 deletions tests/fixtures/make-schedule/expected/DefaultScheduleEmpty.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace App\Scheduler;

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;

#[AsSchedule]
final class MainSchedule implements ScheduleProviderInterface
{
public function __construct(
private CacheInterface $cache,
) {
}

public function getSchedule(): Schedule
{
return (new Schedule())
->add(
// @TODO - Create a Message to schedule
// RecurringMessage::every('1 hour', new App\Message\Message()),
)
->stateful($this->cache)
;
}
}
29 changes: 29 additions & 0 deletions tests/fixtures/make-schedule/expected/MyScheduleEmpty.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace App\Scheduler;

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;

#[AsSchedule]
final class MySchedule implements ScheduleProviderInterface
{
public function __construct(
private CacheInterface $cache,
) {
}

public function getSchedule(): Schedule
{
return (new Schedule())
->add(
// @TODO - Create a Message to schedule
// RecurringMessage::every('1 hour', new App\Message\Message()),
)
->stateful($this->cache)
;
}
}
30 changes: 30 additions & 0 deletions tests/fixtures/make-schedule/expected/MyScheduleWithMessage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace App\Scheduler;

use App\Message\MessageFixture;
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;

#[AsSchedule]
final class MessageFixtureSchedule 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 MessageFixture()),
)
->stateful($this->cache)
;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace App\Message;

final class MessageFixture
{
public function __construct(
public string $message = 'Howdy!',
) {
}
}

0 comments on commit 74a8918

Please sign in to comment.