Vai al contenuto principale
Per aggiungere a un pacchetto una sincronizzazione periodica o un heartbeat, separate la fornitura dell’elaborazione dalla registrazione nello scheduler. La semplice installazione non deve aggiungere task periodici all’applicazione che usa il pacchetto: progettatelo in modo che sia l’utente a scegliere l’attivazione, la frequenza di esecuzione e il comportamento su più server. Questa pagina esamina il codice sorgente ufficiale di Laravel Framework v13.39.0 e applica allo sviluppo di pacchetti le operazioni di base del Task scheduling. Per il caricamento dei provider consultate Struttura interna del package auto-discovery, per la pubblicazione della configurazione Merge e cache della configurazione dei pacchetti.

Registrare dopo la risoluzione di Schedule

In una normale applicazione Laravel, FoundationServiceProvider registra Schedule::class come singleton. La sua creazione chiama resolveConsoleSchedule() del Console Kernel, che applica il fuso orario dell’applicazione e lo store di cache usato dallo scheduler. Il pacchetto estende questo Schedule condiviso, evitando di creare un proprio new Schedule() e fermarsi lì. Il provider seguente presuppone che Acme\Courier\ corrisponda a src/ tramite il PSR-4 di Composer e che il pacchetto contenga config/courier.php.
callAfterResolving(), se il servizio non è ancora risolto, attende la risoluzione futura; se è già risolto, lo recupera e invoca subito anche la callback. In questo modo la registrazione avviene anche se un altro provider ha usato Schedule prima. Per i dettagli consultate Hook di risoluzione dei servizi nei pacchetti. runningInConsole() non verifica uno specifico comando Artisan. In questo esempio si esclude l’uso di Schedule tramite HTTP. Inoltre, il provider che registra l’hook non deve essere un provider differito: se boot() non viene eseguito entro il momento necessario, nemmeno l’hook viene registrato.
Registrare un evento ed eseguire un task sono due cose distinte. Caricare questo provider non esegue immediatamente l’heartbeat. La sola registrazione non avvia nemmeno cron o schedule:work.

L’utente attiva esplicitamente il task

Il file config/courier.php del pacchetto imposta lo stato iniziale come disattivato.
Nell’applicazione che usa il pacchetto si pubblica o si crea la configurazione e si imposta schedule.enabled a true. In questo esempio i valori vengono letti al momento della registrazione. Il fuso orario predefinito è UTC; se ne scegliete un altro, tenete conto delle esecuzioni duplicate o mancanti dovute all’ora legale. Poiché la configurazione viene unita in modo superficiale, anche il registratore specifica il valore predefinito di ogni chiave. Così, anche se una sovrascrittura parziale elimina le altre chiavi dell’array schedule, la registrazione avviene con le impostazioni di base.

Evitare la doppia registrazione nello stesso Schedule

Il seguente src/CourierSchedule.php registra un heartbeat minimo che scrive una riga di log. Grazie a WeakMap, questo registratore aggiunge l’evento allo stesso oggetto Schedule una sola volta.
Poiché questo registratore è un singleton, anche se viene recuperato di nuovo dall’hook del provider condivide lo stato di registrazione. D’altra parte, può registrare l’evento in un nuovo Schedule. Evitate un flag statico “registrato” valido per l’intero processo, che escluderebbe anche gli Schedule di nuovi container o dei test. Schedule::call(), exec() e job() aggiungono l’evento a un array. Non sono API che trovano e sostituiscono un evento con lo stesso nome o che eliminano i duplicati.
withoutOverlapping() non corregge le registrazioni duplicate. Se lo stesso task viene registrato due volte, una volta terminata la prima esecuzione e rilasciato il lock, anche il secondo evento può essere eseguito. Neppure onOneServer() è una funzione che rimuove le doppie registrazioni.La WeakMap sopra impedisce solo i duplicati di questo registratore. Se l’utente aggiunge lo stesso task in routes/console.php, disattivate la registrazione automatica del pacchetto e mantenete la responsabilità della registrazione in un solo punto.

Il nome dell’evento influisce anche sulla compatibilità dei lock

name() è un alias di description(), ma l’effetto sul nome del lock dipende dal tipo di evento. Di seguito i nomi di mutex predefiniti. Per i CallbackEvent, assegnate un nome non vuoto prima di withoutOverlapping() o onOneServer(). Se il nome non è impostato, questi metodi lanciano un’eccezione. job() di norma usa come nome iniziale il nome della classe del job o simili. Tuttavia, se registrate lo stesso job con argomenti o destinatari diversi, specificate esplicitamente un nome per ciascun destinatario. Cambiare solo gli argomenti non rende automaticamente diverso il nome iniziale.
SyncCatalogJob è un esempio di classe job fornita dal pacchetto. Per l’implementazione dei job consultate Code e job. Modificando il nome, il comando o l’espressione cron, a seconda del tipo cambia anche l’identificatore del lock. In un deploy in cui convivono processi della vecchia e della nuova versione, la stessa elaborazione potrebbe acquisire lock diversi. Non trattatelo come una semplice modifica del testo visualizzato: gestite il nome del task come una specifica pubblica stabile.

Distinguere onOneServer e withoutOverlapping

  • onOneServer() sceglie il server incaricato dell’esecuzione per quell’orario. Il CacheSchedulingMutex esaminato aggiunge ore e minuti al nome del mutex dell’evento e lo mantiene con una scadenza di un’ora. È diverso dal lock anti-sovrapposizione che viene rilasciato al termine del task.
  • withoutOverlapping() impedisce la sovrapposizione mentre l’esecuzione precedente è ancora in corso. La scadenza indicata è espressa in minuti e il valore predefinito è 1.440 minuti. Con una scadenza troppo breve, il lock scade prima che l’elaborazione finisca.
  • Quando job() accoda un job, il completamento dell’evento pianificato non coincide con il completamento dell’elaborazione del job. Il controllo dei duplicati nei worker va progettato separatamente, ad esempio con i job univoci o il middleware WithoutOverlapping lato job.
Agli utenti che attivano onOneServer() indicate come requisito una cache condivisa tra database, memcached, dynamodb e redis, come specificato dalla documentazione ufficiale. I lock non garantiscono l’esecuzione exactly-once della logica di business: serve anche un’elaborazione che non si rompa se rieseguita.

Risolvere le dipendenze pesanti al momento dell’esecuzione

Il LoggerInterface dell’heartbeat visto sopra viene risolto da CallbackEvent tramite call() del container al momento dell’esecuzione dell’evento. È una configurazione che non scrive log né crea client durante la registrazione dell’hook. Evitare connessioni esterne e logica di business nel costruttore è importante anche per la Registrazione dei comandi Artisan di un pacchetto e risoluzione delle dipendenze. Inoltre, poiché call() e job() sono CallbackEvent, non è possibile usare runInBackground(). Delegate alle code le elaborazioni lunghe oppure, se necessario, scegliete l’esecuzione in background di command().

Non sottrarre le impostazioni globali dell’applicazione

Schedule::useCache() cambia lo store dell’oggetto mutex posseduto da Schedule. Non è un’impostazione limitata agli eventi del pacchetto: influisce anche sugli eventi che condividono lo stesso mutex. Non chiamatelo incondizionatamente nel boot() del pacchetto, ma trattatelo come un’impostazione scelta dall’applicazione che lo usa. Allo stesso modo, il pacchetto non deve modificare di propria iniziativa Schedule::alwaysOnOneServer(), la sospensione dello scheduler o la politica di esecuzione durante la modalità di manutenzione.

Dopo una modifica della configurazione, creare un nuovo Schedule

Nell’esempio, enabled, l’espressione cron e il fuso orario sono impostazioni lette al momento della registrazione. Cambiare solo i valori di config() per uno Schedule già creato non aggiunge né rimuove eventi e non rilegge la configurazione. Anche il controllo della WeakMap non serve a riconfigurare. Nei deploy che usano la cache della configurazione, ricostruite la cache e verificate schedule:list in un processo che carica la nuova configurazione. Verificate anche che scheduler a lunga esecuzione o schedule:run che eseguono task sotto il minuto non mantengano il vecchio codice o la vecchia configurazione. Per l’interruzione dei task sotto il minuto durante il deploy consultate Task scheduling.

Testare la registrazione separatamente dall’esecuzione

Quello che segue è un esempio di tests/CourierScheduleTest.php che usa l’autoload Composer del pacchetto e PHPUnit. Il mutex è sostituito da un test double, così da verificare il risultato della registrazione senza cache esterne né cron reale.
Questo è un test unitario del registratore. L’auto-discovery del provider, il merge della configurazione e l’integrazione con il Console Kernel vanno verificati separatamente con Orchestra Testbench o con un’applicazione di verifica. Per far passare i test di registrazione, non eseguite tutti i task dell’applicazione con schedule:run. Separando i test del corpo dei task dai test di integrazione dello scheduler in un ambiente controllato, eviterete gli effetti collaterali di task periodici non correlati.

Pagine correlate

Task scheduling

Frequenza di esecuzione, configurazione di cron, delega alle code e gestione durante i deploy.

Hook di risoluzione dei servizi nei pacchetti

Applicazione a servizi non ancora risolti o già risolti e precauzioni in caso di nuova registrazione.

Fonti primarie consultate

Ultima modifica il 1 novembre 2026