laravel-updateorcreate-replace-first-if-tip.png
Шаблон кодуLaravel

Laravel updateOrCreate(): замість ручного first() та if

Як замінити ручний first() з гілками update/create на updateOrCreate(), правильно розділити умови пошуку й дані для оновлення, використати wasRecentlyCreated та не плутати цей метод з атомарним upsert().

Коли код має знайти запис, а потім або оновити його, або створити новий, дуже легко отримати зайвий first(), ручний if і дві майже однакові гілки.

updateOrCreate() виражає цей намір напряму: знайти модель за одними атрибутами, а потім зберегти потрібні значення незалежно від того, чи запис уже існував.

Базова заміна first() та if

До
php
$setting = UserSetting::query()    ->where('user_id', $userId)    ->where('key', 'timezone')    ->first(); if ($setting) {    $setting->update([        'value' => $timezone,    ]);} else {    $setting = UserSetting::create([        'user_id' => $userId,        'key' => 'timezone',        'value' => $timezone,    ]);}
Після
php
$setting = UserSetting::updateOrCreate(    [        'user_id' => $userId,        'key' => 'timezone',    ],    [        'value' => $timezone,    ],);

Код став коротшим не тому, що ми сховали той самий if у helper заради стилю. Тепер структура виклику показує дві різні ролі даних: як знайти запис і що потрібно в ньому зберегти.

Перший масив - ідентичність, другий - новий стан

PHP - Розділяємо lookup attributes та values
$subscription = Subscription::updateOrCreate(    [        'user_id' => $userId,        'provider' => 'github',    ],    [        'external_id' => $externalId,        'status' => $status,        'expires_at' => $expiresAt,    ],);

Перший масив використовується для пошуку відповідного запису. Якщо запису немає, ці атрибути також стають частиною нової моделі.

Другий масив містить значення, які треба встановити. Для наявної моделі вони оновлюються, а для нової - додаються до атрибутів із першого масиву.

Не кладіть у lookup array поля, які регулярно змінюються. Якщо status або last_seen_at є частиною пошуку, зміна цього значення може змусити updateOrCreate() шукати вже інший запис замість оновлення існуючого.

Метод повертає збережену Eloquent модель

PHP - Повернена модель уже збережена
$profile = Profile::updateOrCreate(    ['user_id' => $user->id],    [        'display_name' => $displayName,        'bio' => $bio,    ],); $profileId = $profile->id;

Окремий save() після updateOrCreate() не потрібний. Метод сам зберігає модель і повертає її, тому можна одразу працювати з id, relations, casts та іншими властивостями Eloquent.

Як дізнатися, запис створили чи оновили

PHP - wasRecentlyCreated після updateOrCreate()
$setting = UserSetting::updateOrCreate(    [        'user_id' => $userId,        'key' => 'locale',    ],    [        'value' => $locale,    ],); if ($setting->wasRecentlyCreated) {    // A new row was inserted.}

wasRecentlyCreated корисний, якщо після створення потрібна окрема побічна дія. При цьому сама persistence logic залишається однією операцією на рівні application intent.

updateOrCreate() і firstOrCreate() мають різний намір

PHP - firstOrCreate() проти updateOrCreate()
// Existing row is returned unchanged.$setting = UserSetting::firstOrCreate(    ['user_id' => $userId, 'key' => 'theme'],    ['value' => 'dark'],); // Existing row receives the new value.$setting = UserSetting::updateOrCreate(    ['user_id' => $userId, 'key' => 'theme'],    ['value' => $theme],);

firstOrCreate() добре підходить, коли наявний запис потрібно просто повернути без зміни. updateOrCreate() потрібний, коли другий масив повинен стати актуальним станом навіть для вже існуючої моделі.

Метод працює і через Eloquent relationship

PHP - updateOrCreate() через relationship
$setting = $user->settings()->updateOrCreate(    [        'key' => 'timezone',    ],    [        'value' => $timezone,    ],);

Якщо модель природно належить до parent model, виклик через relationship часто виразніший. Relationship уже задає контекст батьківської моделі, а в lookup array залишається тільки бізнес-ключ дочірнього запису.

Важливо: це не гарантія меншої кількості SQL queries

updateOrCreate() прибирає ручні гілки програми, але його не варто описувати як магічний спосіб завжди зменшити кількість SQL запитів.

Laravel спочатку повинен визначити, чи існує відповідна модель. Для існуючого запису після пошуку може виконуватися окремий update. Тому головна перевага тут - менш шаблонний і точніший намір програми, а не обіцянка одного SQL-запиту.

Якщо вам потрібна саме одна атомарна операція з базою даних для вставки або оновлення запису, використовуйте upsert(), а не припускайте, що updateOrCreate() завжди виконується одним SQL-запитом.

Унікальність повинна гарантувати база даних

PHP - Unique constraint для lookup identity
Schema::table(    'user_settings',    function (Blueprint $table) {        $table->unique([            'user_id',            'key',        ]);    },);

Якщо пара user_id + key логічно ідентифікує один запис, це правило повинно існувати не тільки в PHP. Unique index захищає інваріант на рівні БД, у тому числі коли кілька requests виконуються паралельно.

updateOrCreate() робить код виразним, але обмеження бази даних залишається остаточною гарантією того, що одна бізнес-ідентичність не перетвориться на дублікати.

Для bulk або atomic upsert використовуйте upsert()

PHP - Bulk upsert для кількох записів
UserSetting::upsert(    [        [            'user_id' => $userId,            'key' => 'timezone',            'value' => $timezone,        ],        [            'user_id' => $userId,            'key' => 'locale',            'value' => $locale,        ],    ],    [        'user_id',        'key',

upsert() краще підходить, коли потрібно обробити кілька записів однією операцією з базою даних або коли важлива саме атомарна логіка вставки чи оновлення на рівні БД.

updateOrCreate() натомість повертає конкретну Eloquent model і добре читається в звичайній application logic, де працюємо з одним записом.

Коли updateOrCreate() не варто використовувати

  • коли існуючий запис не треба оновлювати - використовуйте firstOrCreate();

  • коли потрібно лише отримати незбережену модель - подивіться на firstOrNew();

  • коли оновлюєте багато рядків - розгляньте upsert() або звичайний bulk update;

  • коли lookup identity не має чіткого unique constraint - спочатку визначте інваріант даних;

  • коли update/create гілки справді мають різну складну бізнес-логіку - один updateOrCreate() може приховати важливу різницю.

Основна користь updateOrCreate() не в тому, що він робить збереження даних "магічно дешевшим". Він прибирає зайву складність із коду і дозволяє прямо сказати: ось ідентичність запису, ось його актуальний стан, збережи його.

#eloquent #laravel-12 #laravel-13 #php-8-4 #php-8-5 #update-or-create #model-persistence #upsert

Схожі поради

laravel-firstorcreate-replace-first-create-tip.pngПорада
Переглянути пораду
#eloquent #laravel-12 #laravel-13 #php-8-4 #php-8-5 #model-persistence #first-or-create #first-or-new
laravel-withwherehas-avoid-duplicate-conditions-tip.pngПорада
Переглянути пораду
#eloquent #laravel-12 #laravel-13 #php-8-4 #php-8-5 #where-has #with-where-has #eager-loading #relationships
laravel-tap-side-effects-keep-original-value-tip.pngПорада
Переглянути пораду
#eloquent #laravel-12 #laravel-13 #php-8-4 #php-8-5 #tap #helpers #tappable
laravel-whererelation-replace-wherehas-tip.pngПорада
Переглянути пораду
#eloquent #laravel-12 #laravel-13 #php-8-4 #php-8-5 #where-relation #where-has #relationship-queries