infab / eloquent-sluggable
在Laravel中轻松为Eloquent模型创建别名
Requires
- php: ^7.3.0
- cocur/slugify: ^4.0
- illuminate/config: ^8.0
- illuminate/database: ^8.0
- illuminate/support: ^8.0
Requires (Dev)
- limedeck/phpunit-detailed-printer: ^5.0
- mockery/mockery: ^1.2.3
- orchestra/database: ^6.0
- orchestra/testbench: ^6.0
- phpunit/phpunit: ^8.0
This package is not auto-updated.
Last update: 2024-09-20 03:13:05 UTC
README
在Laravel中为Eloquent模型轻松创建别名。
注意:以下说明适用于Laravel 6.0。如果您正在使用Laravel 5.8,请参阅旧版本的文档。
- 背景:什么是别名
- 安装
- 更新您的Eloquent模型
- 用法
- SlugService类
- 事件
- 配置
- 扩展Sluggable
- SluggableScopeHelpers特质
- 路由模型绑定
- 错误、建议、贡献和支持
- 版权和许可
背景:什么是别名?
别名是字符串的简化版本,通常是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.0 | 7.0.* |
6.0 | 6.0.* |
5.8 | 4.8.* |
5.7 | 4.6.|4.7. |
5.6 | 4.5.* |
5.5 | 4.3.|4.4. |
5.4 | 4.2.* |
较旧版本的Laravel可以使用较旧版本的包,尽管它们不再受支持或维护。有关具体信息,请参阅CHANGELOG.md和UPGRADING.md,并确保您正在阅读您版本的正确的README.md(GitHub默认显示master分支的版本,这可能不是您想要的)。
使用Composer安装包
$ composer require cviebrock/eloquent-sluggable
该包将自动注册其服务提供者。
可选地,如果您想更改任何默认设置,可以发布配置文件
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的方法。此配置有三个可能的选项
当
method
为null(默认设置)时,包将使用默认的slugging引擎-- cocur/slugify --来创建slug。当
method
是可调用的,那么将使用该函数或类方法。函数/方法应期望两个参数:要处理的字符串和分隔符字符串。例如,要使用Laravel的Str::slug
,可以这样做
'method' => ['Illuminate\\Support\\Str', 'slug'],
- 您还可以将
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!
请使用Github来报告错误,以及发表评论或建议。
有关如何贡献更改的详细信息,请参阅CONTRIBUTING.md。
版权和许可
eloquent-sluggable是由Colin Viebrock编写的,并发布在MIT许可证下。
版权所有 (c) 2013 Colin Viebrock