Cron
Cron состоит из двух частей: CronParser проверяет выражение и вычисляет секунды запуска внутри выбранной минуты; Cron регистрирует callback и ставит одноразовые таймеры Localzet Server.
Это расписание внутри PHP-процесса. Оно не добавляет задания в системный crontab и не сохраняет их после завершения worker.
Подключение
Заголовок раздела «Подключение»Манифест требует PHP ^8.1 и Localzet Server ^4.5:
composer config repositories.localzet-cron vcs https://github.com/localzet/Croncomposer require localzet/cron:dev-mainТребование Server 4.x относится к планировщику Cron. Для самостоятельного разбора строки класс CronParser не вызывает Server или Timer. Не подменяйте требуемую версию Server 7.x только по совпадению имён классов.
Формат расписания
Заголовок раздела «Формат расписания»Пять полей задают минуту, час, день месяца, месяц и день недели. В шестипольном выражении перед ними добавляются секунды.
| Поле | Диапазон |
|---|---|
| Секунда | 0–59; при пяти полях используется 0 |
| Минута | 0–59 |
| Час | 0–23 |
| День месяца | 1–31 |
| Месяц | 1–12 |
| День недели | 0–6, воскресенье — 0 |
В поле можно использовать число, *, список через запятую, диапазон через дефис и шаг через /.
*/15 * * * * * каждые 15 секунд выбранной минуты0 9 * * 1-5 в 09:00 по будним дням0,30 * * * * * на секундах 0 и 300 */2 * * * * на нулевой секунде каждой второй минутыНазвания месяцев/дней, @daily, L, W, # и номер воскресенья 7 не являются поддерживаемыми формами. День месяца и день недели проверяются одновременно: выражение с обоими ограничениями требует совпадения обоих полей. Это отличается от crontab-реализаций с правилом «день месяца ИЛИ день недели».
Разбор одной минуты
Заголовок раздела «Разбор одной минуты»use localzet\CronParser;
date_default_timezone_set('UTC');$parser = new CronParser();$minute = strtotime('2026-10-17 12:00:00 UTC');
foreach ($parser->parse('0,30 * * * * *', $minute) as $timestamp) { echo gmdate('H:i:s', $timestamp), PHP_EOL;}Результат:
12:00:0012:00:30parse() сначала проверяет минуту, час, календарную дату и день недели у $startTime. Если они совпали с правилом, возвращает $startTime + second для каждого значения поля секунд. Если нет — пустой массив.
Передавайте начало минуты. При 12:00:20 для секунд 0,30 получатся 12:00:20 и 12:00:50; парсер не обнуляет секунды самостоятельно. Если время приходит извне, нормализуйте его:
$minute = $timestamp - $timestamp % 60;$times = $parser->parse($rule, $minute);Парсер не перебирает будущие минуты. Пустой массив не означает, что расписание никогда не сработает: это отсутствие совпадений в рассматриваемой минуте. Часовой пояс берётся из date_default_timezone_set()/настроек PHP, отдельного параметра timezone нет.
Проверка входного правила
Заголовок раздела «Проверка входного правила»if (!$parser->isValid($rule)) { throw new InvalidArgumentException('Unsupported schedule');}isValid() проверяет форму строки и разбор сегментов, но не гарантирует наличие допустимых значений. Например, 99 * * * * проходит эту проверку и не даст ни одной подходящей минуты. Положительный шаг обязателен: */0 отклоняется.
Для пользовательского интерфейса ограничьте ввод перечисленными формами и диапазонами. Не используйте один isValid() как доказательство, что задача получит хотя бы одно время запуска. Не предоставляйте числовую форму 5/10 как «начиная с 5»: реализация шага начинает перебор с нижней границы поля; для заданного начала используйте явный диапазон 5-59/10.
Регистрация callback
Заголовок раздела «Регистрация callback»Создавайте расписание после запуска event loop worker, например в его обработчике старта:
use localzet\Cron;
$job = new Cron('0 */5 * * * *', static function (): void { refreshCounters();}, 'refresh-counters');refreshCounters() — прикладная функция. Сам Cron не создаёт очередь, отдельный процесс или транзакцию. Callback выполняется в event loop; блокирующий запрос или длительный расчёт задерживает остальные события этого процесса.
Первое зарегистрированное задание ставит почти немедленный timer сканирования. Дальше сканирование планируется к следующей границе минуты. Поскольку сканер вызывает парсер с текущим временем, а не нормализованным началом минуты, первоначальный запуск посреди минуты может сдвинуть времена на текущую секунду. Для задач с жёсткой точностью и пропуском прошедших срабатываний эту семантику нужно учитывать отдельно.
Удаление и жизненный цикл
Заголовок раздела «Удаление и жизненный цикл»$id = $job->getId();$removed = Cron::remove($id);// Equivalent to $job->destroy().Удаление исключает задание из следующего сканирования. Уже поставленные для него одноразовые Timer callback не отменяются: их ID в объекте задания не сохраняются. remove() не является гарантированной отменой ближайшего запуска.
Каждый worker имеет независимое статическое расписание. Если одинаковый код старта выполняется в четырёх процессах, будут четыре запуска. Для единственного исполнителя регистрируйте Cron только в назначенном процессе либо используйте прикладную блокировку с понятной политикой истечения.
Необработанная ошибка проверки правила возникает при работе сканера, а не обязательно в конструкторе Cron. Проверяйте правило до регистрации. Ошибки callback обрабатываются нижележащим Timer/event loop, собственного механизма повторов Cron не добавляет.
Пример разбора
Заголовок раздела «Пример разбора»<?php
declare(strict_types=1);
use localzet\CronParser;
require __DIR__ . '/vendor/autoload.php';
date_default_timezone_set('UTC');$parser = new CronParser();$minute = strtotime('2026-10-17 12:00:00 UTC');$times = $parser->parse('*/15 * * * * *', $minute);
echo json_encode(array_map(static fn (int $time): string => gmdate('H:i:s', $time), $times), JSON_THROW_ON_ERROR), PHP_EOL;Методы, возвращаемые значения и ограничения
Исходный код · AGPL-3.0