Reputation: 12323
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
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
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
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
Reputation: 361
$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);
}
//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
use Illuminate\Support\Str;
Upvotes: 36
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
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
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
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