Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама
Новини 25 вересня 2026

Eloquent Refreshes: автоматичне завантаження згенерованих колонок після збереження

Проблема застарілих даних у моделях

Під час роботи з Eloquent моделями, які мають колонки зі значеннями, згенерованими базою даних, виникає типова проблема: після створення або оновлення моделі ці колонки залишаються порожніми або містять застарілі дані.

Розглянемо таблицю orders, де база даних автоматично обчислює колонку total на основі двох інших колонок:

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();
});

Хоча колонку total можна індексувати та використовувати в запитах як звичайну колонку, модель, отримана після create(), не містить її значення:

$order = Order::create(['subtotal' => 100, 'tax' => 8.25]);
$order->total; // null
$order->update(['tax' => 9.00]);
$order->total; // все ще null

До виходу Laravel 13.33 доводилося вручну викликати $order->refresh() після кожного запису, що перезавантажувало всі колонки та всі завантажені зв'язки.

Атрибут #[Refreshes]

Caleb White додав атрибут #[Refreshes] у pull request #61523. Цей атрибут розміщується на моделі зі списком колонок, які встановлює база даних:

use Illuminate\Database\Eloquent\Attributes\Refreshes;
use Illuminate\Database\Eloquent\Model;

#[Refreshes('total')]
class Order extends Model
{
    protected $fillable = ['subtotal', 'tax'];
}

Після кожної операції вставки або оновлення Eloquent автоматично виконує SELECT цих колонок для збереженого рядка та копіює їх у модель:

$order = Order::create(['subtotal' => 100, 'tax' => 8.25]);
$order->total; // "108.25"
$order->update(['tax' => 9.00]);
$order->total; // "109.00"

Для оновлення кількох колонок можна передати масив або кілька аргументів. Обидва варіанти #[Refreshes(['total', 'slug'])] та #[Refreshes('total', 'slug')] працюють однаково.

Альтернативна конфігурація через властивість

Якщо ви налаштовуєте моделі через властивості замість атрибутів, можна використати властивість $refreshes:

class Order extends Model
{
    protected array $refreshes = ['total'];
}

Якщо модель має обидва варіанти конфігурації, використовується властивість, а атрибут ігнорується. Модель без жодного з них не виконує додаткових запитів.

Згенеровані значення в подіях моделі

Оновлення виконується після INSERT або UPDATE, але перед тим, як Eloquent викликає події created або updated. Це означає, що обробники цих подій бачать нові значення. Спостерігач (observer), який надсилає квитанцію, може прочитати total, обчислений базою даних:

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));
        }
    }
}

Перевірка wasChanged('total') працює коректно, оскільки Eloquent реєструє зміни після оновлення. Хоча ваш код ніколи безпосередньо не встановлює total, ця колонка з'являється у getChanges(), коли база даних обчислює нове значення.

Які операції запису запускають оновлення

Оновлення виконується для екземпляра моделі в таких сценаріях:

  • Вставки через create(), save(), saveQuietly() та saveOrIgnore()
  • Оновлення через update(), save() та saveQuietly()
  • Виклики increment/decrement: increment(), decrement(), incrementEach() та decrementEach()

Як працює запит оновлення

Оновлення виконується одним запитом SELECT для перелічених колонок з обмеженням по первинному ключу моделі:

select `total` from `orders` where `id` = ? limit 1

Запит виконується на з'єднанні для запису. При використанні read replicas репліка може ще не мати нового рядка.

Запит також пропускає глобальні scope, що означає, що tenant або soft-delete scope не можуть приховати рядок, який Eloquent щойно зберіг. Використовується firstOrFail(): якщо рядок зникне до моменту виконання запиту, збереження викине ModelNotFoundException.

#[Refreshes] або refresh()

Атрибут #[Refreshes] підходить для колонок, які ви читаєте одразу після запису: колонки storedAs та virtualAs, значення, встановлені тригерами, та значення за замовчуванням з бази даних, які Eloquent не встановлює. Атрибут додає запит до кожного запису, тому вказуйте лише ті колонки, які ви дійсно читаєте.

Викликайте $model->refresh(), коли потрібно перечитати весь рядок або завантажені зв'язки. Також можна використовувати його для разових операцій запису, де додавання атрибута до моделі створило б додатковий запит при кожному іншому збереженні.

Документація Eloquent висвітлює цей атрибут у розділі "Refreshing Attributes After Writes".

10

Читати в документації

Коментарі

Увійдіть, щоб залишити коментар

Будьте першим, хто залишить коментар!

Читайте також

Filament v5.10.0
Новини 12 жовтня 2026

Filament v5.10.0: сітка в rich editor, скелетони таблиць і десятки виправлень

Вийшов Filament v5.10.0: елементи керування сіткою в rich editor, допоміжний контент для віджетів-графіків, скелетони завантаження таблиць, submitOnCompletion() для OneTimeCodeInput та багато виправлень авторизації, доступності й стану.

17
Laravel AI SDK на практиці
Новини 09 жовтня 2026

Laravel AI SDK на практиці: агент, що стоїть за There There

Фрік Ван дер Гертен розповідає, як у Spatie побудували AI-шар хелпдеска There There на Laravel AI SDK: клас агента, інструменти з типізованою JSON-схемою та делегування роботи звичайним domain-діям.

12

Вакансії за темою

Z Global E-Commerce
16 днів тому

Full-Stack Developer (PHP/Laravel + Vue 3)

Full-Stack розробник з глибоким досвідом бекенду. Розробляєте масштабовані веб-платформи з комплексною бізнес-логікою, працюєте з PHP/Laravel, SQL, Vue 3/TypeScript. Основні вимоги: досвід з Laravel, Eloquent, проектування API, оптимізація баз даних (включно з мільйонами записів), фронтенд на Vue 3 з Composition API, TypeScript. Цінується розуміння архітектури, продуктивності й довгострокової підтримуваності коду.

Wooden Sword
17 днів тому

Senior PHP Developer (Game Backend)

Розробник backend для мобільної RPG з високим навантаженням. Основний стек: PHP Laravel API та TypeScript Nakama Game Server. Відповідальність за повний цикл розробки функцій — від логіки до тестування, code review, оптимізації та документації. Вимоги: 5+ років backend-досвіду, бездоганне знання Laravel/PHP, сильні навички TypeScript, PostgreSQL та асинхронного програмування.

Kredyt Kapital
17 днів тому

Middle Full-Stack Developer (PHP / Laravel / Vue / React)

Middle Full-Stack Developer для розробки внутрішніх IT-продуктів. Основний стек: PHP 8.3+, Laravel, PostgreSQL, REST API, Vue.js/React. Розробник займатиметься backend (бізнес-логіка, Eloquent, Jobs/Queues) і frontend (компоненти, інтерфейси), інтеграціями та оптимізацією. Вимоги: 2-3+ років комерційного досвіду з PHP, 2+ років практики з Laravel, впевнене знання PostgreSQL, Git, JavaScript ES6+.

Пакети за темою

Sushi

calebporzio/sushi

Додатковий драйвер для Eloquent, який дозволяє працювати з масивами даних як з повноцінними Eloquent моделями.

3,007 v2.5.4 13 16

Eloquent Has Many Deep

staudenmeir/eloquent-has-many-deep

Laravel Eloquent відносини HasManyThrough з необмеженою кількістю рівнів вкладеності.

2,873 v1.22.1 8