0

I'm using a creating Eloquent model observer to automatically generate a slug before inserting a new product into the database.

Here’s the code from my ProductObserver:

public function creating(Product $product): void
{
    if (empty($product->slug)) {
        $product->slug = ProductSlugManager::handle($product);
        \Log::info('Observer:creating', $product->only(['id', 'slug']));
    }
}

The slug is generated correctly — I can confirm it via the logs:

Observer:creating Product ID=null slug=some-generated-slug

But the problem is that the final INSERT query (captured via DB::listen()) does not include the slug field:

insert into "products" ("name", "description", "created_at", "updated_at") values (?, ?, ?, ?) returning "id"

So the slug is never saved to the database — it appears on the returned model, but not in the DB itself.

Notes: The slug does show up in the returned model after save, but it’s not in the actual SQL statement. Looks like Laravel builds the INSERT query before the observer executes? DB is PostgreSQL if it matters.

How can I ensure that the slug generated in a creating observer is actually included in the INSERT SQL query?

Is this behavior expected when using Octane or model observers? Should I move slug generation elsewhere (e.g., overriding performInsert() or using model boot())? What’s the recommended way to safely mutate attributes just before creation?

1 Answer 1

0

This isn’t an Octane thing. If creating runs, Eloquent will include whatever is in $model->attributes at insert time. If your INSERT is missing slug while your log shows a value, you’re almost certainly reading a computed value (accessor / appended attribute / custom cast get) instead of an actual stored attribute. So Eloquent “sees” nothing to insert for slug.

make you observer like this

public function creating(Product $product): void
{
    if (blank($product->getAttribute('slug'))) {
        $slug = ProductSlugManager::handle($product);

        // bypass mass-assignment and write straight to attributes
        $product->forceFill(['slug' => $slug]);

        // your logs
    }
}

I don't know if you are using custom cast or mutator api.

if using mutator api do tjis

protected function slug(): Attribute
{
    return Attribute::make(
        get: fn ($value) => $value,
        set: fn ($value) => (string) $value,
    );
}

Keeping slug generation in creating (or saving) is the recommended. You don’t need to override performInsert(). Just ensure nothing (accessor/appends/cast) masks the real attribute write.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.