thunderwolf / sluggable
Sluggable for the Laravel Eloquent based on Propel sluggable behavior
Requires
- php: >=8.1
- illuminate/database: ^7.0|^8.0|^9.0|^10.0
- illuminate/events: ^7.0|^8.0|^9.0|^10.0
- illuminate/support: ^7.0|^8.0|^9.0|^10.0
Requires (Dev)
- phpunit/phpunit: ^9.5
This package is auto-updated.
Last update: 2024-10-25 20:42:56 UTC
README
The sluggable
behavior allows a model to offer a human-readable identifier that can be used for search engine friendly URLs.
To work it requires Sluggable
trait in your Model with a configuration which in the most simple configuration just looks like this:
public function sluggable(): array
{
return [];
}
Basic Usage
The most basic way to use this package is to Create Model with the Sluggable
trait in use like this:
<?php
use Illuminate\Database\Eloquent\Model;
use Thunderwolf\EloquentSluggable\Sluggable;
class Post extends Model {
use Sluggable; // Attach the Sluggable trait to the model
protected $fillable = ['title'];
public $timestamps = false;
public function sluggable(): array
{
return [];
}
}
After registering SluggableServiceProvider
you can also use Blueprints to create tables with a use of createSluggable
helper method similar to this:
$schema->create('posts', function (Blueprint $table1) {
$table1->increments('id');
$table1->string('title');
$table1->createSluggable([]);
});
In similar way you will be working with migrations.
The model now has an additional getter for its slug, which is automatically set before the object is saved:
<?php
$p1 = new Post();
$p1->setAttribute('title', '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 lower-cased.
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->setAttribute('title', '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 = Post::findOneBySlug('hello-world');
Configuration
By default, with the configuration below:
public static function sluggable(): array
{
return [];
}
There will be column slug
added to the model. You can configure this with the configuration looking like this:
public static function sluggable(): array
{
return ['slug_column' => 'link'];
}
there are other configuration parameters like:
- slug_pattern
- replace_pattern
- replacement
- separator
- permanent
- transliterate
slug_pattern
Default pattern is {title}
and it assumes that column which will be used to generate slug is title
. You can use different Model attributes and there can be multiple used.
For example when set to: {title}--{section}
the slug will be combination of title
and section
with --
between them.
Any substring enclosed between brackets {}
will end to be used as a key by the getAttribute
method. This means you can even create own Attributes to use with it.
replace_pattern
Default replace pattern used by the slugify method is /[^\w\/]+/u
. You can use any regex pater you like instead.
replacement
Default replacement for replace pattern is -
.
separator
The separator parameter is the character that separates the slug from the incremental index added in case of non-unicity.
By default is 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'
...
permanent
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();
transliterate
If set to false
transliterate will not be used. This is by default set to true
and first will check if intl
is installed and if yes will use transliterator_transliterate
function. If is not available will use https://github.com/LukeMadhanga/transliterator class to do similar work.
Whatever slug_column name you choose, the sluggable behavior always adds the following proxy methods, which are mapped to the correct column:
<?php
$post->getSlugColumnName() // returns name of the slug column
$post->getSlug(); // returns value of the slug column
$post->getSlugPattern(); // returns slug_pattern configuration value
$post->getReplacePattern(); // returns replace_pattern configuration value
$post->getReplacement(); // returns replacement configuration value
$post->getSeparator(); // returns separator configuration value
$post->isPermanent(); // returns permanent configuration value
$post->shouldTransliterate() // returns transliterate configuration value
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:
/**
* Create a unique slug based on the object
*
* @return string The object slug
* @throws SluggableException
*/
protected function createSlug(): string
{
$slug = $this->createRawSlug($this->getSlugPattern());
$slug = $this->limitSlugSize($slug);
return $this->makeSlugUnique($slug, $this->getSeparator());
}
/**
* Create the slug from the appropriate columns
*
* @param string $slugPattern
* @return string
* @throws SluggableException
*/
protected function createRawSlug(string $slugPattern): string
{
$attributes = SluggableHelper::parseSlugPattern($slugPattern);
$outputArray = [];
foreach ($attributes as $attribute) {
$outputArray['{'.$attribute.'}'] = $this->cleanupSlugPart($this->getAttribute($attribute));
}
return str_replace(array_keys($outputArray), array_values($outputArray), $slugPattern);
}
You can override any of these methods in your model class, in order to implement a custom slug logic.
To Do
Potential migration of a code part which is doing url sanitization to:
or one of those: