Modernizzare i Model Eloquent Laravel: guida al refactoring da $casts array (L9/L10) al potente metodo casts() in Laravel 12
In un progetto per un'azienda del settore servizi digitali, il gestionale Laravel 10 aveva 35 model Eloquent con proprietà $casts che in alcuni casi superavano le 20 entries. Il problema non era la dimensione degli array ma la sintassi: cast a enum collection richiedevano concatenazioni di stringhe come AsEnumCollection::class . ':' . OrderStatus::class, cast a collection tipizzate usavano stringhe opache come 'collection' invece delle classi esplicite, e qualsiasi cast che necessitasse di parametri diventava una stringa poco leggibile. La migrazione al metodo casts() introdotto in Laravel 11 ha risolto tutti questi problemi con una singola modifica sintattica - senza breaking change, senza impatto sulle performance, e con piena retrocompatibilità con la proprietà $casts esistente.
Quando conviene migrare da $casts al metodo casts()?
La proprietà $casts non è deprecata - né in Laravel 11 né in Laravel 12. La guida di upgrade non menziona alcuna rimozione programmata. La PR #47237 di Nuno Maduro, mergiata nel giugno 2023, è esplicita: proprietà e metodo coesistono e vengono fusi a runtime, con il metodo che ha la precedenza in caso di duplicati. Nessun impatto sulle performance - la fusione avviene una sola volta durante il boot del model.
La migrazione ha senso quando i model usano cast complessi che con la proprietà richiedono concatenazione di stringhe. PHP non permette chiamate a metodi statici nelle definizioni di proprietà, quindi con $casts l'unico modo di passare parametri a una classe di cast è MyCast::class . ':arg1,arg2'. Il metodo casts() sblocca la sintassi nativa:
// $casts (L9/L10): concatenazione stringhe, nessun supporto IDE
protected $casts = [
'status' => OrderStatus::class,
'tags' => AsEnumCollection::class . ':' . ProductTag::class,
'settings' => AsCollection::class,
'metadata' => AsEncryptedArrayObject::class,
'price' => 'decimal:2',
];
// casts() (L11/L12): chiamate statiche, autocomplete IDE, tipizzazione
protected function casts(): array
{
return [
'status' => OrderStatus::class,
'tags' => AsEnumCollection::of(ProductTag::class),
'settings' => AsCollection::using(SettingsCollection::class),
'metadata' => AsEncryptedArrayObject::class,
'price' => 'decimal:2',
];
}AsEnumCollection::of(), AsCollection::using(), AsEncryptedCollection::using() - queste chiamate statiche sono il vantaggio concreto. Per model con solo cast semplici ('integer', 'boolean', 'datetime'), la migrazione è cosmetica: funziona uguale, l'unico beneficio è la coerenza con i model che necessitano di cast complessi.
Custom cast class: Value Object nei model Eloquent
Il metodo casts() non cambia come funzionano le custom cast class - queste erano già disponibili in Laravel 9. Ma rende più leggibile il loro utilizzo, specialmente con l'interfaccia Castable che permette alla classe stessa di dichiarare come vuole essere castata:
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
class AddressCast implements CastsAttributes
{
public function get(Model $model, string $key, mixed $value, array $attributes): ?Address
{
if (is_null($value)) return null;
$data = json_decode($value, true);
return new Address($data['street'], $data['city'], $data['zip_code'], $data['country'] ?? 'IT');
}
public function set(Model $model, string $key, mixed $value, array $attributes): ?string
{
if (is_null($value)) return null;
if (!$value instanceof Address) {
throw new \InvalidArgumentException('Expected Address instance');
}
return json_encode([
'street' => $value->street,
'city' => $value->city,
'zip_code' => $value->zipCode,
'country' => $value->country,
]);
}
}
// Nel model: 'shipping_address' => AddressCast::classLa classe AddressCast è riutilizzabile su qualsiasi model che abbia un campo indirizzo JSON. Il pattern è particolarmente utile per i gestionali delle PMI dove entità come indirizzi, importi con valuta, coordinate geografiche e preferenze utente ricorrono in più tabelle. I test automatici possono testare ogni cast class in isolamento, indipendentemente dal model che la usa.
Errori comuni nella migrazione dei cast
Il primo errore è migrare per motivi cosmetici su un codebase senza test. La modifica da $casts a casts() è meccanica, ma se un model ha logica di boot complessa o trait che interagiscono con i cast, l'unico modo di verificare che il comportamento sia invariato è attraverso i test. Senza copertura, la migrazione è un rischio inutile per un beneficio estetico.
Il secondo è non sapere che proprietà e metodo vengono fusi. Se un model ha $casts definito in un trait e casts() nella classe, entrambi vengono applicati - con casts() che vince sui duplicati. Questo può causare comportamenti inattesi se il trait definisce un cast che il metodo sovrascrive silenziosamente.
Il terzo è castare a enum senza backed value. Eloquent richiede backed enum (enum Status: string o enum Status: int) per il cast - pure enum senza scalar backing non possono essere salvati nel database. L'errore emerge solo a runtime quando si tenta il salvataggio.
Il quarto è sottovalutare l'impatto delle custom cast class sulle performance di query batch. Un cast che fa json_decode nel metodo get viene eseguito per ogni riga di ogni query che include quell'attributo. Su query che restituiscono centinaia di righe, il tempo cumulativo del casting può diventare significativo - in questi casi, l'ottimizzazione delle query Eloquent deve considerare anche il costo dei cast.
La migrazione al metodo casts() è parte di un percorso più ampio di modernizzazione dell'applicativo Laravel - insieme alla struttura slim di bootstrap/app.php, alla rotazione delle chiavi e al rehashing delle password, e all'aggiornamento a PHP 8 che abilita le enum native. Per conoscere il mio approccio alla modernizzazione di applicativi Laravel, visita la mia pagina professionale. Se i tuoi model Eloquent necessitano di un refactoring strutturato e vuoi pianificare la migrazione senza rischi, contattami per una consulenza dedicata - partiamo dall'inventario dei cast e dalla copertura dei test.