Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating yii2 dynamic pages with url: www.example.com/pageName

In my system, users need to have their profile pages. It is requested from me that these pages will be displayed in url like this:

www.example.com/John-Doe

www.example.com/Mary-Smith

How to achieve these URLs in yii2 ? These John-Doe and Mary-Smith can be user usernames or profile names. For example I have field in user table called "name" and it will hold names "John Doe", "Mary Smith". Pay attention that I need SEO friendly URLs with "-" instead of blank spaces.

URLs like this: www.example.com/profile/view?id=1 are not an option.

like image 849
black-room-boy Avatar asked Jan 16 '15 14:01

black-room-boy


1 Answers

www.example.com/John-Doe

www.example.com/Mary-Smith

I think there is no normal way to use these urls because at first controller (in your case it's ProfileController) needs to be determined. From these urls it's impossible to do.

Second problem with the urls you provided - uniqueness is not guaranteed. What if another user with name John Doe will sign up on site?

Look for example at your profile link at Stack Overflow:

http://stackoverflow.com/users/4395794/black-room-boy

It's not http://stackoverflow.com/black-room-boy and not even http://stackoverflow.com/users/black-room-boy.

Combining id and name is more widespread and robust approach. Also they can be combined with dash like this: http://stackoverflow.com/users/4395794-black-room-boy

Yii 2 has built-in behavior for this, it's called SluggableBehavior.

Attach it to your model:

use yii\behaviors\SluggableBehavior;

public function behaviors()
{
    return [
        [
            'class' => SluggableBehavior::className(),
            'attribute' => 'name',
            // In case of attribute that contains slug has different name
            // 'slugAttribute' => 'alias',
        ],
    ];
}

For your specific url format you can also specify $value:

'value' => function ($event) {
    return str_replace(' ', '-', $this->name);
}

This is just an example of generating custom slug. Correct it according to your name attribute features and validation / filtering before save.

Another way of achieving unique url is setting $ensureUnique property to true.

So in case of John-Doe existense John-Doe-1 slug will be generated and so on.

Note that you can also specify your own unique generator by setting $uniqueSlugGenerator callable.

Personally I don't like this approach.

If you choose the option similar to what Stack Overflow uses, then add this to your url rules:

'profile/<id:\d+>/<slug:[-a-zA-Z]+>' => 'profile/view',

In ProfileController:

public function actionView($id, $slug)
{
    $model = $this->findModel($id, $slug);
    ...
}

protected function findModel($id, $slug)
{
    if (($model = User::findOne(['id' => $id, 'name' => $slug]) !== null) {
        return $model;
    } else {
        throw new NotFoundHttpException('User was not found.');
    }
}

But actually id is enough to find user. Stack Overflow does redirect if you access with correct id but different slug. The redirects occurs when you are completely skipping the name too.

For example http://stackoverflow.com/users/4395794/black-room-bo redirects to original page http://stackoverflow.com/users/4395794/black-room-boy to avoid content duplicates that are undesirable for SEO.

If you want use this as well, modify findModel() method like so:

protected function findModel($id)
{
    if (($model = User::findOne($id) !== null) {
        return $model;
    } else {
        throw new NotFoundHttpException('User was not found.');
    }
}

And actionView() like so:

public function actionView($id, $slug = null)
{
    $model = $this->findModel($id);
    if ($slug != $model->slug) {
        return $this->redirect(['profile/view', ['id' => $id, 'slug' => $model->slug]]);
    }
}
like image 144
arogachev Avatar answered Sep 21 '22 09:09

arogachev