Eloquent Refreshes: caricare le colonne generate dopo il salvataggio
Se create un model Eloquent e leggete subito una colonna generata, ottenete null. Quando aggiornate un model, la colonna conserva il vecchio valore finché non chiamate refresh().
Laravel 13.33 ha introdotto l'attributo #[Refreshes] per risolvere questo potenziale problema: elencate le colonne che il database calcola e Eloquent le rilegge dopo ogni insert e update.
Vediamo come usare questo trait per caricare automaticamente le colonne generate dopo il salvataggio, senza dovervi ricordare di fare refresh.
Colonne generate e model obsoleti
Prendiamo una tabella orders in cui il database calcola total a partire da altre due colonne:
Schema::create('orders', function (Blueprint $table) {
$table->id();
$table->decimal('subtotal', 10, 2);
$table->decimal('tax', 10, 2);
$table->decimal('total', 10, 2)->storedAs('subtotal + tax');
$table->timestamps();
});
Potete interrogare e indicizzare total come qualsiasi altra colonna, ma il model che ottenete da create() non la contiene:
$order = Order::create(['subtotal' => 100, 'tax' => 8.25]);
$order->total; // null
$order->update(['tax' => 9.00]);
$order->total; // still null
Prima di questa release dovevate chiamare manualmente $order->refresh() dopo ogni scrittura, il che ricarica ogni colonna e ogni relazione già caricata.
Aggiungere l'attributo Refreshes
Caleb White ha aggiunto #[Refreshes] nella PR #61523. Mettetelo sul model ed elencate le colonne che il database imposta:
use Illuminate\Database\Eloquent\Attributes\Refreshes;
use Illuminate\Database\Eloquent\Model;
#[Refreshes('total')]
class Order extends Model
{
protected $fillable = ['subtotal', 'tax'];
}
Dopo ogni insert o update, Eloquent seleziona quelle colonne per la riga salvata e le copia sul model:
$order = Order::create(['subtotal' => 100, 'tax' => 8.25]);
$order->total; // "108.25"
$order->update(['tax' => 9.00]);
$order->total; // "109.00"
Per aggiornare più di una colonna, passate un array o più argomenti. #[Refreshes(['total', 'slug'])] e #[Refreshes('total', 'slug')] fanno la stessa cosa.
Se configurate i model con proprietà invece che con attributi, impostate $refreshes:
class Order extends Model
{
protected array $refreshes = ['total'];
}
Quando un model ha entrambi, viene usata la proprietà e l'attributo viene ignorato. Un model che non ha né l'una né l'altro non esegue nessuna query aggiuntiva.
Valori generati negli eventi del model
Il refresh viene eseguito dopo l'INSERT o l'UPDATE e prima che Eloquent scateni created o updated, quindi i listener di entrambi gli eventi vedono il nuovo valore. Un observer che invia una ricevuta può leggere il totale calcolato dal database:
class OrderObserver
{
public function created(Order $order): void
{
Mail::to($order->customer)->send(new OrderReceipt($order));
}
public function updated(Order $order): void
{
if ($order->wasChanged('total')) {
$order->customer->notify(new OrderTotalChanged($order));
}
}
}
Il controllo wasChanged('total') funziona perché Eloquent registra le modifiche dopo il refresh. Il vostro codice non imposta mai total, ma la colonna compare in getChanges() quando il database calcola un nuovo valore.
Quali scritture attivano un refresh
Il refresh viene eseguito su un'istanza del model in questi scenari:
- Insert da
create(),save(),saveQuietly()esaveOrIgnore() - Update da
update(),save()esaveQuietly() - Chiamate di increment/decrement:
increment(),decrement(),incrementEach()edecrementEach()
Come funziona la query di refresh
Il refresh è una sola SELECT per le colonne elencate, limitata alla primary key del model:
select `total` from `orders` where `id` = ? limit 1
La query viene eseguita sulla connessione di scrittura. Con le read replica, una replica potrebbe non avere ancora la nuova riga.
Inoltre salta i global scope, il che significa che uno scope di tenant o di soft delete non può nascondere la riga appena salvata da Eloquent. E usa firstOrFail(): se la riga è sparita nel momento in cui viene eseguita la query, il salvataggio lancia una ModelNotFoundException.
Refreshes o refresh()
#[Refreshes] è adatto alle colonne che leggete subito dopo una scrittura: colonne storedAs e virtualAs, valori impostati da un trigger e default del database che Eloquent non imposta. L'attributo aggiunge una query a ogni scrittura, quindi elencate solo le colonne che rileggete.
Chiamate $model->refresh() quando vi serve rileggere l'intera riga o le relazioni caricate. Potete anche chiamarlo per una scrittura isolata, dove aggiungere l'attributo al model metterebbe una query extra su ogni altro salvataggio.
La documentazione di Eloquent tratta l'attributo nella sezione "Refreshing Attributes After Writes".
Per approfondire
- Tagged Memoized Cache and Model Refreshes in Laravel 13.33, il post di rilascio
- Una guida agli eventi dei model di Laravel
- Consigli sui model di Laravel
- Laravel 13 Released: PHP 8.3, Attributes, Laravel AI, and a Smoother Upgrade Path, che ha introdotto la configurazione dei model basata sugli attributi
Fonte originale:
https://laravel-news.com/eloquent-refreshes-attributeAltri articoli recenti
Laravel AI 1.1 e 1.2: Agent Skills, Cohere e structured output più solidi
Le release 1.1.0 e 1.2.0 di laravel/ai portano gli Agent Skills, l'approvazione obbligatoria per i tool built-in e nuovi provider come Cohere e Laya self-hosted. Sul fronte qualità arrivano i fix allo structured output per Bedrock e il supporto alle provider options. Aggiornati anche i modelli di default, con Claude 4.7+ che ignora i sampling params.
Laravel 13.35.0: scheduler più controllabile, code più flessibili e response Markdown
Laravel 13.35.0 porta Schedule::alwaysOnOneServer(), il supporto a schedule:interrupt per i comandi di lunga durata, --memory in percentuale per queue:work e il nuovo metodo markdown() sulla response factory. Tante piccole migliorie anche a test, view compilate in parallelo e dischi local.
BashBox: un interprete Bash in sandbox per agenti AI in PHP
BashBox è un interprete bash scritto in puro PHP che permette di eseguire gli script generati dagli agenti AI senza mai ricorrere a proc_open, exec o shell_exec. Ogni comando è una classe PHP che lavora su un filesystem virtuale, con limiti di esecuzione, quote disco e accesso di rete configurabili. Ideale per chi sviluppa agenti con il Laravel AI SDK o altre librerie simili.