Symfony 8.2 New Published on

Validate a cron expression for real

The Cron constraint parses the expression instead of checking its shape: 60 * * * * is rejected, and three modes tell whether aliases and the Scheduler's "#" are allowed.

Topics covered: Validator

New in Symfony 8.2 (in development). To validate a cron expression typed by a user, we wrote a regular expression. It checks the shape, not the meaning: 60 * * * * (minute 60) or */0 * * * * (every zero minutes) go through, and the scheduler fails later, in the middle of the night.

The Cron constraint

#[Assert\Cron] parses the expression with the same package as the Scheduler component, dragonmantank/cron-expression:

use Symfony\Component\Validator\Constraints as Assert;

class Report
{
    // the five fields ("0 6 * * 1-5") and the aliases ("@daily")
    #[Assert\Cron]
    public string $schedule;

    // the five fields only
    #[Assert\Cron(mode: Assert\Cron::MODE_STANDARD)]
    public string $strictSchedule;

    // on top of that, the Scheduler's hashed expressions ("# # * * *", "#daily")
    #[Assert\Cron(mode: Assert\Cron::MODE_HASHED)]
    public string $task;
}

A hashed expression lets the Scheduler replace each # with a value drawn from the name of the task: recurring tasks spread out over time instead of all starting at midnight.

Symfony 8.2 adds two other constraints: EntityExists, which checks that a Doctrine entity exists for an identifier received, and Audio, which checks the duration, the bitrate or the codec of a sound file.

Your task

PUT /reports/schedule saves the three schedules of a report: the delivery, started by our scheduler, the export copied over to a partner whose tool only knows the five fields, and the cleanup handed to the Scheduler. The regular expressions let impossible schedules through: the request in the "Requests" tab shows one.

Replace them with the Cron constraint, with the right mode for each field.

Where the feature comes from: the original pull request.