Torno di nuovo a scrivere di Orchid, il progetto di Alexandr Chernyaev dedicato alla costruzione di pannelli di amministrazione con Laravel. Sto continuando ad usarlo e, devo ammetterlo, mi ci sto appassionando.
Il progetto è ben fatto, funziona bene ed ha una logica che mi piace molto.
Nel primo articolo avevo riportato le parole del suo autore riguardo all’aspetto CRUD, fondamentale per un qualsiasi pannello di amministrazione. Le sue parole sono molto specifiche: “this is not a CRUD solution”.
Tuttavia… è successa una cosa interessante.
Un modulo CRUD c’è!
Qualche giorno fa, dopo aver letto questo articolo (good catch, apprezzo gli sviluppatori che monitorano le conversazioni sui propri progetti) Alexandr mi ha contattato, per spiegarmi che esiste anche un modulo aggiuntivo per Orchid da usare specificatamente per fare CRUD.
Il nome è… beh, CRUD ovviamente! E si può trovare qui.
Arriviamo alla domanda fatidica: è un buon sostituto gratuito di Nova?
La risposta è sì. Vi spiego come sono giunto a questa conclusione.
Installazione molto semplice
L’installazione è semplice, richiede solo ed esclusivamente l’installazione da Composer.
1 |
composer require orchid/crud |
Creare una risorsa
CRUD sta per Create, Read, Update and Delete. Con una nomenclatura che ricorda molto quella di Nova, in Orchid CRUD è possibile creare una “Risorsa” tramite il comando Artisan
1 |
php artisan orchid:resource MovieResource |
che va a creare uno stub della nuova classe nella cartella Orchid/Resources. Per questo esempio userò un’ipotetica risorsa MovieResource, supponendo di aver creato un model Movie.
Esattamente come visto anche in Nova, ogni risorsa deve avere un suo model collegato, che possiamo specificare nell’attributo statico $model.
1 2 3 4 5 6 |
/** * The model the resource corresponds to. * * @var string */ public static $model = Movie::class; |
Lo step successivo è andare ad aggiungere alcuni trait usati da Orchid al model. Come recita la documentazione, questi sono
1 |
use AsSource, Filterable, Attachable; |
Definire Field e Column
In Orchid, a differenza di Nova, la definizione dei campi con cui andremo a costruire il form per il CRUD e delle colonne che andranno a comporre “la lista” delle entità non avviene nello stesso metodo. In Orchid, infatti:
- tramite il metodo “fields()” andiamo a definire quelli che saranno i campi del form di creazione / modifica;
- tramite il metodo “columns()” andiamo a definire quelle che saranno le colonne della tabella in cui vedremo i nostri dati;
Se inizialmente la cosa mi aveva confuso, dopo un po’ ho apprezzato la possibilità di tenere le cose ben separate. Un altro punto in più per Orchid.
Ecco un esempio del contenuto che ho messo in fields:
1 2 3 4 5 6 7 8 |
public function fields(): array { return [ Input::make('title')->title('Title'), Input::make('year')->title('Year'), Input::make('director')->title('Director'), ]; } |
Questo invece è il contenuto di columns:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public function columns(): array { return [ TD::make('id'), TD::make('title'), TD::make('year'), TD::make('director'), TD::make('created_at', 'Date of creation') ->render(function ($model) { return $model->created_at->toDateTimeString(); }), ]; } |
Tramite TD::make possiamo definire nuove colonne per la nostra tabella. Il metodo aggiuntivo “render” ci permette di personalizzare il modo in cui mostrare il dato.
Una volta definiti campi e colonne, ecco come appare il nostro form di creazione/modifica:
Ed ecco come appare la lista dei film disponibili:
Più libertà, meno consuetudini
Il modulo CRUD, fino a questo momento, mi ha lasciato molto soddisfatto. Permette di fare un po’ tutto quello che già fa Nova.
Una domanda però sorge spontanea: come vengono gestite le relazioni? Ho così creato due nuove tabelle, di cui per comodità riporto il codice della relativa migration:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
Schema::create('genres', function (Blueprint $table) { $table->id(); $table->string('name'); $table->timestamps(); }); Schema::create('genre_movie', function(Blueprint $table) { $table->id(); $table->unsignedBigInteger('genre_id'); $table->unsignedBigInteger('movie_id'); $table->timestamps(); }); |
L’idea è semplice: vado direttamente sul “difficile” e cerco di capire come vengono gestite le relazioni many-to-many.
Come già detto prima, Orchid sacrifica un po’ di convenzioni per dare maggiore libertà. Di conseguenza, se vogliamo gestire un’associazione molti a molti, avremo bisogno di modificare la nostra risorsa, esplicitando il meccanismo di salvataggio e cancellazione.
Niente di traumatico comunque. modifichiamo i field della nostra risorsa MovieResource:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public function fields(): array { return [Input::make('title')->title('Title'), Input::make('year')->title('Year'), Input::make('director')->title('Director'), // la notazione con il "." indica che è un // array di zero o più elementi Relation::make('genres.') // definiamo il modello da prendere come riferimento e l'attributo // da usare come label ->fromModel(Genre::class, 'name') // sarà possibile scegliere più di un elemento // come valore del form ->multiple() ->title('Genres'), ]; } |
Quel “Relation” è di tipo Orchid\Screen\Fields\Relation e non è niente di relativo al modulo CRUD. In realtà, infatti, è un campo già disponibile in Orchid “base”.
Una volta modificati i campi, ecco come appare il nostro form:
Arrivati a questo punto non abbiamo più a che fare con il semplice CRUD dell’inizio. Dobbiamo implementare il codice per definire come comportarsi con quella relazione. Andiamo a lavorare su due metodi della classe base Orchid\Crud\Resource che vanno ridefiniti.
Questi due metodi sono onSave ed onDelete, che di default si comportano così:
1 2 3 4 5 6 7 8 9 |
public function onSave(ResourceRequest $request, Model $model) { $model->forceFill($request->all())->save(); } public function onDelete(Model $model) { $model->delete(); } |
Per qualsiasi entità “isolata” va benissimo, ma nel caso della nostra resource MovieResource, ovviamente, le cose cambiano. Nella MovieResource, quindi, aggiungiamo queste due definizioni:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public function onSave(ResourceRequest $request, Model $model) { // non scordiamoci di rimuovere con "except" il campo "inutile" // per il model o avremo dei problemi! $model->forceFill($request->except('genres'))->save(); $model->genres()->sync($request->genres); } public function onDelete(Model $model) { $model->genres()->sync([]); $model->delete(); } |
A questo punto, il form funziona tranquillamente.
Qualche altra piccola chicca!
Riporto qui al volo, in modo molto stringato, le altre piccole cose che mi sono piaciute di questo modulo CRUD di Orchid:
- Il supporto all’eager loading per i nostri model, comodo in caso di elenchi in cui è importante mostrare dei campi legati alle relazioni;
- la presenza di un sistema che impedisce conflitti in caso di modifica della risorsa da due operatori contemporaneamente, chiamato Traffic Cop;
- il supporto ai filtri di Orchid, che permettono di migliorare l’esperienza utente quando si lavora con tanti record;
- stando alle parole dello sviluppatore (molto disponibile su Github) a breve dovrebbero essere disponibili anche le custom action da definire per le specifiche risorse;
Tiriamo le somme!
Insomma… ‘sto benedetto CRUD può essere usato come sostituto di Nova?
Secondo me, sì.
Come soluzione è ottima, a mio parere. Si fonda su pochi concetti di base molto solidi ma flessibili. Per quanto mi riguarda, posso fare a meno di alcune convenzioni “comode” se ho maggiore libertà di manovra.
Va detto anche che Orchid è totalmente gratuito. Non penso sia un aspetto fondamentale (99 euro di licenza per Nova sono assolutamente ben spesi, ricaricabili su qualsiasi progetto) (se 99 euro di licenza pesano, facciamoci qualche domanda su che progetti ci prendiamo sulle spalle).
Rispetto a Nova manca di alcune piccole cose, secondo me trascurabili o in via di implementazione. A questo punto, non nego che “la perfezione” secondo me sarebbe rendere Orchid compatibile con i field di Nova, in modo tale da aumentarne anche l’adozione. Chissà, magari potrei aprire qualche PR prossimamente…
Parliamoci chiaro: già così parliamo di un progetto BOMBA.
Qualcuno di voi l’ha provato? Fatemi sapere, sono curioso di conoscere le vostre esperienze.