thunderwolf/sluggable

Sluggable for the Laravel Eloquent based on Propel sluggable behavior

1.0.1 2024-08-25 22:17 UTC

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: