Questa è una traduzione del package laracasts-commander.
Laravel Commander
Prima di procedere con la spiegazione di questo package. Guarda insieme a me questo semplice esempio:
fails())
{
return Redirect::back()->withInput()->withErrors($validator);
}
/**
* Creazione e salvataggio del nuovo utente registrato
*/
$user = new User(Input::all());
$user->save();
/**
* Invio email di notifica all’admin per una nuova iscrizione
*/
Mail::send(‘emails.welcome’, $user->toArray(), function($message) use($user)
{
$message->to(‘admin@example.com’, $user->username)->subject(‘Nuova registrazione!’);
});
/**
* Invio email di notifica al nuovo utente appena registrato
*/
Mail::send(‘emails.welcome’, $user->toArray(), function($message) use($user)
{
$message->to($user->email, $user->username)->subject(‘Benvenuto!’);
});
}
Abbiamo un controller che gestisce l’iscrizioni di nuovi utenti sul nostro sito.
Per prima cosa validiamo i dati inseriti. Se tutto è ok, procediamo al salvataggio.
Inoltre, decidiamo di inviare delle email con messaggi di benvenuto e/o di notifica, rispettivamente all’utente appena iscritto e all’admin.
Noti nulla di strano ? No ? Male! Scherzo.
Se stai realizzando una piccola applicazione questo esempio appena visto è del tutto lecito.
Ma prova ad immaginare se, a queste semplici operazioni, dovessi aggiungerne delle altre.
Cosa potrebbe succedere ? Le linee di codice aumenterebbero, così da renderti difficili futuri aggiornamenti.
Perchè non snellire il codice ? Lo so, l’estate è appena finita quindi non abbiamo più il problema della prova costume. Bando alle ciance.
Con questo package, basato sul pattern DTO (data transfer object, se non hai dimestichezza non ti preoccupare, ti sarà più chiaro a fine articolo) ti insegnerò un modo alternativo per rendere il tuo codice, molto più leggibile e facile da aggiornare. Cominciamo!
Installazione
L’installazione del package avviene tramite composer. Inserisci quindi questa linea all’interno del campo require del file composer.json. Ed infine esegui il comando composer update.
"require": {
"laracasts/commander": "~1.0"
}
Successivamente, aggiorna il file app.php all’interno di app/config ed includi la stringa seguente all’interno dell’array providers:
'providers' => [
'LaracastsCommanderCommanderServiceProvider'
]
Uso
Il consiglio più importante che posso darti, è di tenere in mente che questo tipo di approccio non è adatto a tutti. Ma cercherò di farti capire la sua importanza all’interno di un contesto come applicazioni su grande scala.
Se stai realizzando una semplice app che segue il pattern CRUD (create, read, update, delete) allora l’utilizzo di questo package probabilmente non ha senso.
Ma lo so che tu sei sempre affamato di conoscenza. Quindi ? Hai ancora voglia di continuare ? Bene, lo sapevo, welcome to the real, in questo caso, commands world. Cominciamo !
Lo scopo
Immagina di realizzare un’applicazione per la pubblicazione di annunci di lavoro.
Ora, ipotizziamo che dopo ogni pubblicazione di un nuovo annuncio, debbano essere eseguite una serie di operazioni. Invio di una mail ad ogni utente iscritto all’annuncio, notifica all’admin dell’avvenuto invio, o ancora invio di una mail di notifica all’impiegato o azienda che ha pubblicato l’annuncio.
Insomma il nostro limite è la nostra immaginazione.
Bene, ora a questo punto, la mole di operazioni da eseguire, tradotte in codice verranno messe all’interno del nostro caro controller…
Scherzo, ovviamente e qui che entrano in gioco i comandi, e ti assicuro che sarà davvero semplice organizzare e mantenere il codice pulito.
Il Controller
Per iniziare, devi iniettare la libreria CommandTrait nel tuo controller di base (BaseController), in maniera tale da non dover ogni volta includerla negli altri Controllers.
CommandTrait, non è nient’altro che un helper di funzionalità e metodi per la gestione del passaggio dei comandi al Command bus.
Vediamo subito un esempio:
execute(PostJobListingCommand::class);
return Redirect::to(‘/’);
}
Ah dimenticato questo package si base sull’ultima versione di PHP ( > 5.4), che introduce il comando “class”. Se dovessi avere problemi, puoi tranquillamente indicare il path della classe come una stringa.
Es.:
$this->execute('AppCommandsPostJobListingCommand')
La prima cosa che noterai, è la rappresentazione dell’istruzione da eseguire. Un nome di una classe che dà subito l’idea di cosa dovrà fare il comando.
Il comando DTO (Data Transfer Object).
Molto semplice vero ? Abbiamo un comando per rappresentare un istruzione, che viene poi gestita attraverso il command bus.
Ora vediamo come è fatta la classe comando:
title = $title;
$this->description = $description;
}
Quando viene eseguito il metodo execute della libreria CommandTrait, verrà eseguita una mappatura dei dati contenuti in Input::all().
Ora, cosa fa esattamente la classe CommandBus ?
Pensala come ad una sorta di semplice utility, che si comporta da “Traduttore” del comando.
Nell’esempio precedente la classe “PostJobListingCommand”, delegherà alla classe associata la pubblicazione di un nuovo annuncio di lavoro.
Di default, il command bus eseguirà una rapida sostituzione e ricerca, sul nome della classe del comando. In questo modo saprà quale gestore usare all’interno dell’IoC Container.
Ma vediamo anche qui un rapido esempio:
Prendi la classe vista poco fa “PostJobListingCommand”. Seguendo quando detto sopra avrai un gestore per la classe con la seguente denominazione “PostJobListingCommandHandler”.
Oppure se hai un comando per l’archiviazione degli annunci “ArchiveJobCommand” la sua controparte sarà “ArchiveJobCommandHandler”.
Tutto chiaro ? Bene. Tieni in mente però, che se preferisci una nomenclatura differente, si può facilmente sovrascrivere il processo di base. Lo vedrai più avanti nel corso dell’articolo. Non ti lascio per strada tranquillo !
Decorare il Command Bus
Ci possono essere volte in cui hai la necessità di “decorare” un comando. Cioè eseguire un qualche tipo di operazione. Ad esempio la codifica di dati (come la pulizia da tag html per avere una stringa “pulita”), prima di passare la gestione al commandBus.
NB: Innanzitutto per decorare, si intende una classe che aggiunge funzionalità ad un’altra classe. Senza modificare la struttura delle altre classi.
Eccoti un esempio.
Per prima cosa, dobbiamo creare una classe che implementa la classe CommandBus in questo modo:
job = $job;
}
La chiamata all’evento verrà eseguita dal metodo raise di “EventGenerator”, che non farà nient’altro che salvarlo all’interno di un array.
Listener di Eventi
Ora, la classe gestore dell’evento dovrà in qualche modo eseguire tutti gli eventi che verranno salvati. Questo significa che possono esserci un numero qualsiasi di gestori di tali eventi. Chiaro ?
Il nome dell’evento, segue ancora una volta, una semplice convenzione:
- PercorsoPerEvetiEvento => Percorso.Per.Eventi.Evento
In questo modo il meccanismo di esecuzione degli eventi farà una sostituzione con i punti.
In questo modo se viene lanciata l’esecuzione dell’evento:
- AppJobsEventsJobWasPublished
Avremo che l’evento in attesa di essere eseguito sarà:
- App.Jobs.Events.JobWasPublished
Andiamo a registrare un listener di eventi base.
Per esempio, invio di email.
Vediamo subito un esempio:
Event::listen('App.Jobs.Events.JobWasPublished', function($event)
{
var_dump('invia un'email di notifica al creatore dell'annuncio di lavoro.');
});
NB: puoi registrare molteplici ascoltatori per lo stesso evento.
Ma allo stesso modo, puoi anche non aver bisogno di eseguire nessuna operazione correlata quando un lavoro viene pubblicato.
Aggiungiamo un’altro listener di eventi.
In questo esempio abbiamo usato una closure, ma se vuoi, puoi usare approccio più “catch-all”.
Riprendiamo l’esempio visto sopra riguardo l’invio di email.
Per prima cosa creiamo una classe “EmailNotifier” che andrà a gestire determinati eventi della nostra applicazione.
Event::listen('App.*', 'AppListenersEmailNotifier');
Il primo parametro del metodo listen della classe Event, vedila come una espressione regolare che intereccettare dove e quando eseguire determinati eventi.
Ogni volta che si eseguirà un evento che si trova all’interno del namespace App, una volta inviato, la classe EmailNotifier si preoccuperà della sua esecuzione finale. Naturalmente ci possono essere casi in cui non hai il bisogno di rispondere ad ogni evento !
Meglio fare i fatti che parlare vero ? Bene, quello che puoi fare è, essere più specifici sul percorso da seguire.
La classe JobWasPublished
cerca un metodo whenJobWasPublished
sul nostro listener di eventi.
Se esiste, viene eseguito. In caso contrario, passerà oltre.
Ciò significa che la nostra classe EmailNotifier
potrebbe apparire in questo modo:
PostJobListingValidator
E’ sufficiente creare quella classe (PostJobListingValidator
), e includere un metodo validate
, che riceverà l’oggetto PostJobListingCommand
.
In caso di convalida non riuscita, verrà lanciata dal sistema un eccezione – ValidationFailedException
.
In questo modo, sia all’interno del controller o in global.php
, è possibile gestire in modo appropriato la validazione fallita.
Sovrascrivere i percorsi
Come ti ho detto poco fa, questo package adotta alcune assunzioni riguardo la struttura dei file. Come dimostrato da questo esempio:
- Percorso/Per/PostJobListingCommand => Percorso/Per/PostJobListingCommandHandler
- Percorso/Per/PostJobListingCommand => Percorso/Per/PostJobListingValidator
E comunque non ti deve limitare affatto.
Supponiamo di voler creare un comando ad hoc, di traduzioni delle ‘classi gestori’ dei comandi.
Niente di più semplice. Basta creare una classe che estende l’interfaccia LaracastsCommanderCommandTranslator
. Questa classe include due metodi:
toCommandHandler
toValidator
Bene, supponiamo ancora di voler salvare i nostri validatori all’interno di una directory Validators.
In questo modo avrai un organizzazione migliore dei file nella tua app root.
Vediamo come si trasforma il nostro traduttore:
first = $first;
$this->last = $last;
}
Bello vero ? Quindi mi raccomando tieni a mente dell’esistenza di questo comando artisan!
NB: Quando usi questo comando artisan, è consigliato usare lo slash per indicare il path. Se però dovessi usare il backslashes ricorda di metterlo tra doppi apici.