'Laravel generate slug before save

Im trying to learn laravel 5 with help of this wondefull website. For my activity model I want to generate slugs before I save one to my database so I've created the following model.

<?php namespace App;

use Illuminate\Database\Eloquent\Model;

class Activity extends Model {

    protected $table = 'activitys';

    protected $fillable = [
        'title',
        'text',
        'subtitle'
    ];

    // Here I want to auto generate slug based on the title
    public function setSlugAttribute(){
        $this->attributes['slug'] = str_slug($this->title , "-");
    }
    //    
}

But when I save an object with help of the Activity model slug is not filled, i tried changing it to $this->attributes['title'] = "test" for testing but it didnt run. Also I tried adding parameters $title, $slug to setSlugAttribute() but it didnt help.

What am I doing wrong and could someone explain the parameter that is used in some examples for setSomeAttribute($whyParameterHere).

Note : there is a slug field in my database.

As suggested by user3158900 I've tried :

public function setTitleAttribute($title){
    $this->title = $title;
    $this->attributes['slug'] = str_slug($this->title , "-");
}
//

This makes my title field empty but saves the slug the way I want it, why is $this->title empty then ? If I remove $this->title = $title; both title and slug are empty



Solution 1:[1]

You got 2 ways:

1. Add localy in your controller method this line:

$request['slug'] = Str::slug($request->title);

Example:

//use Illuminate\Support\Str;

public function store(Request $request)
{
    $request['slug'] = Str::slug($request->title);

    auth()->user()->question()->create($request->all());
    return response('Created!',Response::HTTP_CREATED);
}

2. Add it in your model to check it every save in db

//use Illuminate\Support\Str;

protected static function boot() {
    parent::boot();

    static::creating(function ($question) {
        $question->slug = Str::slug($question->title);
    });
}

Example:

<?php

namespace App\Model;

use Illuminate\Database\Eloquent\Model;
use App\User;
use Illuminate\Support\Str;

class Question extends Model
{
    protected static function boot() {
        parent::boot();

        static::creating(function ($question) {
            $question->slug = Str::slug($question->title);
        });
    }

//The rest of methods

In each way you have to add this code before class declaration:

use Illuminate\Support\Str;

Solution 2:[2]

One way to accomplish this would be to hook into model events. In this instance, we want to generate a slug upon creating.

/**
 * Laravel provides a boot method which is 'a convenient place to register your event bindings.'
 * See: https://laravel.com/docs/4.2/eloquent#model-events
 */
public static function boot()
{
    parent::boot();

    // registering a callback to be executed upon the creation of an activity AR
    static::creating(function($activity) {

        // produce a slug based on the activity title
        $slug = \Str::slug($news->title);

        // check to see if any other slugs exist that are the same & count them
        $count = static::whereRaw("slug RLIKE '^{$slug}(-[0-9]+)?$'")->count();

        // if other slugs exist that are the same, append the count to the slug
        $activity->slug = $count ? "{$slug}-{$count}" : $slug;

    });

}

You will also need to add the following to your applications list of aliases (app.php):

'Str' => Illuminate\Support\Str::class,

Solution 3:[3]

You could use this package which I use https://github.com/cviebrock/eloquent-sluggable or check how it applies an observer on the model saving and how it generates a unique Slug, then do the same.

Solution 4:[4]

You want to set the slug based off the title when the title attribute is being set.

public function setTitleAttribute($value)
{
    $this->attributes['title'] = $value;
    $this->attributes['slug'] = str_slug($value);
}

/// Later that same day...

$activity->title = 'Foo Bar Baz';

echo $activity->slug; // prints foo-bar-baz

Another alternative would be to use a ModelObserver and listen to the saving event. This will allow you to generate the slug right before the model is written to the database.

class ActivityObserver {

    public function saving($activity) 
    {
        $activity->slug = str_slug($activity->title);
    }
}

In both cases you probably want to add some logic to test if the slug already exists in the DB, adding an incrementing number if it does. ie foo-bar-baz-2. The safest place for this logic would be in the ModelObserver as it is executed immediately prior to the write action.

Solution 5:[5]

You can use this method. This is one that I am using to get unique SEO friendly slug. This will work in all laravel versions. https://stackoverflow.com/a/72137537/7147060

Solution 6:[6]

It's an old post, but this is what you find these days when searching for modern solutions, so here is a modern solution:

When using Laravel ^9.x, the Attribute mutator can be used, and must look something like this:

use Str;
use Illuminate\Database\Eloquent\Casts\Attribute;

protected function name(): Attribute
{
    return Attribute::make(
        set: fn($value) => ['slug' => Str::slug($value), 'name' => $value]
    );
}

Setting it with $this->slug inside the closure won't work as the result vanishes in a merge.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Community
Solution 2 Dilworth
Solution 3 Mahmoud Zalt
Solution 4 Collin James
Solution 5 Mohit Prajapati
Solution 6 SomeOne_1