Fork me on GitHub

Sluggable Behavior

The sluggable behavior allows a model to offer a human readable identifier that can be used for search engine friendly URLs.

Basic Usage

In the schema.xml, use the <behavior> tag to add the sluggable behavior to a table:

<table name="post">
  <column name="id" required="true" primaryKey="true" autoIncrement="true" type="integer" />
  <column name="title" type="varchar" required="true" primaryString="true" />
  <behavior name="sluggable" />
</table>

Rebuild your model, insert the table creation sql again, and you're ready to go. The model now has an additional getter for its slug, which is automatically set before the object is saved:

<?php
$p1 = new Post();
$p1->setTitle('Hello, World!');
$p1->save();
echo $p1->getSlug(); // 'hello-world'

By default, the behavior uses the string representation of the object to build the slug. In the example above, the title column is defined as primaryString, so the slug uses this column as a base string. The string is then cleaned up in order to allow it to appear in a URL. In the process, blanks and special characters are replaced by a dash, and the string is lowercased.

Tip
The slug is unique by design. That means that if you create a new object and that the behavior calculates a slug that already exists, the string is modified to be unique:

<?php
$p2 = new Post();
$p2->setTitle('Hello, World!');
$p2->save();
echo $p2->getSlug(); // 'hello-world-1'

The generated model query offers a findOneBySlug() method to easily retrieve a model object based on its slug:

<?php
$p = PostQuery::create()->findOneBySlug('hello-world');

Parameters

By default, the behavior adds one columns to the model. If this column is already described in the schema, the behavior detects it and doesn't add it a second time. The behavior parameters allow you to use custom patterns for the slug composition. The following schema illustrates a complete customization of the behavior:

<table name="post">
  <column name="id" required="true" primaryKey="true" autoIncrement="true" type="integer" />
  <column name="title" type="varchar" required="true" primaryString="true" />
  <column name="url" type="varchar" size="100" />
  <behavior name="sluggable">
    <parameter name="slug_column" value="url" />
    <parameter name="slug_pattern" value="/posts/{Title}" />
    <parameter name="replace_pattern" value="/[^\w\/]+/u" />
    <parameter name="replacement" value="-" />
    <parameter name="separator" value="/" />
    <parameter name="permanent" value="true" />
  </behavior>
</table>

Whatever slug_column name you choose, the sluggable behavior always adds the following proxy methods, which are mapped to the correct column:

<?php
$post->getSlug();         // returns $post->url
$post->setSlug($slug);    // $post->url = $slug

The slug_pattern parameter is the rule used to build the raw slug based on the object properties. Any substring enclosed between brackets '{}' is turned into a getter, so the Post class generates slugs as follows:

<?php
protected function createRawSlug()
{
  return '/posts/' . $this->getTitle();
}

Incidentally, that means that you can use names that don't match a real column phpName, as long as your model provides a getter for it.

The replace_pattern parameter is a regular expression that shows all the characters that will end up replaced by the replacement parameter. In the above example, special characters like '!' or ':' are replaced by '-', but not letters, digits, nor '/'.

The separator parameter is the character that separates the slug from the incremental index added in case of non-unicity. Set as '/', it makes Post objects sharing the same title have the following slugs:

'posts/hello-world'
'posts/hello-world/1'
'posts/hello-world/2'
...

A permanent slug is not automatically updated when the fields that constitute it change. This is useful when the slug serves as a permalink, that should work even when the model object properties change. Note that you can still manually change the slug in a model using the permanent setting by calling setSlug();

Further Customization

The slug is generated by the object when it is saved, via the createSlug() method. This method does several operations on a simple string:

<?php
protected function createSlug()
{
  // create the slug based on the `slug_pattern` and the object properties
  $slug = $this->createRawSlug();
  // truncate the slug to accommodate the size of the slug column
  $slug = $this->limitSlugSize($slug);
  // add an incremental index to make sure the slug is unique
  $slug = $this->makeSlugUnique($slug);

  return $slug;
}

protected function createRawSlug()
{
  // here comes the string composition code, generated according to `slug_pattern`
  $slug = 'posts/' . $this->cleanupSlugPart($this->getTitle());
  // cleanupSlugPart() cleans up the slug part
  // based on the `replace_pattern` and `replacement` parameters

  return $slug;
}

You can override any of these methods in your model class, in order to implement a custom slug logic.

Found a typo ? Something is wrong in this documentation ? Just fork and edit it !