bytespin/console-command-scheduler-bundle
Copyright (c) 2023 Greg LAMY greg@bytespin.net
This is a public project hosted on GitHub : https://github.com/ByteSpin/ConsoleCommandSchedulerBundle.git
This bundle was originally developed as part of an ETL project.
ByteSpin/ConsoleCommandSchedulerBundle is a Symfony 6.3 simple bundle that allows you to schedule console commands easily:
[!NOTE]
This project is still at beta state.
Feel free to submit bug and/or pull requests!
You can check the CHANGELOG to see the latest improvements and fixes.
Just keep in mind that I want to keep it as simple as possible!
[!IMPORTANT]
Version 1.0.13 requires schema update. Please run :
php php bin/console doctrine:schema:update --force
composer require bytespin/console-commande-scheduler-bundle
php bin/console doctrine:schema:update --force
For now, the bundle still lacks a custom recipe to manage database schema upgrade when needed.
Do not forget to update the database schema when updating the bundle
The last version that includes schema modifications is : 1.1.0
You will need to manually register the bundle in your application.
To do this, follow these steps:
Open the file config/bundles.php in your Symfony application.
Add the following line to the array returned by this file:
ByteSpin\ConsoleCommandSchedulerBundle\ConsoleCommandSchedulerBundle::class => ['all' => true],
Save the file. Your bundle is now registered and ready to be used in your application.
Make sure to perform this step after you have installed the bundle using Composer, but before you use any of its features in your application.
You will have to configure the entity manager to be used with the ByteSpin\ConsoleCommandSchedulerBundle entities. This has to be done once after installation. We provide a script to automatise this step ; please run :
bin/console bytespin:configure-console-command-scheduler
If you prefer to do this by yourself, add the following lines just within your entity manager 'mappings:' key in doctrine.yaml :
# src/config/packages/doctrine.yaml
doctrine:
dbal:
(...)
orm:
(...)
entity_managers:
your_entity_manager:
(...)
mappings:
ByteSpin\ConsoleCommandSchedulerBundle:
is_bundle: false
type: attribute
dir: '%kernel.project_dir%/vendor/bytespin/console-command-scheduler-bundle/src/Entity'
prefix: ByteSpin\ConsoleCommandSchedulerBundle\Entity
alias: ByteSpin\ConsoleCommandSchedulerBundle
[!IMPORTANT]
If your project contains entities mapped to multiple entity managers, be careful to not use the auto_mapping: true in your doctrine configuration.
This would prevent the getManagerForClass() function used in the bundle to get the correct entity manager to work properly!
In such case :
- Choose the correct entity manager when you run the configuration script,
- Be sure to remove the 'auto_mapping: true' key from your doctrine.yaml (or set it to false),
- Be sure that ALL your entities are correctly mapped in the 'mappings:' sections of your doctrine.yaml
The bundle supports an optional tags system that allows you to categorize and filter scheduled commands. This feature is disabled by default and requires configuration to enable.
ByteSpin\ConsoleCommandSchedulerBundle\Model\TagInterface:<?php
namespace App\Entity;
use ByteSpin\ConsoleCommandSchedulerBundle\Model\TagInterface;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class Tag implements TagInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 50, unique: true)]
private ?string $name = null;
#[ORM\Column(length: 100)]
private ?string $label = null;
#[ORM\Column(length: 7, nullable: true)]
private ?string $color = null;
public function __toString(): string
{
return $this->label ?? $this->name ?? '';
}
public function getId(): ?int { return $this->id; }
public function getName(): ?string { return $this->name; }
public function getLabel(): ?string { return $this->label; }
public function getColor(): ?string { return $this->color; }
// Add setters as needed...
}
config/packages/bytespin_console_command_scheduler.yaml:bytespin_console_command_scheduler:
tags:
enabled: true
class: App\Entity\Tag
crud_controller: App\Controller\Admin\TagCrudController # Optional: for autocomplete in EasyAdmin
TagInterface to your Tag entity in config/packages/doctrine.yaml:doctrine:
orm:
resolve_target_entities:
ByteSpin\ConsoleCommandSchedulerBundle\Model\TagInterface: App\Entity\Tag
php bin/console doctrine:schema:update --force
Your Tag entity must implement the following methods:
| Method | Return Type | Description |
|---|---|---|
getId() |
?int |
Returns the tag ID |
getName() |
?string |
Returns a unique identifier name |
getLabel() |
?string |
Returns the display label |
getColor() |
?string |
Returns a hex color code (e.g., #FF5733) |
__toString() |
string |
Returns a string representation |
When tags are enabled:
crud_controller is configured)[!NOTE]
The tags feature is completely optional. If not configured, the bundle works exactly as before. No database changes are required if tags remain disabled.
[!NOTE]
Please note that the administration interface is based on EasyAdmin Symfony bundle.
Because you might already use EasyAdmin in your project, no DashboardController is provided with the bundle.
If you don't have one, generate it with
bin/console make:admin:dashboard
You need to manually add the menu to your DashboardController.php file, for example:
use ByteSpin\ConsoleCommandSchedulerBundle\Entity\Scheduler;
use ByteSpin\ConsoleCommandSchedulerBundle\Entity\SchedulerLog;
use ByteSpin\ConsoleCommandSchedulerBundle\Controller\Admin\SchedulerCrudController;
use ByteSpin\ConsoleCommandSchedulerBundle\Controller\Admin\SchedulerLogCrudController;
(...)
yield MenuItem::subMenu('Symfony Scheduler', 'fa-duotone fa-folder-gear')->setSubItems([
MenuItem::linkToCrud('Scheduled Tasks', 'fa-light fa-clock', Scheduler::class)->setController(SchedulerCrudController::class),
MenuItem::linkToCrud('Logs', 'fa-duotone fa-clock-rotate-left', SchedulerLog::class)->setController(SchedulerLogCrudController::class),
]);
(The previous lines make use of FontAwesome icons. You are free to use any other solution)
[!NOTE] The bundle makes use of the very new symfony/scheduler component that is said to be experimental on 6.3 symfony version
That will change in the forthcoming 6.4 release.
The only documentation available for the moment is on the official symfony blog, with some useful examples. Please read it carefully at https://symfony.com/blog/new-in-symfony-6-3-scheduler-component
The 'from_date', 'from_time', 'until_date', 'until_time' bundle parameters are used to construct the expected scheduler trigger
The administration interface provides two sections:
The main console command scheduler section:
When you click on the menu, EasyAdmin provides the default list view for Console Commands Scheduler

You can add/view/edit any entry in this list:

The log section: provides a simple log viewing interface

The standard way of consuming scheduler messages is
php bin/console messenger:consume scheduler_scheduler
If you want the command to be verbose, please use:
bin/console messenger:consume scheduler_scheduler -vv
You can use cron or supervisor to achieve this ; The console commands are then executed according to the generated triggers.
The commands returning code, date and duration are logged in the dedicated table and in a dedicated log
You can view the logs in the administration interface
New generic events are now dispatched by the bundle for deeper integration with your application and/or a notification system:
The event subject is normalized and contains all console command useful data.
Use ByteSpin events through an event subscriber with a simple mail notification example:
(...)
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\EventDispatcher\GenericEvent;
use Symfony\Component\Mailer\MailerInterface;
use ByteSpin\ConsoleCommandSchedulerBundle\Event\ScheduledConsoleCommandGenericEvent;
readonly class MyEventSubscriber implements EventSubscriberInterface
{
public function __construct(
private MailerInterface $mailer,
) {
}
public static function getSubscribedEvents(): array
{
return [
'bytespin.failure.scheduled.console.command' => [
['notifyInCaseOfFailure'],
],
];
}
public function notifyInCaseOfFailure(GenericEvent $event): void
{
/** @var ScheduledConsoleCommandGenericEvent $consoleCommand */
$consoleCommand = $event->getSubject();
$message = 'The following scheduled console command failed:' . PHP_EOL .
'- Command: ' . $consoleCommand->command . ' ' . implode(' ', $consoleCommand->commandArguments) . PHP_EOL .
'- Scheduled at: ' . $consoleCommand->start->format('Y-m-d H:i:s') . PHP_EOL .
'- Failed at: ' . $consoleCommand->end->format('Y-m-d H:i:s') . PHP_EOL .
'- Duration: ' . $consoleCommand->duration . PHP_EOL .
'- Return code was: ' . $consoleCommand->returnCode . PHP_EOL .
'- Please see log file ' . $consoleCommand->logFile;
try {
$notification = new Email();
$notification
->from('hello@example.com')
->subject('Scheduled Console Command Failure!')
->to('recipient@example.com')
->text($message);
$this->mailer->send($notification);
} catch (TransportExceptionInterface $e) {
throw new Exception('Error while sending notification. Error was: ' . $e->getMessage());
}
}
}
A new notification system is now provided by the bundle that can be extended in your own console commands.
First, you will have to add a new environment variable in your project .env file: BYTESPIN_FROM_EMAIL=yourmailsender@mail.com Provide a valid mail from address. Do not forget to configure the mailer dsn in you project.
The administration interface (easyadmin) now provides 3 more fields:
When notification is enabled and valid email address provided:
The bundle natively only sends details about the main console command scheduled by the bundle. In some cases, your command can run several sub-steps for which you may need more details in notification. For this, the bundle provides a new event you can dispatch in your commands
(...)
use ByteSpin\ConsoleCommandSchedulerBundle\Converter\DurationConverter;
use ByteSpin\ConsoleCommandSchedulerBundle\Event\ScheduledConsoleCommandOutputEvent;
use ByteSpin\ConsoleCommandSchedulerBundle\Job\JobIdOptionTrait;
use ByteSpin\ConsoleCommandSchedulerBundle\Model\CommandType;
(...)
class YourCommandScheduledByTheBundle extends Command
{
// this is mandatory to get the job id that when executed by the bundle
use JobIdOptionTrait;
public function __construct(
private readonly DurationConverter $durationConverter, // convert duration in human-readable format
private readonly EventDispatcherInterface $eventDispatcher,
(...)
) {
parent::__construct();
}
protected function configure(): void
{
(...)
// configure option provided by ByteSpin\ConsoleCommandSchedulerBundle\Job\JobIdOptionTrait
$this->configureJobIdOption();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
// get ByteSpin scheduled job id
$jobId = $input->getOption('job-id');
$start = time();
$returnCode = $this->yourClass->yourMethod($yourParameter, ...);
$end = time();
$duration = $end - $start;
// build the output event to be caught in notification email
$outputEvent = new ScheduledConsoleCommandOutputEvent(
commandId: $jobId,
commandType: CommandType::CHILD, // (can be of CommandType::MASTER if you want to insert the main command in your details)
dateTime: (new DateTime())->setTimestamp($start),
command: $this->getName(),
commandArguments: $commandArguments, // you can format them using $input->getOptions())
duration: $this->durationConverter->convert($duration),
returnCode: $returnCode // 0 is SUCCESS, any other values are FAILURE
commandOutput: 'You can catch here and format Exceptions or any output you need in notification email'
)
;
$this->eventDispatcher->dispatch($outputEvent);
}
That's it, you will get detailed notification in your mailbox!
This project is licensed under the MIT License - see the LICENSE file for details.
How can I help you explore Laravel packages today?