# Sluggable behavior extension for Doctrine 2
**Sluggable** behavior will build the slug of predefined fields on a given field
which should store the slug
Features:
- Automatic predifined field transformation into slug
- ORM and ODM support using same listener
- Slugs can be unique and styled, even with prefixes and/or suffixes
- Can be nested with other behaviors
- Annotation, Yaml and Xml mapping support for extensions
- Multiple slugs, diferent slugs can link to same fields
[blog_reference]: http://gediminasm.org/article/sluggable-behavior-extension-for-doctrine-2 "Sluggable extension for Doctrine 2 makes automatic record field transformations into url friendly names"
[blog_test]: http://gediminasm.org/test "Test extensions on this blog"
Update **2013-08-23**
- Added 'prefix' and 'suffix' configuration parameter #812
Update **2013-08-19**
- allow empty slug #807 regenerate slug only if set to `null`
Update **2013-03-10**
- Added 'unique_base' configuration parameter to the Sluggable behaviour
Update **2012-11-30**
- Recreated slug handlers, as they are used by many people
Update **2012-02-26**
- Remove slug handlers were removed because of complications it brought together
Update **2011-09-11**
- Refactored sluggable for doctrine2.2 by specifieng slug fields directly in slug annotation
- Slug handler functionality, possibility to create custom ones or use built-in
tree path handler or linked slug through single valued association
- Updated documentation mapping examples for 2.1.x version or higher
Update **2011-04-04**
- Made single listener, one instance can be used for any object manager and any number of them
Update **2010-12-23**
- Full support for unique index on slug field,
no more exceptions during concurrent flushes.
**Note:**
- There is a reported [issue](https://github.com/l3pp4rd/DoctrineExtensions/issues/254) that sluggable transliterator
does not work on OSX 10.6 its ok starting again from 10.7 version. To overcome the problem
you can use your [custom transliterator](#transliterator)
- You can [test live][blog_test] on this blog
- Public [Sluggable repository](http://github.com/l3pp4rd/DoctrineExtensions "Sluggable extension on Github") is available on github
- Last update date: **2012-02-26**
**Portability:**
- **Sluggable** is now available as [Bundle](http://github.com/stof/StofDoctrineExtensionsBundle)
ported to **Symfony2** by **Christophe Coevoet**, together with all other extensions
This article will cover the basic installation and functionality of **Sluggable**
behavior
Content:
- [Including](#including-extension) the extension
- Entity [example](#entity-mapping)
- Document [example](#document-mapping)
- [Yaml](#yaml-mapping) mapping example
- [Xml](#xml-mapping) mapping example
- Basic usage [examples](#basic-examples)
- Custom [transliterator](#transliterator)
- Advanced usage [examples](#advanced-examples)
- Using [slug handlers](#slug-handlers)
## Setup and autoloading
Read the [documentation](http://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/annotations.md#em-setup)
or check the [example code](http://github.com/l3pp4rd/DoctrineExtensions/tree/master/example)
on how to setup and use the extensions in most optimized way.
## Sluggable Entity example:
### Sluggable annotations:
- **@Gedmo\Mapping\Annotation\Slug** it will use this column to store **slug** generated
**fields** option must be specified, an array of field names to slug
**Note:** that Sluggable interface is not necessary, except in cases there
you need to identify entity as being Sluggable. The metadata is loaded only once then
cache is activated
**Note:** 2.0.x version of extensions used @Gedmo\Mapping\Annotation\Sluggable to identify
the field for slug
``` php
id;
}
public function setTitle($title)
{
$this->title = $title;
}
public function getTitle()
{
return $this->title;
}
public function setCode($code)
{
$this->code = $code;
}
public function getCode()
{
return $this->code;
}
public function getSlug()
{
return $this->slug;
}
}
```
## Sluggable Document example:
``` php
id;
}
public function setTitle($title)
{
$this->title = $title;
}
public function getTitle()
{
return $this->title;
}
public function setCode($code)
{
$this->code = $code;
}
public function getCode()
{
return $this->code;
}
public function getSlug()
{
return $this->slug;
}
}
```
## Yaml mapping example
Yaml mapped Article: **/mapping/yaml/Entity.Article.dcm.yml**
```
---
Entity\Article:
type: entity
table: articles
id:
id:
type: integer
generator:
strategy: AUTO
fields:
title:
type: string
length: 64
code:
type: string
length: 16
slug:
type: string
length: 128
gedmo:
slug:
separator: _
style: camel
fields:
- title
- code
indexes:
search_idx:
columns: slug
```
## Xml mapping example
**Note:** xml driver is not yet adapted for single slug mapping
``` xml
```
## Basic usage examples:
### To save **Article** and generate slug simply use:
``` php
setTitle('the title');
$article->setCode('my code');
$this->em->persist($article);
$this->em->flush();
echo $article->getSlug();
// prints: the-title-my-code
```
### Some other configuration options for **slug** annotation:
- **fields** (required, default=[]) - list of fields for slug
- **updatable** (optional, default=true) - **true** to update the slug on sluggable field changes, **false** - otherwise
- **unique** (optional, default=true) - **true** if slug should be unique and if identical it will be prefixed, **false** - otherwise
- **unique_base** (optional, default=null) - used in conjunction with **unique**. The name of the entity property that should be used as a key when doing a uniqueness check.
- **separator** (optional, default="-") - separator which will separate words in slug
- **prefix** (optional, default="") - prefix which will be added to the generated slug
- **suffix** (optional, default="") - suffix which will be added to the generated slug
- **style** (optional, default="default") - **"default"** all letters will be lowercase, **"camel"** - first word letter will be uppercase
- **handlers** (optional, default=[]) - list of slug handlers, like tree path slug, or customized, for example see bellow
**Note**: handlers are totally optional
**TreeSlugHandler**
``` php
setTitle('the title');
$article->setCode('my code');
$this->em->persist($article);
$this->em->flush();
echo $article->getSlug();
// prints: The_Title_My_Code
```
## Custom transliterator
To set your own custom transliterator, which would be used to generate the slug, use:
``` php
setTransliterator($callable);
// or use a closure
$callable = function($text, $separatorUsed, $objectBeingSlugged) {
// ...
return $transliteratedText;
};
$sluggableListener->setTransliterator($callable);
```
## Advanced examples:
### Regenerating slug
In case if you want the slug to regenerate itself based on sluggable fields, set the slug to **null**.
*Note: in previous versions empty strings would also cause the slug to be regenerated. This behaviour was changed in v2.3.8.*
``` php
find('Entity\Something', $id);
$entity->setSlug(null);
$em->flush();
```
### Setting the slug manually
Sometimes you might need to set it manually, etc if generated one does not look satisfying enough.
Sluggable will ensure uniqueness of the slug.
``` php
setSluggableField('won\'t be taken into account');
$entity->setSlug('the required slug, set manually');
$em->persist($entity);
$em->flush();
echo $entity->getSlug(); // outputs: "the-required-slug-set-manually"
```
### Using TranslationListener to translate our slug
If you want to attach **TranslationListener** also add it to EventManager after
the **SluggableListener**. It is important because slug must be generated first
before the creation of it`s translation.
``` php
addEventSubscriber($sluggableListener);
$translatableListener = new \Gedmo\Translatable\TranslationListener();
$translatableListener->setTranslatableLocale('en_us');
$evm->addEventSubscriber($translatableListener);
// now this event manager should be passed to entity manager constructor
```
And the Entity should look like:
``` php
id;
}
public function setTitle($title)
{
$this->title = $title;
}
public function getTitle()
{
return $this->title;
}
public function setCode($code)
{
$this->code = $code;
}
public function getCode()
{
return $this->code;
}
public function getSlug()
{
return $this->slug;
}
public function getUniqueSlug()
{
return $this->uniqueSlug;
}
}
```
Now the generated slug will be translated by Translatable behavior
## Using slug handlers:
There are built-in slug handlers like described in configuration options of slug, but there
can be also customized slug handlers depending on use cases. Usually the most logic use case
is for related slug. For instance if user has a **ManyToOne relation to a **Company** we
would like to have a url like **http://example.com/knplabs/gedi where **KnpLabs**
is a company and user name is **Gedi**. In this case relation has a path separator **/**
User entity example:
``` php
company = $company;
}
public function getCompany()
{
return $this->company;
}
public function getId()
{
return $this->id;
}
public function setName($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
public function getSlug()
{
return $this->slug;
}
}
```
Company entity example:
``` php
id;
}
public function setTitle($title)
{
$this->title = $title;
}
public function getTitle()
{
return $this->title;
}
public function getAlias()
{
return $this->alias;
}
}
```
For other mapping drivers see
[xml](https://github.com/l3pp4rd/DoctrineExtensions/blob/master/tests/Gedmo/Mapping/Driver/Xml/Mapping.Fixture.Xml.Sluggable.dcm.xml) or [yaml](https://github.com/l3pp4rd/DoctrineExtensions/blob/master/tests/Gedmo/Mapping/Driver/Yaml/Mapping.Fixture.Yaml.Category.dcm.yml) examples from tests
And the example usage:
``` php
setTitle('KnpLabs');
$em->persist($company);
$gedi = new User;
$gedi->setUsername('Gedi');
$gedi->setCompany($company);
$em->persist($gedi);
$em->flush();
echo $gedi->getSlug(); // outputs "knplabs/gedi"
$company->setTitle('KnpLabs Nantes');
$em->persist($company);
$em->flush();
echo $gedi->getSlug(); // outputs "knplabs-nantes/gedi"
```
**Note:** tree slug handler, takes a parent relation to build slug recursively.
Any suggestions on improvements are very welcome