How to make morre readable URIs using unique-id-plus-redundant-information (UPRI) using PHP with Laravel

222 views Asked by At

I would like to know the best (and most consistent) way to add redundant bits to my restful uris so that they are more readable while remaining unchanging when some things such as username change.

I read of the concept at this excellent blog post and it is something like what stack overflow does /users/3836923/inkalimeva where the last segment of the URI is redundant and may change but makes the URI more readable and SEO friendly.

Currently I am using Laravel's Route::resource() but that creates routes with only the id segment.

2

There are 2 answers

3
MartinJH On BEST ANSWER

You can use eloquent-sluggable to create slugs for your users. That way the slug will change when they update their username. You can also simply call their username in the url method, though this will result in uglier urls.

This method still requires that you drop Route::resource() and write your routes explicitly.

Here is the code, tested and working:

ROUTES.PHP (don't mind the route details)

Route::get('route-name/{id}/{slugOrUsernameAsYouPlease}', [
    'as' => 'admin-confirm-detach-admin',
    'uses' => 'AdminController@confirmDetachAdmin'
]);

IN YOUR VIEW

<a href="{{ url('route-name/'. $user->id . '/' . $user->name) }}">Click me!</a>

OR

<a href="{{ url('route-name/'. $user->id . '/' . $user->slug) }}">Click me!</a>

URL RESULT (My users name here is Fnup. Just for testing)

With Username: http://website.local/route-name/8/Fnup

With Slug: http://website.local/route-name/8/fnup

A quick final note

I just changed fnup's username to fnupper and here is the result:

http://website.local/route-name/8/Fnupper

However the slug didn't change automatically. You have to add that code yourself to the user update method. Otherwise the slug stays as what it was the first time the resource was made. Here is my code when using eloquent-sluggable

public function update(UpdateUserRequest $request)
{
    $user = \Auth::user();
    $user->name = $request->name;
    $user->email = $request->email;
    $user->resluggify();
    $user->save();

    session()->flash('message', 'Din profil er opdateret!');
    return redirect()->route('user-show');
}

Which result in: http://website.local/route-name/8/fnupper

New edit per request: Controller method example

Here is my confirmDetachAdmin() method in AdminController.php. Just to clarify, the methods job is to show a "confirm" view before modifying a users status. Just like edit/update & create/store, I made up confirm to accompany destroy (since I'd like a javascript free confirmation option should javascript be disabled).

    public function confirmAttachAdmin($id)
    {
        $user = User::findOrFail($id);

        /* Prevent error if user already has role */
        if ( $user->hasRole('admin')) {
            return redirect()->back();
        }

        return view('admin.confirmAttachAdmin', compact('user')); 
    }

You can add your slug/username as a second parameter if you want to, but I don't see a reason, as you can access it from $user when you find them by id.

4
Hkan On

As opposed to @MartinJH's answer, I don't think you should store your slugs in database if you don't rely only on them in your URIs. A simple link() method on your model, and an explicit route is enough.

App\User

class User extends \Illuminate\Database\Eloquent\Model {
    public function link()
    {
        return route('user-profile', [ $this->id, Str::slug($this->username) ]);
    }
}

routes.php

Route::get('{id}/{username}', [ 'as' => 'user-profile', 'uses' => 'UserController@profile' ])
    ->where('id', '\d+')
    ->where('username', '[a-zA-Z0-9\-\_]+');

App\Http\Controllers\UserController

...

public function profile($id, $username)
{
    $user = \App\User::findOrFail($id);

    return view('profile')->with('user', $user);
}

...