Skip to content

Commit 74a8918

Browse files
authored
feature #1487 [make:schedule] a new command for creating recurring Symfony Schedules
1 parent 0a5fcd4 commit 74a8918

File tree

9 files changed

+368
-0
lines changed

9 files changed

+368
-0
lines changed

src/Maker/MakeSchedule.php

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony MakerBundle package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\MakerBundle\Maker;
13+
14+
use Symfony\Bundle\MakerBundle\ConsoleStyle;
15+
use Symfony\Bundle\MakerBundle\DependencyBuilder;
16+
use Symfony\Bundle\MakerBundle\FileManager;
17+
use Symfony\Bundle\MakerBundle\Generator;
18+
use Symfony\Bundle\MakerBundle\InputConfiguration;
19+
use Symfony\Bundle\MakerBundle\Str;
20+
use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator;
21+
use Symfony\Component\Console\Command\Command;
22+
use Symfony\Component\Console\Input\InputInterface;
23+
use Symfony\Component\Finder\Finder;
24+
use Symfony\Component\Process\Process;
25+
use Symfony\Component\Scheduler\Attribute\AsSchedule;
26+
use Symfony\Component\Scheduler\RecurringMessage;
27+
use Symfony\Component\Scheduler\Schedule;
28+
use Symfony\Component\Scheduler\ScheduleProviderInterface;
29+
use Symfony\Contracts\Cache\CacheInterface;
30+
31+
/**
32+
* @author Jesse Rushlow <[email protected]>
33+
*
34+
* @internal
35+
*/
36+
final class MakeSchedule extends AbstractMaker
37+
{
38+
private string $scheduleName;
39+
private ?string $message = null;
40+
41+
public function __construct(
42+
private FileManager $fileManager,
43+
private Finder $finder = new Finder(),
44+
) {
45+
}
46+
47+
public static function getCommandName(): string
48+
{
49+
return 'make:schedule';
50+
}
51+
52+
public static function getCommandDescription(): string
53+
{
54+
return 'Create a scheduler component';
55+
}
56+
57+
public function configureCommand(Command $command, InputConfiguration $inputConfig): void
58+
{
59+
$command
60+
->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeScheduler.txt'))
61+
;
62+
}
63+
64+
public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void
65+
{
66+
if (!class_exists(AsSchedule::class)) {
67+
$io->writeln('Running composer require symfony/scheduler');
68+
$process = Process::fromShellCommandline('composer require symfony/scheduler');
69+
$process->run();
70+
$io->writeln('Scheduler successfully installed!');
71+
}
72+
73+
// Loop over existing src/Message/* and ask which message the user would like to schedule
74+
$availableMessages = ['Empty Schedule'];
75+
$messageDir = $this->fileManager->getRootDirectory().'/src/Message';
76+
77+
if ($this->fileManager->fileExists($messageDir)) {
78+
$finder = $this->finder->in($this->fileManager->getRootDirectory().'/src/Message');
79+
80+
foreach ($finder->files() as $file) {
81+
$availableMessages[] = $file->getFilenameWithoutExtension();
82+
}
83+
}
84+
85+
$scheduleNameHint = 'MainSchedule';
86+
87+
// If the count is 1, no other messages were found - don't ask to create a message
88+
if (1 !== \count($availableMessages)) {
89+
$selectedMessage = $io->choice('Select which message', $availableMessages);
90+
91+
if ('Empty Schedule' !== $selectedMessage) {
92+
$this->message = $selectedMessage;
93+
94+
// We don't want SomeMessageSchedule, so remove the "Message" suffix to give us SomeSchedule
95+
$scheduleNameHint = sprintf('%sSchedule', Str::removeSuffix($selectedMessage, 'Message'));
96+
}
97+
}
98+
99+
// Ask the name of the new schedule
100+
$this->scheduleName = $io->ask(question: 'What should we call the new schedule?', default: $scheduleNameHint);
101+
}
102+
103+
public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
104+
{
105+
$scheduleClassDetails = $generator->createClassNameDetails(
106+
$this->scheduleName,
107+
'Scheduler\\',
108+
);
109+
110+
$useStatements = new UseStatementGenerator([
111+
AsSchedule::class,
112+
RecurringMessage::class,
113+
Schedule::class,
114+
ScheduleProviderInterface::class,
115+
CacheInterface::class,
116+
]);
117+
118+
if (null !== $this->message) {
119+
$useStatements->addUseStatement('App\\Message\\'.$this->message);
120+
}
121+
122+
$generator->generateClass(
123+
$scheduleClassDetails->getFullName(),
124+
'scheduler/Schedule.tpl.php',
125+
[
126+
'use_statements' => $useStatements,
127+
'has_custom_message' => null !== $this->message,
128+
'message_class_name' => $this->message,
129+
],
130+
);
131+
132+
$generator->writeChanges();
133+
134+
$this->writeSuccessMessage($io);
135+
}
136+
137+
public function configureDependencies(DependencyBuilder $dependencies): void
138+
{
139+
}
140+
}

src/Resources/config/makers.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@
9292
<tag name="maker.command" />
9393
</service>
9494

95+
<service id="maker.maker.make_schedule" class="Symfony\Bundle\MakerBundle\Maker\MakeSchedule">
96+
<argument type="service" id="maker.file_manager" />
97+
<tag name="maker.command" />
98+
</service>
99+
95100
<service id="maker.maker.make_serializer_encoder" class="Symfony\Bundle\MakerBundle\Maker\MakeSerializerEncoder">
96101
<tag name="maker.command" />
97102
</service>

src/Resources/help/MakeScheduler.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
The <info>%command.name%</info> command generates a schedule to automate repeated
2+
tasks using Symfony's Scheduler Component.
3+
4+
If the Scheduler Component is not installed, <info>%command.name%</info> will
5+
install it automatically using composer. You can of course do this manually by
6+
running <info>composer require symfony/scheduler</info>.
7+
8+
<info>php %command.full_name%</info>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?= "<?php\n" ?>
2+
3+
namespace <?= $namespace; ?>;
4+
5+
<?= $use_statements; ?>
6+
7+
#[AsSchedule]
8+
final class <?= $class_name; ?> implements ScheduleProviderInterface
9+
{
10+
public function __construct(
11+
private CacheInterface $cache,
12+
) {
13+
}
14+
15+
public function getSchedule(): Schedule
16+
{
17+
return (new Schedule())
18+
->add(
19+
<?php if ($has_custom_message): ?>
20+
// @TODO - Modify the frequency to suite your needs
21+
RecurringMessage::every('1 hour', new <?= $message_class_name; ?>()),
22+
<?php else: ?>
23+
// @TODO - Create a Message to schedule
24+
// RecurringMessage::every('1 hour', new App\Message\Message()),
25+
<?php endif ?>
26+
)
27+
->stateful($this->cache)
28+
;
29+
}
30+
}

tests/Maker/MakeScheduleTest.php

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony MakerBundle package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\MakerBundle\Tests\Maker;
13+
14+
use Symfony\Bundle\MakerBundle\Maker\MakeSchedule;
15+
use Symfony\Bundle\MakerBundle\Test\MakerTestCase;
16+
use Symfony\Bundle\MakerBundle\Test\MakerTestRunner;
17+
18+
class MakeScheduleTest extends MakerTestCase
19+
{
20+
protected function getMakerClass(): string
21+
{
22+
return MakeSchedule::class;
23+
}
24+
25+
public function getTestDetails(): \Generator
26+
{
27+
yield 'it_generates_a_schedule' => [$this->createMakerTest()
28+
->run(function (MakerTestRunner $runner) {
29+
$output = $runner->runMaker([
30+
'', // use default schedule name "MainSchedule"
31+
]);
32+
33+
$this->assertStringContainsString('Success', $output);
34+
35+
self::assertFileEquals(
36+
\dirname(__DIR__).'/fixtures/make-schedule/expected/DefaultScheduleEmpty.php',
37+
$runner->getPath('src/Scheduler/MainSchedule.php')
38+
);
39+
}),
40+
];
41+
42+
yield 'it_generates_a_schedule_select_empty' => [$this->createMakerTest()
43+
->preRun(function (MakerTestRunner $runner) {
44+
$runner->copy(
45+
'make-schedule/standard_setup',
46+
''
47+
);
48+
})
49+
->run(function (MakerTestRunner $runner) {
50+
$output = $runner->runMaker([
51+
0, // Select "Empty Schedule"
52+
'MySchedule', // Go with the default name "MainSchedule"
53+
]);
54+
55+
$this->assertStringContainsString('Success', $output);
56+
57+
self::assertFileEquals(
58+
\dirname(__DIR__).'/fixtures/make-schedule/expected/MyScheduleEmpty.php',
59+
$runner->getPath('src/Scheduler/MySchedule.php')
60+
);
61+
}),
62+
];
63+
64+
yield 'it_generates_a_schedule_select_existing_message' => [$this->createMakerTest()
65+
->preRun(function (MakerTestRunner $runner) {
66+
$runner->copy(
67+
'make-schedule/standard_setup',
68+
''
69+
);
70+
})
71+
->run(function (MakerTestRunner $runner) {
72+
$output = $runner->runMaker([
73+
1, // Select "MyMessage" from choice
74+
'', // Go with the default name "MessageFixtureSchedule"
75+
]);
76+
77+
$this->assertStringContainsString('Success', $output);
78+
79+
self::assertFileEquals(
80+
\dirname(__DIR__).'/fixtures/make-schedule/expected/MyScheduleWithMessage.php',
81+
$runner->getPath('src/Scheduler/MessageFixtureSchedule.php')
82+
);
83+
}),
84+
];
85+
}
86+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace App\Scheduler;
4+
5+
use Symfony\Component\Scheduler\Attribute\AsSchedule;
6+
use Symfony\Component\Scheduler\RecurringMessage;
7+
use Symfony\Component\Scheduler\Schedule;
8+
use Symfony\Component\Scheduler\ScheduleProviderInterface;
9+
use Symfony\Contracts\Cache\CacheInterface;
10+
11+
#[AsSchedule]
12+
final class MainSchedule implements ScheduleProviderInterface
13+
{
14+
public function __construct(
15+
private CacheInterface $cache,
16+
) {
17+
}
18+
19+
public function getSchedule(): Schedule
20+
{
21+
return (new Schedule())
22+
->add(
23+
// @TODO - Create a Message to schedule
24+
// RecurringMessage::every('1 hour', new App\Message\Message()),
25+
)
26+
->stateful($this->cache)
27+
;
28+
}
29+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace App\Scheduler;
4+
5+
use Symfony\Component\Scheduler\Attribute\AsSchedule;
6+
use Symfony\Component\Scheduler\RecurringMessage;
7+
use Symfony\Component\Scheduler\Schedule;
8+
use Symfony\Component\Scheduler\ScheduleProviderInterface;
9+
use Symfony\Contracts\Cache\CacheInterface;
10+
11+
#[AsSchedule]
12+
final class MySchedule implements ScheduleProviderInterface
13+
{
14+
public function __construct(
15+
private CacheInterface $cache,
16+
) {
17+
}
18+
19+
public function getSchedule(): Schedule
20+
{
21+
return (new Schedule())
22+
->add(
23+
// @TODO - Create a Message to schedule
24+
// RecurringMessage::every('1 hour', new App\Message\Message()),
25+
)
26+
->stateful($this->cache)
27+
;
28+
}
29+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace App\Scheduler;
4+
5+
use App\Message\MessageFixture;
6+
use Symfony\Component\Scheduler\Attribute\AsSchedule;
7+
use Symfony\Component\Scheduler\RecurringMessage;
8+
use Symfony\Component\Scheduler\Schedule;
9+
use Symfony\Component\Scheduler\ScheduleProviderInterface;
10+
use Symfony\Contracts\Cache\CacheInterface;
11+
12+
#[AsSchedule]
13+
final class MessageFixtureSchedule implements ScheduleProviderInterface
14+
{
15+
public function __construct(
16+
private CacheInterface $cache,
17+
) {
18+
}
19+
20+
public function getSchedule(): Schedule
21+
{
22+
return (new Schedule())
23+
->add(
24+
// @TODO - Modify the frequency to suite your needs
25+
RecurringMessage::every('1 hour', new MessageFixture()),
26+
)
27+
->stateful($this->cache)
28+
;
29+
}
30+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace App\Message;
4+
5+
final class MessageFixture
6+
{
7+
public function __construct(
8+
public string $message = 'Howdy!',
9+
) {
10+
}
11+
}

0 commit comments

Comments
 (0)