Статьи

Framework-Agnostic PHP Cronjobs стало проще с Crunz!

В этой статье мы познакомимся с относительно новой библиотекой планирования заданий под названием Crunz . Crunz — это независимая от фреймворка библиотека, вдохновленная планировщиком заданий Laravel , но улучшенная многими способами. Полное раскрытие: я являюсь автором и приветствую вклады и комментарии о том, как улучшить его дальше!

Часы фото

Прежде чем приступить к работе, вы должны иметь представление о cronjobs, поэтому, пожалуйста, прочитайте наше подробное руководство, если вы не знакомы с тем, как они работают.

Установка

Для его установки мы используем Composer как обычно:

composer require lavary/crunz 

Утилита командной строки с именем crunz будет символически связана с vendor/bin нашего проекта. Эта утилита командной строки предоставляет набор полезных команд, о которых мы вскоре поговорим.

Как это работает?

Вместо установки заданий cron в файл crontab мы определяем их в одном или нескольких файлах PHP с помощью интерфейса Crunz.

Вот основной пример:

 <?php // tasks/backupTasks.php use Crunz\Schedule; $schedule = new Schedule(); $schedule->run('cp project project-bk') ->daily(); return $schedule; 

Для запуска задач мы устанавливаем обычное задание cron (запись crontab), которое запускается каждую минуту и делегирует ответственность исполнителю событий Crunz:

 * * * * * /project/vendor/bin/crunz schedule:run 

schedule:run команды schedule:run отвечает за сбор всех файлов задач PHP и schedule:run задач, которые должны быть выполнены.

Файлы задач

Файлы задач напоминают файлы crontab. Как и файлы crontab, они могут содержать одну или несколько задач.

Чтобы создать файл задачи, нам нужно решить, где мы хотим их сохранить. Местоположение не имеет значения, пока мы сообщаем Крунцу о местоположении. Обычно мы создаем наши файлы tasks/ в tasks/ в корневом каталоге проекта. Однако мы можем хранить их вне каталога проекта, если для этого есть причина.

По умолчанию Crunz предполагает, что все файлы задач находятся в каталоге tasks/ в корневом каталоге проекта.

Существует два способа указать исходный каталог: файл конфигурации (подробнее об этом ниже) и в качестве параметра команды обработчика событий:

 * * * * * /project/vendor/bin/crunz schedule:run /path/to/tasks/directory 

Создание простой задачи

Давайте создадим нашу первую задачу.

 <?php // tasks/FirstTasks.php use Crunz\Schedule; $schedule = new Schedule(); $schedule->run('cp project project-bk') ->daily() ->description('Create a backup of the project directory.'); // ... // IMPORTANT: You must return the schedule object return $schedule; 

В предыдущем коде сначала мы создаем экземпляр класса Schedule . run() определяет команду для выполнения. daily() запускает команду ежедневно, а description() добавляет описание к задаче (описания полезны для идентификации задач в файлах журнала).

Есть несколько соглашений для создания файла задачи. Имя файла должно заканчиваться на Tasks.php если мы не изменим это через настройки конфигурации. Кроме того, мы должны возвращать экземпляр класса Schedule в конце каждого файла, в противном случае все задачи внутри файла будут пропущены обработчиком событий.

Поскольку Crunz рекурсивно сканирует каталог задач, мы можем поместить все задачи в один файл или в разные файлы (или каталоги) в зависимости от их использования. Такое поведение помогает нам иметь хорошо организованный каталог задач.

Команда

Мы можем запустить любую команду или закрытие PHP с помощью run() . Этот метод принимает два аргумента: команду или замыкание для выполнения и параметры команды (в виде ассоциативного массива), если первый аргумент является командой.

 <?php use Crunz\Schedule; $schedule = new Schedule(); $schedule->run('/usr/bin/php backup.php', ['--destination' => 'path/to/destination']) ->everyMinute() ->description('Copying the project directory'); return $schedule; 

В приведенном выше примере --destination — это опция, поддерживаемая backup.php .

Затворы

Кроме команд, также можно запланировать закрытие PHP, передав его методу run() .

 <?php use Crunz\Schedule; $schedule = new Schedule(); $x = 'Some data'; $schedule->run(function() use ($x) { echo 'Doing some cool stuff in here with ' . $x; }) ->everyMinute() ->description('Cool stuff'); return $schedule; 

Если есть ошибка PHP на любом уровне, она будет перехвачена, зарегистрирована и сообщена.

Смена каталогов

Также можно изменить текущий рабочий каталог перед запуском задачи, используя метод in() :

 <?php // ... $schedule->run('./deploy.sh') ->in('/home') ->daily(); // ... return $schedule; 

Частота выполнения

Существует множество способов указать, когда и как часто должна выполняться задача. Мы можем объединить эти методы, чтобы получить желаемые частоты.

Единицы времени

Частотные методы обычно заканчиваются ly , например, hourly() , daily() , weekly() , monthly() , quarterly() и yearly() .

 <?php // ... $schedule->run('/usr/bin/php backup.php') ->daily(); // ... 

Вышеуказанная задача будет выполняться ежедневно в полночь .

Вот еще один, который работает в первый день каждого месяца .

 <?php // ... $schedule->run('/usr/bin/php email.php') ->monthly(); // ... 

Все события, запланированные с помощью этого набора методов, происходят в начале этой единицы времени. Например, weekly() будет запускать событие по воскресеньям, а month monthly() будет запускать его в первый день каждого месяца.

Динамические Методы

Динамические методы дают нам множество вариантов частот на лету. Нам просто нужно следовать этой схеме:

 every[NumberInCamelCaseWords]Minute|Hour|Day|Months? 

Метод должен начинаться со слова « every , за которым следует число в словах из верблюжьего падежа, заканчивающееся одной из следующих единиц времени: минута, час, день и месяц .

Символ s в конце не обязателен и используется только для грамматики.

При этом допустимы следующие методы:

  • everyFiveMinutes()
  • everyMinute()
  • everyTwelveHours()
  • everyMonth()
  • everySixMonths()
  • everyFifteenDays()
  • everyFiveHundredThirtySevenMinutes()
  • everyThreeThousandAndFiveHundredFiftyNineMinutes()

Вот как мы используем это в файле задачи:

 <?php // ... $schedule->run('/usr/bin/php email.php') ->everyTenDays(); $schedule->run('/usr/bin/php some_other_stuff.php') ->everyThirteenMinutes(); // ... return $schedule; 

Проведение событий в определенное время

Чтобы запланировать одноразовые задачи, мы можем использовать метод on() следующим образом:

 <?php // ... $schedule->run('/usr/bin/php email.php') ->on('13:30 2016-03-01'); // ... 

Вышеуказанное задание будет выполнено первого марта 2016 года в 13:30.

on() принимает любой формат даты, проанализированный функцией PHP strtotime .

Чтобы указать только время, мы используем at() :

 <?php // ... $schedule->run('/usr/bin/php script.php') ->daily() ->at('13:30'); // ... 

Мы можем использовать dailyAt() чтобы получить тот же результат:

 <?php // ... $schedule->run('/usr/bin/php script.php') ->dailyAt('13:30'); // ... 

Если мы только передаем время на on() , это имеет тот же эффект, что и использование at()

 <?php // ... $schedule->run('/usr/bin/php email.php') ->mondays() ->on('13:30'); // is the sames as $schedule->run('/usr/bin/php email.php') ->mondays() ->at('13:30'); // ... 

Будние дни

Crunz также предоставляет набор методов, которые определяют определенный день недели. Эти методы были разработаны для использования в качестве ограничения и не должны использоваться отдельно. Причина в том, что методы дня недели просто изменяют поле Day of Week выражения задания cron.

Рассмотрим следующий пример:

 <?php // Cron equivalent: * * * * 1 $schedule->run('/usr/bin/php email.php') ->mondays(); 

На первый взгляд задача, кажется, запускается каждый понедельник , но, поскольку она изменяет только поле «день недели» в выражении задания cron, задача выполняется каждую минуту по понедельникам .

Это правильный способ использования методов дня недели:

 <?php // ... $schedule->run('/usr/bin/php email.php') ->everyThreeHours() ->mondays(); // ... 

В вышеупомянутой задаче мы используем mondays() как ограничение, чтобы запускать задачу каждые три часа по понедельникам .

Установка отдельных полей

Методы Крунца не ограничиваются описанными готовыми методами. Мы также можем установить отдельные поля для составления наших пользовательских частот. Эти методы либо принимают массив значений, либо перечисляют аргументы, разделенные запятыми:

 <?php // ... $schedule->run('/usr/bin/php email.php') ->minute(['1-30', 45, 55]) ->hour('1-5', 7, 8) ->dayOfMonth(12, 15) ->month(1); 

Или:

 <?php // ... $schedule->run('/usr/bin/php email.php') ->minute('30') ->hour('13') ->month([1,2]) ->dayofWeek('Mon', 'Fri', 'Sat'); // ... 

Классический Путь

Мы также можем выполнять планирование по-старому, как в файле crontab:

 <?php $schedule->run('/usr/bin/php email.php') ->cron('30 12 * 5-6,9 Mon,Fri'); 

Задача Время жизни

В записи crontab мы не можем легко указать время жизни задачи (период, в течение которого она активна). С Crunz это возможно:

 <?php // $schedule->run('/usr/bin/php email.php') ->everyFiveMinutes() ->from('12:30 2016-03-04') ->to('04:55 2016-03-10'); // 

В качестве альтернативы, мы можем использовать метод between() чтобы получить тот же результат:

 <?php // $schedule->run('/usr/bin/php email.php') ->everyFiveMinutes() ->between('12:30 2016-03-04', '04:55 2016-03-10'); // 

Если мы не укажем часть даты, задача будет активна каждый день, но только в течение указанной продолжительности:

 <?php // $schedule->run('/usr/bin/php email.php') ->everyFiveMinutes() ->between('12:30', '04:55'); // 

Вышеуказанная задача выполняется каждые пять минут с 12:30 до 16:55 каждый день.

Условия выполнения

Еще одна вещь, которую мы не можем сделать очень легко в традиционном файле crontab, это создать условия для выполнения задач. Введите методы when() и skip() .

Рассмотрим следующий код:

 <?php // $schedule->run('/usr/bin/php email.php') ->everyFiveMinutes() ->between('12:30 2016-03-04', '04:55 2016-03-10') ->when(function() { if ($some_condition_here) { return true; } }); // 

when() принимает обратный вызов, который должен возвращать значение TRUE для запуска задачи. Это может быть очень полезно, например, когда нам нужно проверить наши ресурсы перед выполнением ресурсоемкой задачи. Мы также можем пропустить задачу при определенных условиях, используя метод skip() . Если переданный обратный вызов возвращает TRUE , задача будет пропущена.

 <?php // $schedule->run('/usr/bin/php email.php') ->everyFiveMinutes() ->between('12:30 2016-03-04', '04:55 2016-03-10') ->skip(function() { if ($some_condition_here) { return true; } }); // 

Мы можем использовать эти методы несколько раз для одной задачи. Они оцениваются последовательно.

конфигурация

В формате YAML предусмотрено несколько параметров конфигурации. Настоятельно рекомендуется иметь собственную копию файла конфигурации, вместо того, чтобы изменять исходный файл.

Чтобы создать копию файла конфигурации, сначала нам нужно опубликовать файл конфигурации:

 crunz publish:config The configuration file was generated successfully 

В результате копия файла конфигурации будет создана в корневом каталоге нашего проекта.

Файл конфигурации выглядит так:

 # Crunz Configuration Settings # This option defines where the task files and # directories reside. # The path is relative to the project's root directory, # where the Crunz is installed (Trailing slashes will be ignored). source: tasks # The suffix is meant to target the task files inside the ":source" directory. # Please note if you change this value, you need # to make sure all the existing tasks files are renamed accordingly. suffix: Tasks.php # By default the errors are not logged by Crunz # You may set the value to true for logging the errors log_errors: false # This is the absolute path to the errors' log file # You need to make sure you have the required permission to write to this file though. errors_log_file: # By default the output is not logged as they are redirected to the # null output. # Set this to true if you want to keep the outputs log_output: false # This is the absolute path to the global output log file # The events which have dedicated log files (defined with them), won't be # logged to this file though. output_log_file: # This option determines whether the output should be emailed or not. email_output: false # This option determines whether the error messages should be emailed or not. email_errors: false # Global Swift Mailer settings # mailer: # Possible values: smtp, mail, and sendmail transport: smtp recipients: sender_name: sender_email: # SMTP settings # smtp: host: port: username: password: encryption: 

Каждый раз, когда мы запускаем команды Crunz, он просматривает корневой каталог проекта, чтобы увидеть, есть ли измененный пользователем файл конфигурации. Если файл конфигурации не существует, он будет использовать файл по умолчанию.

Параллелизм и механизм блокировки

Crunz запускает запланированные события параллельно (в отдельных процессах), поэтому все события с одинаковой частотой выполнения будут выполняться в одно и то же время асинхронно. Для этого Crunz использует symfony / Process для выполнения задач в подпроцессах.

Если выполнение задачи продолжается до следующего интервала или даже после этого, мы говорим, что одни и те же экземпляры задачи перекрываются. В некоторых случаях это не является проблемой, но это время, когда эти задачи изменяют данные или файлы базы данных, что может вызвать непредвиденное поведение или даже потерю данных.

Чтобы предотвратить наложение критических задач, Crunz предоставляет механизм блокировки с помощью preventOverlapping() который гарантирует, что ни одна задача не будет запущена, если предыдущий экземпляр уже запущен. За кулисами Crunz создает специальный файл для каждой задачи, в котором хранится идентификатор процесса последнего запущенного экземпляра. Каждый раз, когда задача собирается выполняться, она считывает идентификатор процесса соответствующей задачи и проверяет, все ли еще выполняется. Если это не так, пришло время запустить новый экземпляр.

 <?php // $schedule->run('/usr/bin/php email.php') ->everyFiveMinutes() ->preventOverlapping(); // 

Сохраняя вывод

Задания Cron обычно имеют выходные данные, которые обычно отправляются по электронной почте владельцу файла crontab или пользователю или пользователям, MAILTO переменной среды MAILTO в файле crontab.

Мы также можем перенаправить стандартный вывод в физический файл, используя оператор >> :

 * * * * * /command/to/run >> /var/log/crons/cron.log 

Это было автоматизировано в Crunz. Чтобы автоматически отправлять вывод каждого события в файл журнала, мы можем установить log_output и output_log_file в файле конфигурации следующим образом:

 # Configuration settings ## ... log_output: true output_log_file: /var/log/crunz.log ## ... 

Это отправит вывод события (если оно выполнено успешно) в /var/log/crunz.log . Однако нам нужно убедиться, что нам разрешено писать в соответствующий каталог.

Если нам нужно регистрировать выходные данные для каждого события, мы можем использовать appendOutputTo() или sendOutputTo() например:

 <?php // $schedule->run('/usr/bin/php email.php') ->everyFiveMinutes() ->appendOutputTo('/var/log/crunz/emails.log'); // 

Метод appendOutputTo() добавляет вывод в указанный файл. Чтобы переопределить файл журнала новыми данными после каждого запуска, мы используем saveOutputTo() .

Также можно отправить вывод в виде электронных писем группе получателей, установив настройки email_output и email_output в файле конфигурации.

Обработка ошибок

Обработка ошибок довольно проста с Crunz. Мы можем настроить Crunz для регистрации ошибки или уведомить нас по электронной почте. Кроме того, мы можем определить несколько обратных вызовов, которые будут вызываться в случае ошибки.

Чтобы регистрировать возможные ошибки во время каждого запуска, мы можем использовать настройки log_error и error_log_file в файле конфигурации, как log_error ниже:

 # Configuration settings # ... log_errors: true errors_log_file: /var/log/error.log # ... 

В результате, если по какой-либо причине выполнение события не удалось, сообщение об ошибке добавляется в указанный файл журнала ошибок. Каждая запись содержит полезную информацию, включая время, когда это произошло, описание события, выполненную команду, вызвавшую ошибку, и само сообщение об ошибке.

Так же, как выходные данные события, также можно отправить ошибки по электронной почте группе получателей, установив настройки email_error и email_error в файле конфигурации.

Обратные вызовы ошибок

Мы можем установить столько колбэков, сколько необходимо для запуска в случае ошибки. Они будут вызываться последовательно:

 <?php use Crunz\Schedule; $schedule = new Schedule(); $schedule->add('command/to/run') ->everyFiveMinutes(); $schedule ->onError(function(){ // Send mail }) ->onError(function(){ // Do something else }); return $schedule; 

Крюки до и после обработки

Есть моменты, когда мы хотим сделать некоторые операции до и после события. Это возможно путем присоединения обратных вызовов до и после обработки к соответствующему объекту события или расписания с использованием методов before() и after() , передавая им функцию обратного вызова.

У нас могут быть обратные вызовы до и после обработки как на уровне событий, так и на уровне расписания. Обратные вызовы предварительной обработки уровня расписания будут вызваны до того, как начнутся какие-либо события (в объекте расписания). Обратные вызовы уровня обработки на уровне расписания будут вызваны после завершения всех событий — успешно или с проблемами.

 <?php // ... $schedule = new Schedule(); $schedule->run('/usr/bin/php email.php') ->everyFiveMinutes() ->before(function() { // Do something before the task runs }) ->before(function() { // Do something else }) ->after(function() { // After the task is run }); $schedule ->before(function () { // Do something before all events }) ->after(function () { // Do something after all events are finished } ->before(function () { // Do something before all events }); // ... 

Мы можем использовать эти методы столько раз, сколько нам нужно, объединяя их в цепочку. Основанные на событии обратные вызовы после выполнения вызываются, только если выполнение события было успешным.

Другие полезные команды

Мы уже использовали несколько команд crunz таких как schedule:run и publish:config .

Чтобы увидеть все допустимые параметры и аргументы crunz , мы можем запустить следующую команду:

 vendor/bin/crunz --help 

Листинг Задачи

Одной из этих команд является crunz schedule:list , в котором перечислены определенные задачи (в собранных файлах *.Tasks.php ) в табличном формате.

 vendor/bin/crunz schedule:list +---+---------------+-------------+--------------------+ | # | Task | Expression | Command to Run | +---+---------------+-------------+--------------------+ | 1 | Sample Task | * * * * 1 * | command/to/execute | +---+---------------+-------------+--------------------+ 

Генерация задач

Также есть полезная команда make:task , которая генерирует скелет файла задачи со всеми значениями по умолчанию, поэтому вам не нужно писать их с нуля.

Например, чтобы создать задачу, которая по понедельникам запускает /var/www/script.php каждый час, мы запускаем следующую команду:

 vendor/bin/crunz make:task exampleOne --run scripts.php --in /var/www --frequency everyHour --constraint mondays Where do you want to save the file? (Press enter for the current directory) 

В результате событие определяется в exampleOneTasks.php в exampleOneTasks.php заданных задач.

Чтобы увидеть, было ли событие успешно создано, мы можем перечислить события:

 crunz schedule:list +---+------------------+-------------+----------------+ | # | Task | Expression | Command to Run | +---+------------------+-------------+----------------+ | 1 | Task description | 0 * * * 1 * | scripts.php | +---+------------------+-------------+----------------+ 

Чтобы увидеть все параметры make:task со всеми настройками по умолчанию, мы запускаем его с help :

 vendor/bin/crunz make:task --help 

Использование Crunz с веб-интерфейсом

Создать веб-интерфейс для Crunz довольно просто. Нам просто нужно поместить свойства событий в таблицу базы данных. Затем мы можем получить все записи через API. Наконец, мы перебираем извлеченные записи, создавая объекты событий. Независимо от того, было ли событие создано динамически или статически, Crunz будет запускать его, пока оно находится в допустимом файле задач.

Если вам нужно попробовать что-то готовое и расширить его в соответствии с вашими требованиями, вы можете использовать этот веб-интерфейс, созданный с помощью Laravel: lavary / crunz-ui . Вы можете прочитать инструкции по установке в файле README.md пакета. Чтобы позволить пользователям реализовать собственный механизм аутентификации, этот веб-интерфейс не предоставляет никакой системы аутентификации из коробки.

Вывод

Имея наши задачи в коде, мы можем поставить их под контроль версий, чтобы другие разработчики могли добавлять или изменять элементы без доступа к серверу.

Таким образом, мы можем контролировать задачи любым удобным для нас способом, и, чтобы окончательно остановить их все, мы просто удаляем задание master cron.

Если у вас есть вопросы или комментарии, пожалуйста, оставьте их ниже, и мы сделаем все возможное, чтобы ответить своевременно — и не стесняйтесь, чтобы высказать некоторые предложения и мнения о самом пакете!