
Hacking Nova
alberto • February 27, 2021
novaIntroduciamo questa nuova serie dedicata a Laravel Nova con un articolo su come gestire le cosiddette Typed Resources. Queste sono delle particolari risorse che presentano un campo, solitamente una tendina <select>
, che viene utilizzata per gestire una tipizzazione.
Per fare un esempio fantacalcististico, un eventuale risorsa Player potrebbe avere un campo type che potrebbe assumere uno tra 4 diversi valori: portiere, difensore, controcampista, attaccante.
Scenario base
Uno scenario out-of-the-box potrebbe essere questo:
public function fields(Request $request)
{
return [
ID::make(__('ID'), 'id')->sortable(),
Text::make('Name'),
Select::make('Role')
->options([
'GK' => 'Goalkeeper',
'DF' => 'Defender',
'MF' => 'Midfielder',
'FW' => 'Forward',
])
->displayUsingLabels()
];
}
Potrebbe però capitare che il cliente, che poi diventerà il principale utilizzatore di Nova, non ha ben assimilato questa struttura e preferisca ragionare ignorando il concetto di Player, ma concentrandosi sui vari ruoli come entità tra loro distinte.
Introduciamo le Typed Resources
Le Typed Resources rappresentano quindi risorse virtuale che referenziano il medesimo modello ma che implicitamente fanno riferimento ad un particolare tipizzazione, senza la necessità di doverlo specificare ogni volta.
Iniziamo trasformando la risorsa Player in una classe astratta e modificandola in questo modo:
abstract class Player {
public static $model = \App\Models\Player::class;
public static $title = 'id';
public static $search = [
'id', 'name'
];
public static abstract function getRoleValue();
public function fields(Request $request)
{
return [
ID::make(__('ID'), 'id')->sortable(),
Text::make('Name'),
Hidden::make('Role', 'role')
->default(static::getRoleValue())
];
}
public static function indexQuery(NovaRequest $request, $query)
{
return $query->where('role', static::getRoleValue());
}
public static function scoutQuery(NovaRequest $request, $query)
{
return $query->where('role', static::getRoleValue());
}
public static function detailQuery(NovaRequest $request, $query)
{
return parent::detailQuery($request, $query)->where('role', static::getRoleValue());
}
public static function relatableQuery(NovaRequest $request, $query)
{
return parent::relatableQuery($request, $query)->where('role', static::getRoleValue());
}
}
Praticamente abbiamo aggiunto un medodo statico astratto getRoleValue
che permetterà ad ogni sotto-classe di specificare il proprio ruolo. Questo metodo viene poi utilizzato in due contesti:
- come default value di un Fields/Hidden, permettendoci quindi di creare nuovi Player senza la necessità di scegliere il ruolo
- come condizione di query nei metodi
indexQuery
,scoutQuery
,detailQuery
erelatableQuery
per filtrare i risultati ottenuti utilizzando questa risorsa.
Ora possiamo creare una classe per ciascun ruolo in questo modo:
class Defender extends Player
{
public static function getRoleValue()
{
return 'DF';
}
}
Il risultato
Se tutto è stato implementato a dovere, quello che dovremmo ottenere è il seguente risultato:
Lato utente sembra che esistano 4 diverse entità all'interno del nostro database, mentre nella realtà dei fatti il modello è unico, ma viene "esposto" tramite nova con una virtualizzazione.
E se non ci bastasse?
L'idea di avere Typed Resources non si esaurisce con questo esempio.
Grazie a questa strategia è possibile anche personalizzare campi e relazioni per ciascuna risorsa.
Ricalcando il nostro esempio potremmo per esempio introdurre un campo reti subite da utilizzare solo nel caso di portieri o una relazione Velina da utilizzare solo in corrispondenza di attaccanti.
Con una discreta conoscenza dello strumento e un po' di fantasia, possiamo davvero adattare Nova alle nostre esigenze. Alla prossima!