Scheduling di Task

Introduzione

In passato, potresti aver scritto una voce di configurazione cron per ogni task che dovevi programmare sul tuo server. Tuttavia, questo può rapidamente diventare un problema perché il tuo piano delle attività non è più sotto controllo del codice sorgente e devi effettuare l’SSH sul tuo server per visualizzare le voci cron esistenti o aggiungere voci aggiuntive.

Il command scheduler di Laravel offre un nuovo approccio per gestire i task programmati sul tuo server. Lo scheduler ti permette di definire in modo fluido ed espressivo il programma dei tuoi comandi direttamente nell’applicazione Laravel. Quando utilizzi lo scheduler, è necessaria solo una singola voce cron sul tuo server. Il programma delle tue attività è generalmente definito nel file routes/console.php della tua applicazione.

Definire le Attività

Puoi definire tutte le tue attività programmate nel file routes/console.php della tua applicazione. Per iniziare, vediamo un esempio. In questo caso, programmeremo una closure che verrà eseguita ogni giorno a mezzanotte. All’interno della closure eseguiremo una query al database per svuotare una tabella:

<?php

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schedule;

Schedule::call(function () {
    DB::table('recent_users')->delete();
})->daily();

Oltre a programmare con le closure, puoi anche programmare oggetti invocabili. Gli oggetti invocabili sono semplici classi PHP che contengono un metodo __invoke:

Schedule::call(new DeleteRecentUsers)->daily();

Se preferisci utilizzare il file routes/console.php solo per le definizioni dei comandi, puoi usare il metodo withSchedule nel file bootstrap/app.php della tua applicazione per definire le tue attività programmate. Questo metodo accetta una closure che riceve un’istanza dello scheduler:

use Illuminate\Console\Scheduling\Schedule;

->withSchedule(function (Schedule $schedule) {
    $schedule->call(new DeleteRecentUsers)->daily();
})

Se vuoi vedere una panoramica delle tue attività programmate e il prossimo orario di esecuzione, puoi usare il comando Artisan schedule:list:

php artisan schedule:list

Pianificazione dei Comandi Artisan

Oltre a pianificare chiusure, puoi anche programmare comandi Artisan e comandi di sistema. Ad esempio, puoi utilizzare il metodo command per pianificare un comando Artisan usando il nome o la classe del comando.

Quando pianifichi i comandi Artisan usando il nome della classe del comando, puoi passare un array di argomenti aggiuntivi della riga di comando che devono essere forniti al comando quando viene eseguito:

    use App\Console\Commands\SendEmailsCommand;
    use Illuminate\Support\Facades\Schedule;
    
    Schedule::command('emails:send Taylor --force')->daily();
    
    Schedule::command(SendEmailsCommand::class, ['Taylor', '--force'])->daily();

Programmazione dei comandi Artisan Closure

Se vuoi programmare un comando Artisan definito tramite una closure, puoi concatenare i metodi relativi alla programmazione dopo la definizione del comando:

Artisan::command('delete:recent-users', function () {
    DB::table('recent_users')->delete();
})->purpose('Elimina utenti recenti')->daily();

Se hai bisogno di passare argomenti al comando closure, puoi fornirli al metodo schedule:

Artisan::command('emails:send {user} {--force}', function ($user) {
    // ...
})->purpose('Invia email all\'utente specificato')->schedule(['Taylor', '--force'])->daily();

Pianificare Job in Coda

Il metodo job può essere usato per pianificare un job in coda. Questo metodo offre un modo comodo per pianificare job in coda senza usare il metodo call per definire closure che mettono in coda il job:

use App\Jobs\Heartbeat;
use Illuminate\Support\Facades\Schedule;

Schedule::job(new Heartbeat)->everyFiveMinutes();

È possibile fornire un secondo e terzo argomento opzionale al metodo job che specificano il nome della coda e la connessione della coda da usare per mettere in coda il job:

use App\Jobs\Heartbeat;
use Illuminate\Support\Facades\Schedule;

// Invia il job alla coda "heartbeats" sulla connessione "sqs"...
Schedule::job(new Heartbeat, 'heartbeats', 'sqs')->everyFiveMinutes();

Pianificare comandi Shell

Il metodo exec può essere usato per eseguire un comando nel sistema operativo:

    use Illuminate\Support\Facades\Schedule;

    Schedule::exec('node /home/forge/script.js')->daily();

Opzioni di Frequenza per la Pianificazione

Abbiamo già visto alcuni esempi di come configurare un’attività per essere eseguita a intervalli specifici. Tuttavia, ci sono molte altre frequenze di pianificazione che puoi assegnare a un’attività:

Metodo Descrizione
->cron('* * * * *'); Esegui l’attività secondo una pianificazione cron personalizzata.
->everySecond(); Esegui l’attività ogni secondo.
->everyTwoSeconds(); Esegui l’attività ogni due secondi.
->everyFiveSeconds(); Esegui l’attività ogni cinque secondi.
->everyTenSeconds(); Esegui l’attività ogni dieci secondi.
->everyFifteenSeconds(); Esegui l’attività ogni quindici secondi.
->everyTwentySeconds(); Esegui l’attività ogni venti secondi.
->everyThirtySeconds(); Esegui l’attività ogni trenta secondi.
->everyMinute(); Esegui l’attività ogni minuto.
->everyTwoMinutes(); Esegui l’attività ogni due minuti.
->everyThreeMinutes(); Esegui l’attività ogni tre minuti.
->everyFourMinutes(); Esegui l’attività ogni quattro minuti.
->everyFiveMinutes(); Esegui l’attività ogni cinque minuti.
->everyTenMinutes(); Esegui l’attività ogni dieci minuti.
->everyFifteenMinutes(); Esegui l’attività ogni quindici minuti.
->everyThirtyMinutes(); Esegui l’attività ogni trenta minuti.
->hourly(); Esegui l’attività ogni ora.
->hourlyAt(17); Esegui l’attività ogni ora a 17 minuti dopo l’ora.
->everyOddHour($minutes = 0); Esegui l’attività ogni ora dispari.
->everyTwoHours($minutes = 0); Esegui l’attività ogni due ore.
->everyThreeHours($minutes = 0); Esegui l’attività ogni tre ore.
->everyFourHours($minutes = 0); Esegui l’attività ogni quattro ore.
->everySixHours($minutes = 0); Esegui l’attività ogni sei ore.
->daily(); Esegui l’attività ogni giorno a mezzanotte.
->dailyAt('13:00'); Esegui l’attività ogni giorno alle 13:00.
->twiceDaily(1, 13); Esegui l’attività ogni giorno alle 1:00 e alle 13:00.
->twiceDailyAt(1, 13, 15); Esegui l’attività ogni giorno alle 1:15 e alle 13:15.
->weekly(); Esegui l’attività ogni domenica a mezzanotte.
->weeklyOn(1, '8:00'); Esegui l’attività ogni settimana il lunedì alle 8:00.
->monthly(); Esegui l’attività il primo giorno di ogni mese a mezzanotte.
->monthlyOn(4, '15:00'); Esegui l’attività ogni mese il 4° giorno alle 15:00.
->twiceMonthly(1, 16, '13:00'); Esegui l’attività mensilmente il 1° e il 16° giorno alle 13:00.
->lastDayOfMonth('15:00'); Esegui l’attività l’ultimo giorno del mese alle 15:00.
->quarterly(); Esegui l’attività il primo giorno di ogni trimestre a mezzanotte.
->quarterlyOn(4, '14:00'); Esegui l’attività ogni trimestre il 4° giorno alle 14:00.
->yearly(); Esegui l’attività il primo giorno di ogni anno a mezzanotte.
->yearlyOn(6, 1, '17:00'); Esegui l’attività ogni anno il 1° giugno alle 17:00.
->timezone('America/New_York'); Imposta il fuso orario per l’attività.

Questi metodi possono essere combinati con vincoli aggiuntivi per creare pianificazioni ancora più precise che si eseguono solo in determinati giorni della settimana. Ad esempio, puoi programmare un comando per essere eseguito settimanalmente il lunedì:

use Illuminate\Support\Facades\Schedule;

// Esegui una volta alla settimana il lunedì alle 13:00...
Schedule::call(function () {
    // ...
})->weekly()->mondays()->at('13:00');

// Esegui ogni ora dalle 8:00 alle 17:00 nei giorni feriali...
Schedule::command('foo')
          ->weekdays()
          ->hourly()
          ->timezone('America/Chicago')
          ->between('8:00', '17:00');

Di seguito è riportato un elenco di vincoli aggiuntivi per la pianificazione:

Metodo Descrizione
->weekdays(); Limita l’attività ai giorni feriali.
->weekends(); Limita l’attività ai fine settimana.
->sundays(); Limita l’attività alla domenica.
->mondays(); Limita l’attività al lunedì.
->tuesdays(); Limita l’attività al martedì.
->wednesdays(); Limita l’attività al mercoledì.
->thursdays(); Limita l’attività al giovedì.
->fridays(); Limita l’attività al venerdì.
->saturdays(); Limita l’attività al sabato.
->days(array|mixed); Limita l’attività a giorni specifici.
->between($startTime, $endTime); Limita l’attività a eseguirsi tra orari di inizio e fine.
->unlessBetween($startTime, $endTime); Limita l’attività a non eseguirsi tra orari di inizio e fine.
->when(Closure); Limita l’attività in base a una condizione.
->environments($env); Limita l’attività a ambienti specifici.

Vincoli Giornalieri

Il metodo days può essere utilizzato per limitare l’esecuzione di un task a giorni specifici della settimana. Ad esempio, puoi programmare un comando per essere eseguito ogni ora la domenica e il mercoledì:

use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')
                ->hourly()
                ->days([0, 3]);

In alternativa, puoi usare le costanti disponibili nella classe Illuminate\Console\Scheduling\Schedule quando definisci i giorni in cui un task deve essere eseguito:

use Illuminate\Support\Facades;
use Illuminate\Console\Scheduling\Schedule;

Facades\Schedule::command('emails:send')
                ->hourly()
                ->days([Schedule::SUNDAY, Schedule::WEDNESDAY]);

Vincoli di Tempo

Il metodo between può essere usato per limitare l’esecuzione di un compito in base all’orario del giorno:

    Schedule::command('emails:send')
                        ->hourly()
                        ->between('7:00', '22:00');

Allo stesso modo, il metodo unlessBetween può essere usato per escludere l’esecuzione di un compito in un determinato periodo di tempo:

    Schedule::command('emails:send')
                        ->hourly()
                        ->unlessBetween('23:00', '4:00');

Vincoli di Test di Verità

Il metodo when può essere utilizzato per limitare l’esecuzione di un’attività in base al risultato di un determinato test di verità. In altre parole, se la closure fornita restituisce true, l’attività verrà eseguita a meno che altre condizioni vincolanti ne impediscano l’esecuzione:

Schedule::command('emails:send')->daily()->when(function () {
    return true;
});

Il metodo skip può essere visto come l’inverso di when. Se il metodo skip restituisce true, l’attività programmata non verrà eseguita:

Schedule::command('emails:send')->daily()->skip(function () {
    return true;
});

Quando si utilizzano metodi when concatenati, il comando programmato verrà eseguito solo se tutte le condizioni when restituiscono true.

Vincoli sull’Ambiente

Il metodo environments può essere usato per eseguire attività solo negli ambienti specificati (come definito dalla variabile di ambiente APP_ENV environment variable):

Schedule::command('emails:send')
            ->daily()
            ->environments(['staging', 'production']);

Fusi orari

Usando il metodo timezone, puoi specificare che l’orario di un’attività pianificata deve essere interpretato in un determinato fuso orario:

use Illuminate\Support\Facades\Schedule;

Schedule::command('report:generate')
         ->timezone('America/New_York')
         ->at('2:00')

Se assegni ripetutamente lo stesso fuso orario a tutte le tue attività pianificate, puoi specificare quale fuso orario deve essere assegnato a tutte le pianificazioni definendo un’opzione schedule_timezone nel file di configurazione app della tua applicazione:

'timezone' => env('APP_TIMEZONE', 'UTC'),

'schedule_timezone' => 'America/Chicago',

Ricorda che alcuni fusi orari utilizzano l’ora legale. Quando si verificano cambiamenti dovuti all’ora legale, la tua attività pianificata potrebbe essere eseguita due volte o addirittura non essere eseguita affatto. Per questo motivo, consigliamo di evitare la pianificazione basata sul fuso orario quando possibile.

Prevenire Sovrapposizioni di Task

Per impostazione predefinita, i task programmati verranno eseguiti anche se un’istanza precedente è ancora in esecuzione. Per evitare questo, puoi usare il metodo withoutOverlapping:

use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')->withoutOverlapping();

In questo esempio, il comando emails:send Artisan command verrà eseguito ogni minuto se non è già in esecuzione. Il metodo withoutOverlapping è particolarmente utile se hai task con tempi di esecuzione molto variabili, rendendo difficile prevedere quanto tempo impiegherà un determinato task.

Se necessario, puoi specificare quanti minuti devono passare prima che il blocco "senza sovrapposizione" scada. Per impostazione predefinita, il blocco scade dopo 24 ore:

Schedule::command('emails:send')->withoutOverlapping(10);

Dietro le quinte, il metodo withoutOverlapping utilizza la cache della tua applicazione per ottenere i blocchi. Se necessario, puoi cancellare questi blocchi di cache usando il comando Artisan schedule:clear-cache. Questo è generalmente necessario solo se un task si blocca a causa di un problema imprevisto del server.

Esecuzione dei Task su un Solo Server

Per utilizzare questa funzionalità, la tua applicazione deve usare il driver di cache database, memcached, dynamodb o redis come driver di cache predefinito. Inoltre, tutti i server devono comunicare con lo stesso server di cache centrale.

Se il scheduler della tua applicazione è in esecuzione su più server, puoi limitare un lavoro programmato a essere eseguito solo su un singolo server. Ad esempio, supponi di avere un task programmato che genera un nuovo report ogni venerdì sera. Se il task scheduler è in esecuzione su tre server di lavoro, il task programmato verrà eseguito su tutti e tre i server e genererà il report tre volte. Non è ottimo!

Per indicare che il task debba essere eseguito su un solo server, usa il metodo onOneServer quando definisci il task programmato. Il primo server che ottiene il task acquisirà un lock atomico sul job per impedire ad altri server di eseguire lo stesso task contemporaneamente:

use Illuminate\Support\Facades\Schedule;

Schedule::command('report:generate')
                ->fridays()
                ->at('17:00')
                ->onOneServer();

Assegnare un Nome ai Job su un Singolo Server

A volte potresti aver bisogno di programmare lo stesso job con parametri differenti, mantenendo Laravel istruito a eseguire ogni variante del job su un singolo server. Per ottenere ciò, puoi assegnare a ogni definizione di schedule un nome unico tramite il metodo 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();

Allo stesso modo, le closure programmate devono essere assegnate a un nome se si desidera eseguirle su un singolo server:

Schedule::call(fn () => User::resetApiRequestCount())
    ->name('reset-api-request-count')
    ->daily()
    ->onOneServer();

Task in background

Di default, più task schedulati allo stesso momento verranno eseguiti in sequenza in base all’ordine in cui sono definiti nel metodo schedule. Se hai task che richiedono molto tempo, questo potrebbe far sì che i task successivi inizino molto più tardi del previsto. Se desideri eseguire i task in background in modo che possano avviarsi tutti contemporaneamente, puoi utilizzare il metodo runInBackground:

use Illuminate\Support\Facades\Schedule;

Schedule::command('analytics:report')
         ->daily()
         ->runInBackground();

Il metodo runInBackground può essere utilizzato solo quando si schedulano task tramite i metodi command ed exec.

Modalità di Manutenzione

I task programmati della tua applicazione non verranno eseguiti quando l’applicazione è in modalità di manutenzione, poiché non vogliamo che i tuoi task interferiscano con eventuali lavori di manutenzione in corso sul tuo server. Tuttavia, se desideri forzare l’esecuzione di un task anche in modalità di manutenzione, puoi chiamare il metodo evenInMaintenanceMode durante la definizione del task:

    Schedule::command('emails:send')->evenInMaintenanceMode();

Gruppi di Schedule

Quando definisci più task pianificati con configurazioni simili, puoi utilizzare la funzionalità di raggruppamento di Laravel per evitare di ripetere le stesse impostazioni per ogni task. Raggruppare i task semplifica il tuo codice e garantisce coerenza tra i task correlati.

Per creare un gruppo di task pianificati, invoca i metodi di configurazione dei task desiderati, seguiti dal metodo group. Il metodo group accetta una closure che definisce i task che condividono la configurazione specificata:

use Illuminate\Support\Facades\Schedule;

Schedule::daily()
    ->onOneServer()
    ->timezone('America/New_York')
    ->group(function () {
        Schedule::command('emails:send --force');
        Schedule::command('emails:prune');
    });

Esecuzione dello Scheduler

Ora che abbiamo imparato come definire le attività programmate, discutiamo di come eseguirle effettivamente sul nostro server. Il comando Artisan schedule:run valuterà tutte le tue attività programmate e determinerà se devono essere eseguite in base all’orario attuale del server.

Quindi, quando usiamo lo scheduler di Laravel, dobbiamo aggiungere una sola voce di configurazione cron al nostro server che esegue il comando schedule:run ogni minuto. Se non sai come aggiungere voci cron al tuo server, considera l’uso di un servizio come Laravel Forge che può gestire le voci cron per te:

* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1

Attività Pianificate Sub-Minute

Nei maggiori sistemi operativi, i cron job possono essere eseguiti al massimo una volta per minuto. Tuttavia, lo scheduler di Laravel ti permette di pianificare attività con intervalli più frequenti, fino ad arrivare a una volta al secondo:

use Illuminate\Support\Facades\Schedule;

Schedule::call(function () {
    DB::table('recent_users')->delete();
})->everySecond();

Quando definisci attività sub-minute nella tua applicazione, il comando schedule:run continuerà a funzionare fino alla fine del minuto corrente invece di uscire immediatamente. Ciò permette al comando di invocare tutte le attività sub-minute necessarie durante il minuto.

Poiché le attività sub-minute che impiegano più tempo del previsto potrebbero ritardare l’esecuzione di attività sub-minute successive, si raccomanda che tutte le attività sub-minute inviino job in coda o comandi in background per gestire l’effettiva elaborazione dell’attività:

use App\Jobs\DeleteRecentUsers;

Schedule::job(new DeleteRecentUsers)->everyTenSeconds();

Schedule::command('users:delete')->everyTenSeconds()->runInBackground();

Interrompere i Task Sub-Minute

Quando sono definiti task sub-minuto, il comando schedule:run viene eseguito per l’intero minuto di invocazione. A volte potresti dover interrompere il comando durante il deploy della tua applicazione. Altrimenti, un’istanza già in esecuzione di schedule:run continuerà a usare il codice precedentemente deployato fino alla fine del minuto corrente.

Per interrompere le invocazioni di schedule:run in corso, puoi aggiungere il comando schedule:interrupt allo script di deploy della tua applicazione. Questo comando dovrebbe essere eseguito dopo che il deploy è terminato:

php artisan schedule:interrupt

Esecuzione del Scheduler Localmente

Di solito, non aggiungi un’entrata cron per il scheduler sulla tua macchina di sviluppo locale. Invece, puoi usare il comando Artisan schedule:work. Questo comando verrà eseguito in primo piano e attiverà il scheduler ogni minuto fino a quando non lo interrompi. Quando sono definite attività con intervalli inferiori al minuto, il scheduler continuerà a funzionare all’interno di ogni minuto per elaborarle:

php artisan schedule:work

Output dei Task

Lo scheduler di Laravel offre diversi metodi utili per gestire l’output generato dalle attività programmate. Innanzitutto, usando il metodo sendOutputTo, puoi inviare l’output a un file per una successiva ispezione:

use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')
         ->daily()
         ->sendOutputTo($filePath);

Se desideri aggiungere l’output a un file esistente, puoi usare il metodo appendOutputTo:

Schedule::command('emails:send')
         ->daily()
         ->appendOutputTo($filePath);

Utilizzando il metodo emailOutputTo, puoi inviare l’output via email a un indirizzo di tua scelta. Prima di inviare via email l’output di un’attività, dovresti configurare i servizi email di Laravel:

Schedule::command('report:generate')
         ->daily()
         ->sendOutputTo($filePath)
         ->emailOutputTo('taylor@example.com');

Se desideri inviare via email l’output solo se il comando Artisan o di sistema programmato termina con un codice di uscita diverso da zero, usa il metodo emailOutputOnFailure:

Schedule::command('report:generate')
         ->daily()
         ->emailOutputOnFailure('taylor@example.com');

I metodi emailOutputTo, emailOutputOnFailure, sendOutputTo e appendOutputTo sono esclusivi dei metodi command e exec.

Hook delle Attività

Usando i metodi before e after, puoi specificare il codice da eseguire prima e dopo l’esecuzione dell’attività programmata:

    use Illuminate\Support\Facades\Schedule;

    Schedule::command('emails:send')
             ->daily()
             ->before(function () {
                 // L'attività sta per essere eseguita...
             })
             ->after(function () {
                 // L'attività è stata eseguita...
             });

I metodi onSuccess e onFailure ti permettono di specificare il codice da eseguire se l’attività programmata ha successo o fallisce. Un fallimento indica che il comando Artisan o di sistema programmato si è terminato con un codice di uscita diverso da zero:

    Schedule::command('emails:send')
             ->daily()
             ->onSuccess(function () {
                 // L'attività ha avuto successo...
             })
             ->onFailure(function () {
                 // L'attività è fallita...
             });

Se l’output è disponibile dal tuo comando, puoi accedervi nei tuoi hook after, onSuccess o onFailure indicando un’istanza di Illuminate\Support\Stringable come argomento $output nella definizione della chiusura del tuo hook:

    use Illuminate\Support\Stringable;

    Schedule::command('emails:send')
             ->daily()
             ->onSuccess(function (Stringable $output) {
                 // L'attività ha avuto successo...
             })
             ->onFailure(function (Stringable $output) {
                 // L'attività è fallita...
             });

Pinging URL

Utilizzando i metodi pingBefore e thenPing, lo scheduler può automaticamente pingare un determinato URL prima o dopo l’esecuzione di un task. Questo metodo è utile per notificare un servizio esterno, come Envoyer, che il tuo task programmato sta iniziando o ha terminato l’esecuzione:

Schedule::command('emails:send')
         ->daily()
         ->pingBefore($url)
         ->thenPing($url);

I metodi pingOnSuccess e pingOnFailure possono essere utilizzati per pingare un determinato URL solo se il task ha successo o fallisce. Un fallimento indica che il comando Artisan programmato o il comando di sistema è terminato con un codice di uscita diverso da zero:

Schedule::command('emails:send')
         ->daily()
         ->pingOnSuccess($successUrl)
         ->pingOnFailure($failureUrl);

I metodi pingBeforeIf, thenPingIf, pingOnSuccessIf e pingOnFailureIf possono essere usati per pingare un determinato URL solo se una certa condizione è true:

Schedule::command('emails:send')
         ->daily()
         ->pingBeforeIf($condition, $url)
         ->thenPingIf($condition, $url);             

Schedule::command('emails:send')
         ->daily()
         ->pingOnSuccessIf($condition, $successUrl)
         ->pingOnFailureIf($condition, $failureUrl);

Eventi

Laravel emette una varietà di eventi durante il processo di pianificazione. Puoi definire listeners per uno qualsiasi dei seguenti eventi:

Nome Evento
Illuminate\Console\Events\ScheduledTaskStarting
Illuminate\Console\Events\ScheduledTaskFinished
Illuminate\Console\Events\ScheduledBackgroundTaskFinished
Illuminate\Console\Events\ScheduledTaskSkipped
Illuminate\Console\Events\ScheduledTaskFailed
Lascia un commento

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *