GithubHelp home page GithubHelp logo

cviebrock / eloquent-sluggable Goto Github PK

View Code? Open in Web Editor NEW
3.8K 94.0 460.0 673 KB

Easy creation of slugs for your Eloquent models in Laravel

License: MIT License

PHP 100.00%
laravel eloquent-models slug

eloquent-sluggable's Introduction

Eloquent-Sluggable

Easy creation of slugs for your Eloquent models in Laravel.

NOTE: These instructions are for the latest version of Laravel.
If you are using an older version, please install a version of the package that correlates to your Laravel version.

Build Status Total Downloads Latest Stable Version Latest Unstable Version SensioLabsInsight License

Background: What is a slug?

A slug is a simplified version of a string, typically URL-friendly. The act of "slugging" a string usually involves converting it to one case, and removing any non-URL-friendly characters (spaces, accented letters, ampersands, etc.). The resulting string can then be used as an identifier for a particular resource.

For example, if you have a blog with posts, you could refer to each post via the ID:

http://example.com/post/1
http://example.com/post/2

... but that's not particularly friendly (especially for SEO). You probably would prefer to use the post's title in the URL, but that becomes a problem if your post is titled "My Dinner With André & François", because this is pretty ugly too:

http://example.com/post/My+Dinner+With+Andr%C3%A9+%26+Fran%C3%A7ois

The solution is to create a slug for the title and use that instead. You might want to use Laravel's built-in Str::slug() method to convert that title into something friendlier:

http://example.com/post/my-dinner-with-andre-francois

A URL like that will make users happier (it's readable, easier to type, etc.).

For more information, you might want to read this description on Wikipedia.

Slugs tend to be unique as well. So if you write another post with the same title, you'd want to distinguish between them somehow, typically with an incremental counter added to the end of the slug:

http://example.com/post/my-dinner-with-andre-francois
http://example.com/post/my-dinner-with-andre-francois-1
http://example.com/post/my-dinner-with-andre-francois-2

This keeps the URLs unique.

The Eloquent-Sluggable package for Laravel aims to handle all of this for you automatically, with minimal configuration.

Installation

Depending on your version of Laravel, you should install a different version of the package.

NOTE: As of version 6.0, the package's version should match the Laravel version.

Laravel Version Package Version
11.0 ^11.0
10.0 ^10.0
9.0 ^9.0
8.0 ^8.0
7.0 ^7.0
6.0 ^6.0
5.8 4.8.*
5.7 4.7.*
5.6 4.5.*
5.5 4.4.*
5.4 4.2.*

Older versions of Laravel can use older versions of the package, although they are no longer supported or maintained. See CHANGELOG.md and UPGRADING.md for specifics, and be sure that you are reading the correct README.md for your version (GitHub displays the version in the master branch by default, which might not be what you want).

  1. Install the package via Composer:

    composer require cviebrock/eloquent-sluggable

    The package will automatically register its service provider.

  2. Optionally, publish the configuration file if you want to change any defaults:

    php artisan vendor:publish --provider="Cviebrock\EloquentSluggable\ServiceProvider"

Updating your Eloquent Models

Your models should use the Sluggable trait, which has an abstract method sluggable() that you need to define. This is where any model-specific configuration is set (see Configuration below for details):

use Cviebrock\EloquentSluggable\Sluggable;

class Post extends Model
{
    use Sluggable;

    /**
     * Return the sluggable configuration array for this model.
     *
     * @return array
     */
    public function sluggable(): array
    {
        return [
            'slug' => [
                'source' => 'title'
            ]
        ];
    }
}

Of course, your model and database will need a column in which to store the slug. You can use slug or any other appropriate name you want; your configuration array will determine to which field the data will be stored. You will need to add the column (which should be NULLABLE) manually via your own migration.

That's it ... your model is now "sluggable"!

Usage

Saving a model is easy:

$post = Post::create([
    'title' => 'My Awesome Blog Post',
]);

So is retrieving the slug:

echo $post->slug;

NOTE: that if you are replicating your models using Eloquent's replicate() method, the package will automatically re-slug the model afterwards to ensure uniqueness.

$post = Post::create([
    'title' => 'My Awesome Blog Post',
]);
// $post->slug is "my-awesome-blog-post"

$newPost = $post->replicate();
// $newPost->slug is "my-awesome-blog-post-1"

NOTE: empty strings, non-strings or other "odd" source values will result in different slugs:

Source Value Resulting Slug
string string
empty string no slug will be set
null no slug will be set
0 "0"
1 "1"
false "0"
true "1"

(The above values would be subject to any unique or other checks as well.)

The SlugService Class

All the logic to generate slugs is handled by the \Cviebrock\EloquentSluggable\Services\SlugService class.

Generally, you don't need to access this class directly, although there is one static method that can be used to generate a slug for a given string without actually creating or saving an associated model.

use \Cviebrock\EloquentSluggable\Services\SlugService;

$slug = SlugService::createSlug(Post::class, 'slug', 'My First Post');

This would be useful for Ajax-y controllers or the like, where you want to show a user what the unique slug would be for a given test input, before actually creating a model. The first two arguments to the method are the model and slug field being tested, and the third argument is the source string to use for testing the slug.

You can also pass an optional array of configuration values as the fourth argument. These will take precedence over the normal configuration values for the slug field being tested. For example, if your model is configured to use unique slugs, but you want to generate the "base" version of a slug for some reason, you could do:

$slug = SlugService::createSlug(Post::class, 'slug', 'My First Post', ['unique' => false]);

When Is A Model Slugged?

Currently, the model is slugged on Eloquent's saving event. This means that the slug is generated before any new data is written to the database.

For new models, this means that the primary key has not yet been set, so it could not be used as part of the slug source, e.g.:

public function sluggable(): array
{
    return [
        'slug' => [
            'source' => ['title', 'id']
        ]
    ];
}

$model->id is null before the model is saved. The benefit of hooking into the saving event, however, is that we only needed to make one database query to save all the model's data, including the slug.

Optional, the model can be slugged on Eloquent's saved event.
This means that all the other model attributes will have already been persisted to the database and are available for use as slug sources. So the above configuration would work. The only drawback is that saving the model to the database requires one extra query: the first one to save all the non-slug fields, and then a second one to update just the slug field.

This behaviour is a breaking change, and likely won't affect most users (unless you are doing some pre-saving validation on a model's slug field). We feel the benefits outweigh the drawbacks, and so this will likely become the new default behaviour in a future major release of the package. Although, to make the transition easier, you can configure this behaviour via the sluggableEvent method the trait provides:

    public function sluggableEvent(): string
    {
        /**
         * Default behaviour -- generate slug before model is saved.
         */
        return SluggableObserver::SAVING;

        /**
         * Optional behaviour -- generate slug after model is saved.
         * This will likely become the new default in the next major release.
         */
        return SluggableObserver::SAVED;
    }

Keep in mind that you will need to use SluggableObserver::SAVED if you want to use your model's primary key as part of the source fields for your slugs.

Events

Sluggable models will fire two Eloquent model events: "slugging" and "slugged".

The "slugging" event is fired just before the slug is generated. If the callback from this event returns false, then the slugging is not performed. If anything else is returned, including null, then the slugging will be performed.

The "slugged" event is fired just after a slug is generated. It won't be called in the case where the model doesn't need slugging (as determined by the needsSlugging() method).

You can hook into either of these events just like any other Eloquent model event:

Post::registerModelEvent('slugging', static function($post) {
    if ($post->someCondition()) {
        // the model won't be slugged
        return false;
    }
});

Post::registerModelEvent('slugged', static function($post) {
    Log::info('Post slugged: ' . $post->getSlug());
});

Configuration

Configuration was designed to be as flexible as possible. You can set up defaults for all of your Eloquent models, and then override those settings for individual models.

By default, global configuration is set in the config/sluggable.php file. If a configuration isn't set, then the package defaults are used. Here is an example configuration, with all the default settings shown:

return [
    'source'             => null,
    'method'             => null,
    'onUpdate'           => false,
    'separator'          => '-',
    'unique'             => true,
    'uniqueSuffix'       => null,
    'firstUniqueSuffix'  => 2,
    'includeTrashed'     => false,
    'reserved'           => null,
    'maxLength'          => null,
    'maxLengthKeepWords' => true,
    'slugEngineOptions'  => [],
];

For individual models, configuration is handled in the sluggable() method that you need to implement. That method should return an indexed array where the keys represent the fields where the slug value is stored and the values are the configuration for that field. This means you can create multiple slugs for the same model, based on different source strings and with different configuration options.

public function sluggable(): array
{
    return [
        'title-slug' => [
            'source' => 'title'
        ],
        'author-slug' => [
            'source' => ['author.lastname', 'author.firstname'],
            'separator' => '_'
        ],
    ];
}

source

This is the field or array of fields from which to build the slug. Each $model->field is concatenated (with space separation) to build the sluggable string. These can be model attributes (i.e. fields in the database), relationship attributes, or custom getters.

To reference fields from related models, use dot-notation. For example, the slug for the following book will be generated from its author's name and the book's title:

class Book extends Eloquent
{
    use Sluggable;

    protected $fillable = ['title'];

    public function sluggable(): array
    {
        return [
            'slug' => [
                'source' => ['author.name', 'title']
            ]
        ];
    }
    
    public function author(): \Illuminate\Database\Eloquent\Relations\BelongsTo
    {
        return $this->belongsTo(Author::class);
    }
}
...
class Author extends Eloquent
{
    protected $fillable = ['name'];
}

An example using a custom getter:

class Person extends Eloquent
{
    use Sluggable;

    public function sluggable(): array
    {
        return [
            'slug' => [
                'source' => 'fullname'
            ]
        ];
    }

    public function getFullnameAttribute(): string
    {
        return $this->firstname . ' ' . $this->lastname;
    }
}

If source is empty, false or null, then the value of $model->__toString() is used as the source for slug generation.

method

Defines the method used to turn the sluggable string into a slug. There are three possible options for this configuration:

  1. When method is null (the default setting), the package uses the default slugging engine -- cocur/slugify -- to create the slug.

  2. When method is a callable, then that function or class method is used. The function/method should expect two parameters: the string to process, and a separator string. For example, to use Laravel's Str::slug, you could do:

'method' => ['Illuminate\\Support\\Str', 'slug'],
  1. You can also define method as a closure (again, expecting two parameters):
'method' => static function(string $string, string $separator): string {
    return strtolower(preg_replace('/[^a-z]+/i', $separator, $string));
},

Any other values for method will throw an exception.

For more complex slugging requirements, see Extending Sluggable below.

onUpdate

By default, updating a model will not try and generate a new slug value. It is assumed that once your slug is generated, you won't want it to change (this may be especially true if you are using slugs for URLs and don't want to mess up your SEO mojo).

If you want to regenerate one or more of your model's slug fields, you can set those fields to null or an empty string before the update:

$post->slug = null;
$post->update(['title' => 'My New Title']);

If this is the behaviour you want every time you update a model, then set the onUpdate option to true.

separator

This defines the separator used when building a slug, and is passed to the method defined above. The default value is a hyphen.

unique

This is a boolean defining whether slugs should be unique among all models of the given type. For example, if you have two blog posts and both are called "My Blog Post", then they will both sluggify to "my-blog-post" if unique is false. This could be a problem, e.g. if you use the slug in URLs.

By setting unique to true, then the second Post model will sluggify to "my-blog-post-1". If there is a third post with the same title, it will sluggify to "my-blog-post-2" and so on. Each subsequent model will get an incremental value appended to the end of the slug, ensuring uniqueness.

uniqueSuffix

If you want to use a different way of identifying uniqueness (other than auto-incrementing integers), you can set the uniqueSuffix configuration to a function or callable that generates the "unique" values for you.

The function should take four parameters:

  1. the base slug (i.e. the non-unique slug)
  2. the separator string
  3. an \Illuminate\Support\Collection of all the other slug strings that start with the same slug
  4. the first suffix to use (for the first slug that needs to be made unique) You can then do whatever you want to create a new suffix that hasn't been used by any of the slugs in the collection. For example, if you wanted to use letters instead of numbers as a suffix, this is one way to achieve that:
'uniqueSuffix' => static function(string $slug, string $separator, Collection $list, $firstSuffix): string
    {
      $size = count($list);

      return chr($size + 96);
    }

firstUniqueSuffix

When adding a unique suffix, we start counting at "2", so that the list of generated slugs would look something like:

  • my-unique-slug
  • my-unique-slug-2
  • my-unique-slug-3
  • etc.

If you want to start counting at a different number (or pass a different value into your custom uniqueSuffix function above), then you can define it here.

NOTE: Prior versions of the package started with a unique suffix of 1. This was switched to 2 in version 8.0.5, as it's a more "intuitive" suffix value to attach to the second slug.

includeTrashed

Setting this to true will also check deleted models when trying to enforce uniqueness. This only affects Eloquent models that are using the softDelete feature. Default is false, so soft-deleted models don't count when checking for uniqueness.

reserved

An array of values that will never be allowed as slugs, e.g. to prevent collisions with existing routes or controller methods, etc.. This can be an array, or a closure that returns an array. Defaults to null: no reserved slug names.

maxLength

Setting this to a positive integer will ensure that your generated slugs are restricted to a maximum length (e.g. to ensure that they fit within your database fields). By default, this value is null and no limit is enforced.

NOTE: If unique is enabled (which it is by default), and you anticipate having several models with the same slug, then you should set this value to a few characters less than the length of your database field. The reason why is that the class will append "-2", "-3", "-4", etc., to subsequent models in order to maintain uniqueness. These incremental extensions aren't included in part of the maxLength calculation.

maxLengthKeepWords

If you are truncating your slugs with the maxLength setting, than you probably want to ensure that your slugs don't get truncated in the middle of a word. For example, if your source string is "My First Post", and your maxLength is 10, the generated slug would end up being "my-first-p", which isn't ideal.

By default, the maxLengthKeepWords value is set to true which would trim the partial words off the end of the slug, resulting in "my-first" instead of "my-first-p".

If you want to keep partial words, then set this configuration to false.

slugEngineOptions

When method is null (the default setting), the package uses the default slugging engine -- cocur/slugify -- to create the slug. If you want to pass a custom set of options to the Slugify constructor when the engine is instantiated, this is where you would define that. See the documentation for Slugify for what those options are. Also, look at customizeSlugEngine for other ways to customize Slugify for slugging.

Short Configuration

The package supports a really short configuration syntax, if you are truly lazy:

public function sluggable(): array
{
    return ['slug'];
}

This will use all the default options from config/sluggable.php, use the model's __toString() method as the source, and store the slug in the slug field.

Extending Sluggable

Sometimes the configuration options aren't sufficient for complex needs (e.g. maybe the uniqueness test needs to take other attributes into account).

In instances like these, the package offers hooks into the slugging workflow where you can use your own functions, either on a per-model basis, or in your own trait that extends the package's trait.

NOTE: If you are putting these methods into your own trait, you will need to indicate in your models that PHP should use your trait methods instead of the packages (since a class can't use two traits with the same methods), e.g.

/**
 * Your trait where you collect your common Sluggable extension methods
 */
class MySluggableTrait {
    public function customizeSlugEngine(...) {}
    public function scopeWithUniqueSlugConstraints(...) {}
    // etc.
}

/**
 * Your model
 */
class MyModel {
    // Tell PHP to use your methods instead of the packages:
    use Sluggable,
        MySluggableTrait  {
            MySluggableTrait::customizeSlugEngine insteadof Sluggable;
            MySluggableTrait::scopeWithUniqueSlugConstraints insteadof Sluggable;
        }

    // ...
}

customizeSlugEngine

/**
 * @param \Cocur\Slugify\Slugify $engine
 * @param string $attribute
 * @return \Cocur\Slugify\Slugify
 */
public function customizeSlugEngine(Slugify $engine, string $attribute): \Cocur\Slugify\Slugify
{
    // ...
    return $engine;
}

If you extend this method, the Slugify engine can be customized before slugging occurs. This might be where you change the character mappings that are used, or alter language files, etc..

You can customize the engine on a per-model and per-attribute basis (maybe your model has two slug fields, and one of them needs customization).

Take a look at tests/Models/PostWithCustomEngine.php for an example.

Also, take a look at the slugEngineOptions configuration for other ways to customize Slugify.

scopeWithUniqueSlugConstraints

/**
 * @param \Illuminate\Database\Eloquent\Builder $query
 * @param \Illuminate\Database\Eloquent\Model $model
 * @param string $attribute
 * @param array $config
 * @param string $slug
 * @return \Illuminate\Database\Eloquent\Builder
 */
public function scopeWithUniqueSlugConstraints(
    Builder $query,
    Model $model,
    string $attribute,
    array $config,
    string $slug
): Builder
{
    // ...
}

This method is applied to the query that is used to determine if a given slug is unique. The arguments passed to the scope are:

  • $model -- the object being slugged
  • $attribute -- the slug field being generated,
  • $config -- the configuration array for the given model and attribute
  • $slug -- the "base" slug (before any unique suffixes are applied)

Feel free to use these values anyway you like in your query scope. As an example, look at tests/Models/PostWithUniqueSlugConstraints.php where the slug is generated for a post from its title, but the slug is scoped to the author. So Bob can have a post with the same title as Pam's post, but both will have the same slug.

scopeFindSimilarSlugs

/**
 * Query scope for finding "similar" slugs, used to determine uniqueness.
 *
 * @param \Illuminate\Database\Eloquent\Builder $query
 * @param string $attribute
 * @param array $config
 * @param string $slug
 * @return \Illuminate\Database\Eloquent\Builder
 */
public function scopeFindSimilarSlugs(Builder $query, string $attribute, array $config, string $slug): Builder
{
    // ...
}

This is the default scope for finding "similar" slugs for a model. Basically, the package looks for existing slugs that are the same as the $slug argument, or that start with $slug plus the separator string. The resulting collection is what is passed to the uniqueSuffix handler.

Generally, this query scope (which is defined in the Sluggable trait) should be left alone. However, you are free to overload it in your models.

SluggableScopeHelpers Trait

Adding the optional SluggableScopeHelpers trait to your model allows you to work with models and their slugs. For example:

$post = Post::whereSlug($slugString)->get();

$post = Post::findBySlug($slugString);

$post = Post::findBySlugOrFail($slugString);

Because models can have more than one slug, this requires a bit more configuration. See SCOPE-HELPERS.md for all the details.

Route Model Binding

See ROUTE-MODEL-BINDING.md for details.

Bugs, Suggestions, Contributions and Support

Thanks to everyone who has contributed to this project!

Please use GitHub for reporting bugs, and making comments or suggestions.

See CONTRIBUTING.md for how to contribute changes.

Copyright and License

eloquent-sluggable was written by Colin Viebrock and is released under the MIT License.

Copyright (c) 2013 Colin Viebrock

eloquent-sluggable's People

Contributors

alnutile avatar anahkiasen avatar artrz avatar cbl avatar cviebrock avatar fkupper avatar fulopattila122 avatar geosot avatar gregpeden avatar haddowg avatar hannesvdvreken avatar keithturkowski avatar keoghan avatar kilburn avatar kkiernan avatar laravel-shift avatar leepownall avatar llewellyn-kevin avatar m4rii0 avatar maddhatter avatar meathanjay avatar nicolasbinet avatar phroggyy avatar scrutinizer-auto-fixer avatar sleavely avatar sloveniangooner avatar standaniels avatar tabacitu avatar unstoppablecarl avatar xcopy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

eloquent-sluggable's Issues

Sluggable doesn't run with saving model

When I save a model (ex. Product), the sluggable feature doesn't sluggify my model. This is on a brand new install of Laravel as of today.

I can see that the event listener fires (line 43 in SluggableServiceProvider.php), but it seems as though the if ($model instanceof SluggableInterface) statement never returns true and never sluggifies my model. Could this be a name spacing issue? Any other ideas?

Here is my Product model:

'title', 'save_to' => 'slug', ); ``` }

Cache and delete records

Hello,

Just to be sure.

If I use the cache option (i.e : with Redis) with the unique slugs option and I delete a record with a slug, I also need to manually flush the slug from the cache engine, right ?

EDIT : I didn't notice the cache time (30 sec). So, to be sure again : if I create a record with a slug, then delete this record and create the same record within 30 sec, the last record will have an incremented slug (not the same as the first one), right ? That's fine by me, just checking if I understand it right.

Thanks

implement reserved slugs

It would be nice to be able to reserve slugs in situations where a slug might potentially inadvertently interfere with another route or appear overly official.

For example, if my routing is set up with contacts/add and contacts/{slug}, I should be able to prevent the slug add from being generated for a Contact instance. I should also be able to prevent the slug delete from being created even if I don't have a route, to avoid confusing the user.

Add findBySlug() to README

I just spent some time trying to come up with something like findBySlug() until I realized it's already in the code. Which is really awesome! IMO it would be helpful, however, to include that function in the README without having it to discover in the code. What do you think? (Or is it already in the README and I just didn't see it?)

on_update = true does not work correctly in certain instances

The problem occurs if you have on_update = true and unique = true set in your config and you already have a slug named 'asdfasdf'

If you create another record that would produce the same name for the slug it produces the correct unique slug 'asdfasdf-1', however, if you update the record again without changing the name the slug will automatically update to 'asdfasdf-2' even though it could stay 'asdfasdf-1'.

I think to fix this you need to already account and ignore the current slug when performing the unqiue comparison based off the models ID, Simliar to laravels validation unique rule when performing updates

'email' => 'unique:users,email_address,10'

Null slug when changed record

If record has a slug and I change the title, the slug becoming null and then reupdating the record it generates slug.

limitation on the length

Hi
is there any limit on slug?
Maybe it would be a good idea to add configuration option, somthing like: "max_length". That could correspond with table column length, escepcially if we're enabling sluggable for pre-existing model with some restrictions. Also different browsers have different max url length values.
cheers

Using sluggable while using eloquent outside laravel

I'm also trying to use Eloquent in a vanilla PHP application and I would like to include "sluggable". I get an error that the class "App" doesn't exist (in SluggableTrait.php:210) when I try to use sluggify(). Since I'm not using Laravel I don't have the "App" class. Any suggestions?

Signs are removed from slug

I noticed that signs such as "+" are removed from slug. For example, church+ turn to church. Is there a configuration for that.

First check on instanceof Closure, followed by is_callable is redundant

https://github.com/cviebrock/eloquent-sluggable/blob/develop/src/Cviebrock/EloquentSluggable/SluggableTrait.php#L57-L60

I checked:

$ php -a
Interactive mode enabled
php > $foo = function () { echo 'bar' . PHP_EOL; };
php > var_dump(is_callable($foo));
bool(true)
php > var_dump($foo instanceof Closure);
bool(true)
php > call_user_func($foo);
bar
php >

Both checks are true, and you can pass on the anonymous function to call_user_func as well.

when slug is dirty use its value to generate the slug instead of the source field

hi there,

say i have a page with a slug set from the title! when i update the title the slug does not change and thats perfect.

But what if a user updates the slug to something manually which contains e.g a space! i would expect then sluggable to detect that the slug has been changed manually (that is dirty) and automatically resluggify to make sure that my slug does not contain anything that it shouldn't and that is unique! but based on the manually entered slug not on my title!

hope this makes sence

Having both unique and on_update set to true

There seems to be an issue where having both of these set to true results in the slug switching between having the "-1" appended and not having it appended after each update.

Closure for "reserved" option does not work

Hi,

I've implemented the SluggableTrait on one of my classes and I wanted to dynamically get the reserved slugs using a closure, like so:

<?php

namespace Project\Page\Model;

use Cviebrock\EloquentSluggable\SluggableInterface;
use Cviebrock\EloquentSluggable\SluggableTrait;

class StaticPage extends \Eloquent implements SluggableInterface
{

    use SluggableTrait;

    protected $sluggable = array(
        'build_from' => ['title'],
        'save_to'    => 'slug',
        'separator'  => '-',
    );

    public function __construct(array $attributes = array())
    {
        parent::__construct($attributes);

        $this->sluggable['reserved'] = function() {
            $result = [];
            // ??? -> $$$
            return $result;
        };
    }

}

// And the following is added just so you can see it in action:
$page = new StaticPage;
$page->title = 'About';
$page->sluggify();

Now, when trying to generate the slug, an UnexpectedValueException is thrown: Sluggable reserved is not null, an array, or a closure that returns null/array.

This happens because the class (or the trait) is namespaced, but the type checks (SluggableTrait line 57 and 87) say:

        // check for reserved names
        if ( $reserved instanceof Closure )

Thanks to PHP's namespaces, this should have been:

        // check for reserved names
        if ( $reserved instanceof \Closure )

I can submit a pull request with the fix for this, but I cannot provide unittests because I can't get them to work on my machine.

Cheers!

With "on_update" saving a model with same title generates new slug

See title...
For now I disabled on_update and do it manually in my controllers.

Maybe the unique option has to do with this.

I have multiple users with Models with the same title, so when the Model gets saved with same title as before, a new slug gets generated..

Empty slug when using synbols

When a model's sluggable content is symbols such as #$%^^#$%^#$%$#% the slug is empty which breaks the model when used as a url.

there needs to be a check to see if null and then set some default value such as a random unique hash.

Update to latest version broke SLUG

Hi
I have been using this package for months now and since yesterday i am getting:

1364 Field 'slug' doesn't have a default value

It seems it does not render the slug anymore?

How does the model know to call sluggify()?

Hi,

This might seem like an obvious question, but how does the model know to call the sluggify() method when a new record is created?

The reason I'm asking is because I'm trying to implement a custom timestamp for a published date. I can get it to work with the following code, but it overrides sluggify():

    public static function boot()
    {
        parent::boot();

        static::saving(function($model) {
            if($model->published == 1 && $model->published_at == null)
            {
                $model->published_at = Carbon::now();
            }

            return true;

        });
    }

I could manually add your method to the above code, but it appears you figured out a simpler solution. Do you have any suggestions?

Sluggable does not update to "smaller" slugs

If you have the on_update set to true, sluggable should update the slug always when the build_from field is changed.

This does not happen if you have a smaller but simillar build_from field.

Example:
"My name is" -> my-name-is
"My name is A" -> my-name-is-a
"My name is" -> my-name-is-a ( it does not update back )

This happens because when you check for uniqueness, you do:

            $collection = $class::where( $save_to, 'LIKE', $slug.'%' )
                ->orderBy( $save_to, 'DESC' )
                ->get();

            // extract the slug fields

            $list = $collection->lists( $save_to, $model->getKeyName() );

            // if the current model exists in the list -- i.e. the existing slug is either
            // equal to or an incremented version of the new slug -- then the slug doesn't
            // need to change and we can just return

            if ( array_key_exists($model->getKey(), $list) ) {
                return true;
            }

This way, when you change back to "My name is", it will find its own entry "my-name-is-a" and will return true. The stored slug has a bigger length but its not the incremented version.

Ability to limit slug length

It's often desirable to limit slug length to some arbitrary number of characters for better URL's. E.g.:

Page title:
How I enabled clean slugs for my site for fun and profit using Eloquent-Sluggable and enabled better SEO

With 35 characters limit:
how-i-enabled-clean-slugs-for-my-s.html

Normally it would look ugly IMO (and probably would be bad for SEO/nice URL's etc):
how-i-enabled-clean-slugs-for-my-site-for-fun-and-profit-using-eloquent-sluggable-and-enabled-better-seo.html

It's probably better to customize the slug in such cases. In fact, a yet better option would be educating the editors to write more concise titles. But you know, these options are not always available.

Based on responses, -and if I have time- I may try to send a pull request for this.

Two Column , Two Slug in same table for Multi Language

Hi cviebrock ,
Thanks for amazing library. I have two column like 'title_en' , 'title_fr'. I didn't use a language table so both of column at the same table. I want to build two different 'slug_en' and 'slug_fr'. Do you have any suggest for this? How about I define one more sluggable function with different name?

Thanks for your concern..

Overriding build_from when "title" and slug are different values (store and update)...

Hi Colin,

Firstly thank you for creating this wonderful package - its been a bit of a life saver for various projects.

I've posted this same question on Stack Overflow but thought to write here in case it may be quicker. Imagine that someone creates a Post with a title "This is my post", then it converts it to "this-is-my-post" and does checks to ensure theres no duplicates in the table, or appends a "-1, -2" etc. this is great... however what I want is to do is add an optional form input (for the slug entry) on either the Create or Edit views which enables the user to override the "build_from" variable in the Model, but retain all the Sluggable checks to ensure there are no duplicates.

So a title "This is my post", with a slug of 'foo-bar' should make it retain the slug... however at the moment when I save it then it simply reverts back to "this-is-my-post" from the title.

Is it possible to bypass build_from in eloquent-sluggable? At the moment even if I change "Slug" manually after a save it still generates from "Title".

Appreciate any help, and thank you.

Hooking into model events causes no slug to be created

In controller:

$input = Input::all();
$this->model->fill($input)->save()

In model:

public static function boot() {
    parent::boot();
    static::saving(function($model) {
        return $model->arbitraryFunction();
    });
}

When I try to use this method, no slug is generated. Specifically, the return causes the failure. Confirmed by commenting like so:

In model:

public static function boot() {
    parent::boot();

    static::saving(function($model) {
        // return $model->arbitraryFunction();
    });
}

This causes the slugs to be generated again.

Not working

I know the issue title is not great, but that is what is happening here.

Using Laravel 4.2, eloquent-sluggable dev-master and Ardent.

Here is my model:

<?php
use Cviebrock\EloquentSluggable\SluggableInterface;
use Cviebrock\EloquentSluggable\SluggableTrait;

class Page extends Ardent implements SluggableInterface {

  use SluggableTrait;

  protected $fillable = ['title', 'body'];

  protected $sluggable = array('build_from' => 'title', 'on_update' => true);

  public static $rules = array(
    'title' => 'required',
    'body' => 'required'
  );

  public $autoHydrateEntityFromInput = true;
  public $forceEntityHydrationFromInput = true;

  public function beforeValidate()
  {
    $this->sluggify();
  }
}

Initially I did not have beforeValidate function. However, it did not generate slugs (even though slug is not required in Ardent rules), so I used beforeValidate(). This time it started with undefined index save_to, and it moved to next config item as I added those config items to sluggable array in model. Basically it did not read package config.

Yes, I did publish the package config.

I gave up when the error turned to

Call to undefined method Illuminate\Database\Query\Builder::withTrashed()

Argument #2 is not an array

I am just trying to set up your plugin using Laravel 4.0.x and my user mode looks like this:
capture decran 2014-07-04 a 16 26 14

But for some reason when I try to create and save my user I am getting this error:
capture decran 2014-07-04 a 16 24 50

My users table does have the slug column as mentioned in the readme.
Any idea what the issue could be?

Edit: Started to investigate a bit more and if I set $this->sluggable hardcoded I am getting this error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'sluggable' in 'field list' (SQL: insert into users....
Should this have been added automatically? Can't find anything in the readme about that.

Ignore id when on_update is true

For example, I have this sequence when on_update is true:

I created "My Blog Post" (id:1) that created "my-blog-post" slug.
I create another "My Blog Post" (id:2) that created "my-blog-post-1" slug.
I updated "My Blog Post" (id:2) that created "my-blog-post-2" slug.
I updated "My Blog Post" (id:2) that created "my-blog-post-3" slug.
I updated "My Blog Post" (id:2) that created "my-blog-post-4" slug.
I updated "My Blog Post" (id:2) that created "my-blog-post-5" slug.
...

How can I check for uniqueness based on another model?

I have Parent and Child models. I'd like Child to be unique based on the Parent then the Child slug so I would end up with a URL /{parent_slug}/{child_slug}. It looks like I need to override getExistingSlugs($slug) and pass in the $parent somehow to it to find if it's unique to that parent? Does this seem right? How do I pass the $parent_slug to the method if so?

Thanks for your help :)

Uniqueness using the replicate method

When a record is copied using the Eloquent replicate method the slug is not updated to be unique. The replicate method should check for uniqueness when saving the record.

Slug creation is bugged after creating enough similar slugs

I think I found a bug in your otherwise lovely piece of code :)

It seems like once slug creation reaches -9, it tries to restart at -1, and my seeder then comes to a halt because -1 already exists.

To recreate: Set up an infinite loop in a seed with a single row to insert that has the same slug each time. Slug column should have unique constraint. Error should appear after a short while, something like this:

Unique violation: 7 ERROR: duplicate key value violates unique constraint "slug_unique".

Allow to manually set slug to avoid problems when 'build_from' field is null

If my 'build_from' db field is null, and 'unique' set to true, then the slug will be something like '-1', '-2', '-3' ...
In some cases, I want the slug to be null.
If I try to save the slug manually by doing something like $model->slug = null; $model->save() then the slug will be automatically generated.

Uniqueness for model instance.

Hey. I really like your eloquent-sluggable.

Yet I could not be able to perform one action I really want and need.

Let's say I have multiple sub domains: foo, bar, foobar. And those domains have same directory and image file inside - those two sit in Project model.

Directory is: /bar
and image is: bar.jpg

I want to display either image or directory.

When you use unique u get:
foo.laravel.com/bar
foo.laravel.com/bar-1
bar.laravel.com/bar-2
bar.laravel.com/bar-3
foobar.laravel.com/bar-3
foobar.laravel.com/bar-4

When you won't use unique:
foo.laravel.com/bar
foo.laravel.com/bar
bar.laravel.com/bar
bar.laravel.com/bar
foobar.laravel.com/bar
foobar.laravel.com/bar

And you won't be able to display both file and dir. Some of it will take over.

The perfect solution is:
foo.laravel.com/bar
foo.laravel.com/bar-1
bar.laravel.com/bar
bar.laravel.com/bar-1
foobar.laravel.com/bar
foobar.laravel.com/bar-1

Can you achieve it using sluggable without editing your source?

Generate slug for existing records

Right now it only creates a migration. Would be nice to have a command that creates slugs for existing records in that table. Even nicer with a --seed flag that also creates a seed file for it.

Argument 1 passed to Cviebrock\EloquentSluggable\Sluggable::make() must be an instance of Eloquent, instance of Cartalyst\Sentry\Throttling\Eloquent\Throttle given

I am using Sentry for authentication and now when I am using Sluggable I get this error

Argument 1 passed to Cviebrock\EloquentSluggable\Sluggable::make() must be an    
instance of Eloquent, instance of Cartalyst\Sentry\Throttling\Eloquent\Throttle given, 
called in /Applications/MAMP/htdocs/laravel-site/vendor/cviebrock/
eloquent-sluggable/src/Cviebrock/EloquentSluggable/SluggableServiceProvider.php
on line 65 and defined

Using ID as part of the slug

I'm trying to create slugs that start with the ID - so we'd be able to lookup by ID but keep potentially changing slugs in the URLs. eg. /posts/101-my-post-slug

In the config file, I have:

'build_from' => ['id', 'title'],

Only the title is used. If I specify different fields, they seem to work too. It's just ID that doesn't want to work.

Is there something I've done incorrectly?
Thanks

[Suggestion] Setting uniqueness across multiple columns. (Like a composite index)

With Laravel's validator you can set the uniqueness on a table and column like so:

unique:users,posts

Would it be possible to have the 'unique' config setting accept something like this as a parameter?

The benefit of this would be that one user's slugs would not interfere with another user's and it would enable each user to have a clean url string that is still functional (assuming that in the controller you are filtering by the user).

Would you consider implementing this in a future version? Otherwise do you have any suggestions on how to integrate this smoothly?

Oh and thanks for making such an incredibly awesome package. This is by no means a complaint, just the one killer feature that I would love to use.

Update composer.json on dev-develop for Laravel 5 Support

I see you are making eloquent sluggable 5.* ready, is it possible for you to update composer.json on the development branche so we can add it to a laravel 5 project for testing?

(the dependencies need to be set to 5.* and these packages are not stable yet so the minimum stability needs to be dev as well.

Database will never be hit when using cache

So I'm not sure whether this was a simple oversight on your part, or I'm just not understanding this correctly. Please forgive me if it's the latter.

If you have the related config option set, calling makeSlugUnique() will immediately check the cache for the slug. Then, on line 127, you return the value incorporating the cache. The problem is that the database isn't hit at all.

The issue stems from the fact that the cache entry expires. After x minutes there will be nothing relating to the slug in the cache store. And as the database won't be hit, you'd end up with a collision.

I haven't tested it (I seriously apologize) but I didn't want to forget to file this. And again, really sorry if it's a misunderstanding on my part.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.