From dc50dc6e5565298f8e407fca1ba60cbb351c93db Mon Sep 17 00:00:00 2001 From: Loulier Guillaume Date: Tue, 9 Nov 2021 15:29:46 +0100 Subject: [PATCH 01/10] dic(conf): triggers started --- composer.json | 2 + .../SchedulerBundleConfiguration.php | 49 +++++++++++++++++ .../EmailTaskLifecycleSubscriber.php | 55 +++++++++++++++++++ .../SchedulerBundleConfigurationTest.php | 33 +++++++++++ .../EmailTaskLifecycleSubscriberTest.php | 14 +++++ 5 files changed, 153 insertions(+) create mode 100644 src/EventListener/EmailTaskLifecycleSubscriber.php create mode 100644 tests/EventListener/EmailTaskLifecycleSubscriberTest.php diff --git a/composer.json b/composer.json index 2fe43a17..f75c7e2a 100644 --- a/composer.json +++ b/composer.json @@ -142,11 +142,13 @@ "symfony/framework-bundle": "^5.2", "symfony/http-client": "^5.2", "symfony/http-kernel": "^5.2", + "symfony/mailer": "^5.2", "symfony/mercure": "^0.5.3", "symfony/messenger": "^5.2", "symfony/notifier": "^5.2", "symfony/phpunit-bridge": "^5.2", "symfony/rate-limiter": "^5.2", + "symfony/translation": "^5.2", "thecodingmachine/safe": "^1.3.3" }, "suggest": { diff --git a/src/DependencyInjection/SchedulerBundleConfiguration.php b/src/DependencyInjection/SchedulerBundleConfiguration.php index 11eed084..4e92f78a 100644 --- a/src/DependencyInjection/SchedulerBundleConfiguration.php +++ b/src/DependencyInjection/SchedulerBundleConfiguration.php @@ -89,6 +89,55 @@ public function getConfigTreeBuilder(): TreeBuilder ->end() ->end() ->end() + ->arrayNode('triggers') + ->addDefaultsIfNotSet() + ->children() + ->arrayNode('email') + ->children() + ->arrayNode('on_failure') + ->children() + ->scalarNode('triggered_at') + ->info('The amount of task that need to fail before the email is sent') + ->defaultValue(1) + ->end() + ->scalarNode('to') + ->info('The receiver email') + ->defaultValue(null) + ->end() + ->scalarNode('from') + ->info('The sender email') + ->defaultValue(null) + ->end() + ->scalarNode('subject') + ->info('The subject of the email') + ->defaultValue('An task failed during its execution') + ->end() + ->end() + ->end() + ->arrayNode('on_success') + ->children() + ->scalarNode('triggered_at') + ->info('The amount of task that need to succeed before the email is sent') + ->defaultValue(1) + ->end() + ->scalarNode('to') + ->info('The receiver email') + ->defaultValue(null) + ->end() + ->scalarNode('from') + ->info('The sender email') + ->defaultValue(null) + ->end() + ->scalarNode('subject') + ->info('The subject of the email') + ->defaultValue('An task succeed during its execution') + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() ->arrayNode('probe') ->children() ->scalarNode('enabled') diff --git a/src/EventListener/EmailTaskLifecycleSubscriber.php b/src/EventListener/EmailTaskLifecycleSubscriber.php new file mode 100644 index 00000000..5530ea90 --- /dev/null +++ b/src/EventListener/EmailTaskLifecycleSubscriber.php @@ -0,0 +1,55 @@ + + */ +final class EmailTaskLifecycleSubscriber implements EventSubscriberInterface +{ + private int $taskFailureAmount; + private int $taskSuccessAmount; + private ?MailerInterface $mailer; + + public function __construct( + int $taskFailureAmount, + int $taskSuccessAmount, + ?MailerInterface $mailer = null + ) { + $this->taskFailureAmount = $taskFailureAmount; + $this->taskSuccessAmount = $taskSuccessAmount; + $this->mailer = $mailer; + } + + public function onTaskFailure(TaskExecutedEvent $event): void + { + } + + public function onTaskSuccess(TaskFailedEvent $event): void + { + } + + private function send(Email $email): void + { + if (null === $this->mailer) { + return; + } + + $this->mailer->send($email); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents(): array + { + } +} diff --git a/tests/DependencyInjection/SchedulerBundleConfigurationTest.php b/tests/DependencyInjection/SchedulerBundleConfigurationTest.php index 4b0d5acf..2ef919d5 100644 --- a/tests/DependencyInjection/SchedulerBundleConfigurationTest.php +++ b/tests/DependencyInjection/SchedulerBundleConfigurationTest.php @@ -25,6 +25,7 @@ public function testConfigurationCanBeEmpty(): void self::assertArrayHasKey('tasks', $configuration); self::assertArrayNotHasKey('probe', $configuration); self::assertArrayHasKey('lock_store', $configuration); + self::assertArrayHasKey('triggers', $configuration); } public function testConfigurationCannotDefineTasksWithoutTransport(): void @@ -514,4 +515,36 @@ public function testConfigurationCanDefineConfigurationTransportWithLazyMode(): self::assertArrayHasKey('mode', $configuration['configuration']); self::assertSame('lazy', $configuration['configuration']['mode']); } + + public function testConfigurationCanDefineTriggers(): void + { + $configuration = (new Processor())->processConfiguration(new SchedulerBundleConfiguration(), [ + 'scheduler_bundle' => [ + 'transport' => [ + 'dsn' => 'cache://app', + ], + 'triggers' => [ + 'email' => [ + 'on_failure' => [ + 'triggered_at' => 10, + 'to' => 'foo@foo.foo', + 'from' => 'bar@bar.bar', + 'subject' => 'An error occurred', + ], + 'on_success' => [ + 'triggered_at' => 10, + 'to' => 'foo@foo.foo', + 'from' => 'bar@bar.bar', + 'subject' => 'An task succeed', + ], + ], + ], + ], + ]); + + self::assertCount(1, $configuration['triggers']); + self::assertCount(2, $configuration['triggers']['email']); + self::assertCount(4, $configuration['triggers']['email']['on_failure']); + self::assertCount(4, $configuration['triggers']['email']['on_success']); + } } diff --git a/tests/EventListener/EmailTaskLifecycleSubscriberTest.php b/tests/EventListener/EmailTaskLifecycleSubscriberTest.php new file mode 100644 index 00000000..b693a16d --- /dev/null +++ b/tests/EventListener/EmailTaskLifecycleSubscriberTest.php @@ -0,0 +1,14 @@ + + */ +final class EmailTaskLifecycleSubscriberTest extends TestCase +{ +} From dc64eb47ab1a6e7c2c9548fb443e2699beda2ed0 Mon Sep 17 00:00:00 2001 From: Loulier Guillaume Date: Wed, 10 Nov 2021 09:08:43 +0100 Subject: [PATCH 02/10] refactor(core): trigger introduced --- composer.json | 1 + .../EmailTaskLifecycleSubscriber.php | 43 ++++++++-- src/Trigger/EmailTriggerConfiguration.php | 86 +++++++++++++++++++ 3 files changed, 122 insertions(+), 8 deletions(-) create mode 100644 src/Trigger/EmailTriggerConfiguration.php diff --git a/composer.json b/composer.json index f75c7e2a..c9bf6f6a 100644 --- a/composer.json +++ b/composer.json @@ -49,6 +49,7 @@ "SchedulerBundle\\Test\\Constraint\\Scheduler\\": "src/Test/Constraint/Scheduler/", "SchedulerBundle\\Transport\\": "src/Transport/", "SchedulerBundle\\Transport\\Configuration\\": "src/Transport/Configuration/", + "SchedulerBundle\\Trigger\\": "src/Trigger/", "SchedulerBundle\\Worker\\": "src/Worker/" } }, diff --git a/src/EventListener/EmailTaskLifecycleSubscriber.php b/src/EventListener/EmailTaskLifecycleSubscriber.php index 5530ea90..6daf03ca 100644 --- a/src/EventListener/EmailTaskLifecycleSubscriber.php +++ b/src/EventListener/EmailTaskLifecycleSubscriber.php @@ -6,6 +6,9 @@ use SchedulerBundle\Event\TaskExecutedEvent; use SchedulerBundle\Event\TaskFailedEvent; +use SchedulerBundle\Task\Output; +use SchedulerBundle\Task\TaskInterface; +use SchedulerBundle\Trigger\EmailTriggerConfiguration; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Mailer\MailerInterface; use Symfony\Component\Mime\Email; @@ -15,26 +18,31 @@ */ final class EmailTaskLifecycleSubscriber implements EventSubscriberInterface { - private int $taskFailureAmount; - private int $taskSuccessAmount; + private int $failedTasks; + private int $succeededTasks; private ?MailerInterface $mailer; + private EmailTriggerConfiguration $emailTriggerConfiguration; public function __construct( - int $taskFailureAmount, - int $taskSuccessAmount, + EmailTriggerConfiguration $emailTriggerConfiguration, ?MailerInterface $mailer = null ) { - $this->taskFailureAmount = $taskFailureAmount; - $this->taskSuccessAmount = $taskSuccessAmount; + $this->emailTriggerConfiguration = $emailTriggerConfiguration; $this->mailer = $mailer; } - public function onTaskFailure(TaskExecutedEvent $event): void + public function onTaskFailure(): void { + ++$this->failedTasks; } - public function onTaskSuccess(TaskFailedEvent $event): void + public function onTaskExecuted(TaskExecutedEvent $event): void { + $task = $event->getTask(); + $output = $event->getOutput(); + + $this->onTaskFailed($task, $output); + $this->onTaskSuccess($task, $output); } private function send(Email $email): void @@ -46,10 +54,29 @@ private function send(Email $email): void $this->mailer->send($email); } + private function onTaskFailed(TaskInterface $task, Output $output): void + { + if ($this->failedTasks !== $this->emailTriggerConfiguration->getFailureTriggeredAt()) { + return; + } + + $this->send(); + } + + private function onTaskSuccess(TaskInterface $task, Output $output): void + { + if ($task->getExecutionState() !== TaskInterface::SUCCEED) { + return; + } + } + /** * {@inheritdoc} */ public static function getSubscribedEvents(): array { + return [ + TaskFailedEvent::class => 'onTaskFailure', + ]; } } diff --git a/src/Trigger/EmailTriggerConfiguration.php b/src/Trigger/EmailTriggerConfiguration.php new file mode 100644 index 00000000..42964e3c --- /dev/null +++ b/src/Trigger/EmailTriggerConfiguration.php @@ -0,0 +1,86 @@ + + */ +final class EmailTriggerConfiguration +{ + private int $failureTriggeredAt; + private int $successTriggeredAt; + private ?string $failureFrom; + private ?string $successFrom; + private ?string $failureTo; + private ?string $successTo; + private string $failureSubject; + private string $successSubject; + + private function __construct() {} + + public static function create( + int $failureTriggeredAt, + int $successTriggeredAt, + ?string $failureFrom, + ?string $successFrom, + ?string $failureTo, + ?string $successTo, + string $failureSubject, + string $successSubject + ): self { + $emailTriggerConfiguration = new self(); + + $emailTriggerConfiguration->failureTriggeredAt = $failureTriggeredAt; + $emailTriggerConfiguration->successTriggeredAt = $successTriggeredAt; + $emailTriggerConfiguration->failureFrom = $failureFrom; + $emailTriggerConfiguration->successFrom = $successFrom; + $emailTriggerConfiguration->failureTo = $failureTo; + $emailTriggerConfiguration->successTo = $successTo; + $emailTriggerConfiguration->failureSubject = $failureSubject; + $emailTriggerConfiguration->successSubject = $successSubject; + + return $emailTriggerConfiguration; + } + + public function getFailureTriggeredAt(): int + { + return $this->failureTriggeredAt; + } + + public function getSuccessTriggeredAt(): int + { + return $this->successTriggeredAt; + } + + public function getFailureFrom(): ?string + { + return $this->failureFrom; + } + + public function getSuccessFrom(): ?string + { + return $this->successFrom; + } + + public function getFailureTo(): ?string + { + return $this->failureTo; + } + + public function getSuccessTo(): ?string + { + return $this->successTo; + } + + public function getFailureSubject(): string + { + return $this->failureSubject; + } + + public function getSuccessSubject(): string + { + return $this->successSubject; + } +} From 87e07fcee805f018827977d1550cb7c095cc0447 Mon Sep 17 00:00:00 2001 From: Loulier Guillaume Date: Wed, 10 Nov 2021 19:32:50 +0100 Subject: [PATCH 03/10] feat(eventlistener): progress --- src/EventListener/EmailTaskLifecycleSubscriber.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/EventListener/EmailTaskLifecycleSubscriber.php b/src/EventListener/EmailTaskLifecycleSubscriber.php index 6daf03ca..270740ae 100644 --- a/src/EventListener/EmailTaskLifecycleSubscriber.php +++ b/src/EventListener/EmailTaskLifecycleSubscriber.php @@ -68,6 +68,12 @@ private function onTaskSuccess(TaskInterface $task, Output $output): void if ($task->getExecutionState() !== TaskInterface::SUCCEED) { return; } + + ++$this->succeededTasks; + + if ($this->succeededTasks !== $this->emailTriggerConfiguration->getSuccessTriggeredAt()) { + return; + } } /** From 69d3fa1cf9b9e29b70a403ed5e6c9e68536248f2 Mon Sep 17 00:00:00 2001 From: Loulier Guillaume Date: Fri, 12 Nov 2021 18:40:19 +0100 Subject: [PATCH 04/10] feat(core): progress on listener --- .../EmailTaskLifecycleSubscriber.php | 41 +++++++++++-------- src/EventListener/MercureEventSubscriber.php | 36 ++++++++-------- src/EventListener/ProbeStateSubscriber.php | 20 ++++----- .../StopWorkerOnFailureLimitSubscriber.php | 22 +++++----- .../StopWorkerOnSignalSubscriber.php | 34 +++++++-------- .../StopWorkerOnTaskLimitSubscriber.php | 20 ++++----- .../StopWorkerOnTimeLimitSubscriber.php | 22 +++++----- src/EventListener/TaskLifecycleSubscriber.php | 26 ++++++------ src/EventListener/TaskLoggerSubscriber.php | 20 ++++----- src/EventListener/TaskSubscriber.php | 20 ++++----- .../WorkerLifecycleSubscriber.php | 30 +++++++------- 11 files changed, 149 insertions(+), 142 deletions(-) diff --git a/src/EventListener/EmailTaskLifecycleSubscriber.php b/src/EventListener/EmailTaskLifecycleSubscriber.php index 270740ae..cf9b7f77 100644 --- a/src/EventListener/EmailTaskLifecycleSubscriber.php +++ b/src/EventListener/EmailTaskLifecycleSubscriber.php @@ -6,8 +6,11 @@ use SchedulerBundle\Event\TaskExecutedEvent; use SchedulerBundle\Event\TaskFailedEvent; +use SchedulerBundle\Task\FailedTask; use SchedulerBundle\Task\Output; use SchedulerBundle\Task\TaskInterface; +use SchedulerBundle\Task\TaskList; +use SchedulerBundle\Task\TaskListInterface; use SchedulerBundle\Trigger\EmailTriggerConfiguration; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Mailer\MailerInterface; @@ -18,8 +21,8 @@ */ final class EmailTaskLifecycleSubscriber implements EventSubscriberInterface { - private int $failedTasks; - private int $succeededTasks; + private TaskListInterface $failedTasksList; + private TaskListInterface $succeedTasksList; private ?MailerInterface $mailer; private EmailTriggerConfiguration $emailTriggerConfiguration; @@ -29,11 +32,25 @@ public function __construct( ) { $this->emailTriggerConfiguration = $emailTriggerConfiguration; $this->mailer = $mailer; + + $this->failedTasksList = new TaskList(); + $this->succeedTasksList = new TaskList(); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents(): array + { + return [ + TaskExecutedEvent::class => 'onTaskExecuted', + TaskFailedEvent::class => 'onTaskFailure', + ]; } - public function onTaskFailure(): void + public function onTaskFailure(FailedTask $failedTask): void { - ++$this->failedTasks; + $this->failedTasksList->add($failedTask->getTask()); } public function onTaskExecuted(TaskExecutedEvent $event): void @@ -56,7 +73,7 @@ private function send(Email $email): void private function onTaskFailed(TaskInterface $task, Output $output): void { - if ($this->failedTasks !== $this->emailTriggerConfiguration->getFailureTriggeredAt()) { + if ($this->failedTasksList->count() !== $this->emailTriggerConfiguration->getFailureTriggeredAt()) { return; } @@ -69,20 +86,10 @@ private function onTaskSuccess(TaskInterface $task, Output $output): void return; } - ++$this->succeededTasks; + $this->succeedTasksList->add($task); - if ($this->succeededTasks !== $this->emailTriggerConfiguration->getSuccessTriggeredAt()) { + if ($this->succeedTasksList->count() !== $this->emailTriggerConfiguration->getSuccessTriggeredAt()) { return; } } - - /** - * {@inheritdoc} - */ - public static function getSubscribedEvents(): array - { - return [ - TaskFailedEvent::class => 'onTaskFailure', - ]; - } } diff --git a/src/EventListener/MercureEventSubscriber.php b/src/EventListener/MercureEventSubscriber.php index 700b354a..5651fba5 100644 --- a/src/EventListener/MercureEventSubscriber.php +++ b/src/EventListener/MercureEventSubscriber.php @@ -41,6 +41,24 @@ public function __construct( $this->serializer = $serializer; } + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents(): array + { + return [ + TaskScheduledEvent::class => ['onTaskScheduled', -255], + TaskUnscheduledEvent::class => ['onTaskUnscheduled', -255], + TaskExecutedEvent::class => ['onTaskExecuted', -255], + TaskFailedEvent::class => ['onTaskFailed', -255], + WorkerPausedEvent::class => ['onWorkerPaused', -255], + WorkerStartedEvent::class => ['onWorkerStarted', -255], + WorkerStoppedEvent::class => ['onWorkerStopped', -255], + WorkerForkedEvent::class => ['onWorkerForked', -255], + WorkerRestartedEvent::class => ['onWorkerRestarted', -255], + ]; + } + /** * @throws JsonException {@see json_encode()} */ @@ -185,22 +203,4 @@ public function onWorkerRestarted(WorkerRestartedEvent $event): void ], ], JSON_THROW_ON_ERROR))); } - - /** - * {@inheritdoc} - */ - public static function getSubscribedEvents(): array - { - return [ - TaskScheduledEvent::class => ['onTaskScheduled', -255], - TaskUnscheduledEvent::class => ['onTaskUnscheduled', -255], - TaskExecutedEvent::class => ['onTaskExecuted', -255], - TaskFailedEvent::class => ['onTaskFailed', -255], - WorkerPausedEvent::class => ['onWorkerPaused', -255], - WorkerStartedEvent::class => ['onWorkerStarted', -255], - WorkerStoppedEvent::class => ['onWorkerStopped', -255], - WorkerForkedEvent::class => ['onWorkerForked', -255], - WorkerRestartedEvent::class => ['onWorkerRestarted', -255], - ]; - } } diff --git a/src/EventListener/ProbeStateSubscriber.php b/src/EventListener/ProbeStateSubscriber.php index ec036b57..7161f395 100644 --- a/src/EventListener/ProbeStateSubscriber.php +++ b/src/EventListener/ProbeStateSubscriber.php @@ -28,6 +28,16 @@ public function __construct(ProbeInterface $probe, string $path = '/_probe') $this->path = $path; } + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::REQUEST => [['onKernelRequest', 50]], + ]; + } + /** * @throws Throwable {@see SchedulerInterface::getTasks()} */ @@ -49,14 +59,4 @@ public function onKernelRequest(RequestEvent $event): void 'failedTasks' => $this->probe->getFailedTasks(), ])); } - - /** - * {@inheritdoc} - */ - public static function getSubscribedEvents(): array - { - return [ - KernelEvents::REQUEST => [['onKernelRequest', 50]], - ]; - } } diff --git a/src/EventListener/StopWorkerOnFailureLimitSubscriber.php b/src/EventListener/StopWorkerOnFailureLimitSubscriber.php index b6d74b34..92c86613 100644 --- a/src/EventListener/StopWorkerOnFailureLimitSubscriber.php +++ b/src/EventListener/StopWorkerOnFailureLimitSubscriber.php @@ -33,6 +33,17 @@ public function __construct( } } + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents(): array + { + return [ + TaskFailedEvent::class => 'onTaskFailedEvent', + WorkerRunningEvent::class => 'onWorkerStarted', + ]; + } + public function onTaskFailedEvent(): void { ++$this->failedTasks; @@ -56,15 +67,4 @@ public function onWorkerStarted(WorkerRunningEvent $workerRunningEvent): void )); } } - - /** - * {@inheritdoc} - */ - public static function getSubscribedEvents(): array - { - return [ - TaskFailedEvent::class => 'onTaskFailedEvent', - WorkerRunningEvent::class => 'onWorkerStarted', - ]; - } } diff --git a/src/EventListener/StopWorkerOnSignalSubscriber.php b/src/EventListener/StopWorkerOnSignalSubscriber.php index f1c3a30c..3bcbf6b7 100644 --- a/src/EventListener/StopWorkerOnSignalSubscriber.php +++ b/src/EventListener/StopWorkerOnSignalSubscriber.php @@ -33,6 +33,23 @@ public function __construct(?LoggerInterface $logger = null) $this->logger = $logger ?? new NullLogger(); } + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents(): array + { + if (!function_exists('pcntl_signal')) { + return []; + } + + return [ + TaskExecutingEvent::class => ['onTaskExecuting', 100], + WorkerStartedEvent::class => ['onWorkerStarted', 100], + WorkerRunningEvent::class => ['onWorkerRunning', 100], + WorkerSleepingEvent::class => ['onWorkerSleeping', 100], + ]; + } + public function onTaskExecuting(TaskExecutingEvent $taskExecutingEvent): void { foreach ([SIGTERM, SIGINT] as $signal) { @@ -69,23 +86,6 @@ public function onWorkerSleeping(WorkerSleepingEvent $workerSleepingEvent): void $this->stopWorker($workerSleepingEvent); } - /** - * {@inheritdoc} - */ - public static function getSubscribedEvents(): array - { - if (!function_exists('pcntl_signal')) { - return []; - } - - return [ - TaskExecutingEvent::class => ['onTaskExecuting', 100], - WorkerStartedEvent::class => ['onWorkerStarted', 100], - WorkerRunningEvent::class => ['onWorkerRunning', 100], - WorkerSleepingEvent::class => ['onWorkerSleeping', 100], - ]; - } - private function stopWorker(WorkerEventInterface $event): void { foreach ([SIGTERM, SIGINT, SIGQUIT, SIGHUP] as $signal) { diff --git a/src/EventListener/StopWorkerOnTaskLimitSubscriber.php b/src/EventListener/StopWorkerOnTaskLimitSubscriber.php index dbcb9069..978cc023 100644 --- a/src/EventListener/StopWorkerOnTaskLimitSubscriber.php +++ b/src/EventListener/StopWorkerOnTaskLimitSubscriber.php @@ -26,6 +26,16 @@ public function __construct( $this->logger = $logger ?? new NullLogger(); } + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents(): array + { + return [ + WorkerRunningEvent::class => 'onWorkerRunning', + ]; + } + public function onWorkerRunning(WorkerRunningEvent $workerRunningEvent): void { if (!$workerRunningEvent->isIdle() && ++$this->consumedTasks >= $this->maximumTasks) { @@ -38,14 +48,4 @@ public function onWorkerRunning(WorkerRunningEvent $workerRunningEvent): void ]); } } - - /** - * {@inheritdoc} - */ - public static function getSubscribedEvents(): array - { - return [ - WorkerRunningEvent::class => 'onWorkerRunning', - ]; - } } diff --git a/src/EventListener/StopWorkerOnTimeLimitSubscriber.php b/src/EventListener/StopWorkerOnTimeLimitSubscriber.php index 85945c8b..378418e6 100644 --- a/src/EventListener/StopWorkerOnTimeLimitSubscriber.php +++ b/src/EventListener/StopWorkerOnTimeLimitSubscriber.php @@ -30,6 +30,17 @@ public function __construct( $this->logger = $logger ?? new NullLogger(); } + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents(): array + { + return [ + WorkerStartedEvent::class => 'onWorkerStarted', + WorkerRunningEvent::class => 'onWorkerRunning', + ]; + } + public function onWorkerStarted(): void { $this->endTime = microtime(true) + $this->timeLimitInSeconds; @@ -47,15 +58,4 @@ public function onWorkerRunning(WorkerRunningEvent $workerRunningEvent): void ]); } } - - /** - * {@inheritdoc} - */ - public static function getSubscribedEvents(): array - { - return [ - WorkerStartedEvent::class => 'onWorkerStarted', - WorkerRunningEvent::class => 'onWorkerRunning', - ]; - } } diff --git a/src/EventListener/TaskLifecycleSubscriber.php b/src/EventListener/TaskLifecycleSubscriber.php index 595ebe48..0b73f5e3 100644 --- a/src/EventListener/TaskLifecycleSubscriber.php +++ b/src/EventListener/TaskLifecycleSubscriber.php @@ -24,6 +24,19 @@ public function __construct(LoggerInterface $logger = null) $this->logger = $logger ?? new NullLogger(); } + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents(): array + { + return [ + TaskScheduledEvent::class => 'onTaskScheduled', + TaskUnscheduledEvent::class => 'onTaskUnscheduled', + TaskExecutedEvent::class => 'onTaskExecuted', + TaskFailedEvent::class => 'onTaskFailed', + ]; + } + public function onTaskScheduled(TaskScheduledEvent $taskScheduledEvent): void { $this->logger->info('A task has been scheduled', [ @@ -51,17 +64,4 @@ public function onTaskFailed(TaskFailedEvent $taskFailedEvent): void 'task' => $taskFailedEvent->getTask()->getTask()->getName(), ]); } - - /** - * {@inheritdoc} - */ - public static function getSubscribedEvents(): array - { - return [ - TaskScheduledEvent::class => 'onTaskScheduled', - TaskUnscheduledEvent::class => 'onTaskUnscheduled', - TaskExecutedEvent::class => 'onTaskExecuted', - TaskFailedEvent::class => 'onTaskFailed', - ]; - } } diff --git a/src/EventListener/TaskLoggerSubscriber.php b/src/EventListener/TaskLoggerSubscriber.php index ab5bbc75..8f66bf15 100644 --- a/src/EventListener/TaskLoggerSubscriber.php +++ b/src/EventListener/TaskLoggerSubscriber.php @@ -25,16 +25,6 @@ public function __construct() $this->events = new TaskEventList(); } - public function onTask(TaskEventInterface $taskEvent): void - { - $this->events->addEvent($taskEvent); - } - - public function getEvents(): TaskEventList - { - return $this->events; - } - /** * {@inheritdoc} */ @@ -48,4 +38,14 @@ public static function getSubscribedEvents(): array TaskUnscheduledEvent::class => ['onTask', -255], ]; } + + public function onTask(TaskEventInterface $taskEvent): void + { + $this->events->addEvent($taskEvent); + } + + public function getEvents(): TaskEventList + { + return $this->events; + } } diff --git a/src/EventListener/TaskSubscriber.php b/src/EventListener/TaskSubscriber.php index 1753a2ee..c76b1a31 100644 --- a/src/EventListener/TaskSubscriber.php +++ b/src/EventListener/TaskSubscriber.php @@ -55,6 +55,16 @@ public function __construct( $this->tasksPath = $tasksPath; } + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::REQUEST => [['onKernelRequest', 50]], + ]; + } + /** * @throws Throwable {@see SchedulerInterface::getTasks()} * @throws ExceptionInterface {@see SerializerInterface::serialize()} @@ -104,14 +114,4 @@ public function onKernelRequest(RequestEvent $requestEvent): void 'tasks' => $this->serializer->normalize($tasks, 'json'), ])); } - - /** - * {@inheritdoc} - */ - public static function getSubscribedEvents(): array - { - return [ - KernelEvents::REQUEST => [['onKernelRequest', 50]], - ]; - } } diff --git a/src/EventListener/WorkerLifecycleSubscriber.php b/src/EventListener/WorkerLifecycleSubscriber.php index cdbfebb2..0a91dbf0 100644 --- a/src/EventListener/WorkerLifecycleSubscriber.php +++ b/src/EventListener/WorkerLifecycleSubscriber.php @@ -27,6 +27,21 @@ public function __construct(LoggerInterface $logger = null) $this->logger = $logger ?? new NullLogger(); } + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents(): array + { + return [ + WorkerForkedEvent::class => 'onWorkerForked', + WorkerPausedEvent::class => 'onWorkerPaused', + WorkerRestartedEvent::class => 'onWorkerRestarted', + WorkerRunningEvent::class => 'onWorkerRunning', + WorkerStartedEvent::class => 'onWorkerStarted', + WorkerStoppedEvent::class => 'onWorkerStopped', + ]; + } + public function onWorkerForked(WorkerForkedEvent $workerForkedEvent): void { $forkedWorker = $workerForkedEvent->getForkedWorker(); @@ -95,19 +110,4 @@ public function onWorkerStopped(WorkerStoppedEvent $workerStoppedEvent): void 'lastExecutedTask' => $lastExecutedTask instanceof TaskInterface ? $lastExecutedTask->getName() : null, ]); } - - /** - * {@inheritdoc} - */ - public static function getSubscribedEvents(): array - { - return [ - WorkerForkedEvent::class => 'onWorkerForked', - WorkerPausedEvent::class => 'onWorkerPaused', - WorkerRestartedEvent::class => 'onWorkerRestarted', - WorkerRunningEvent::class => 'onWorkerRunning', - WorkerStartedEvent::class => 'onWorkerStarted', - WorkerStoppedEvent::class => 'onWorkerStopped', - ]; - } } From 335dfef90db6c64d30ce74f37143616c230ce209 Mon Sep 17 00:00:00 2001 From: Loulier Guillaume Date: Mon, 15 Nov 2021 09:11:06 +0100 Subject: [PATCH 05/10] feat(trigger): progress --- .../SchedulerBundleConfiguration.php | 4 +++ .../SchedulerBundleExtension.php | 28 +++++++++++++++++++ .../EmailTaskLifecycleSubscriber.php | 12 ++++---- src/Trigger/EmailTriggerConfiguration.php | 12 +++++++- .../SchedulerBundleConfigurationTest.php | 2 ++ .../EmailTaskLifecycleSubscriberTest.php | 25 +++++++++++++++++ 6 files changed, 76 insertions(+), 7 deletions(-) diff --git a/src/DependencyInjection/SchedulerBundleConfiguration.php b/src/DependencyInjection/SchedulerBundleConfiguration.php index 4e92f78a..dcc08742 100644 --- a/src/DependencyInjection/SchedulerBundleConfiguration.php +++ b/src/DependencyInjection/SchedulerBundleConfiguration.php @@ -92,6 +92,10 @@ public function getConfigTreeBuilder(): TreeBuilder ->arrayNode('triggers') ->addDefaultsIfNotSet() ->children() + ->scalarNode('enabled') + ->info('Enable the triggers') + ->defaultValue(false) + ->end() ->arrayNode('email') ->children() ->arrayNode('on_failure') diff --git a/src/DependencyInjection/SchedulerBundleExtension.php b/src/DependencyInjection/SchedulerBundleExtension.php index 1d868369..3e2359b1 100644 --- a/src/DependencyInjection/SchedulerBundleExtension.php +++ b/src/DependencyInjection/SchedulerBundleExtension.php @@ -115,6 +115,7 @@ use SchedulerBundle\Transport\TransportFactory; use SchedulerBundle\Transport\TransportFactoryInterface; use SchedulerBundle\Transport\TransportInterface; +use SchedulerBundle\Trigger\EmailTriggerConfiguration; use SchedulerBundle\Worker\Worker; use SchedulerBundle\Worker\WorkerInterface; use Symfony\Bundle\FrameworkBundle\Console\Application; @@ -192,6 +193,7 @@ public function load(array $configs, ContainerBuilder $container): void $this->registerMiddlewareStacks($container, $config); $this->registerProbeContext($container, $config); $this->registerMercureSupport($container, $config); + $this->registerTriggers($container, $config); $this->registerDataCollector($container); } @@ -206,6 +208,7 @@ private function registerParameters(ContainerBuilder $container, array $configur $container->setParameter('scheduler.probe_enabled', $configuration['probe']['enabled'] ?? false); $container->setParameter('scheduler.mercure_support', $configuration['mercure']['enabled']); $container->setParameter('scheduler.pool_support', $configuration['pool']['enabled']); + $container->setParameter('scheduler.trigger_support', $configuration['triggers']['enabled']); } private function registerAutoConfigure(ContainerBuilder $container): void @@ -1328,6 +1331,31 @@ private function registerMercureSupport(ContainerBuilder $container, array $conf ; } + private function registerTriggers(ContainerBuilder $container, array $config): void + { + if (!$container->getParameter('scheduler.trigger_support')) { + return; + } + + $container->register(EmailTriggerConfiguration::class, EmailTriggerConfiguration::class) + ->setArguments([ + $container->getParameter('scheduler.trigger_support'), + $config['triggers']['email']['on_failure']['triggered_at'], + $config['triggers']['email']['on_success']['triggered_at'], + $config['triggers']['email']['on_failure']['from'], + $config['triggers']['email']['on_success']['from'], + $config['triggers']['email']['on_failure']['to'], + $config['triggers']['email']['on_success']['to'], + $config['triggers']['email']['on_failure']['subject'], + $config['triggers']['email']['on_success']['subject'], + ]) + ->setPublic(false) + ->addTag('container.preload', [ + 'class' => EmailTriggerConfiguration::class, + ]) + ; + } + private function registerDataCollector(ContainerBuilder $container): void { $container->register(SchedulerDataCollector::class, SchedulerDataCollector::class) diff --git a/src/EventListener/EmailTaskLifecycleSubscriber.php b/src/EventListener/EmailTaskLifecycleSubscriber.php index cf9b7f77..b97c5153 100644 --- a/src/EventListener/EmailTaskLifecycleSubscriber.php +++ b/src/EventListener/EmailTaskLifecycleSubscriber.php @@ -44,11 +44,11 @@ public static function getSubscribedEvents(): array { return [ TaskExecutedEvent::class => 'onTaskExecuted', - TaskFailedEvent::class => 'onTaskFailure', + TaskFailedEvent::class => 'onTaskFailed', ]; } - public function onTaskFailure(FailedTask $failedTask): void + public function onTaskFailed(FailedTask $failedTask): void { $this->failedTasksList->add($failedTask->getTask()); } @@ -58,8 +58,8 @@ public function onTaskExecuted(TaskExecutedEvent $event): void $task = $event->getTask(); $output = $event->getOutput(); - $this->onTaskFailed($task, $output); - $this->onTaskSuccess($task, $output); + $this->handleTaskFailure($task, $output); + $this->handleTaskSuccess($task, $output); } private function send(Email $email): void @@ -71,7 +71,7 @@ private function send(Email $email): void $this->mailer->send($email); } - private function onTaskFailed(TaskInterface $task, Output $output): void + private function handleTaskFailure(TaskInterface $task, Output $output): void { if ($this->failedTasksList->count() !== $this->emailTriggerConfiguration->getFailureTriggeredAt()) { return; @@ -80,7 +80,7 @@ private function onTaskFailed(TaskInterface $task, Output $output): void $this->send(); } - private function onTaskSuccess(TaskInterface $task, Output $output): void + private function handleTaskSuccess(TaskInterface $task, Output $output): void { if ($task->getExecutionState() !== TaskInterface::SUCCEED) { return; diff --git a/src/Trigger/EmailTriggerConfiguration.php b/src/Trigger/EmailTriggerConfiguration.php index 42964e3c..92166036 100644 --- a/src/Trigger/EmailTriggerConfiguration.php +++ b/src/Trigger/EmailTriggerConfiguration.php @@ -9,6 +9,7 @@ */ final class EmailTriggerConfiguration { + private bool $enabled; private int $failureTriggeredAt; private int $successTriggeredAt; private ?string $failureFrom; @@ -18,9 +19,12 @@ final class EmailTriggerConfiguration private string $failureSubject; private string $successSubject; - private function __construct() {} + private function __construct() + { + } public static function create( + bool $enabled, int $failureTriggeredAt, int $successTriggeredAt, ?string $failureFrom, @@ -32,6 +36,7 @@ public static function create( ): self { $emailTriggerConfiguration = new self(); + $emailTriggerConfiguration->enabled = $enabled; $emailTriggerConfiguration->failureTriggeredAt = $failureTriggeredAt; $emailTriggerConfiguration->successTriggeredAt = $successTriggeredAt; $emailTriggerConfiguration->failureFrom = $failureFrom; @@ -44,6 +49,11 @@ public static function create( return $emailTriggerConfiguration; } + public function isEnabled(): bool + { + return $this->enabled; + } + public function getFailureTriggeredAt(): int { return $this->failureTriggeredAt; diff --git a/tests/DependencyInjection/SchedulerBundleConfigurationTest.php b/tests/DependencyInjection/SchedulerBundleConfigurationTest.php index 2ef919d5..96bec690 100644 --- a/tests/DependencyInjection/SchedulerBundleConfigurationTest.php +++ b/tests/DependencyInjection/SchedulerBundleConfigurationTest.php @@ -524,6 +524,7 @@ public function testConfigurationCanDefineTriggers(): void 'dsn' => 'cache://app', ], 'triggers' => [ + 'enabled' => true, 'email' => [ 'on_failure' => [ 'triggered_at' => 10, @@ -543,6 +544,7 @@ public function testConfigurationCanDefineTriggers(): void ]); self::assertCount(1, $configuration['triggers']); + self::assertTrue($configuration['triggers']['enabled']); self::assertCount(2, $configuration['triggers']['email']); self::assertCount(4, $configuration['triggers']['email']['on_failure']); self::assertCount(4, $configuration['triggers']['email']['on_success']); diff --git a/tests/EventListener/EmailTaskLifecycleSubscriberTest.php b/tests/EventListener/EmailTaskLifecycleSubscriberTest.php index b693a16d..6f4d18ca 100644 --- a/tests/EventListener/EmailTaskLifecycleSubscriberTest.php +++ b/tests/EventListener/EmailTaskLifecycleSubscriberTest.php @@ -5,10 +5,35 @@ namespace Tests\SchedulerBundle\EventListener; use PHPUnit\Framework\TestCase; +use SchedulerBundle\Event\TaskExecutedEvent; +use SchedulerBundle\Event\TaskFailedEvent; +use SchedulerBundle\EventListener\EmailTaskLifecycleSubscriber; /** * @author Guillaume Loulier */ final class EmailTaskLifecycleSubscriberTest extends TestCase { + public function testSubscriberIsConfigured(): void + { + self::assertCount(2, EmailTaskLifecycleSubscriber::getSubscribedEvents()); + + self::assertArrayHasKey(TaskExecutedEvent::class, EmailTaskLifecycleSubscriber::getSubscribedEvents()); + self::assertSame([ + 'onTaskExecuted', + ], EmailTaskLifecycleSubscriber::getSubscribedEvents()[TaskExecutedEvent::class]); + + self::assertArrayHasKey(TaskFailedEvent::class, EmailTaskLifecycleSubscriber::getSubscribedEvents()); + self::assertSame([ + 'onTaskFailed', + ], EmailTaskLifecycleSubscriber::getSubscribedEvents()[TaskFailedEvent::class]); + } + + public function testSubscriberCanListenTaskFailure(): void + { + } + + public function testSubscriberCanListenTaskExecution(): void + { + } } From da1199a813ad48f21bb2e064944d11dca67e98fe Mon Sep 17 00:00:00 2001 From: Loulier Guillaume Date: Mon, 15 Nov 2021 18:38:09 +0100 Subject: [PATCH 06/10] feat(trigger): progress --- src/Middleware/TriggerMiddleware.php | 30 ++++++++++++++++ src/Trigger/EmailTriggerConfiguration.php | 35 ++++++++----------- src/Trigger/TriggerConfigurationInterface.php | 16 +++++++++ src/Trigger/TriggerConfigurationRegistry.php | 32 +++++++++++++++++ .../TriggerConfigurationRegistryInterface.php | 15 ++++++++ 5 files changed, 108 insertions(+), 20 deletions(-) create mode 100644 src/Middleware/TriggerMiddleware.php create mode 100644 src/Trigger/TriggerConfigurationInterface.php create mode 100644 src/Trigger/TriggerConfigurationRegistry.php create mode 100644 src/Trigger/TriggerConfigurationRegistryInterface.php diff --git a/src/Middleware/TriggerMiddleware.php b/src/Middleware/TriggerMiddleware.php new file mode 100644 index 00000000..0a92f8c5 --- /dev/null +++ b/src/Middleware/TriggerMiddleware.php @@ -0,0 +1,30 @@ + + */ +final class TriggerMiddleware implements PreExecutionMiddlewareInterface, RequiredMiddlewareInterface +{ + private TriggerConfigurationRegistryInterface $triggerConfigurationRegistry; + + public function __construct(TriggerConfigurationRegistryInterface $triggerConfigurationRegistry) + { + $this->triggerConfigurationRegistry = $triggerConfigurationRegistry; + } + + /** + * {@inheritdoc} + */ + public function preExecute(TaskInterface $task): void + { + $enabledTriggers = $this->triggerConfigurationRegistry->filter(static fn (TriggerConfigurationInterface $triggerConfiguration) => $triggerConfiguration->isEnabled()); + } +} diff --git a/src/Trigger/EmailTriggerConfiguration.php b/src/Trigger/EmailTriggerConfiguration.php index 92166036..05ea3e44 100644 --- a/src/Trigger/EmailTriggerConfiguration.php +++ b/src/Trigger/EmailTriggerConfiguration.php @@ -7,7 +7,7 @@ /** * @author Guillaume Loulier */ -final class EmailTriggerConfiguration +final class EmailTriggerConfiguration implements TriggerConfigurationInterface { private bool $enabled; private int $failureTriggeredAt; @@ -19,11 +19,7 @@ final class EmailTriggerConfiguration private string $failureSubject; private string $successSubject; - private function __construct() - { - } - - public static function create( + public function __construct( bool $enabled, int $failureTriggeredAt, int $successTriggeredAt, @@ -33,22 +29,21 @@ public static function create( ?string $successTo, string $failureSubject, string $successSubject - ): self { - $emailTriggerConfiguration = new self(); - - $emailTriggerConfiguration->enabled = $enabled; - $emailTriggerConfiguration->failureTriggeredAt = $failureTriggeredAt; - $emailTriggerConfiguration->successTriggeredAt = $successTriggeredAt; - $emailTriggerConfiguration->failureFrom = $failureFrom; - $emailTriggerConfiguration->successFrom = $successFrom; - $emailTriggerConfiguration->failureTo = $failureTo; - $emailTriggerConfiguration->successTo = $successTo; - $emailTriggerConfiguration->failureSubject = $failureSubject; - $emailTriggerConfiguration->successSubject = $successSubject; - - return $emailTriggerConfiguration; + ) { + $this->enabled = $enabled; + $this->failureTriggeredAt = $failureTriggeredAt; + $this->successTriggeredAt = $successTriggeredAt; + $this->failureFrom = $failureFrom; + $this->successFrom = $successFrom; + $this->failureTo = $failureTo; + $this->successTo = $successTo; + $this->failureSubject = $failureSubject; + $this->successSubject = $successSubject; } + /** + * {@inheritdoc} + */ public function isEnabled(): bool { return $this->enabled; diff --git a/src/Trigger/TriggerConfigurationInterface.php b/src/Trigger/TriggerConfigurationInterface.php new file mode 100644 index 00000000..44e1d7ef --- /dev/null +++ b/src/Trigger/TriggerConfigurationInterface.php @@ -0,0 +1,16 @@ + + */ +interface TriggerConfigurationInterface +{ + /** + * Specify if the configuration is enabled. + */ + public function isEnabled(): bool; +} diff --git a/src/Trigger/TriggerConfigurationRegistry.php b/src/Trigger/TriggerConfigurationRegistry.php new file mode 100644 index 00000000..5d0309aa --- /dev/null +++ b/src/Trigger/TriggerConfigurationRegistry.php @@ -0,0 +1,32 @@ + + */ +final class TriggerConfigurationRegistry implements TriggerConfigurationRegistryInterface +{ + /** + * @var TriggerConfigurationInterface[] + */ + private iterable $configurationList; + + /** + * @param TriggerConfigurationInterface[] $configurationList + */ + public function __construct(iterable $configurationList) + { + $this->configurationList = $configurationList; + } + + public function filter(Closure $func): TriggerConfigurationRegistryInterface + { + return new self(array_filter($this->configurationList, $func)); + } +} diff --git a/src/Trigger/TriggerConfigurationRegistryInterface.php b/src/Trigger/TriggerConfigurationRegistryInterface.php new file mode 100644 index 00000000..46f13f6d --- /dev/null +++ b/src/Trigger/TriggerConfigurationRegistryInterface.php @@ -0,0 +1,15 @@ + + */ +interface TriggerConfigurationRegistryInterface +{ + public function filter(Closure $func): TriggerConfigurationRegistryInterface; +} From a34bcb7766ca6ad06c9a80e18aba087ef24d05a8 Mon Sep 17 00:00:00 2001 From: Loulier Guillaume Date: Tue, 16 Nov 2021 09:32:21 +0100 Subject: [PATCH 07/10] feat(triggers): progress on emails --- .../TriggerConfigurationNotFoundException.php | 14 +++++++ src/Middleware/TriggerMiddleware.php | 34 +++++++++++++++- src/Trigger/EmailTriggerConfiguration.php | 8 ++++ src/Trigger/TriggerConfigurationInterface.php | 2 + src/Trigger/TriggerConfigurationRegistry.php | 39 +++++++++++++++++++ .../TriggerConfigurationRegistryInterface.php | 7 +++- tests/Middleware/TriggerMiddlewareTest.php | 14 +++++++ .../TriggerConfigurationRegistryTest.php | 14 +++++++ 8 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 src/Exception/TriggerConfigurationNotFoundException.php create mode 100644 tests/Middleware/TriggerMiddlewareTest.php create mode 100644 tests/Trigger/TriggerConfigurationRegistryTest.php diff --git a/src/Exception/TriggerConfigurationNotFoundException.php b/src/Exception/TriggerConfigurationNotFoundException.php new file mode 100644 index 00000000..10938326 --- /dev/null +++ b/src/Exception/TriggerConfigurationNotFoundException.php @@ -0,0 +1,14 @@ + + */ +final class TriggerConfigurationNotFoundException extends RuntimeException +{ +} diff --git a/src/Middleware/TriggerMiddleware.php b/src/Middleware/TriggerMiddleware.php index 0a92f8c5..e5bf8d70 100644 --- a/src/Middleware/TriggerMiddleware.php +++ b/src/Middleware/TriggerMiddleware.php @@ -4,20 +4,37 @@ namespace SchedulerBundle\Middleware; +use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; +use SchedulerBundle\EventListener\EmailTaskLifecycleSubscriber; +use SchedulerBundle\Exception\TriggerConfigurationNotFoundException; use SchedulerBundle\Task\TaskInterface; use SchedulerBundle\Trigger\TriggerConfigurationInterface; use SchedulerBundle\Trigger\TriggerConfigurationRegistryInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Mailer\MailerInterface; /** * @author Guillaume Loulier */ final class TriggerMiddleware implements PreExecutionMiddlewareInterface, RequiredMiddlewareInterface { + private EventDispatcherInterface $eventDispatcher; + private LoggerInterface $logger; private TriggerConfigurationRegistryInterface $triggerConfigurationRegistry; + private ?MailerInterface $mailer; - public function __construct(TriggerConfigurationRegistryInterface $triggerConfigurationRegistry) - { + public function __construct( + EventDispatcherInterface $eventDispatcher, + TriggerConfigurationRegistryInterface $triggerConfigurationRegistry, + ?LoggerInterface $logger = null, + ?MailerInterface $mailer = null + ) { + $this->eventDispatcher = $eventDispatcher; $this->triggerConfigurationRegistry = $triggerConfigurationRegistry; + $this->logger = $logger ?? new NullLogger(); + + $this->mailer = $mailer; } /** @@ -26,5 +43,18 @@ public function __construct(TriggerConfigurationRegistryInterface $triggerConfig public function preExecute(TaskInterface $task): void { $enabledTriggers = $this->triggerConfigurationRegistry->filter(static fn (TriggerConfigurationInterface $triggerConfiguration) => $triggerConfiguration->isEnabled()); + + $this->enableEmailsTrigger($enabledTriggers); + } + + private function enableEmailsTrigger(TriggerConfigurationRegistryInterface $registry): void + { + try { + $emailTriggerConfiguration = $registry->get('emails'); + + $this->eventDispatcher->addSubscriber(new EmailTaskLifecycleSubscriber($emailTriggerConfiguration, $this->mailer)); + } catch (TriggerConfigurationNotFoundException $exception) { + $this->logger->warning('The "emails" trigger cannot be registered'); + } } } diff --git a/src/Trigger/EmailTriggerConfiguration.php b/src/Trigger/EmailTriggerConfiguration.php index 05ea3e44..e89a4a6e 100644 --- a/src/Trigger/EmailTriggerConfiguration.php +++ b/src/Trigger/EmailTriggerConfiguration.php @@ -49,6 +49,14 @@ public function isEnabled(): bool return $this->enabled; } + /** + * {@inheritdoc} + */ + public function support(string $trigger): bool + { + return 'emails' === $trigger; + } + public function getFailureTriggeredAt(): int { return $this->failureTriggeredAt; diff --git a/src/Trigger/TriggerConfigurationInterface.php b/src/Trigger/TriggerConfigurationInterface.php index 44e1d7ef..edf14361 100644 --- a/src/Trigger/TriggerConfigurationInterface.php +++ b/src/Trigger/TriggerConfigurationInterface.php @@ -13,4 +13,6 @@ interface TriggerConfigurationInterface * Specify if the configuration is enabled. */ public function isEnabled(): bool; + + public function support(string $trigger): bool; } diff --git a/src/Trigger/TriggerConfigurationRegistry.php b/src/Trigger/TriggerConfigurationRegistry.php index 5d0309aa..651728b0 100644 --- a/src/Trigger/TriggerConfigurationRegistry.php +++ b/src/Trigger/TriggerConfigurationRegistry.php @@ -5,7 +5,11 @@ namespace SchedulerBundle\Trigger; use Closure; +use SchedulerBundle\Exception\InvalidArgumentException; +use SchedulerBundle\Exception\RuntimeException; use function array_filter; +use function count; +use function current; /** * @author Guillaume Loulier @@ -29,4 +33,39 @@ public function filter(Closure $func): TriggerConfigurationRegistryInterface { return new self(array_filter($this->configurationList, $func)); } + + public function get(string $string): TriggerConfigurationInterface + { + $list = $this->filter(static fn (TriggerConfigurationInterface $configuration): bool => $configuration->support($string)); + if (0 === $list->count()) { + throw new InvalidArgumentException('No configuration found for this trigger'); + } + + if (1 < $list->count()) { + throw new InvalidArgumentException('More than one configuration found, consider improving the trigger discriminator'); + } + + return $list->current(); + } + + /** + * {@inheritdoc} + */ + public function current(): TriggerConfigurationInterface + { + $currentConfiguration = current($this->configurationList); + if (false === $currentConfiguration) { + throw new RuntimeException('The current configuration cannot be found'); + } + + return $currentConfiguration; + } + + /** + * {@inheritdoc} + */ + public function count(): int + { + return count($this->configurationList); + } } diff --git a/src/Trigger/TriggerConfigurationRegistryInterface.php b/src/Trigger/TriggerConfigurationRegistryInterface.php index 46f13f6d..ef6b22b3 100644 --- a/src/Trigger/TriggerConfigurationRegistryInterface.php +++ b/src/Trigger/TriggerConfigurationRegistryInterface.php @@ -5,11 +5,16 @@ namespace SchedulerBundle\Trigger; use Closure; +use Countable; /** * @author Guillaume Loulier */ -interface TriggerConfigurationRegistryInterface +interface TriggerConfigurationRegistryInterface extends Countable { public function filter(Closure $func): TriggerConfigurationRegistryInterface; + + public function get(string $string): TriggerConfigurationInterface; + + public function current(): TriggerConfigurationInterface; } diff --git a/tests/Middleware/TriggerMiddlewareTest.php b/tests/Middleware/TriggerMiddlewareTest.php new file mode 100644 index 00000000..5b92fc13 --- /dev/null +++ b/tests/Middleware/TriggerMiddlewareTest.php @@ -0,0 +1,14 @@ + + */ +final class TriggerMiddlewareTest extends TestCase +{ +} diff --git a/tests/Trigger/TriggerConfigurationRegistryTest.php b/tests/Trigger/TriggerConfigurationRegistryTest.php new file mode 100644 index 00000000..aa1a7fe7 --- /dev/null +++ b/tests/Trigger/TriggerConfigurationRegistryTest.php @@ -0,0 +1,14 @@ + + */ +final class TriggerConfigurationRegistryTest extends TestCase +{ +} From 00dd344b422704a2fdc7b4db9648f622adc84d71 Mon Sep 17 00:00:00 2001 From: Loulier Guillaume Date: Wed, 17 Nov 2021 18:43:19 +0100 Subject: [PATCH 08/10] tests(core): progress --- composer.json | 1 + .../SchedulerBundleConfiguration.php | 6 ++- .../SchedulerBundleExtension.php | 38 +++++++++++++ .../EmailTaskLifecycleSubscriber.php | 2 + src/Middleware/TriggerMiddleware.php | 13 +++-- src/Trigger/TriggerConfigurationRegistry.php | 14 +++-- .../TriggerConfigurationRegistryInterface.php | 18 ++++++- .../SchedulerBundleConfigurationTest.php | 4 +- .../SchedulerBundleExtensionTest.php | 54 +++++++++++++++++++ 9 files changed, 136 insertions(+), 14 deletions(-) diff --git a/composer.json b/composer.json index c9bf6f6a..2463d6cb 100644 --- a/composer.json +++ b/composer.json @@ -89,6 +89,7 @@ "Tests\\SchedulerBundle\\Transport\\": "tests/Transport/", "Tests\\SchedulerBundle\\Transport\\Assets\\": "tests/Transport/Assets/", "Tests\\SchedulerBundle\\Transport\\Configuration\\": "tests/Transport/Configuration/", + "Tests\\SchedulerBundle\\Trigger\\": "tests/Trigger/", "Tests\\SchedulerBundle\\Worker\\": "tests/Worker/", "Tests\\SchedulerBundle\\Worker\\Assets\\": "tests/Worker/Assets/" } diff --git a/src/DependencyInjection/SchedulerBundleConfiguration.php b/src/DependencyInjection/SchedulerBundleConfiguration.php index dcc08742..d4db4189 100644 --- a/src/DependencyInjection/SchedulerBundleConfiguration.php +++ b/src/DependencyInjection/SchedulerBundleConfiguration.php @@ -93,11 +93,15 @@ public function getConfigTreeBuilder(): TreeBuilder ->addDefaultsIfNotSet() ->children() ->scalarNode('enabled') - ->info('Enable the triggers') + ->info('Enable the triggers support') ->defaultValue(false) ->end() ->arrayNode('email') ->children() + ->scalarNode('enabled') + ->info('Enable the email triggers') + ->defaultValue(false) + ->end() ->arrayNode('on_failure') ->children() ->scalarNode('triggered_at') diff --git a/src/DependencyInjection/SchedulerBundleExtension.php b/src/DependencyInjection/SchedulerBundleExtension.php index 3e2359b1..cbdc8ec1 100644 --- a/src/DependencyInjection/SchedulerBundleExtension.php +++ b/src/DependencyInjection/SchedulerBundleExtension.php @@ -52,6 +52,7 @@ use SchedulerBundle\Middleware\TaskExecutionMiddleware; use SchedulerBundle\Middleware\TaskLockBagMiddleware; use SchedulerBundle\Middleware\TaskUpdateMiddleware; +use SchedulerBundle\Middleware\TriggerMiddleware; use SchedulerBundle\Middleware\WorkerMiddlewareStack; use SchedulerBundle\Middleware\PostExecutionMiddlewareInterface; use SchedulerBundle\Middleware\PreExecutionMiddlewareInterface; @@ -116,6 +117,9 @@ use SchedulerBundle\Transport\TransportFactoryInterface; use SchedulerBundle\Transport\TransportInterface; use SchedulerBundle\Trigger\EmailTriggerConfiguration; +use SchedulerBundle\Trigger\TriggerConfigurationInterface; +use SchedulerBundle\Trigger\TriggerConfigurationRegistry; +use SchedulerBundle\Trigger\TriggerConfigurationRegistryInterface; use SchedulerBundle\Worker\Worker; use SchedulerBundle\Worker\WorkerInterface; use Symfony\Bundle\FrameworkBundle\Console\Application; @@ -127,6 +131,7 @@ use Symfony\Component\DependencyInjection\Extension\Extension; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\Component\Mailer\MailerInterface; use Symfony\Component\Mercure\Hub; use Symfony\Component\Mercure\Jwt\StaticTokenProvider; use Symfony\Component\Lock\LockFactory; @@ -159,6 +164,7 @@ final class SchedulerBundleExtension extends Extension private const SCHEDULER_SCHEDULE_POLICY = 'scheduler.schedule_policy'; private const TRANSPORT_CONFIGURATION_TAG = 'scheduler.configuration'; private const TRANSPORT_CONFIGURATION_FACTORY_TAG = 'scheduler.configuration_factory'; + private const SCHEDULER_TRIGGER_CONFIGURATION_TAG = 'scheduler.trigger_configuration'; public function load(array $configs, ContainerBuilder $container): void { @@ -209,6 +215,7 @@ private function registerParameters(ContainerBuilder $container, array $configur $container->setParameter('scheduler.mercure_support', $configuration['mercure']['enabled']); $container->setParameter('scheduler.pool_support', $configuration['pool']['enabled']); $container->setParameter('scheduler.trigger_support', $configuration['triggers']['enabled']); + $container->setParameter('scheduler.trigger_support.emails_enabled', $configuration['triggers']['email']['enabled']); } private function registerAutoConfigure(ContainerBuilder $container): void @@ -229,6 +236,7 @@ private function registerAutoConfigure(ContainerBuilder $container): void $container->registerForAutoconfiguration(BuilderInterface::class)->addTag(self::SCHEDULER_TASK_BUILDER_TAG); $container->registerForAutoconfiguration(ProbeInterface::class)->addTag(self::SCHEDULER_PROBE_TAG); $container->registerForAutoconfiguration(TaskBagInterface::class)->addTag('scheduler.task_bag'); + $container->registerForAutoconfiguration(TriggerConfigurationInterface::class)->addTag(self::SCHEDULER_TRIGGER_CONFIGURATION_TAG); } private function registerConfigurationFactories(ContainerBuilder $container): void @@ -1337,6 +1345,35 @@ private function registerTriggers(ContainerBuilder $container, array $config): v return; } + $container->register(TriggerConfigurationRegistry::class, TriggerConfigurationRegistry::class) + ->setArguments([ + new TaggedIteratorArgument(self::SCHEDULER_TRIGGER_CONFIGURATION_TAG), + ]) + ->setPublic(false) + ->addTag('container.preload', [ + 'class' => TriggerConfigurationRegistry::class, + ]) + ; + $container->setAlias(TriggerConfigurationRegistryInterface::class, TriggerConfigurationRegistry::class); + + $container->register(TriggerMiddleware::class, TriggerMiddleware::class) + ->setArguments([ + new Reference(EventDispatcherInterface::class, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE), + new Reference(TriggerConfigurationRegistryInterface::class, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE), + new Reference(LoggerInterface::class, ContainerInterface::NULL_ON_INVALID_REFERENCE), + new Reference(MailerInterface::class, ContainerInterface::NULL_ON_INVALID_REFERENCE), + ]) + ->setPublic(false) + ->addTag(self::SCHEDULER_WORKER_MIDDLEWARE_TAG) + ->addTag('container.preload', [ + 'class' => TriggerMiddleware::class, + ]) + ; + + if (!$container->getParameter('scheduler.trigger_support.emails_enabled')) { + return; + } + $container->register(EmailTriggerConfiguration::class, EmailTriggerConfiguration::class) ->setArguments([ $container->getParameter('scheduler.trigger_support'), @@ -1349,6 +1386,7 @@ private function registerTriggers(ContainerBuilder $container, array $config): v $config['triggers']['email']['on_failure']['subject'], $config['triggers']['email']['on_success']['subject'], ]) + ->addTag(self::SCHEDULER_TRIGGER_CONFIGURATION_TAG) ->setPublic(false) ->addTag('container.preload', [ 'class' => EmailTriggerConfiguration::class, diff --git a/src/EventListener/EmailTaskLifecycleSubscriber.php b/src/EventListener/EmailTaskLifecycleSubscriber.php index b97c5153..55052c0a 100644 --- a/src/EventListener/EmailTaskLifecycleSubscriber.php +++ b/src/EventListener/EmailTaskLifecycleSubscriber.php @@ -91,5 +91,7 @@ private function handleTaskSuccess(TaskInterface $task, Output $output): void if ($this->succeedTasksList->count() !== $this->emailTriggerConfiguration->getSuccessTriggeredAt()) { return; } + + $this->send(); } } diff --git a/src/Middleware/TriggerMiddleware.php b/src/Middleware/TriggerMiddleware.php index e5bf8d70..345a8eab 100644 --- a/src/Middleware/TriggerMiddleware.php +++ b/src/Middleware/TriggerMiddleware.php @@ -9,7 +9,6 @@ use SchedulerBundle\EventListener\EmailTaskLifecycleSubscriber; use SchedulerBundle\Exception\TriggerConfigurationNotFoundException; use SchedulerBundle\Task\TaskInterface; -use SchedulerBundle\Trigger\TriggerConfigurationInterface; use SchedulerBundle\Trigger\TriggerConfigurationRegistryInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Mailer\MailerInterface; @@ -17,7 +16,7 @@ /** * @author Guillaume Loulier */ -final class TriggerMiddleware implements PreExecutionMiddlewareInterface, RequiredMiddlewareInterface +final class TriggerMiddleware implements PreExecutionMiddlewareInterface { private EventDispatcherInterface $eventDispatcher; private LoggerInterface $logger; @@ -42,19 +41,19 @@ public function __construct( */ public function preExecute(TaskInterface $task): void { - $enabledTriggers = $this->triggerConfigurationRegistry->filter(static fn (TriggerConfigurationInterface $triggerConfiguration) => $triggerConfiguration->isEnabled()); - - $this->enableEmailsTrigger($enabledTriggers); + $this->enableEmailsTrigger(); } - private function enableEmailsTrigger(TriggerConfigurationRegistryInterface $registry): void + private function enableEmailsTrigger(): void { try { - $emailTriggerConfiguration = $registry->get('emails'); + $emailTriggerConfiguration = $this->triggerConfigurationRegistry->get('emails'); $this->eventDispatcher->addSubscriber(new EmailTaskLifecycleSubscriber($emailTriggerConfiguration, $this->mailer)); } catch (TriggerConfigurationNotFoundException $exception) { $this->logger->warning('The "emails" trigger cannot be registered'); + + return; } } } diff --git a/src/Trigger/TriggerConfigurationRegistry.php b/src/Trigger/TriggerConfigurationRegistry.php index 651728b0..6d45591a 100644 --- a/src/Trigger/TriggerConfigurationRegistry.php +++ b/src/Trigger/TriggerConfigurationRegistry.php @@ -6,7 +6,7 @@ use Closure; use SchedulerBundle\Exception\InvalidArgumentException; -use SchedulerBundle\Exception\RuntimeException; +use SchedulerBundle\Exception\TriggerConfigurationNotFoundException; use function array_filter; use function count; use function current; @@ -29,14 +29,20 @@ public function __construct(iterable $configurationList) $this->configurationList = $configurationList; } + /** + * {@inheritdoc} + */ public function filter(Closure $func): TriggerConfigurationRegistryInterface { return new self(array_filter($this->configurationList, $func)); } - public function get(string $string): TriggerConfigurationInterface + /** + * {@inheritdoc} + */ + public function get(string $triggerName): TriggerConfigurationInterface { - $list = $this->filter(static fn (TriggerConfigurationInterface $configuration): bool => $configuration->support($string)); + $list = $this->filter(static fn (TriggerConfigurationInterface $configuration): bool => $configuration->support($triggerName)); if (0 === $list->count()) { throw new InvalidArgumentException('No configuration found for this trigger'); } @@ -55,7 +61,7 @@ public function current(): TriggerConfigurationInterface { $currentConfiguration = current($this->configurationList); if (false === $currentConfiguration) { - throw new RuntimeException('The current configuration cannot be found'); + throw new TriggerConfigurationNotFoundException('The current configuration cannot be found'); } return $currentConfiguration; diff --git a/src/Trigger/TriggerConfigurationRegistryInterface.php b/src/Trigger/TriggerConfigurationRegistryInterface.php index ef6b22b3..833eb1c3 100644 --- a/src/Trigger/TriggerConfigurationRegistryInterface.php +++ b/src/Trigger/TriggerConfigurationRegistryInterface.php @@ -6,15 +6,31 @@ use Closure; use Countable; +use SchedulerBundle\Exception\TriggerConfigurationNotFoundException; /** * @author Guillaume Loulier */ interface TriggerConfigurationRegistryInterface extends Countable { + /** + * Allow to filter the configuration list using @param Closure $func. + * + * A new {@see TriggerConfigurationRegistryInterface} will be returned. + */ public function filter(Closure $func): TriggerConfigurationRegistryInterface; - public function get(string $string): TriggerConfigurationInterface; + /** + * Return a {@see TriggerConfigurationInterface} depending on @param string $triggerName. + * + * @throws TriggerConfigurationNotFoundException {@see TriggerConfigurationRegistryInterface::current()} + */ + public function get(string $triggerName): TriggerConfigurationInterface; + /** + * Return the current trigger configuration. + * + * @throws TriggerConfigurationNotFoundException + */ public function current(): TriggerConfigurationInterface; } diff --git a/tests/DependencyInjection/SchedulerBundleConfigurationTest.php b/tests/DependencyInjection/SchedulerBundleConfigurationTest.php index 96bec690..a72210de 100644 --- a/tests/DependencyInjection/SchedulerBundleConfigurationTest.php +++ b/tests/DependencyInjection/SchedulerBundleConfigurationTest.php @@ -526,6 +526,7 @@ public function testConfigurationCanDefineTriggers(): void 'triggers' => [ 'enabled' => true, 'email' => [ + 'enabled' => true, 'on_failure' => [ 'triggered_at' => 10, 'to' => 'foo@foo.foo', @@ -545,7 +546,8 @@ public function testConfigurationCanDefineTriggers(): void self::assertCount(1, $configuration['triggers']); self::assertTrue($configuration['triggers']['enabled']); - self::assertCount(2, $configuration['triggers']['email']); + self::assertCount(3, $configuration['triggers']['email']); + self::assertTrue($configuration['triggers']['email']['enabled']); self::assertCount(4, $configuration['triggers']['email']['on_failure']); self::assertCount(4, $configuration['triggers']['email']['on_success']); } diff --git a/tests/DependencyInjection/SchedulerBundleExtensionTest.php b/tests/DependencyInjection/SchedulerBundleExtensionTest.php index 5bd708e3..f49b21d8 100644 --- a/tests/DependencyInjection/SchedulerBundleExtensionTest.php +++ b/tests/DependencyInjection/SchedulerBundleExtensionTest.php @@ -57,6 +57,7 @@ use SchedulerBundle\Middleware\TaskExecutionMiddleware; use SchedulerBundle\Middleware\TaskLockBagMiddleware; use SchedulerBundle\Middleware\TaskUpdateMiddleware; +use SchedulerBundle\Middleware\TriggerMiddleware; use SchedulerBundle\Middleware\WorkerMiddlewareStack; use SchedulerBundle\Probe\Probe; use SchedulerBundle\Probe\ProbeInterface; @@ -116,6 +117,10 @@ use SchedulerBundle\Transport\TransportFactory; use SchedulerBundle\Transport\TransportFactoryInterface; use SchedulerBundle\Transport\TransportInterface; +use SchedulerBundle\Trigger\EmailTriggerConfiguration; +use SchedulerBundle\Trigger\TriggerConfigurationInterface; +use SchedulerBundle\Trigger\TriggerConfigurationRegistry; +use SchedulerBundle\Trigger\TriggerConfigurationRegistryInterface; use SchedulerBundle\Worker\Worker; use SchedulerBundle\Worker\WorkerInterface; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; @@ -125,6 +130,7 @@ use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\Component\Mailer\MailerInterface; use Symfony\Component\Mercure\Hub; use Symfony\Component\Mercure\Jwt\StaticTokenProvider; use Symfony\Component\Lock\LockFactory; @@ -216,6 +222,8 @@ public function testInterfacesForAutoconfigureAreRegistered(): void self::assertTrue($autoconfigurationInterfaces[ProbeInterface::class]->hasTag('scheduler.probe')); self::assertArrayHasKey(TaskBagInterface::class, $autoconfigurationInterfaces); self::assertTrue($autoconfigurationInterfaces[TaskBagInterface::class]->hasTag('scheduler.task_bag')); + self::assertArrayHasKey(TriggerConfigurationInterface::class, $autoconfigurationInterfaces); + self::assertTrue($autoconfigurationInterfaces[TriggerConfigurationInterface::class]->hasTag('scheduler.trigger_configuration')); } public function testConfigurationFactoriesAreRegistered(): void @@ -1733,6 +1741,52 @@ public function testPoolSupportCanBeRegistered(): void self::assertTrue($container->getParameter('scheduler.pool_support')); } + public function testTriggersCanBeRegistered(): void + { + $container = $this->getContainer([ + 'path' => '/_foo', + 'timezone' => 'Europe/Paris', + 'transport' => [ + 'dsn' => 'memory://first_in_first_out', + ], + 'triggers' => [ + 'enabled' => true, + ], + ]); + + self::assertTrue($container->hasAlias(TriggerConfigurationRegistryInterface::class)); + self::assertTrue($container->hasDefinition(TriggerConfigurationRegistry::class)); + self::assertCount(1, $container->getDefinition(TriggerConfigurationRegistry::class)->getArguments()); + self::assertInstanceOf(TaggedIteratorArgument::class, $container->getDefinition(TriggerConfigurationRegistry::class)->getArgument(0)); + self::assertSame('scheduler.trigger_configuration', $container->getDefinition(TriggerConfigurationRegistry::class)->getArgument(0)->getTag()); + self::assertFalse($container->getDefinition(TriggerConfigurationRegistry::class)->isPublic()); + self::assertCount(1, $container->getDefinition(TriggerConfigurationRegistry::class)->getTags()); + self::assertTrue($container->getDefinition(TriggerConfigurationRegistry::class)->hasTag('container.preload')); + self::assertSame(TriggerConfigurationRegistry::class, $container->getDefinition(TriggerConfigurationRegistry::class)->getTag('container.preload')[0]['class']); + + self::assertTrue($container->hasDefinition(TriggerMiddleware::class)); + self::assertCount(4, $container->getDefinition(TriggerMiddleware::class)->getArguments()); + self::assertInstanceOf(Reference::class, $container->getDefinition(TriggerMiddleware::class)->getArgument(0)); + self::assertSame(EventDispatcherInterface::class, (string) $container->getDefinition(TriggerMiddleware::class)->getArgument(0)); + self::assertSame(ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $container->getDefinition(TriggerMiddleware::class)->getArgument(0)->getInvalidBehavior()); + self::assertInstanceOf(Reference::class, $container->getDefinition(TriggerMiddleware::class)->getArgument(1)); + self::assertSame(TriggerConfigurationRegistryInterface::class, (string) $container->getDefinition(TriggerMiddleware::class)->getArgument(1)); + self::assertSame(ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $container->getDefinition(TriggerMiddleware::class)->getArgument(1)->getInvalidBehavior()); + self::assertInstanceOf(Reference::class, $container->getDefinition(TriggerMiddleware::class)->getArgument(2)); + self::assertSame(LoggerInterface::class, (string) $container->getDefinition(TriggerMiddleware::class)->getArgument(2)); + self::assertSame(ContainerInterface::NULL_ON_INVALID_REFERENCE, $container->getDefinition(TriggerMiddleware::class)->getArgument(2)->getInvalidBehavior()); + self::assertInstanceOf(Reference::class, $container->getDefinition(TriggerMiddleware::class)->getArgument(3)); + self::assertSame(MailerInterface::class, (string) $container->getDefinition(TriggerMiddleware::class)->getArgument(3)); + self::assertSame(ContainerInterface::NULL_ON_INVALID_REFERENCE, $container->getDefinition(TriggerMiddleware::class)->getArgument(3)->getInvalidBehavior()); + self::assertFalse($container->getDefinition(TriggerMiddleware::class)->isPublic()); + self::assertCount(2, $container->getDefinition(TriggerMiddleware::class)->getTags()); + self::assertTrue($container->getDefinition(TriggerMiddleware::class)->hasTag('scheduler.worker_middleware')); + self::assertTrue($container->getDefinition(TriggerMiddleware::class)->hasTag('container.preload')); + self::assertSame(TriggerMiddleware::class, $container->getDefinition(TriggerMiddleware::class)->getTag('container.preload')[0]['class']); + + self::assertFalse($container->hasDefinition(EmailTriggerConfiguration::class)); + } + public function testConfiguration(): void { $extension = new SchedulerBundleExtension(); From 616f460e5c15fc9fd35c14c1c692cc1707787376 Mon Sep 17 00:00:00 2001 From: Loulier Guillaume Date: Thu, 18 Nov 2021 18:23:59 +0100 Subject: [PATCH 09/10] feat(triggers): progress --- composer.json | 1 + .../SchedulerBundleConfiguration.php | 1 + .../SchedulerBundleExtension.php | 4 +- .../EmailTaskLifecycleSubscriber.php | 42 ++++++++++++++----- src/Trigger/EmailTriggerConfiguration.php | 10 ++--- .../SchedulerBundleConfigurationTest.php | 2 +- .../SchedulerBundleExtensionTest.php | 34 +++++++++++++++ .../EmailTaskLifecycleSubscriberTest.php | 40 ++++++++++++++---- 8 files changed, 108 insertions(+), 26 deletions(-) diff --git a/composer.json b/composer.json index 2463d6cb..7543cb25 100644 --- a/composer.json +++ b/composer.json @@ -151,6 +151,7 @@ "symfony/phpunit-bridge": "^5.2", "symfony/rate-limiter": "^5.2", "symfony/translation": "^5.2", + "symfony/twig-bundle": "^5.3", "thecodingmachine/safe": "^1.3.3" }, "suggest": { diff --git a/src/DependencyInjection/SchedulerBundleConfiguration.php b/src/DependencyInjection/SchedulerBundleConfiguration.php index d4db4189..7251fb61 100644 --- a/src/DependencyInjection/SchedulerBundleConfiguration.php +++ b/src/DependencyInjection/SchedulerBundleConfiguration.php @@ -97,6 +97,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->defaultValue(false) ->end() ->arrayNode('email') + ->addDefaultsIfNotSet() ->children() ->scalarNode('enabled') ->info('Enable the email triggers') diff --git a/src/DependencyInjection/SchedulerBundleExtension.php b/src/DependencyInjection/SchedulerBundleExtension.php index cbdc8ec1..4385168e 100644 --- a/src/DependencyInjection/SchedulerBundleExtension.php +++ b/src/DependencyInjection/SchedulerBundleExtension.php @@ -1379,12 +1379,12 @@ private function registerTriggers(ContainerBuilder $container, array $config): v $container->getParameter('scheduler.trigger_support'), $config['triggers']['email']['on_failure']['triggered_at'], $config['triggers']['email']['on_success']['triggered_at'], + $config['triggers']['email']['on_failure']['subject'], + $config['triggers']['email']['on_success']['subject'], $config['triggers']['email']['on_failure']['from'], $config['triggers']['email']['on_success']['from'], $config['triggers']['email']['on_failure']['to'], $config['triggers']['email']['on_success']['to'], - $config['triggers']['email']['on_failure']['subject'], - $config['triggers']['email']['on_success']['subject'], ]) ->addTag(self::SCHEDULER_TRIGGER_CONFIGURATION_TAG) ->setPublic(false) diff --git a/src/EventListener/EmailTaskLifecycleSubscriber.php b/src/EventListener/EmailTaskLifecycleSubscriber.php index 55052c0a..a0890b6f 100644 --- a/src/EventListener/EmailTaskLifecycleSubscriber.php +++ b/src/EventListener/EmailTaskLifecycleSubscriber.php @@ -12,8 +12,10 @@ use SchedulerBundle\Task\TaskList; use SchedulerBundle\Task\TaskListInterface; use SchedulerBundle\Trigger\EmailTriggerConfiguration; +use Symfony\Bridge\Twig\Mime\TemplatedEmail; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Mailer\MailerInterface; +use Symfony\Component\Mime\Address; use Symfony\Component\Mime\Email; /** @@ -62,22 +64,22 @@ public function onTaskExecuted(TaskExecutedEvent $event): void $this->handleTaskSuccess($task, $output); } - private function send(Email $email): void - { - if (null === $this->mailer) { - return; - } - - $this->mailer->send($email); - } - private function handleTaskFailure(TaskInterface $task, Output $output): void { if ($this->failedTasksList->count() !== $this->emailTriggerConfiguration->getFailureTriggeredAt()) { return; } - $this->send(); + $this->send( + (new TemplatedEmail()) + ->from($this->emailTriggerConfiguration->getFailureFrom()) + ->to(new Address($this->emailTriggerConfiguration->getFailureTo())) + ->subject($this->emailTriggerConfiguration->getFailureSubject()) + ->htmlTemplate('emails/task_failure.html.twig') + ->context([ + 'tasks' => $this->failedTasksList->toArray(false), + ]) + ); } private function handleTaskSuccess(TaskInterface $task, Output $output): void @@ -92,6 +94,24 @@ private function handleTaskSuccess(TaskInterface $task, Output $output): void return; } - $this->send(); + $this->send( + (new TemplatedEmail()) + ->from($this->emailTriggerConfiguration->getSuccessFrom()) + ->to(new Address($this->emailTriggerConfiguration->getSuccessTo())) + ->subject($this->emailTriggerConfiguration->getFailureSubject()) + ->htmlTemplate('emails/task_success.html.twig') + ->context([ + 'tasks' => $this->succeedTasksList->toArray(false), + ]) + ); + } + + private function send(Email $email): void + { + if (null === $this->mailer) { + return; + } + + $this->mailer->send($email); } } diff --git a/src/Trigger/EmailTriggerConfiguration.php b/src/Trigger/EmailTriggerConfiguration.php index e89a4a6e..48f59788 100644 --- a/src/Trigger/EmailTriggerConfiguration.php +++ b/src/Trigger/EmailTriggerConfiguration.php @@ -23,12 +23,12 @@ public function __construct( bool $enabled, int $failureTriggeredAt, int $successTriggeredAt, - ?string $failureFrom, - ?string $successFrom, - ?string $failureTo, - ?string $successTo, string $failureSubject, - string $successSubject + string $successSubject, + ?string $failureFrom = null, + ?string $successFrom = null, + ?string $failureTo = null, + ?string $successTo = null ) { $this->enabled = $enabled; $this->failureTriggeredAt = $failureTriggeredAt; diff --git a/tests/DependencyInjection/SchedulerBundleConfigurationTest.php b/tests/DependencyInjection/SchedulerBundleConfigurationTest.php index a72210de..92ac8fa9 100644 --- a/tests/DependencyInjection/SchedulerBundleConfigurationTest.php +++ b/tests/DependencyInjection/SchedulerBundleConfigurationTest.php @@ -544,7 +544,7 @@ public function testConfigurationCanDefineTriggers(): void ], ]); - self::assertCount(1, $configuration['triggers']); + self::assertCount(2, $configuration['triggers']); self::assertTrue($configuration['triggers']['enabled']); self::assertCount(3, $configuration['triggers']['email']); self::assertTrue($configuration['triggers']['email']['enabled']); diff --git a/tests/DependencyInjection/SchedulerBundleExtensionTest.php b/tests/DependencyInjection/SchedulerBundleExtensionTest.php index f49b21d8..af443b3c 100644 --- a/tests/DependencyInjection/SchedulerBundleExtensionTest.php +++ b/tests/DependencyInjection/SchedulerBundleExtensionTest.php @@ -1787,6 +1787,40 @@ public function testTriggersCanBeRegistered(): void self::assertFalse($container->hasDefinition(EmailTriggerConfiguration::class)); } + public function testEmailsTriggersCanBeRegistered(): void + { + $container = $this->getContainer([ + 'path' => '/_foo', + 'timezone' => 'Europe/Paris', + 'transport' => [ + 'dsn' => 'memory://first_in_first_out', + ], + 'triggers' => [ + 'enabled' => true, + 'email' => [ + 'enabled' => true, + 'on_failure' => [ + 'triggered_at' => 10, + 'to' => 'foo@foo.foo', + 'from' => 'bar@bar.bar', + 'subject' => 'An error occurred', + ], + 'on_success' => [ + 'triggered_at' => 10, + 'to' => 'foo@foo.foo', + 'from' => 'bar@bar.bar', + 'subject' => 'An task succeed', + ], + ], + ], + ]); + + self::assertTrue($container->hasDefinition(TriggerMiddleware::class)); + self::assertTrue($container->hasDefinition(TriggerConfigurationRegistry::class)); + + self::assertTrue($container->hasDefinition(EmailTriggerConfiguration::class)); + } + public function testConfiguration(): void { $extension = new SchedulerBundleExtension(); diff --git a/tests/EventListener/EmailTaskLifecycleSubscriberTest.php b/tests/EventListener/EmailTaskLifecycleSubscriberTest.php index 6f4d18ca..6d9f8368 100644 --- a/tests/EventListener/EmailTaskLifecycleSubscriberTest.php +++ b/tests/EventListener/EmailTaskLifecycleSubscriberTest.php @@ -8,6 +8,11 @@ use SchedulerBundle\Event\TaskExecutedEvent; use SchedulerBundle\Event\TaskFailedEvent; use SchedulerBundle\EventListener\EmailTaskLifecycleSubscriber; +use SchedulerBundle\Task\FailedTask; +use SchedulerBundle\Task\NullTask; +use SchedulerBundle\Task\Output; +use SchedulerBundle\Trigger\EmailTriggerConfiguration; +use Symfony\Component\Mailer\MailerInterface; /** * @author Guillaume Loulier @@ -19,18 +24,39 @@ public function testSubscriberIsConfigured(): void self::assertCount(2, EmailTaskLifecycleSubscriber::getSubscribedEvents()); self::assertArrayHasKey(TaskExecutedEvent::class, EmailTaskLifecycleSubscriber::getSubscribedEvents()); - self::assertSame([ - 'onTaskExecuted', - ], EmailTaskLifecycleSubscriber::getSubscribedEvents()[TaskExecutedEvent::class]); + self::assertSame('onTaskExecuted', EmailTaskLifecycleSubscriber::getSubscribedEvents()[TaskExecutedEvent::class]); self::assertArrayHasKey(TaskFailedEvent::class, EmailTaskLifecycleSubscriber::getSubscribedEvents()); - self::assertSame([ - 'onTaskFailed', - ], EmailTaskLifecycleSubscriber::getSubscribedEvents()[TaskFailedEvent::class]); + self::assertSame('onTaskFailed', EmailTaskLifecycleSubscriber::getSubscribedEvents()[TaskFailedEvent::class]); } - public function testSubscriberCanListenTaskFailure(): void + public function testSubscriberCanListenTaskFailureWithoutSendingAnEmail(): void { + $mailer = $this->createMock(MailerInterface::class); + $mailer->expects(self::never())->method('send'); + + $task = new NullTask('foo'); + + $configuration = new EmailTriggerConfiguration(true, 2, 2, 'foo', 'bar'); + + $subscriber = new EmailTaskLifecycleSubscriber($configuration, $mailer); + + $subscriber->onTaskFailed(new FailedTask($task, 'why not')); + $subscriber->onTaskExecuted(new TaskExecutedEvent($task, new Output($task))); + } + + public function testSubscriberCanListenTaskFailureThenSendingAnEmail(): void + { + $mailer = $this->createMock(MailerInterface::class); + $mailer->expects(self::once())->method('send'); + + $task = new NullTask('foo'); + $configuration = new EmailTriggerConfiguration(true, 1, 1, 'foo', 'bar'); + + $subscriber = new EmailTaskLifecycleSubscriber($configuration, $mailer); + + $subscriber->onTaskFailed(new FailedTask($task, 'why not')); + $subscriber->onTaskExecuted(new TaskExecutedEvent($task, new Output($task))); } public function testSubscriberCanListenTaskExecution(): void From 592cf3a18dcaf8ae7df00efbb2e0589ccbdf1984 Mon Sep 17 00:00:00 2001 From: Loulier Guillaume Date: Fri, 19 Nov 2021 13:32:39 +0100 Subject: [PATCH 10/10] feat(triggers): progresss --- src/EventListener/EmailTaskLifecycleSubscriber.php | 8 ++++++++ tests/EventListener/EmailTaskLifecycleSubscriberTest.php | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/EventListener/EmailTaskLifecycleSubscriber.php b/src/EventListener/EmailTaskLifecycleSubscriber.php index a0890b6f..cca15faf 100644 --- a/src/EventListener/EmailTaskLifecycleSubscriber.php +++ b/src/EventListener/EmailTaskLifecycleSubscriber.php @@ -70,6 +70,10 @@ private function handleTaskFailure(TaskInterface $task, Output $output): void return; } + if (null === $this->emailTriggerConfiguration->getFailureTo()) { + return; + } + $this->send( (new TemplatedEmail()) ->from($this->emailTriggerConfiguration->getFailureFrom()) @@ -94,6 +98,10 @@ private function handleTaskSuccess(TaskInterface $task, Output $output): void return; } + if (null === $this->emailTriggerConfiguration->getSuccessTo()) { + return; + } + $this->send( (new TemplatedEmail()) ->from($this->emailTriggerConfiguration->getSuccessFrom()) diff --git a/tests/EventListener/EmailTaskLifecycleSubscriberTest.php b/tests/EventListener/EmailTaskLifecycleSubscriberTest.php index 6d9f8368..0ec70373 100644 --- a/tests/EventListener/EmailTaskLifecycleSubscriberTest.php +++ b/tests/EventListener/EmailTaskLifecycleSubscriberTest.php @@ -37,7 +37,7 @@ public function testSubscriberCanListenTaskFailureWithoutSendingAnEmail(): void $task = new NullTask('foo'); - $configuration = new EmailTriggerConfiguration(true, 2, 2, 'foo', 'bar'); + $configuration = new EmailTriggerConfiguration(true, 2, 2, 'foo.foo@foo.com', 'bar.bar@bar.com'); $subscriber = new EmailTaskLifecycleSubscriber($configuration, $mailer); @@ -51,7 +51,7 @@ public function testSubscriberCanListenTaskFailureThenSendingAnEmail(): void $mailer->expects(self::once())->method('send'); $task = new NullTask('foo'); - $configuration = new EmailTriggerConfiguration(true, 1, 1, 'foo', 'bar'); + $configuration = new EmailTriggerConfiguration(true, 1, 1, 'foo.foo@foo.com', 'bar.bar@bar.com'); $subscriber = new EmailTaskLifecycleSubscriber($configuration, $mailer);