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.
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]]);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With