Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама
Новини 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".

9

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

Коментарі

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

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

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

Cache Tags
Новини 26 вересня 2026

Мемоізація читання тегованого кешу в Laravel

Laravel 13.33 додає підтримку тегів до мемоізованого кешу Cache::memo(). Тепер можна ефективно зберігати теговані значення в пам'яті протягом запиту, що особливо корисно для частих операцій перевірки прав доступу та налаштувань.

14
JetBrains Air
Новини 26 вересня 2026

JetBrains Air: запуск AI-агентів у середовищі розробки JetBrains

JetBrains представила JetBrains Air - екосистему продуктів для роботи з AI-агентами в IDE. Система включає безкоштовний плагін для PhpStorm, хмарну платформу для команд та інструменти управління для організацій, підтримуючи агентів від різних провайдерів.

14

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

Key2Law
12 днів тому

Full Stack Developer (Laravel)

Full Stack розробник на Laravel для розробки та підтримки веб-додатків. Відповідальний за реалізацію backend (REST APIs, бізнес-логіка, оптимізація БД) і frontend (Blade, Tailwind, Alpine/Livewire/Vue), тестування, деплой та моніторинг. Вимоги: PHP 8.1+, Laravel 10+, знання Eloquent, Queue, Redis, MySQL, JavaScript ES6+, PHPUnit/Pest, Docker, Git, основи AWS. Бажано досвід з Fintech, криптовалютами, high-availability системами.

Гарант-Інфо
19 днів тому

Middle PHP Developer (Laravel)

Middle PHP Developer для міжнародного B2B-продукту у телекомунікаціях. Розробка нового функціоналу на PHP 8.x/Laravel, підтримка кодової бази, робота з REST API, платіжними інтеграціями та MySQL. Вимоги: 2+ років комерційного досвіду PHP, впевнене знання Laravel, ООП, SOLID, тестування (PHPUnit/Pest), Git, Docker.

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

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