In Eloquent, how do firstOrNew(), firstOrCreate() and updateOrCreate() differ, and which of them write to the database?
answer
- search array plus values array
- firstOrNew returns an unsaved model
- firstOrCreate falls back to createOrFirst
- updateOrCreate saves only changed columns
- wasRecentlyCreated shows which branch ran
basics
~10 sfirstOrNew() returns the found row or a new unsaved model; firstOrCreate() inserts when nothing matches; updateOrCreate() inserts when missing or fills and saves the found row. Only the last two write.
solid answer
~40 sAll three search with the first array and use the second for extra columns. `firstOrNew()` returns the matching model or a **new, unsaved** instance built from both arrays, so it never writes. `firstOrCreate()` inserts when nothing matches; in Laravel 13 it does that through `createOrFirst()`, which catches a unique-constraint violation and re-selects the row, so a race between two workers is safe **if** the column has a unique index. `updateOrCreate()` calls `firstOrCreate()` and, when the row already existed, fills the second array and calls `save()`, which writes only changed columns. `wasRecentlyCreated` tells you which branch ran, and all three go through `fill()`, so the columns must be mass assignable.
go deeper
Recall that firstOrNew does not save, firstOrCreate saves a new row, and updateOrCreate also updates an existing one.
Explain the two arrays, the select-then-write sequence, the createOrFirst fallback and why updateOrCreate may issue no UPDATE.
Show that race safety comes from a unique index, and switch to batched upserts when per-row round trips dominate an import.
Decide where idempotency for imports lives, database constraints, application helpers or both, and what the team relies on under concurrency.
## Three helpers for "find it or make it" Imports and sync jobs constantly ask: does this row exist yet? Eloquent has three helpers that combine the lookup with the fallback. Each takes two arrays: **`$attributes`**, the columns used to search, and **`$values`**, extra columns used only when a new model is built (or, for `updateOrCreate`, applied either way). | Method | Existing row | No row | Database writes | |---|---|---|---| | `firstOrNew($attributes, $values)` | returns it | returns a **new, unsaved** model | none | | `firstOrCreate($attributes, $values)` | returns it | **inserts** and returns it | one insert when missing | | `updateOrCreate($attributes, $values)` | fills `$values` and **saves** | inserts and returns it | insert, or an update of changed columns | All three build models through `fill()`, so the columns involved must be mass assignable on the model. ## Tracing each one in Laravel 13 1. `firstOrNew()` runs `where($attributes)->first()`. If nothing comes back it returns `new Product(array_merge($attributes, $values))`; you decide whether to `save()` it. 2. `firstOrCreate()` runs the same select. If nothing comes back it calls `createOrFirst()`, which tries the insert first and, if the database raises a unique-constraint violation, re-selects the row on the write connection and returns that instead. Inside an open transaction the insert runs in a savepoint so the failure does not abort the outer transaction. 3. `updateOrCreate()` calls `firstOrCreate()`. When the model was found rather than created (`wasRecentlyCreated` is false), it runs `fill($values)->save()`. `save()` only writes columns that actually changed, so an unchanged row costs no UPDATE at all. `$values` may also be a closure. For `firstOrNew()` and `firstOrCreate()` it is evaluated only when a new model is needed, which avoids computing an expensive default for rows that already exist. ## The nightly feed case An electronics store imports a supplier feed every night, keyed by `sku`: ```php foreach ($feed as $row) { $product = Product::updateOrCreate( ['sku' => $row['sku']], ['name' => $row['name'], 'price_cents' => $row['price_cents'], 'stock' => $row['stock']], ); } ``` - A new SKU is inserted with all four columns. - A known SKU gets only its changed columns written, with `updated_at` bumped; if nothing changed, no UPDATE runs. - `$product->wasRecentlyCreated` tells the loop which branch ran, which is handy for counting new versus updated items in the import report. ## Races and the unique index Two workers importing overlapping feeds can both miss the same SKU in their select. What happens next depends on the schema: - **With a unique index on `sku`**, one insert wins, the other hits the constraint, and `createOrFirst()` turns that into a re-select. No duplicate, no crash. - **Without one**, both inserts succeed and the catalogue now has two rows for the same SKU. So the helper's safety comes from the database constraint, not from the method name. `createOrFirst()` can also be called directly when inserts are the common case: it saves the initial select entirely. ## Cost at scale Each call is one select plus, when needed, an insert or an update: per-row round trips. For a few hundred rows that is fine. For a feed of hundreds of thousands, a batched `upsert()` is the usual answer, at the price of skipping model events and casts. ## Details that trip people up - **Mass assignment still applies.** The helpers build models through `fill()`, so a column that is not fillable is dropped from the new row, or throws `MassAssignmentException` on a model with no fillable settings at all. - **The search array is merged into the new row.** `firstOrCreate(['sku' => 'TV-1'], ['name' => 'OLED 55'])` inserts both `sku` and `name`; you do not repeat the search columns in the values. - **Values are not part of the search.** A row with the same SKU but a different name is still "found"; only `$attributes` go into the `where`. - **`incrementOrCreate()`** is a relative for counters: it creates the row with a default count or increments the column on the existing one. - **Model events still fire.** Because the helpers create and save real models, `creating`, `created`, `updating` and `updated` observers run on the relevant branch.
- Is `firstOrCreate()` safe when two queue workers import the same SKU at the same time?Only with a unique index on the searched column. Both workers may miss the row in their select; the second insert then hits the constraint, and Laravel 13's `createOrFirst()` fallback catches the `UniqueConstraintViolationException` and re-selects the winner's row. Without the index, both inserts succeed and you get duplicate products.
- Why might `updateOrCreate()` run no UPDATE even though the row exists?After filling the values it calls `save()`, and `save()` on an existing model only performs an update when `isDirty()` is true. If the feed row carries the same name, price and stock as the database, nothing is dirty, so no statement runs and `updated_at` is left alone.
- When would you pick `firstOrNew()` over `firstOrCreate()`?When the new model needs more work before it may be saved, such as computing a slug, attaching a relation that needs the parent object, or skipping the save for invalid rows. `firstOrNew()` gives you the unsaved instance and leaves the `save()` decision to you.
saying these in an interview costs you the question
- firstOrNew() inserts a row when nothing matches
- updateOrCreate() runs one atomic database statement
- firstOrCreate() prevents duplicates even without a unique index
- updateOrCreate() overwrites every column on each call
- The values array is also used to search for the row