
Eloquent Casting
alberto • November 11, 2020
focusNella programmazione ad oggetti il termine Casting viene utilizzato per indicare la trasformazione di un determinato valore da una particolare classe ad un'altra per sfruttare al meglio le funzionalità che quest'ultima offre. In questo articolo approfondiremo la Eloquent Casting ovvero la possibilità di migliorare l'aderenza tra i valori presenti a database e le classi PHP, possibilità gestita in automatico da Eloquent in fare di querying.
Per capire al meglio la potenzialità introduciamo subito un esempio. Immaginiamo di avere un database legacy che utilizza una colonna CHAR(1)
che contiene come valori possibili i caratteri "Y"
e "N"
per rappresentare un valore boolean. Grazie ad un casting personalizzato, possiamo configurare Eloquent per trasformare automaticamente questi valori in true
e false
. \
Nel corso dell'articolo capiremo come possiamo arrivare a questo risultato.
La proprietá $casts
Tutta la magia del Casting avviene grazie ad una proprietà protetta, nominata appunto $casts
, che possiamo definire nei nostri modelli. Questa proprietà deve assumere la forma di un array associativo le cui chiavi rappresentano i nomi delle proprietà/colonne mentre i valori rappresentano l'algoritmo di casting da utilizzare.
Un esempio triviale di utilizzo potrebbe essere questo:
class User extends Model
{
protected $casts = [
'is_admin' => 'boolean'
];
}
In questo caso stiamo comunicando ad Eloquent la nostra volontà di ottenere il valore presente a database nella colonna is_admin
sotto forma di boolean.
La regole disponibili
Laravel presenta un set non banale di regole disponibili out-of-the-box. Alcune regole permettono ulteriori opzioni, utilizzando la sintassi nomeregola:opzioni
.
Analizziamole in dettaglio:
int
/ integer
Converte il valore in numero intero sfruttando il relativo casting nativo PHP.
real
/ float
/ double
Converte il valore in numero float sfruttando il relativo casting nativo PHP.
decimal
Converte il valore in numero decimale sfruttando la funzione number_format
nativo PHP. Utilizzando la sintassi decimal:$digits
è possibile definire il numero di cifre dopo la virgola.
string
Converte il valore in stringa sfruttando il relativo casting nativo PHP.
boolean
/ bool
Come già introdotto, converte il valore in booleano sfruttando il relativo casting nativo PHP.
object
Converte una stringa JSON in un oggetto stdClass
utilizzando la funzione json_decode
.
array
/ json
Converte una stringa JSON in un array utilizzando la funzione json_decode
.
collection
Converte una stringa JSON in una Collection utilizzando la funzione json_decode
.
date
/ datetime
/ timestamp
Converte una stringa nel corrispettivo oggetto Carbon
. Nel caso di date
e datetime
è possibile specificare un formato particolare con la sintassi date:$format
o datetime:$format
(un esempio di $format
potrebbe essere Y-m-d
).
encrypted
/ encrypted:object
/ encrypted:array
/ encrypted:collection
Questi nuovi cast permettono di castare l'oggetto nella tipologia indicata dopo i due punti salvando però a database il valore criptato usando l'encription standard di Laravel
Alcuni casi d'uso
Oltre al già citato caso dei boolean descritto sopra possiamo utilizzare questa feature di Eloquent in altri contesti.
Ecco due esempi classici di utilizzo:
Salvataggio di campi date aggiuntivi
I campi created_at
ed updated_at
vengono già convertiti in oggetti durante il recupero del modello dal database. Spesso peró capita che queste due sole date non siano sufficenti.
Supponiamo di dover implementare un blog e di dover creare la colonna published_at
per rappresentare la data di pubblicazione di un articolo. In questo caso è comodo lavorare ad alto livello direttamente con un oggetto Carbon:
// app/Models/Post.php
class Post extends Model
{
protected $casts = [
'published_at' => 'datetime'
];
}
// app/Controller/ExampleController.php
$post = Post::find(1);
$post->published_at = Carbon/Carbon::tomorrow();
$post->save();
// resources/views/test.blade.php
{% raw %}{{ $post->published_at->format('d/m/Y') }}{% endraw %}
Salvataggio di array
In alcuni contesti, un particolare modello potrebbe avere la necessità di avere associato un elenco di informazioni e che queste informazioni siano troppe basiche da richiedere un secondo modello dedicato.
Pensiamo ad esempio ad una semplice applicazione dove un utente può avere associati diversi ruoli, dove ogni ruolo viene identificato da una stringa in uppercase come ad esempio EDITOR, MODERATOR o DEVELOPER.
In questo caso una colonna VARCHAR castata ad array può essere la soluzione più comoda:
// app/Models/User.php
class User extends Model
{
protected $casts = [
'roles' => 'array'
];
}
// app/Controller/ExampleController.php
$user = new User;
$user->roles = ['DEVELOPER', 'EDITOR'];
$user->save();
// resources/views/test.blade.php
<ul>
@foreach($user->roles as $role)
<li>{% raw %}{{ $role }}{% endraw %}</li>
@endforeach
</ul>
In questo caso il dato grezzo che verrà salvato a database sarà un array JSON ["DEVELOPER","EDITOR"]
ma a livello di codice possiamo astrarci rispetto a questo tipo di serializzazione.
Cast personalizzati
Una delle principali caratteristiche di Laravel è la sua estrema flessibilità. E anche sull'argomento Eloquent Casting, il framework si rivela molto personalizzabile.
Grazie all'interfaccia Illuminate\Contracts\Database\Eloquent\CastsAttributes
possiamo infatti creare delle logiche di casting particolari.
Riprendiamo l'esempio introdotto all'inizio, quello del boolean salvato come Y
o N
e creiamo un custom cast:
<?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class CharBoolean implements CastsAttributes
{
public function get($model, $key, $value, $attributes)
{
return $value === 'Y';
}
public function set($model, $key, $value, $attributes)
{
return $value ? 'Y' : 'N';
}
}
La classe è molto semplice: l'interfaccia CastsAttributes
ci obbliga ad implementare due metodi:
- get
che è responsabilie di trasformare il dato grezzo nel dato complesso
- set
che viceversa è responsabile del contrario
L'utilizzo di questo cast personalizzato è alquanto facile:
// app/Models/User.php
class User extends Model
{
protected $casts = [
'admin' => App\Casts\CharBoolean::class
];
}
Utilizziamo sempre la proprietá casts
ma al posto di passare una stringa che identifica una regola standard, passiamo il riferimento alla nostra classe.
Casting o non Casting?
Il tema del casting è abbastanza complesso ma molto utile, soprattutto quando dobbiamo lavorare con strutture dati legacy, nate prima della scelta di utilizzare un framework come Laravel.
Nelle ultime versioni del framework questa parte ha subito parecchie modifiche e miglioramenti. Spesso sono state aggiunte regole out-of-the-box e anche la possibilità di definire dei cast personalizzati è abbastanza recente.
Questo è sintomatico del fatto che la community apprezza molto questa funzionalità, quindi perchè noi dovremmo essere a meno?