infab/eloquent-sluggable

在Laravel中轻松为Eloquent模型创建别名

8.0.0 2020-09-09 11:44 UTC

This package is not auto-updated.

Last update: 2024-09-20 03:13:05 UTC


README

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

注意:以下说明适用于Laravel 6.0。如果您正在使用Laravel 5.8,请参阅旧版本的文档

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

背景:什么是别名?

别名是字符串的简化版本,通常是URL友好的。对字符串进行“别名化”通常涉及将其转换为单一种大小写,并删除任何非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

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

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

这样的URL会让用户更高兴(它可读,易于输入等)。

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

别名通常是唯一的。因此,如果您又写了一篇相同标题的文章,您将需要以某种方式区分它们,通常是在别名的末尾添加一个递增的计数器

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自动处理所有这些,同时配置量最小。

安装

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

Laravel版本包版本
7.07.0.*
6.06.0.*
5.84.8.*
5.74.6.|4.7.
5.64.5.*
5.54.3.|4.4.
5.44.2.*

较旧版本的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"

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

源值生成的别名
字符串字符串
空字符串不会设置别名
null不会设置别名
0"0"
1"1"
false"0"
true"1"

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

SlugService类

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

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

use \Cviebrock\EloquentSluggable\Services\SlugService;

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

这在对Ajax控制器或类似的东西很有用,在这些控制器中,您想在创建模型之前显示用户给定测试输入的唯一别名。该方法的前两个参数是要测试的模型和别名字段,第三个参数是用于测试别名的源字符串。

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

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

事件

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

可生成别名的模型将触发两个Eloquent模型事件:“slugging”和“slugged”。

“slugging”事件在生成别名之前触发。如果该事件的回调返回false,则不会执行别名的生成。如果返回其他任何内容,包括null,则将执行别名的生成。

“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

这是构建slug的字段或字段数组。每个$model->field将通过空格分隔进行连接,以构建可用的slug字符串。这些可以是模型属性(即数据库中的字段)、关联属性或自定义获取器。

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

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()的值作为生成slug的来源。

maxLength

将其设置为正整数将确保生成的slug受最大长度的限制(例如,确保它们适合您的数据库字段)。默认情况下,此值是null,不强制执行限制。

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

maxLengthKeepWords

如果您正在使用maxLength设置截断slug,那么您可能希望确保slug不会在单词中间截断。例如,如果您的源字符串是"My First Post",并且您的maxLength是10,则生成的slug将变为"my-first-p",这不是理想的。

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

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

method

定义用于将可用的字符串转换为slug的方法。此配置有三个可能的选项

  1. method为null(默认设置)时,包将使用默认的slugging引擎-- cocur/slugify --来创建slug。

  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的任何其他值都将抛出异常。

有关更复杂的slugging要求,请参阅下面的扩展Sluggable

onUpdate

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

如果您想重新生成一个或多个模型的重写字段,可以在更新之前将这些字段设置为null或空字符串

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

如果您希望每次更新模型时都使用这种行为,则将onUpdate选项设置为true。

separator

这定义了构建slug时使用的分隔符,并将其传递给上面定义的method。默认值是连字符。

unique

这是一个布尔值,表示slug是否应在给定类型的所有模型中是唯一的。例如,如果您有两个名为"My Blog Post"的博客文章,那么如果unique为false,它们都将转换为"my-blog-post"。这可能是一个问题,例如,如果您在URL中使用slug。

unique设置为true,则第二个帖子模型将转换为"my-blog-post-1"。如果有第三个帖子具有相同的标题,则转换为"my-blog-post-2",依此类推。每个后续模型将在slug的末尾附加增量值,以确保唯一性。

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特质

将可选的 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-sluggable是由Colin Viebrock编写的,并发布在MIT许可证下。

版权所有 (c) 2013 Colin Viebrock