quocphongdn/eloquent-sluggable

在 Laravel 中为 Eloquent 模型轻松创建 slug

6.0.2 2019-10-10 04:32 UTC

README

在 Laravel 中为 Eloquent 模型轻松创建 slug。

注意:以下说明适用于 Laravel 6.0。如果您使用的是 Laravel 5.8,请参阅上一个版本的文档

Build Status Total Downloads Latest Stable Version Latest Unstable Version Scrutinizer Code Quality SensioLabsInsight License: MIT

背景:什么是 slug?

slug 是字符串的简化版本,通常是 URL 友好的。对字符串进行“slug 化”通常涉及将其转换为一种大小写,并删除任何非 URL 友好的字符(空格、带重音的字母、和号等)。然后可以将生成的字符串用作特定资源的标识符。

例如,如果您有一个带有文章的博客,您可以通过 ID 来引用每篇文章

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

...但这并不特别友好(特别是对于SEO)。您可能希望使用文章的标题作为 URL,但如果文章的标题是“我的安德烈晚餐 & 弗朗索瓦”,这看起来也很丑陋

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

解决方案是为标题创建一个 slug 并使用它。您可能想使用 Laravel 内置的 Str::slug() 方法将标题转换为更友好的格式

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

这样的 URL 会使用户更快乐(它可读,易于输入等)。

有关更多信息,您可能想阅读维基百科上的此描述

slugs 通常也是唯一的。因此,如果您写另一篇与同一标题的文章,您将需要以某种方式区分它们,通常是在 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

这保持了 URL 的唯一性。

Eloquent-Sluggable 包旨在自动处理所有这些,配置最少。

安装

根据您的 Laravel 版本,您应该安装包的不同版本。 注意:从版本 6.0 开始,包的版本应与 Laravel 版本匹配。

Laravel 的较旧版本可以使用包的较旧版本,尽管它们不再受支持或维护。有关详细信息,请参阅CHANGELOG.mdUPGRADING.md,并确保您正在阅读您版本的正确 README.md(GitHub 默认在 master 分支显示版本,这可能不是您想要的)。

  1. 通过 Composer 安装包

    $ composer require cviebrock/eloquent-sluggable

    包将自动注册其服务提供者。

  2. 可选地,如果您想更改任何默认值,可以发布配置文件

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

更新您的 Eloquent 模型

您的模型应使用 Sluggable 特性,该特性有一个抽象方法 sluggable(),您需要对其进行定义。这是设置任何模型特定配置的地方(有关详细信息,请参阅下面的配置

use Cviebrock\EloquentSluggable\Sluggable;

class Post extends Model
{
    use Sluggable;

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

当然,您的模型和数据库需要创建一个用于存储缩略名的列。您可以使用 slug 或您想要的任何其他合适名称;您的配置数组将决定数据存储在哪个字段。您需要手动通过自己的迁移添加该列。

就是这样...您的模型现在具有“可缩略”功能!

使用

保存模型很容易

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

$post->save();

检索缩略名也同样简单

echo $post->slug;

注意,如果您正在使用 Eloquent 的 replicate() 方法复制模型,该软件包将自动重新生成缩略名以确保唯一性。

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

$post->save();
// $post->slug is "my-awesome-blog-post"

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

请注意,空字符串、非字符串或其他“奇怪”的源值将产生不同的缩略名

(上述值将受到任何唯一性或其他检查的影响。)

SlugService 类

生成缩略名的所有逻辑都由 \Cviebrock\EloquentSluggable\Services\SlugService 类处理。

通常,您不需要直接访问此类,尽管有一个静态方法可以用来为给定的字符串生成缩略名,而无需实际创建或保存关联的模型。

use \Cviebrock\EloquentSluggable\Services\SlugService;

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

这将在实际创建模型之前,对于给定的测试输入显示用户唯一的缩略名,非常有用。该方法的前两个参数是正在测试的模型和缩略字段,第三个参数是用于测试缩略名的源字符串。

您还可以将可选的配置值数组作为第四个参数传递。这些值将覆盖正在测试的缩略字段的标准配置值。例如,如果您的模型配置为使用唯一缩略名,但您出于某种原因想要生成缩略名的“基础”版本,可以这样做

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

事件

注意:事件应该可以工作,但尚未完全测试。请帮助我

可缩略的模型将触发两个 Eloquent 模型事件:“slugging”和“slugged”。

“slugging”事件在生成缩略名之前触发。如果此事件的回调返回 false,则不会执行缩略。

“slugged”事件在生成缩略名之后触发。如果模型不需要缩略(由 needsSlugging() 方法确定),则不会调用此事件。

您可以通过像其他 Eloquent 模型事件一样的方式挂钩到这两个事件中的任何一个

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

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

配置

配置被设计得尽可能灵活。您可以设置所有 Eloquent 模型的默认值,然后为单个模型覆盖这些设置。

默认情况下,全局配置在 config/sluggable.php 文件中设置。如果没有设置配置,则使用软件包的默认值。以下是一个配置示例,显示了所有默认设置

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

对于单个模型,配置在您需要实现的 sluggable() 方法中处理。该方法应返回一个索引数组,键表示存储缩略值的字段,值是该字段的配置。这意味着您可以为同一模型创建多个基于不同源字符串和不同配置选项的缩略名。

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

source

这是构建缩略名的字段或字段数组。每个 $model->field 都通过空格分隔连接起来,以构建可缩略的字符串。这些可以是模型属性(即数据库中的字段)、关系属性或自定义获取器。

要引用相关模型的字段,使用点表示法。例如,以下书籍的缩略名将根据其作者的姓名和书籍的标题生成

class Book extends Eloquent
{
    use Sluggable;

    protected $fillable = ['title'];

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

使用自定义获取器的示例

class Person extends Eloquent
{
    use Sluggable;

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

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

如果 source 为空、false 或 null,则将使用 $model->__toString() 的值作为生成缩略名的源。

maxLength

将此设置为正整数将确保生成的缩略名限制在最大长度内(例如,以确保它们适合您的数据库字段)。默认情况下,此值为 null,不强制执行任何限制。

注意:如果启用 unique(默认情况下是启用的),并且你预计会有多个具有相同缩略名称的模型,那么你应该将此值设置为比你的数据库字段长度少几个字符。原因在于,类会为了保持唯一性,将 "-1"、" -2"、" -3" 等追加到后续的模型中。这些增量扩展不包括在 maxLength 计算的部分中。

maxLengthKeepWords

如果你正在使用 maxLength 设置截断你的缩略名称,那么你可能想确保你的缩略名称不要在单词中间被截断。例如,如果你的源字符串是 "My First Post",并且你的 maxLength 是 10,生成的缩略名称将是 "my-first-p",这并不理想。

默认情况下,maxLengthKeepWords 的值设置为 true,这将截断缩略名称末尾的截断单词,结果为 "my-first" 而不是 "my-first-p"。

如果你想保留部分单词,则将此配置设置为 false。

method

定义了将可缩略字符串转换为缩略名称的方法。此配置有三个可能的选项。

  1. method 为 null(默认设置)时,包使用默认的缩略名称引擎 -- cocur/slugify -- 来创建缩略名称。

  2. method 是一个可调用的函数时,则使用该函数或类方法。该函数/方法应期望两个参数:要处理的字符串和分隔符字符串。例如,要使用 Laravel 的 Str::slug,可以这样做

'method' => ['Illuminate\\Support\\Str', 'slug'],
  1. 你还可以将 method 定义为一个闭包(同样,期望两个参数)
'method' => function ($string, $separator) {
    return strtolower(preg_replace('/[^a-z]+/i', $separator, $string));
},

method 的其他任何值都会抛出异常。

有关更复杂的缩略名称要求,请参阅下方的 扩展 Sluggable

onUpdate

默认情况下,更新模型不会尝试生成新的缩略名称值。假设一旦生成了缩略名称,就不希望它改变(这可能特别适用于如果你使用缩略名称作为 URL,并且不希望破坏你的 SEO 功效)。

如果你想要重新生成一个或多个模型中的缩略名称字段,可以在更新之前将这些字段设置为 null 或空字符串

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

如果你每次更新模型都想要这种行为,则将 onUpdate 选项设置为 true。

separator

这定义了构建缩略名称时使用的分隔符,并传递给上面定义的 method。默认值是破折号。

unique

这是一个布尔值,定义了缩略名称是否应在给定类型的所有模型中保持唯一性。例如,如果你有两个博客文章,并且两者都称为 "My Blog Post",那么如果 unique 为 false,它们都将缩略为 "my-blog-post"。这可能会成为一个问题,例如如果你在 URL 中使用缩略名称。

unique 设置为 true,则第二个 Post 模型将缩略为 "my-blog-post-1"。如果有第三个具有相同标题的文章,它将缩略为 "my-blog-post-2",依此类推。每个后续模型都将附加一个增量值到缩略名称的末尾,以确保唯一性。

uniqueSuffix

如果你想要使用不同的方式来识别唯一性(除了自动增加整数),可以将 uniqueSuffix 配置设置为函数或可调用的函数,为你生成 "唯一" 值。

该函数应接受三个参数:基本别名(即非唯一别名),分隔符字符串,以及所有以相同别名为开头的其他别名字符串的 \Illuminate\Support\Collection。然后您可以进行任何操作来创建一个尚未被集合中任何别名使用的新的后缀。例如,如果您想用字母而不是数字作为后缀,这是实现这一目标的一种方法

'uniqueSuffix' => function ($slug, $separator, Collection $list) {
    $size = count($list);

    return chr($size + 96);
}

includeTrashed

将此设置为 true 还会在尝试强制唯一性时检查已删除的模型。这仅影响使用 软删除 功能的 Eloquent 模型。默认为 false,因此软删除的模型在检查唯一性时不计算在内。

reserved

一个数组,其中包含永远不会被允许作为别名的值,例如,为了防止与现有的路由或控制器方法冲突等。这可以是一个数组,也可以是一个返回数组的闭包。默认为 null:没有保留的别名名称。

简短配置

如果真的很懒惰,该软件包支持一个非常简短的配置语法

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

这将使用 config/sluggable.php 中的所有默认选项,使用模型的 __toString() 方法作为源,并将别名存储在 slug 字段中。

扩展 Sluggable

有时配置选项不足以满足复杂需求(例如,可能需要考虑其他属性的唯一性测试)。

在这种情况下,该软件包提供了对别名词工流程的钩子,您可以在每个模型的基础上或在自己的特质中扩展软件包的特质,使用自己的函数。

customizeSlugEngine

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

如果此方法存在于您的模型中,则在别名词发生之前可以自定义 Slugify 引擎。这可能就是您更改所使用的字符映射的地方,或者更改语言文件等。

您可以根据模型和属性进行自定义(也许您的模型有两个别名字段,其中一个需要自定义)。

请查看 tests/Models/PostWithCustomEngine.php 以获取示例。

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, $attribute, $config, $slug)
{
    ...
}

如果此作用域存在于您的模型中,则它也将应用于确定给定别名是否唯一的查询。

  • $model -- 正在被别名的对象
  • $attribute -- 正在被生成的别名字段
  • $config -- 给定模型和属性的配置数组
  • $slug -- "基本" 别名(在应用任何唯一后缀之前)

请随意在查询作用域中使用这些值。例如,查看 tests/Models/PostWithUniqueSlugConstraints.php,其中别名为标题生成,但别名被限制在作者上。因此,Bob 可以有一篇与 Pam 的帖子相同的标题,但两者的别名将相同。

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, $attribute, $config, $slug)
{
    ...
}

这是查找模型“类似”别名的默认作用域。基本上,该软件包查找与 $slug 参数相同或以 $slug 加上分隔符字符串开头的现有别名。传递给 uniqueSuffix 处理器的结果是集合。

通常,此查询作用域(在 Sluggable 特质中定义)应保持不变。然而,您可以在模型中自由地重载它。

SluggableScopeHelpers Trait

将可选的 SluggableScopeHelpers 特质添加到您的模型中,允许您处理模型及其别名。例如

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

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

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

由于模型可以有一个以上的别名,这需要更多的配置。有关所有详细信息,请参阅 SCOPE-HELPERS.md

路由模型绑定

有关详细信息,请参阅 ROUTE-MODEL-BINDING.md

错误、建议、贡献和支持

感谢所有为这个项目做出贡献的人!特别感谢JetBrains的开放源代码许可计划...以及当然,出色的PHPStorm IDE!

JetBrains

请使用Github来报告错误、发表评论或建议。

有关如何贡献更改,请参阅CONTRIBUTING.md

版权和许可

eloquent-sluggableColin Viebrock编写,并使用MIT许可证发布。

版权所有(c)2013 Colin Viebrock