You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -164,10 +164,6 @@ It uses the same syntax as the `cron command-line utility`_::
164
164
// optionally you can define the timezone used by the cron expression
165
165
RecurringMessage::cron('* * * * *', new Message(), new \DateTimeZone('Africa/Malabo'));
166
166
167
-
.. versionadded:: 6.4
168
-
169
-
The feature to define the cron timezone was introduced in Symfony 6.4.
170
-
171
167
Before using it, you have to install the following dependency:
172
168
173
169
.. code-block:: terminal
@@ -271,6 +267,216 @@ Finally, the recurring messages has to be attached to a schedule::
271
267
}
272
268
}
273
269
270
+
So, this RecurringMessage will encompass both the trigger, defining the generation frequency of the message, and the message itself, the one to be processed by a specific handler.
271
+
272
+
But what is interesting to know is that it also provides you with the ability to generate your message(s) dynamically.
273
+
274
+
A dynamic vision for the messages generated
275
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
276
+
277
+
This proves particularly useful when the message depends on data stored in databases or third-party services.
278
+
279
+
Taking your example of reports generation, it depends on customer requests.
280
+
Depending on the specific demands, any number of reports may need to be generated at a defined frequency.
281
+
For these dynamic scenarios, it gives you the capability to dynamically define our message(s) instead of statically.
282
+
This is achieved by defining a :class:`Symfony\\Component\\Scheduler\\Trigger\\CallbackMessageProvider`.
283
+
284
+
Essentially, this means you can dynamically, at runtime, define your message(s) through a callback that gets executed each time the scheduler transport checks for messages to be generated::
285
+
286
+
// src/Scheduler/MyScheduleProvider.php
287
+
namespace App\Scheduler;
288
+
289
+
#[AsSchedule('uptoyou')]
290
+
class SaleTaskProvider implements ScheduleProviderInterface
291
+
{
292
+
public function getSchedule(): Schedule
293
+
{
294
+
return $this->schedule ??= (new Schedule())
295
+
->with(
296
+
RecurringMessage::trigger(
297
+
new ExcludeHolidaysTrigger(
298
+
CronExpressionTrigger::fromSpec('@daily'),
299
+
),
300
+
// instead of being static as in the previous example
301
+
new CallbackMessageProvider([$this, 'generateReports'], 'foo')),
302
+
RecurringMessage::cron(‘3 8 * * 1’, new CleanUpOldSalesReport())
303
+
304
+
);
305
+
}
306
+
307
+
public function generateReports(MessageContext $context)
308
+
{
309
+
// ...
310
+
yield new SendDailySalesReports();
311
+
yield new ReportSomethingReportSomethingElse();
312
+
....
313
+
}
314
+
}
315
+
316
+
Exploring alternatives for crafting your Recurring Messages
There is also another way to build a RecurringMessage, and this can be done simply by adding an attribute above a service or a command:
320
+
:class:`Symfony\\Component\\Scheduler\\Attribute\\AsPeriodicTask` attribute and :class:`Symfony\\Component\\Scheduler\\Attribute\\AsCronTask` attribute.
321
+
322
+
For both of these attributes, you have the ability to define the schedule to roll with using the ``schedule``option. By default, the ``default`` named schedule will be used.
323
+
Also, by default, the ``__invoke`` method of your service will be called but, it's also possible to specify the method to call via the ``method``option and you can define arguments via ``arguments``option if necessary.
324
+
325
+
The distinction between these two attributes lies in the options pertaining to the trigger:
#. You can configure various options such as ``expression``, ``jitter``, encompassing options related to the trigger.
334
+
335
+
By defining one of these two attributes, it enables the execution of your service or command, considering all the options that have been specified within the attributes.
336
+
337
+
Managing Scheduled Messages
338
+
---------------------------
339
+
340
+
Modifying Scheduled Messages in real time
341
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
342
+
343
+
While planning a schedule in advance is beneficial, it is rare for a schedule to remain static over time.
344
+
After a certain period, some RecurringMessages may become obsolete, while others may need to be integrated into our planning.
345
+
346
+
As a general practice, to alleviate a heavy workload, the recurring messages in the schedules are stored in memory to avoid recalculation each time the scheduler transport generates messages.
347
+
However, this approach can have a flip side.
348
+
349
+
In the context of our sales company, certain promotions may occur during specific periods and need to be communicated repetitively throughout a given timeframe
350
+
or the deletion of old reports needs to be halted under certain circumstances.
351
+
352
+
This is why the Scheduler incorporates a mechanism to dynamically modify the schedule and consider all changes in real-time.
353
+
354
+
Strategies for adding, removing, and modifying entries within the Schedule
The schedule provides you with the ability to :method:`Symfony\\Component\\Scheduler\Schedule::add`, :method:`Symfony\\Component\\Scheduler\Schedule::remove`, or :method:`Symfony\\Component\\Scheduler\Schedule::clear` all associated recurring messages,
358
+
resulting in the reset and recalculation of the in-memory stack of recurring messages.
359
+
360
+
For instance, for various reasons, if there's no need to generate a report, a callback can be employed to conditionally skip generating of some or all reports.
361
+
362
+
However, if the intention is to completely remove a recurring message and its recurrence,
363
+
the :class:`Symfony\\Component\\Scheduler\Schedule` offers a :method:`Symfony\\Component\\Scheduler\Schedule::remove` or a :method:`Symfony\\Component\\Scheduler\Schedule::removeById` method.
364
+
This can be particularly useful in your case, especially if you need to halt the generation of the recurring message, which involves deleting old reports.
365
+
366
+
In your handler, you can check a condition and, if affirmative, access the :class:`Symfony\\Component\\Scheduler\Schedule` and invoke this method::
367
+
368
+
// src/Scheduler/MyScheduleProvider.php
369
+
namespace App\Scheduler;
370
+
371
+
#[AsSchedule('uptoyou')]
372
+
class SaleTaskProvider implements ScheduleProviderInterface
373
+
{
374
+
public function getSchedule(): Schedule
375
+
{
376
+
$this->removeOldReports = RecurringMessage::cron(‘3 8 * * 1’, new CleanUpOldSalesReport());
public function __invoke(CleanUpOldSalesReport $cleanUpOldSalesReport): void
400
+
{
401
+
// do what you have to do
402
+
403
+
if ($isFinished) {
404
+
$this->mySchedule->removeCleanUpMessage();
405
+
}
406
+
}
407
+
}
408
+
409
+
Nevertheless, this system may not be the most suitable for all scenarios. Also, the handler should ideally be designed to process the type of message it is intended for,
410
+
without making decisions about adding or removing a new recurring message.
411
+
412
+
For instance, if, due to an external event, there is a need to add a recurrent message aimed at deleting reports,
413
+
it can be challenging to achieve within the handler. This is because the handler will no longer be called or executed once there are no more messages of that type.
414
+
415
+
However, the Scheduler also features an event system that is integrated into a Symfony full-stack application by grafting onto Symfony Messenger events.
416
+
These events are dispatched through a listener, providing a convenient means to respond.
417
+
418
+
Managing Scheduled Messages via Events
419
+
--------------------------------------
420
+
421
+
A strategic event handling
422
+
~~~~~~~~~~~~~~~~~~~~~~~~~~
423
+
424
+
The goal is to provide flexibility in deciding when to take action while preserving decoupling.
425
+
Three primary event types have been introduced types
426
+
427
+
#. PRE_RUN_EVENT
428
+
429
+
#. POST_RUN_EVENT
430
+
431
+
#. FAILURE_EVENT
432
+
433
+
Access to the schedule is a crucial feature, allowing effortless addition or removal of message types.
434
+
Additionally, it will be possible to access the currently processed message and its message context.
435
+
436
+
In consideration of our scenario, you can easily listen to the PRE_RUN_EVENT and check if a certain condition is met.
437
+
438
+
For instance, you might decide to add a recurring message for cleaning old reports again, with the same or different configurations, or add any other recurring message(s).
439
+
440
+
If you had chosen to handle the deletion of the recurring message, you could have easily done so in a listener for this event.
441
+
442
+
Importantly, it reveals a specific feature :method:`Symfony\\Component\\Scheduler\\Event\\PreRunEvent::shouldCancel` that allows you to prevent the message of the deleted recurring message from being transferred and processed by its handler::
443
+
444
+
// src/Scheduler/MyScheduleProvider.php
445
+
namespace App\Scheduler;
446
+
447
+
#[AsSchedule('uptoyou')]
448
+
class SaleTaskProvider implements ScheduleProviderInterface
449
+
{
450
+
public function getSchedule(): Schedule
451
+
{
452
+
$this->removeOldReports = RecurringMessage::cron(‘3 8 * * 1’, new CleanUpOldSalesReport());
453
+
454
+
return $this->schedule ??= (new Schedule())
455
+
->with(
456
+
// ...
457
+
);
458
+
->before(function(PreRunEvent $event) {
459
+
$message = $event->getMessage();
460
+
$messageContext = $event->getMessageContext();
461
+
462
+
// can access the schedule
463
+
$schedule = $event->getSchedule()->getSchedule();
464
+
465
+
// can target directly the RecurringMessage being processed
466
+
$schedule->removeById($messageContext->id);
467
+
468
+
//Allow to call the ShouldCancel() and avoid the message to be handled
@@ -323,20 +524,22 @@ recurring messages. You can narrow down the list to a specific schedule:
323
524
# use the --all option to also display the terminated recurring messages
324
525
$ php bin/console --all
325
526
326
-
.. versionadded:: 6.4
327
-
328
-
The ``--date`` and ``--all`` options were introduced in Symfony 6.4.
329
-
330
527
Efficient management with Symfony Scheduler
331
528
-------------------------------------------
332
529
333
-
If a worker becomes idle, the recurring messages won't be generated (because they
334
-
are created on-the-fly by the scheduler transport).
530
+
When a worker is restarted or undergoes shutdown for a period, the Scheduler transport won't be able to generate the messages (because they are created on-the-fly by the scheduler transport).
531
+
This implies that any messages scheduled to be sent during the worker's inactive period are not sent, and the Scheduler will lose track of the last processed message.
532
+
Upon restart, it will recalculate the messages to be generated from that point onward.
533
+
534
+
To illustrate, consider a recurring message set to be sent every 3 days.
535
+
If a worker is restarted on day 2, the message will be sent 3 days from the restart, on day 5.
536
+
537
+
While this behavior may not necessarily pose a problem, there is a possibility that it may not align with what you are seeking.
335
538
336
539
That's why the scheduler allows to remember the last execution date of a message
337
540
via the ``stateful`` option (and the :doc:`Cache component </components/cache>`).
338
-
This way, when it wakes up again, it looks at all the dates and can catch up on
339
-
what it missed::
541
+
This allows the system to retain the state of the schedule, ensuring that when a worker is restarted, it resumes from the point it left off.::
0 commit comments