If you're asking, "What's Yii?" check out my earlier tutorial: Introduction to the Yii Framework, which reviews the benefits of Yii and includes an overview of what's new in Yii 2.0, released in October 2014.
In this Programming With Yii2 series, I'm guiding readers in use of the newly upgraded Yii2 Framework for PHP. In this tutorial, I'm going to show you how to modify Yii's default view URL routes for model objects to be more user friendly and search engine friendly. Yii provides built-in support for this via its sluggable behaviors.
For these examples, we'll continue to imagine we're building a framework for posting simple status updates, e.g. our own mini-Twitter.
Just a reminder, I do participate in the comment threads below. I'm especially interested if you have different approaches, additional ideas or want to suggest topics for future tutorials.
What Is a Slug?
Also known as a semantic URL, Wikipedia says "...a slug [is] the part of a URL which identifies a page using human-readable keywords." For instance, the default Yii URL to the page of our Status view below is:
http://localhost:8888/hello/status/view?id=3
This URL doesn't tell the user or search engines anything about its content. By implementing slugs, we can access the page with a URL such as:
http://localhost:8888/hello/status/looking-forward-to-the-super-bowl
Yii2 makes building slugs easier than ever, using its ActiveRecord behaviors—specifically SluggableBehavior.
Implementing SluggableBehavior
Add a Slug Column to the Status Table
To add slug support to our Hello application, we'll start by creating an ActiveRecord migration to add a slug column to our Status table.
./yii migrate/create extend_status_table_for_slugs Yii Migration Tool (based on Yii v2.0.1) Create new migration '/Users/Jeff/Sites/hello/migrations/m150128_214906_extend_status_table_for_slugs.php'? (yes|no) [no]:yes New migration created successfully.
Here's the migration you should use:
<?php use yii\db\Schema; use yii\db\Migration; class m150128_214906_extend_status_table_for_slugs extends Migration { public function up() { $tableOptions = null; if ($this->db->driverName === 'mysql') { $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB'; } $this->addColumn('{{%status}}','slug',Schema::TYPE_STRING.' NOT NULL'); } public function down() { $this->dropColumn('{{%status}}','slug'); } }
Then apply the migration:
./yii migrate/up Yii Migration Tool (based on Yii v2.0.1) Total 1 new migration to be applied: m150128_214906_extend_status_table_for_slugs Apply the above migration? (yes|no) [no]:yes *** applying m150128_214906_extend_status_table_for_slugs > add column slug string NOT NULL to table {{%status}} ... done (time: 0.022s) *** applied m150128_214906_extend_status_table_for_slugs (time: 0.027s) Migrated up successfully.
Add the SluggableBehavior to the Status Model
Next, we add the sluggable behavior to the apps\models\Status.php
model:
<?php namespace app\models; use Yii; use yii\behaviors\SluggableBehavior; ... class Status extends \yii\db\ActiveRecord { const PERMISSIONS_PRIVATE = 10; const PERMISSIONS_PUBLIC = 20; public function behaviors() { return [ [ 'class' => SluggableBehavior::className(), 'attribute' => 'message', // 'slugAttribute' => 'slug', ], ]; }
The slugAttribute
isn't needed since our column is called slug
, the framework default.
Test the Slug Attribute
Let's test this functionality by creating a new status item. From the Status menu, click Create:
Using PHPMyAdmin, I'll look at the Status table and see that the slug field has been automatically populated by a URL friendly version of the Status message I entered.
But you may notice that the view page URL continues to identify the message by its numerical ID:
http://localhost:8888/hello/status/view?id=4
How do we change this?
Implementing Slugs in Grid Action Links
In app\views\status\index.php
, we need to update the grid view with a custom link. This link will code the proper URL for our slug into the view:
<?= GridView::widget([ 'dataProvider' => $dataProvider, 'filterModel' => $searchModel, 'columns' => [ ['class' => 'yii\grid\SerialColumn'], 'id', 'message:ntext', 'permissions', 'created_at', 'updated_at', ['class' => 'yii\grid\ActionColumn', 'template'=>'{view} {update} {delete}', 'buttons'=>[ 'view' => function ($url, $model) { return Html::a('<span class="glyphicon glyphicon-eye-open"></span>', 'status/'.$model->slug, ['title' => Yii::t('yii', 'View'),]); } ], ], ], ]); ?>
Now, when you visit the index page, you'll see the view link resolves to:
http://localhost:8888/hello/status/test-the-slug-attribute
Of course, that page doesn't exist yet. We need to build support for it in our controller.
Implementing Slugs in Routes
Yii parses incoming requests using a default set of rules built into its UrlManager. We need to add support for our custom slug route into the application's app\config\web.php
:
'components' => [ 'urlManager' => [ 'showScriptName' => false, 'enablePrettyUrl' => true, 'rules' => [ 'status' => 'status/index', 'status/index' => 'status/index', 'status/create' => 'status/create', 'status/view/<id:\d+>' => 'status/view', 'status/update/<id:\d+>' => 'status/update', 'status/delete/<id:\d+>' => 'status/delete', 'status/<slug>' => 'status/slug', 'defaultRoute' => '/site/index', ],
Now, when a URL comes in http://localhost:8888/hello/status/test-the-slug-attribute, Yii will direct the request to the StatusController's slug action with the parameter <slug> or in this case "test-the-slug-attribute".
Notice that we also defined status/index
and status/create
specifically in the route, otherwise Yii might think that "index" or "create" were slugs.
Implementing the Slug Controller Action
Next, we add a new controller action called slug to StatusController.php. It's just like view except it operates off the slug column:
/** * Displays a single Status model. * @param integer $id * @return mixed */ public function actionView($id) { return $this->render('view', [ 'model' => $this->findModel($id), ]); } /** * Displays a single Status model. * @param string $slug * @return mixed */ public function actionSlug($slug) { $model = Status::find()->where(['slug'=>$slug])->one(); if (!is_null($model)) { return $this->render('view', [ 'model' => $model, ]); } else { return $this->redirect('/status/index'); } }
Now, when you visit the page using your slug URL, you should see this:
Managing Permanence and Uniqueness
Yii offers some nice enhancements to SluggableBehavior for useful scenarios.
For example, once a search engine records a slug, you probably don't want the page URL to change. The 'immutable' attribute tells Yii to keep the slug the same after it's first created—even if the message is updated.
If users enter messages that overlap in content, the 'ensureUnique' property will automatically append a unique suffix to duplicates. This makes certain that each message has a unique URL even if the message is identical.
public function behaviors() { return [ [ 'class' => SluggableBehavior::className(), 'attribute' => 'message', 'immutable' => true, 'ensureUnique'=>true, ], ]; }
If you go ahead and create another message with the same exact content, you'll see that its slug is incremented to test-the-slug-attribute-2.
Note: If you get an error related to the immutable property, it may be that you need to run a composer update to get the latest version of Yii.
What's Next?
Watch for upcoming tutorials in my Programming With Yii2 series as I continue diving into different aspects of the framework. You may also want to check out my Building Your Startup With PHP series which is using Yii2's advanced template as I build a real world application.
I welcome feature and topic requests. You can post them in the comments below or email me at my Lookahead Consulting website.
If you'd like to know when the next Yii2 tutorial arrives, follow me @reifman on Twitter or check my instructor page. My instructor page will include all the articles from this series as soon as they are published.
Related Links
- Yii Framework Website
- Introduction to the Yii Framework (Tuts+)
- Yii2 Developer Exchange, my Yii2 resource site
Comments