#Introducción
Anteriormente, es posible que haya escrito una entrada de configuración cron para cada tarea que necesitaba programar en su servidor. Sin embargo, esto puede volverse rápidamente un problema porque su programación de tareas ya no está en el control de versiones y debe acceder por SSH a su servidor para ver las entradas cron existentes o agregar nuevas.
El programador de comandos de Laravel ofrece un enfoque renovado para gestionar tareas programadas en su servidor. El programador le permite definir de manera fluida y expresiva su programación de comandos dentro de su propia aplicación Laravel. Al usar el programador, solo se necesita una única entrada cron en su servidor. Su programación de tareas se define en el método schedule del archivo app/Console/Kernel.php. Para ayudarle a comenzar, se define un ejemplo simple dentro del método.
#Definiendo Horarios
Puede definir todas sus tareas programadas en el método schedule de la clase App\Console\Kernel de su aplicación. Para comenzar, veamos un ejemplo. En este ejemplo, programaremos una closure para que se ejecute todos los días a medianoche. Dentro de la closure ejecutaremos una consulta a la base de datos para limpiar una tabla:
<?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
{
/**
* Definir la programación de comandos de la aplicación.
*/
protected function schedule(Schedule $schedule): void
{
$schedule->call(function () {
DB::table('recent_users')->delete();
})->daily();
}
}
Además de programar usando closures, también puede programar objetos invocables. Los objetos invocables son clases PHP simples que contienen un método __invoke:
$schedule->call(new DeleteRecentUsers)->daily();
Si desea ver un resumen de sus tareas programadas y la próxima vez que están programadas para ejecutarse, puede usar el comando Artisan schedule:list:
php artisan schedule:list
#Programando Comandos Artisan
Además de programar closures, también puede programar comandos Artisan y comandos del sistema. Por ejemplo, puede usar el método command para programar un comando Artisan usando el nombre del comando o la clase.
Al programar comandos Artisan usando el nombre de la clase del comando, puede pasar un arreglo de argumentos adicionales de línea de comandos que deben proporcionarse al comando cuando se invoque:
use App\Console\Commands\SendEmailsCommand;
$schedule->command('emails:send Taylor --force')->daily();
$schedule->command(SendEmailsCommand::class, ['Taylor', '--force'])->daily();
#Programando Trabajos en Cola
El método job puede usarse para programar un trabajo en cola. Este método proporciona una forma conveniente de programar trabajos en cola sin usar el método call para definir closures que encolen el trabajo:
use App\Jobs\Heartbeat;
$schedule->job(new Heartbeat)->everyFiveMinutes();
Se pueden proporcionar opcionalmente segundo y tercer argumentos al método job que especifican el nombre de la cola y la conexión de la cola que se deben usar para encolar el trabajo:
use App\Jobs\Heartbeat;
// Enviar el trabajo a la cola "heartbeats" en la conexión "sqs"...
$schedule->job(new Heartbeat, 'heartbeats', 'sqs')->everyFiveMinutes();
#Programando Comandos Shell
El método exec puede usarse para emitir un comando al sistema operativo:
$schedule->exec('node /home/forge/script.js')->daily();
#Opciones de Frecuencia de Programación
Ya hemos visto algunos ejemplos de cómo puede configurar una tarea para que se ejecute en intervalos específicos. Sin embargo, hay muchas más frecuencias de programación que puede asignar a una tarea:
| Método | Descripción |
|---|---|
->cron('* * * * *'); |
Ejecutar la tarea en un horario cron personalizado |
->everySecond(); |
Ejecutar la tarea cada segundo |
->everyTwoSeconds(); |
Ejecutar la tarea cada dos segundos |
->everyFiveSeconds(); |
Ejecutar la tarea cada cinco segundos |
->everyTenSeconds(); |
Ejecutar la tarea cada diez segundos |
->everyFifteenSeconds(); |
Ejecutar la tarea cada quince segundos |
->everyTwentySeconds(); |
Ejecutar la tarea cada veinte segundos |
->everyThirtySeconds(); |
Ejecutar la tarea cada treinta segundos |
->everyMinute(); |
Ejecutar la tarea cada minuto |
->everyTwoMinutes(); |
Ejecutar la tarea cada dos minutos |
->everyThreeMinutes(); |
Ejecutar la tarea cada tres minutos |
->everyFourMinutes(); |
Ejecutar la tarea cada cuatro minutos |
->everyFiveMinutes(); |
Ejecutar la tarea cada cinco minutos |
->everyTenMinutes(); |
Ejecutar la tarea cada diez minutos |
->everyFifteenMinutes(); |
Ejecutar la tarea cada quince minutos |
->everyThirtyMinutes(); |
Ejecutar la tarea cada treinta minutos |
->hourly(); |
Ejecutar la tarea cada hora |
->hourlyAt(17); |
Ejecutar la tarea cada hora a los 17 minutos |
->everyOddHour($minutes = 0); |
Ejecutar la tarea cada hora impar |
->everyTwoHours($minutes = 0); |
Ejecutar la tarea cada dos horas |
->everyThreeHours($minutes = 0); |
Ejecutar la tarea cada tres horas |
->everyFourHours($minutes = 0); |
Ejecutar la tarea cada cuatro horas |
->everySixHours($minutes = 0); |
Ejecutar la tarea cada seis horas |
->daily(); |
Ejecutar la tarea cada día a medianoche |
->dailyAt('13:00'); |
Ejecutar la tarea cada día a las 13:00 |
->twiceDaily(1, 13); |
Ejecutar la tarea diariamente a la 1:00 y 13:00 |
->twiceDailyAt(1, 13, 15); |
Ejecutar la tarea diariamente a la 1:15 y 13:15 |
->weekly(); |
Ejecutar la tarea cada domingo a las 00:00 |
->weeklyOn(1, '8:00'); |
Ejecutar la tarea semanalmente los lunes a las 8:00 |
->monthly(); |
Ejecutar la tarea el primer día de cada mes a las 00:00 |
->monthlyOn(4, '15:00'); |
Ejecutar la tarea cada mes el día 4 a las 15:00 |
->twiceMonthly(1, 16, '13:00'); |
Ejecutar la tarea mensualmente el 1 y 16 a las 13:00 |
->lastDayOfMonth('15:00'); |
Ejecutar la tarea el último día del mes a las 15:00 |
->quarterly(); |
Ejecutar la tarea el primer día de cada trimestre a las 00:00 |
->quarterlyOn(4, '14:00'); |
Ejecutar la tarea cada trimestre el día 4 a las 14:00 |
->yearly(); |
Ejecutar la tarea el primer día de cada año a las 00:00 |
->yearlyOn(6, 1, '17:00'); |
Ejecutar la tarea cada año el 1 de junio a las 17:00 |
->timezone('America/New_York'); |
Establecer la zona horaria para la tarea |
Estos métodos pueden combinarse con restricciones adicionales para crear horarios aún más precisos que solo se ejecuten ciertos días de la semana. Por ejemplo, puede programar un comando para que se ejecute semanalmente los lunes:
// Ejecutar una vez por semana los lunes a la 1 PM...
$schedule->call(function () {
// ...
})->weekly()->mondays()->at('13:00');
// Ejecutar cada hora de 8 AM a 5 PM en días laborables...
$schedule->command('foo')
->weekdays()
->hourly()
->timezone('America/Chicago')
->between('8:00', '17:00');
A continuación se muestra una lista de restricciones adicionales para la programación:
| Método | Descripción |
|---|---|
->weekdays(); |
Limitar la tarea a días laborables |
->weekends(); |
Limitar la tarea a fines de semana |
->sundays(); |
Limitar la tarea a domingos |
->mondays(); |
Limitar la tarea a lunes |
->tuesdays(); |
Limitar la tarea a martes |
->wednesdays(); |
Limitar la tarea a miércoles |
->thursdays(); |
Limitar la tarea a jueves |
->fridays(); |
Limitar la tarea a viernes |
->saturdays(); |
Limitar la tarea a sábados |
->days(array|mixed); |
Limitar la tarea a días específicos |
->between($startTime, $endTime); |
Limitar la tarea para que se ejecute entre horas de inicio y fin |
->unlessBetween($startTime, $endTime); |
Limitar la tarea para que no se ejecute entre horas de inicio y fin |
->when(Closure); |
Limitar la tarea basada en una prueba de verdad |
->environments($env); |
Limitar la tarea a entornos específicos |
#Restricciones por Día
El método days puede usarse para limitar la ejecución de una tarea a días específicos de la semana. Por ejemplo, puede programar un comando para que se ejecute cada hora los domingos y miércoles:
$schedule->command('emails:send')
->hourly()
->days([0, 3]);
Alternativamente, puede usar las constantes disponibles en la clase Illuminate\Console\Scheduling\Schedule al definir los días en los que una tarea debe ejecutarse:
use Illuminate\Console\Scheduling\Schedule;
$schedule->command('emails:send')
->hourly()
->days([Schedule::SUNDAY, Schedule::WEDNESDAY]);
#Restricciones de Horario Entre
El método between puede usarse para limitar la ejecución de una tarea según la hora del día:
$schedule->command('emails:send')
->hourly()
->between('7:00', '22:00');
De manera similar, el método unlessBetween puede usarse para excluir la ejecución de una tarea durante un período de tiempo:
$schedule->command('emails:send')
->hourly()
->unlessBetween('23:00', '4:00');
#Restricciones Basadas en Pruebas de Verdad
El método when puede usarse para limitar la ejecución de una tarea basado en el resultado de una prueba de verdad dada. En otras palabras, si la closure dada devuelve true, la tarea se ejecutará siempre que ninguna otra condición restrictiva impida la ejecución:
$schedule->command('emails:send')->daily()->when(function () {
return true;
});
El método skip puede considerarse el inverso de when. Si el método skip devuelve true, la tarea programada no se ejecutará:
$schedule->command('emails:send')->daily()->skip(function () {
return true;
});
Al usar métodos when encadenados, el comando programado solo se ejecutará si todas las condiciones when devuelven true.
#Restricciones de Entorno
El método environments puede usarse para ejecutar tareas solo en los entornos dados (según lo definido por la variable de entorno APP_ENV environment variable):
$schedule->command('emails:send')
->daily()
->environments(['staging', 'production']);
#Zonas Horarias
Usando el método timezone, puede especificar que la hora de una tarea programada debe interpretarse dentro de una zona horaria dada:
$schedule->command('report:generate')
->timezone('America/New_York')
->at('2:00')
Si asigna repetidamente la misma zona horaria a todas sus tareas programadas, puede definir un método scheduleTimezone en su clase App\Console\Kernel. Este método debe devolver la zona horaria predeterminada que se asignará a todas las tareas programadas:
use DateTimeZone;
/**
* Obtener la zona horaria que debe usarse por defecto para eventos programados.
*/
protected function scheduleTimezone(): DateTimeZone|string|null
{
return 'America/Chicago';
}
Recuerde que algunas zonas horarias utilizan horario de verano. Cuando ocurren cambios de horario de verano, su tarea programada puede ejecutarse dos veces o incluso no ejecutarse. Por esta razón, recomendamos evitar la programación basada en zonas horarias cuando sea posible.
#Previniendo Solapamientos de Tareas
Por defecto, las tareas programadas se ejecutarán incluso si la instancia anterior de la tarea aún está en ejecución. Para evitar esto, puede usar el método withoutOverlapping:
$schedule->command('emails:send')->withoutOverlapping();
En este ejemplo, el comando Artisan emails:send se ejecutará cada minuto si no está ya en ejecución. El método withoutOverlapping es especialmente útil si tiene tareas que varían drásticamente en su tiempo de ejecución, impidiéndole predecir exactamente cuánto tiempo tomará una tarea dada.
Si es necesario, puede especificar cuántos minutos deben pasar antes de que expire el bloqueo "sin solapamientos". Por defecto, el bloqueo expirará después de 24 horas:
$schedule->command('emails:send')->withoutOverlapping(10);
En segundo plano, el método withoutOverlapping utiliza el cache de su aplicación para obtener bloqueos. Si es necesario, puede limpiar estos bloqueos de cache usando el comando Artisan schedule:clear-cache. Esto generalmente solo es necesario si una tarea queda atascada debido a un problema inesperado en el servidor.
#Ejecutando Tareas en Un Solo Servidor
Para utilizar esta función, su aplicación debe usar el driver de cache database, memcached, dynamodb o redis como driver de cache predeterminado. Además, todos los servidores deben comunicarse con el mismo servidor central de cache.
Si el programador de su aplicación se ejecuta en múltiples servidores, puede limitar un trabajo programado para que solo se ejecute en un único servidor. Por ejemplo, suponga que tiene una tarea programada que genera un nuevo informe cada viernes por la noche. Si el programador se ejecuta en tres servidores de trabajo, la tarea programada se ejecutará en los tres servidores y generará el informe tres veces. ¡No es bueno!
Para indicar que la tarea debe ejecutarse solo en un servidor, use el método onOneServer al definir la tarea programada. El primer servidor que obtenga la tarea asegurará un bloqueo atómico en el trabajo para evitar que otros servidores ejecuten la misma tarea al mismo tiempo:
$schedule->command('report:generate')
->fridays()
->at('17:00')
->onOneServer();
#Nombrando Trabajos de Un Solo Servidor
A veces puede necesitar programar el mismo trabajo para que se despache con diferentes parámetros, mientras aún indica a Laravel que ejecute cada permutación del trabajo en un solo servidor. Para lograr esto, puede asignar a cada definición de programación un nombre único mediante el método name:
$schedule->job(new CheckUptime('https://laravel.com'))
->name('check_uptime:laravel.com')
->everyFiveMinutes()
->onOneServer();
$schedule->job(new CheckUptime('https://vapor.laravel.com'))
->name('check_uptime:vapor.laravel.com')
->everyFiveMinutes()
->onOneServer();
De manera similar, las closures programadas deben asignarse un nombre si se pretende que se ejecuten en un solo servidor:
$schedule->call(fn () => User::resetApiRequestCount())
->name('reset-api-request-count')
->daily()
->onOneServer();
#Tareas en Segundo Plano
Por defecto, múltiples tareas programadas para la misma hora se ejecutarán secuencialmente según el orden en que están definidas en su método schedule. Si tiene tareas de larga duración, esto puede causar que las tareas posteriores comiencen mucho más tarde de lo esperado. Si desea ejecutar tareas en segundo plano para que todas puedan ejecutarse simultáneamente, puede usar el método runInBackground:
$schedule->command('analytics:report')
->daily()
->runInBackground();
El método runInBackground solo puede usarse al programar tareas mediante los métodos command y exec.
#Modo de Mantenimiento
Las tareas programadas de su aplicación no se ejecutarán cuando la aplicación esté en modo de mantenimiento, ya que no queremos que sus tareas interfieran con cualquier mantenimiento no terminado que pueda estar realizando en su servidor. Sin embargo, si desea forzar que una tarea se ejecute incluso en modo de mantenimiento, puede llamar al método evenInMaintenanceMode al definir la tarea:
$schedule->command('emails:send')->evenInMaintenanceMode();
#Ejecutando el Programador
Ahora que hemos aprendido cómo definir tareas programadas, hablemos de cómo ejecutarlas realmente en nuestro servidor. El comando Artisan schedule:run evaluará todas sus tareas programadas y determinará si necesitan ejecutarse según la hora actual del servidor.
Por lo tanto, al usar el programador de Laravel, solo necesitamos agregar una única entrada cron en nuestro servidor que ejecute el comando schedule:run cada minuto. Si no sabe cómo agregar entradas cron a su servidor, considere usar un servicio como Laravel Forge que puede gestionar las entradas cron por usted:
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
#Tareas Programadas Sub-Minuto
En la mayoría de los sistemas operativos, los trabajos cron están limitados a ejecutarse como máximo una vez por minuto. Sin embargo, el programador de Laravel le permite programar tareas para que se ejecuten en intervalos más frecuentes, incluso tan a menudo como una vez por segundo:
$schedule->call(function () {
DB::table('recent_users')->delete();
})->everySecond();
Cuando se definen tareas sub-minuto dentro de su aplicación, el comando schedule:run continuará ejecutándose hasta el final del minuto actual en lugar de salir inmediatamente. Esto permite que el comando invoque todas las tareas sub-minuto requeridas durante el minuto.
Dado que las tareas sub-minuto que tardan más de lo esperado en ejecutarse podrían retrasar la ejecución de tareas sub-minuto posteriores, se recomienda que todas las tareas sub-minuto despachen trabajos en cola o comandos en segundo plano para manejar el procesamiento real de la tarea:
use App\Jobs\DeleteRecentUsers;
$schedule->job(new DeleteRecentUsers)->everyTenSeconds();
$schedule->command('users:delete')->everyTenSeconds()->runInBackground();
#Interrumpiendo Tareas Sub-Minuto
Como el comando schedule:run se ejecuta durante todo el minuto de invocación cuando se definen tareas sub-minuto, a veces puede necesitar interrumpir el comando al desplegar su aplicación. De lo contrario, una instancia del comando schedule:run que ya está en ejecución continuaría usando el código previamente desplegado de su aplicación hasta que termine el minuto actual.
Para interrumpir invocaciones en progreso de schedule:run, puede agregar el comando schedule:interrupt al script de despliegue de su aplicación. Este comando debe invocarse después de que su aplicación haya terminado de desplegarse:
php artisan schedule:interrupt
#Ejecutando el Programador Localmente
Normalmente, no agregaría una entrada cron del programador a su máquina de desarrollo local. En su lugar, puede usar el comando Artisan schedule:work. Este comando se ejecutará en primer plano e invocará el programador cada minuto hasta que termine el comando:
php artisan schedule:work
#Salida de Tareas
El programador de Laravel proporciona varios métodos convenientes para trabajar con la salida generada por las tareas programadas. Primero, usando el método sendOutputTo, puede enviar la salida a un archivo para su inspección posterior:
$schedule->command('emails:send')
->daily()
->sendOutputTo($filePath);
Si desea agregar la salida a un archivo dado, puede usar el método appendOutputTo:
$schedule->command('emails:send')
->daily()
->appendOutputTo($filePath);
Usando el método emailOutputTo, puede enviar la salida por correo electrónico a una dirección de su elección. Antes de enviar por correo la salida de una tarea, debe configurar los servicios de correo de Laravel:
$schedule->command('report:generate')
->daily()
->sendOutputTo($filePath)
->emailOutputTo('taylor@example.com');
Si solo desea enviar por correo la salida si el comando Artisan o sistema programado termina con un código de salida distinto de cero, use el método emailOutputOnFailure:
$schedule->command('report:generate')
->daily()
->emailOutputOnFailure('taylor@example.com');
Los métodos emailOutputTo, emailOutputOnFailure, sendOutputTo y appendOutputTo son exclusivos para los métodos command y exec.
#Hooks de Tareas
Usando los métodos before y after, puede especificar código que se ejecutará antes y después de que se ejecute la tarea programada:
$schedule->command('emails:send')
->daily()
->before(function () {
// La tarea está a punto de ejecutarse...
})
->after(function () {
// La tarea se ha ejecutado...
});
Los métodos onSuccess y onFailure le permiten especificar código que se ejecutará si la tarea programada tiene éxito o falla. Un fallo indica que el comando Artisan o sistema programado terminó con un código de salida distinto de cero:
$schedule->command('emails:send')
->daily()
->onSuccess(function () {
// La tarea tuvo éxito...
})
->onFailure(function () {
// La tarea falló...
});
Si hay salida disponible de su comando, puede acceder a ella en sus hooks after, onSuccess o onFailure tipeando una instancia de Illuminate\Support\Stringable como el argumento $output de la definición de la closure del hook:
use Illuminate\Support\Stringable;
$schedule->command('emails:send')
->daily()
->onSuccess(function (Stringable $output) {
// La tarea tuvo éxito...
})
->onFailure(function (Stringable $output) {
// La tarea falló...
});
#Haciendo ping a URLs
Usando los métodos pingBefore y thenPing, el programador puede hacer ping automáticamente a una URL dada antes o después de que se ejecute una tarea. Este método es útil para notificar a un servicio externo, como Envoyer, que su tarea programada está comenzando o ha terminado su ejecución:
$schedule->command('emails:send')
->daily()
->pingBefore($url)
->thenPing($url);
Los métodos pingBeforeIf y thenPingIf pueden usarse para hacer ping a una URL dada solo si una condición dada es true:
$schedule->command('emails:send')
->daily()
->pingBeforeIf($condition, $url)
->thenPingIf($condition, $url);
Los métodos pingOnSuccess y pingOnFailure pueden usarse para hacer ping a una URL dada solo si la tarea tiene éxito o falla. Un fallo indica que el comando Artisan o sistema programado terminó con un código de salida distinto de cero:
$schedule->command('emails:send')
->daily()
->pingOnSuccess($successUrl)
->pingOnFailure($failureUrl);
Todos los métodos de ping requieren la biblioteca HTTP Guzzle. Guzzle generalmente se instala por defecto en todos los nuevos proyectos Laravel, pero puede instalar Guzzle manualmente en su proyecto usando el gestor de paquetes Composer si ha sido eliminado accidentalmente:
composer require guzzlehttp/guzzle
#Eventos
Si es necesario, puede escuchar eventos despachados por el programador. Normalmente, los mapeos de listeners de eventos se definen dentro de la clase App\Providers\EventServiceProvider de su aplicación:
/**
* Los mapeos de listeners de eventos para la aplicación.
*
* @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',
],
];