git c24b35e43f354f8a20311fb7b7f75fa3b77cdc17
- Введение
- Определение расписаний
- Запуск планировщика
- Результат выполнения задачи
- Хуки выполнения задачи
- События
В прошлом вы могли создавать запись конфигурации cron для каждой задачи, которую нужно было запланировать на своем сервере. Однако это может быстро стать проблемой, потому что ваше расписание задач не находится в системе управления версиями и вы должны подключаться по SSH для просмотра существующих записей cron или добавления дополнительных записей.
Планировщик команд Laravel предлагает новый подход к управлению запланированными задачами на вашем сервере. Планировщик позволяет вам быстро и выразительно определять расписание команд в самом приложении Laravel. При использовании планировщика на вашем сервере требуется только одна запись cron. Расписание задач определяется в методе schedule
файла app/Console/Kernel.php
. Для начала работы в методе определен простой пример.
Вы можете определить все свои запланированные задачи в методе schedule
класса App\Console\Kernel
вашего приложения. Для начала рассмотрим пример. В этом примере мы определим замыкание, которое будет вызываться каждый день в полночь. В замыкании мы выполним запрос к базе данных для очистки таблицы:
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use Illuminate\Support\Facades\DB;
class Kernel extends ConsoleKernel
{
/**
* Определить расписание выполнения команд приложения.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
{
$schedule->call(function () {
DB::table('recent_users')->delete();
})->daily();
}
}
В дополнение к планированию с использованием замыканий вы также можете использовать вызываемые объекты. Вызываемые объекты – это простые классы PHP, содержащие метод __invoke
:
$schedule->call(new DeleteRecentUsers)->daily();
Если вы хотите просмотреть список ваших запланированных задач и их последующего запуска, то вы можете использовать команду schedule:list
Artisan:
php artisan schedule:list
В дополнение к планированию с использованием замыканий вы также можете использовать команды Artisan и системные команды. Например, вы можете использовать метод command
для планирования команды Artisan, используя имя команды или класс.
При планировании команд Artisan с использованием имени класса команды вы можете передать массив дополнительных аргументов командной строки, которые должны быть переданы команде при ее вызове:
use App\Console\Commands\SendEmailsCommand;
$schedule->command('emails:send Taylor --force')->daily();
$schedule->command(SendEmailsCommand::class, ['Taylor', '--force'])->daily();
Метод job
используется для планирования отправки задания в очередь. Этот метод обеспечивает удобный способ планирования таких заданий без использования метода call
с замыканием:
use App\Jobs\Heartbeat;
$schedule->job(new Heartbeat)->everyFiveMinutes();
Необязательные второй и третий аргументы могут быть переданы методу job
для указания имени очереди и соединения очереди, которые должны использоваться для постановки задания в очередь:
use App\Jobs\Heartbeat;
// Отправляем задание в очередь «heartbeats» соединения «sqs» ...
$schedule->job(new Heartbeat, 'heartbeats', 'sqs')->everyFiveMinutes();
Метод exec
используется для передачи команды операционной системе:
$schedule->exec('node /home/forge/script.js')->daily();
Мы уже видели несколько примеров того, как можно настроить задачу на выполнение через определенные промежутки времени. Однако существует гораздо больше параметров планирования, которые можно назначить задаче:
Метод | Описание |
---|---|
->cron('* * * * *'); |
Запустить задачу по расписанию с параметрами cron |
->everyMinute(); |
Запускать задачу ежеминутно |
->everyTwoMinutes(); |
– каждые 2 минуты |
->everyThreeMinutes(); |
– каждые 3 минуты |
->everyFourMinutes(); |
– каждые 4 минуты |
->everyFiveMinutes(); |
– каждые 5 минут |
->everyTenMinutes(); |
– каждые 10 минут |
->everyFifteenMinutes(); |
– каждые 15 минут |
->everyThirtyMinutes(); |
– каждые 30 минут |
->hourly(); |
– каждый час |
->hourlyAt(17); |
– в 17 минут каждого часа |
->everyTwoHours(); |
– каждые 2 часа |
->everyThreeHours(); |
– каждые 3 часа |
->everyFourHours(); |
– каждые 4 часа |
->everySixHours(); |
– каждые 6 часов |
->daily(); |
– каждый день в полночь |
->dailyAt('13:00'); |
– ежедневно в 13:00 |
->twiceDaily(1, 13); |
– ежедневно дважды в день: в 1:00 и 13:00 |
->weekly(); |
– еженедельно в воскресенье в 00:00 |
->weeklyOn(1, '8:00'); |
– еженедельно в понедельник в 8:00 |
->monthly(); |
– ежемесячно первого числа в 00:00 |
->monthlyOn(4, '15:00'); |
– ежемесячно 4 числа в 15:00 |
->twiceMonthly(1, 16, '13:00'); |
– ежемесячно дважды в месяц: 1 и 16 числа в 13:00 |
->lastDayOfMonth('15:00'); |
– ежемесячно в последний день месяца в 15:00 |
->quarterly(); |
– ежеквартально в первый день в 00:00 |
->yearly(); |
– ежегодно в первый день в 00:00 |
->yearlyOn(6, 1, '17:00'); |
– ежегодно в июне первого числа в 17:00 |
->timezone('America/New_York'); |
Установить часовой пояс для задачи |
Эти методы можно комбинировать с дополнительными ограничениями для создания еще более точных расписаний, которые выполняются только в определенные дни недели. Например, вы можете запланировать выполнение команды еженедельно в понедельник:
// Запускаем раз в неделю в понедельник в 13:00 ...
$schedule->call(function () {
//
})->weekly()->mondays()->at('13:00');
// Запускаем по будням ежечасно с 8 утра до 5 вечера ...
$schedule->command('foo')
->weekdays()
->hourly()
->timezone('America/Chicago')
->between('8:00', '17:00');
Список дополнительных ограничений расписания можно найти ниже:
Метод | Описание |
---|---|
->weekdays(); |
Ограничить выполнение задачи рабочими днями |
->weekends(); |
– выходными днями |
->sundays(); |
– воскресным днем |
->mondays(); |
– понедельником |
->tuesdays(); |
– вторником |
->wednesdays(); |
– средой |
->thursdays(); |
– четвергом |
->fridays(); |
– пятницей |
->saturdays(); |
– субботой |
->days(array|mixed); |
– определенными днями |
->between($startTime, $endTime); |
– временными интервалами начала и окончания |
->unlessBetween($startTime, $endTime); |
– через исключение временных интервалов начала и окончания |
->when(Closure); |
– на основе истинности результата выполненного замыкания |
->environments($env); |
– окружением выполнения |
Метод days
можно использовать для ограничения выполнения задачи определенными днями недели. Например, вы можете запланировать выполнение команды ежечасно по воскресеньям и средам:
$schedule->command('emails:send')
->hourly()
->days([0, 3]);
В качестве альтернативы вы можете использовать константы, доступные в классе Illuminate\Console\Scheduling\Schedule
, при указании дней, в которые должна выполняться задача:
use Illuminate\Console\Scheduling\Schedule;
$schedule->command('emails:send')
->hourly()
->days([Schedule::SUNDAY, Schedule::WEDNESDAY]);
Метод between
может использоваться для ограничения выполнения задачи в зависимости от времени суток:
$schedule->command('emails:send')
->hourly()
->between('7:00', '22:00');
Точно так же метод unlessBetween
может использоваться для исключения определенных периодов времени выполнения задачи:
$schedule->command('emails:send')
->hourly()
->unlessBetween('23:00', '4:00');
Метод when
может использоваться для ограничения выполнения задачи на основе истинности результата выполненного замыкания. Другими словами, если переданное замыкание возвращает true
, то задача будет выполняться до тех пор, пока никакие другие ограничивающие условия не препятствуют ее запуску:
$schedule->command('emails:send')->daily()->when(function () {
return true;
});
Метод skip
можно рассматривать как противоположный методу when
. Если метод skip
возвращает true
, то запланированная задача не будет выполнена:
$schedule->command('emails:send')->daily()->skip(function () {
return true;
});
При использовании цепочки методов when
, запланированная команда будет выполняться только в том случае, если все условия when
возвращают значение true
.
Метод environment
может использоваться для выполнения задач только в указанных окружениях, согласно определению переменной APP_ENV
окружения:
$schedule->command('emails:send')
->daily()
->environments(['staging', 'production']);
Используя метод timezone
, вы можете указать, что время запланированной задачи должно интерпретироваться в рамках переданного часового пояса:
$schedule->command('report:generate')
->timezone('America/New_York')
->at('2:00')
Если вы постоянно назначаете один и тот же часовой пояс для всех запланированных задач, то вы можете определить метод scheduleTimezone
в своем классе App\Console\Kernel
. Этот метод должен возвращать часовой пояс, назначаемый по умолчанию для всех запланированных задач:
/**
* Получить часовой пояс, который должен использоваться по умолчанию для запланированных событий.
*
* @return \DateTimeZone|string|null
*/
protected function scheduleTimezone()
{
return 'America/Chicago';
}
{note} Помните, что в некоторых часовых поясах используется летнее время. Когда происходит переход на летнее время, ваша запланированная задача может запускаться дважды или даже не запускаться вообще. По этой причине мы рекомендуем по возможности избегать указаний часовых поясов при планировании.
По умолчанию запланированные задачи будут выполняться, даже если предыдущий экземпляр задачи все еще выполняется. Чтобы предотвратить это, вы можете использовать метод withoutOverlapping
:
$schedule->command('emails:send')->withoutOverlapping();
В этом примере команда emails:send
Artisan будет запускаться каждую минуту при условии, что она еще не запущена. Метод withoutOverlapping
особенно полезен, если у вас есть задачи, которые разнятся по времени выполнения, что не позволяет вам точно предсказать, сколько времени займет текущая задача.
При необходимости вы можете указать, сколько минут должно пройти до окончания блокировки «перекрывающихся» задач. По умолчанию срок блокировки истекает через 24 часа:
$schedule->command('emails:send')->withoutOverlapping(10);
{note} Чтобы использовать этот функционал, ваше приложение должно использовать по умолчанию один из следующих драйверов кеша:
database
,memcached
,dynamodb
, илиredis
. Кроме того, все серверы должны взаимодействовать с одним и тем же центральным сервером кеширования.
Если планировщик вашего приложения работает на нескольких серверах, то вы можете ограничить выполнение запланированного задания только на одном сервере. Например, предположим, что у вас есть запланированная задача, по которой каждую пятницу вечером создается новый отчет. Если планировщик задач работает на трех рабочих серверах, запланированная задача будет запущена на всех трех серверах и трижды сгенерирует отчет. Не очень хорошо!
Чтобы указать, что задача должна выполняться только на одном сервере, используйте метод onOneServer
при определении запланированной задачи. Первый сервер, который получит задачу, обеспечит атомарную блокировку задания, чтобы другие серверы не могли одновременно выполнять ту же задачу:
$schedule->command('report:generate')
->fridays()
->at('17:00')
->onOneServer();
По умолчанию, несколько задач, запланированных одновременно, будут выполняться последовательно в соответствии с порядком, которым они определены в вашем методе schedule
. Если у вас есть длительные задачи, это может привести к тому, что последующие задачи начнутся намного позже, чем ожидалось. Если вы хотите запускать задачи в фоновом режиме в соответствии с планом, то вы можете использовать метод runInBackground
:
$schedule->command('analytics:report')
->daily()
->runInBackground();
{note} Метод
runInBackground
может использоваться только при планировании задач с помощью методовcommand
иexec
.
Запланированные задачи вашего приложения не будут выполняться, когда приложение находится в режиме обслуживания, поскольку мы не хотим, чтобы ваши задачи мешали любому незавершенному процессу обслуживания, выполняющемуся на вашем сервере. Однако, если вы хотите принудительно запустить задачу даже в режиме обслуживания, то используйте метод evenInMaintenanceMode
при определении задачи:
$schedule->command('emails:send')->evenInMaintenanceMode();
Теперь, когда мы узнали, как определять планирование задачи, давайте обсудим, как же запускать их на нашем сервере. Команда schedule:run
Artisan проанализирует все ваши запланированные задачи и определит, нужно ли их запускать, исходя из текущего времени сервера.
Итак, при использовании планировщика Laravel нам нужно добавить только одну конфигурационную запись cron на наш сервер, которая запускает команду schedule:run
каждую минуту. Если вы не знаете, как добавить записи cron на свой сервер, то рассмотрите возможность использования такой службы, как Laravel Forge, которая может управлять записями cron за вас:
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
Как правило, на локальной машине нет необходимости в добавлении записи cron планировщика. Вместо этого вы можете использовать команду schedule:work
Artisan. Эта команда будет работать на переднем плане и вызывать планировщик каждую минуту, пока вы не завершите команду:
php artisan schedule:work
Планировщик Laravel предлагает несколько удобных методов для работы с выводом результатов, созданных запланированными задачами. Во-первых, используя метод sendOutputTo
, вы можете отправить результат в файл для последующей просмотра:
$schedule->command('emails:send')
->daily()
->sendOutputTo($filePath);
Если вы хотите добавить результат в указанный файл, то используйте метод appendOutputTo
:
$schedule->command('emails:send')
->daily()
->appendOutputTo($filePath);
Используя метод emailOutputTo
, вы можете отправить результат по электронной почте на любой адрес. Перед отправкой результатов выполнения задачи по электронной почте вам следует настроить почтовые службы Laravel:
$schedule->command('report:generate')
->daily()
->sendOutputTo($filePath)
->emailOutputTo('[email protected]');
Если вы хотите отправить результат по электронной почте только в том случае, если запланированная (Artisan или системная) команда завершается ненулевым кодом возврата, используйте метод emailOutputOnFailure
:
$schedule->command('report:generate')
->daily()
->emailOutputOnFailure('[email protected]');
{note} Методы
emailOutputTo
,emailOutputOnFailure
,sendOutputTo
, andappendOutputTo
могут использоваться только при планировании задач с помощью методовcommand
иexec
.
Используя методы before
и after
, вы можете указать замыкания, которые будут выполняться до и после выполнения запланированной задачи:
$schedule->command('emails:send')
->daily()
->before(function () {
// Задача готова к выполнению ...
})
->after(function () {
// Задача выполнена ...
});
Методы onSuccess
и onFailure
позволяют указать замыкания, которые будут выполняться в случае успешного или неудачного выполнения запланированной задачи. Ошибка означает, что запланированная (Artisan или системная) команда завершилась ненулевым кодом возврата:
$schedule->command('emails:send')
->daily()
->onSuccess(function () {
// Задача успешно выполнена ...
})
->onFailure(function () {
// Не удалось выполнить задачу ...
});
Если из вашей команды доступен вывод результата, то вы можете получить к нему доступ в ваших хуках after
, onSuccess
или onFailure
, указав тип экземпляра Illuminate\Support\Stringable
в качестве аргумента $output
замыкания при определении вашего хука:
use Illuminate\Support\Stringable;
$schedule->command('emails:send')
->daily()
->onSuccess(function (Stringable $output) {
// Задача успешно выполнена ...
})
->onFailure(function (Stringable $output) {
// Не удалось выполнить задачу ...
});
Используя методы pingBefore
и thenPing
, планировщик может автоматически пинговать по-указанному URL до или после выполнения задачи. Этот метод полезен для уведомления внешней службы, такой как Envoyer, о том, что ваша запланированная задача запущена или завершена:
$schedule->command('emails:send')
->daily()
->pingBefore($url)
->thenPing($url);
Методы pingBeforeIf
и thenPingIf
могут использоваться для пингования по указанному URL, только если переданное условие $condition
истинно:
$schedule->command('emails:send')
->daily()
->pingBeforeIf($condition, $url)
->thenPingIf($condition, $url);
Методы pingOnSuccess
и pingOnFailure
могут использоваться для пингования по-указанному URL только в случае успешного или неудачного выполнения задачи. Ошибка означает, что запланированная (Artisan или системная) команда завершилась ненулевым кодом возврата:
$schedule->command('emails:send')
->daily()
->pingOnSuccess($successUrl)
->pingOnFailure($failureUrl);
Для всех методов пингования требуется библиотека Guzzle HTTP. Guzzle обычно устанавливается во всех новых проектах Laravel по умолчанию, но вы можете вручную установить Guzzle в свой проект с помощью менеджера пакетов Composer, если он был удален:
composer require guzzlehttp/guzzle
При необходимости вы можете прослушивать события отправленные планировщиком. Как правило, сопоставления слушателей событий определяются в классе вашего приложения App\Providers\EventServiceProvider
:
/**
* The event listener mappings for the application.
*
* @var array
*/
protected $listen = [
'Illuminate\Console\Events\ScheduledTaskStarting' => [
'App\Listeners\LogScheduledTaskStarting',
],
'Illuminate\Console\Events\ScheduledTaskFinished' => [
'App\Listeners\LogScheduledTaskFinished',
],
'Illuminate\Console\Events\ScheduledBackgroundTaskFinished' => [
'App\Listeners\LogScheduledBackgroundTaskFinished',
],
'Illuminate\Console\Events\ScheduledTaskSkipped' => [
'App\Listeners\LogScheduledTaskSkipped',
],
'Illuminate\Console\Events\ScheduledTaskFailed' => [
'App\Listeners\LogScheduledTaskFailed',
],
];