0

I'm creating url friendly in my app, but it's not working, the app is giving me some issues related with "-". It's giving me an error of:

ErrorException in PostController.php line 60:
Trying to get property of non-object

My ideal URL is:

http://domain.com/CATEGORY-title-of-post-ID

My route is:

Route::get('{category}-{title}-{id}', 'PostController@show');

PostController show function:

public function show($category,$title,$id)
    {
        $post = Post::find($id);
        $user = Auth::user();

        $comments = Comment::where('post_id',$id)
                            ->where('approved',1)
                            ->get();




        return view('posts.show',compact('post','comments','user'));
    }

Blade View:

 <?php
     $title_seo = str_slug($feature->title, '-');
 ?>
 <a href="{{url($feature->categories[0]->internal_name."-".$title_seo."-".$feature->id)}}" rel="bookmark">
...</a>
Icarus
  • 1,627
  • 7
  • 18
  • 32
Pedro
  • 1,459
  • 6
  • 22
  • 40

2 Answers2

2

There's a library called Eloquent-Sluggable that will create a unique slug for each post and correctly URL encode it.

To install (taken from the docs):

composer require cviebrock/eloquent-sluggable:^4.1

Then, update config/app.php by adding an entry for the service provider.

'providers' => [
    // ...
    Cviebrock\EloquentSluggable\ServiceProvider::class,
];

Finally, from the command line again, publish the default configuration file:

php artisan vendor:publish --provider="Cviebrock\EloquentSluggable\ServiceProvider"

To use, add the Sluggable trait to your model:

use Cviebrock\EloquentSluggable\Sluggable;

class Post extends Model
{
    use Sluggable;

    /**
 * Return the sluggable configuration array for this model.
 *
 * @return array
 */
public function sluggable()
{
    return [
        'slug' => [
            'source' => 'title'
            ]
        ];
    }

}

When you save an instance of your model, the library will automatically create a slug and save it to the newly created slug column of your model's table. So to access the slug you'd use $model->slug

To achieve your desired slug, rather than create it from title set by default. You can pass the source attribute of the sluggable method an array of field names, using a dot notation to access the attributes of a related model, like so:

public function sluggable()
{
    return [
        'slug' => [
            'source' => ['category.name','title','id']
            ]
        ];
    }

}
TimothyBuktu
  • 2,016
  • 5
  • 21
  • 35
  • Hello thanks for the response, i just implement it, and is already saving it in my database, but i have one question, how i would structure it in my route? It still giving me the error. – Pedro Jan 16 '17 at 12:55
  • Is there a reason you have to use `{category}-{title}-{id}`? I'm thinking that because you're seperating each by a dash, and sluggable uses dashes too, it's not sure when your category ends and title begins. If you use `title` like in the default example you can pass it to your controller and do `Posts::where('slug',$slug)->first();`. If you do still want to use category, it would be best to seperate using slashes (`/`) and reflect that in your route – TimothyBuktu Jan 16 '17 at 13:04
2

Why are you genering your "friendly URL" manually?

You have route helper function that builds for you a URL based on the given parameters.

Route::get('{category}-{title}-{id}', [
    'as => 'post.show', 
    'uses' => 'PostController@show'
]);

echo route('post.show', ['testing', 'title', 'id']); // http://domain.dev/testing-title-id

This is not the best approach to implement SEO friendly URLs, anyway.


In your controller you ALWAYS use your ID to find a post, that means that category and title are completely useless to determine which resource needs to be served to the user.

You can make your life easier by doing something like:

Route::get('{id}-{slug}', [
    'as => 'post.show', 
    'uses' => 'PostController@show'
]);

echo route('post.show', ['id', 'slug']); // http://domain.dev/id-slug

In your model you create an helper function that generates the slug for your post:

class Post
{
    [...]

    public function slug()
    {
        return str_slug("{$this->category}-{$this->title}");
    }
}

Then, in your controller you need to check that the slug used to access the article is correct or not, since you don't want Google to index post with wrong slugs. You essentially force a URL to be in a certain way, and you don't lose index points.

class PostController 
{
    [...]

    public function show($id, $slug)
    {
        $post = Post::findOrFail($id);
        $user = Auth::user();

        if ($post->slug() !== $slug) {
            return redirect()->route('posts.show', ['id' => 1, 'slug' => $post->slug()]);
        }

        $comments = Comment::where('post_id', $id)->where('approved', 1)->get();
        return view('posts.show', compact('post', 'comments', 'user'));
    }
}
GiamPy
  • 3,543
  • 3
  • 30
  • 51