Sven van den Boogaart
Sven van den Boogaart

Reputation: 12323

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

Upvotes: 28

Views: 111754

Answers (9)

me shooo
me shooo

Reputation: 21

$page->slug = Str::slug($request->page_name);

Upvotes: 2

Micheal Shoyemi
Micheal Shoyemi

Reputation: 291

You can also generate your slug using an observer, this approach will make your controller and model looks clean

Below are the steps to add an observer in Laravel:

The first thing you need to do is to run the observer command

php artisan make:observer ActivityObserver --model= Activity

You will then go into the observer class, by default, you may not have a creating method but you will have created, updated, deleted, and so on.

In case you did not see the creating method, then you can add it.

app/Observers/ActivityObserver.php

<?php
namespace App\Observers;
use App\Models\Activity;
class ActivityObserver
{
  /**
  * Handle the Activity "creating" event.
  *
  * @param  \App\Models\Activity  $activity
  * @return void
  */
  public function creating(Activity $activity)
  {
   $activity->slug = \Str::slug($activity->title , '_');
  }
  /**
  * Handle the Activity "created" event.
  *
  * @param  \App\Models\Activity  $activity
  * @return void
  */
  public function created(Activity $activity)
  {
  }
  /**
  * Handle the Activity "updated" event.
  *
  * @param  \App\Models\Activity  $activity
  * @return void
  */
  public function updated(Activity $activity)
  {
  }
}

Note: We use the creating function because we want the slug to be created automatically with the title that was passed before the record is saved, not after the record is saved.

If you want to perform an action immediately after a record is saved then you will have to use the created function.

The next thing is to register the observer class on the provider inside the boot function.

app/Providers/EventServiceProvider.php

<?php
namespace App\Providers;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
use App\Observers\ActivityObserver;
use App\Models\Activity;
class EventServiceProvider extends ServiceProvider
{
  /**
  * The event listener mappings for the application.
  *
  * @var array
  */
  protected $listen = [
  Registered::class => [
   SendEmailVerificationNotification::class,
  ],
  ];
  /**
  * Register any events for your application.
  *
  * @return void
  */
  public function boot()
  {
    Activity::observe(ActivityObserver::class);
  }
}

With the above code, whenever an activity is created, the slug will be automatically created.

By doing this, your code will be cleaner as you don't have to add anything to your controller or model, but you should make the slug column fillable in your model by adding the below code to your model.

  protected $fillable = [
    'slug',
  ];

Upvotes: 0

Mohit Prajapati
Mohit Prajapati

Reputation: 440

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

Upvotes: 1

SomeOne_1
SomeOne_1

Reputation: 1002

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.

Upvotes: 3

Piotr
Piotr

Reputation: 361

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;

Upvotes: 36

user1669496
user1669496

Reputation: 33048

I believe this isn't working because you aren't trying to set a slug attribute so that function never gets hit.

I'd suggest setting $this->attributes['slug'] = ... in your setTitleAttribute() function so it runs whenever you set a title.

Otherwise, another solution would be to create an event on save for your model which would set it there.

Edit: According to comments, it's also necessary to actually set the title attribute in this function as well...

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

Upvotes: 43

Dilworth
Dilworth

Reputation: 626

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,

Upvotes: 16

Collin James
Collin James

Reputation: 9280

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.

Upvotes: 3

Mahmoud Zalt
Mahmoud Zalt

Reputation: 31100

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.

Upvotes: 5

Related Questions