khalyomede / laravel-eloquent-uuid-slug
使用自动生成的UUID slugs来标识和检索您的Eloquent模型。
Requires
- php: >=8.2.0
- laravel/framework: 11.*
Requires (Dev)
- friendsofphp/php-cs-fixer: 3.54.0
- larastan/larastan: 2.9.5
- nunomaduro/collision: 8.1.1
- orchestra/testbench: 9.0.4
- phpunit/phpunit: 11.1.3
- rector/rector: 1.0.4
README
摘要
关于
默认情况下,当使用路由模型绑定从控制器获取模型时,Laravel会尝试使用路由中的参数查找模型,并将其关联到相关表的默认标识符(大多数情况下是“id”键)。
// routes/web.php use App\Models\Cart; use Illuminate\Support\Facades\Route; // --> What you see Route::get("/cart/{cart}", function(Cart $cart) { // $cart ready to be used }); // --> What happens behind the scene Route::get("/cart/{cart}", function(string $identifier) { $cart = Cart::findOrFail($identifier); // $cart ready to be used });
这意味着如果您提供查看购物车的可能性,您将暴露如/cart/12之类的路由。这在安全方面并不理想,因为您现在暴露了购物车数据库标识符,如果您的购物车策略出现错误,恶意用户可以访问其他用户的购物车(/cart/41)。
在这种情况下,UUID非常有用,因为
- 它们提供了一个创建随机、难以预测标识符的好方法
- 可以从代码中手动生成 UUID
- 不太可能冲突
最佳场景是将此UUID暴露,而不是数据库自动增加的标识符,例如/cart/e22b86bcb8e24cfea13856a0766bfef2。
此包的目标是尽可能简化此任务。
特性
- 提供一种特性来配置您的路由模型绑定以使用slug列
- 提供一个助手在迁移中创建slug列,根据您的配置
- 提供一个作用域以通过slug列查找模型
- 允许您自定义slug列的名称
需求
- 此包依赖于您模型上的这些方法 ,如果重写它们,则可能无法保证逻辑正常工作
安装
1. 安装此包
composer require khalyomede/laravel-eloquent-uuid-slug
2. 设置您的模型
在您的选择模型中,使用Sluggable
特性。
namespace App\Models; use Illuminate\Database\Eloquent\Model; use Khalyomede\EloquentUuidSlug\Sluggable; class Cart extends Model { use Sluggable; }
3. 在迁移中添加slug列
Sluggable
特性提供了Sluggable::addSlugColumn()
方法,使此步骤变得轻松。
use App\Models\Cart; use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; final class CreateCartsTable extends Migration { public function up(): void { Schema::create('carts', function (Blueprint $table): void { $table->id(); $table->string('name'); Cart::addSlugColumn($table); $table->timestamps(); }); } public function down(): void { Schema::drop('carts'); } }
如果您不希望Sluggable::addSlugColumn(Blueprint)
添加SQL约束(唯一索引和非空列),请使用其对应的方法Sluggable::addUnconstrainedSlugColumn(Blueprint)
。
use App\Models\Cart; use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; final class CreateCartsTable extends Migration { public function up(): void { Schema::create('carts', function (Blueprint $table): void { $table->id(); $table->string('name'); Cart::addUnconstrainedSlugColumn($table); $table->timestamps(); }); } public function down(): void { Schema::drop('carts'); } }
示例
1. 配置slug列名称
默认情况下,Sluggable
特性将假定slug列的名称为slug
。以下是提供您首选名称的方法。
namespace App\Models; use Illuminate\Database\Eloquent\Model; use Khalyomede\EloquentUuidSlug\Sluggable; class Cart extends Model { use Sluggable; public function slugColumn(): string { return 'code'; } }
2. 使用破折号生成UUID
默认情况下,Sluggable
特性将配置UUID生成器以删除破折号,以帮助缩短URL。如果您希望保留它们,以下是实现方法。
namespace App\Models; use Illuminate\Database\Eloquent\Model; use Khalyomede\EloquentUuidSlug\Sluggable; class Cart extends Model { use Sluggable; public function slugWithDashes(): bool { return true; } }
3. 为特定路由自定义路由模型绑定
默认情况下,所有使用Sluggable
特性的模型在执行路由模型绑定时将使用slug列检索其模型。
如果您想为特定路由绕过它,您可以偶尔自定义用于检索您模型所用的列。
例如,这是如何使用id检索特定路由下的Cart模型。
// routes/web.php use App\Models\Cart; use Illuminate\Support\Facades\Route; // --> What you see Route::get("/cart/{cart:id}", function(Cart $cart) { // $cart ready to be used });
作为最后的手段,如果这种方法不起作用,您始终可以回退到从路由获取原始数据,并自行获取模型。
// routes/web.php use App\Models\Cart; use Illuminate\Support\Facades\Route; // --> What you see Route::get("/cart/{cart}", function(string $identifier) { $cart = Cart::findOrFail($identifier); // $cart ready to be used });
4. 在迁移中自定义slug列
在调用方法 Sluggable::addSlugColumn()
后,您可以立即使用所有可用的列修饰符来重新排序列或添加一些注释等。
use App\Models\Cart; use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; final class CreateCartsTable extends Migration { public function up(): void { Schema::create('carts', function (Blueprint $table): void { $table->id(); $table->string('name'); Cart::addSlugColumn($table) ->after('name') ->comment('Auto-generated by a package.'); $table->timestamps(); }); } public function down(): void { Schema::drop('carts'); } }
通过slug检索模型
为了帮助您通过slug手动检索模型,您可以使用Sluggable::scopeWithSlug()
作用域来完成。它遵循您的配置,因此无论您如何命名slug列,它仍然有效。
// routes/web.php use App\Models\Cart; use Illuminate\Support\Facades\Route; Route::get("/cart/{cart}", function(string $identifier) { $cart = Cart::withSlug($identifier)->firstOrFail(); // $cart ready to be used });
还存在简写方法,如Sluggable::findBySlug()
、Sluggable::findBySlugOrFail()
、Sluggable::firstBySlug()
或Sluggable::firstBySlugOrFail()
。
// routes/web.php use App\Models\Cart; use Illuminate\Support\Facades\Route; Route::get("/cart/{cart}", function(string $identifier) { $cart = Cart::findBySlugOrFail($identifier); $cart = Cart::findBySlug($identifier); $cart = Cart::where("id", ">=", 1)->firstBySlug($identifier); $cart = Cart::where("id", ">=", 1)->firstBySlugOrFail($identifier); // $cart ready to be used });
删除slug列
当您只想在现有表中删除slug列时,请使用Sluggable::dropSlugColumn(Blueprint)
。请遵循“down”方法的完整说明,因为有一些需要解决的陷阱。
use App\Models\Cart; use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; final class DropSlugColumnOnCartsTable extends Migration { public function up(): void { Schema::create('carts', function (Blueprint $table): void { Cart::dropSlugColumn($table); }); } public function down(): void { Schema::table('posts', function (Blueprint $table): void { Cart::addUnconstrainedSlugColumn($table); }); Schema::table('posts', function (Blueprint $table): void { Cart::fillEmptySlugs(); Cart::constrainSlugColumn($table); }); } }
通过slug验证值是否存在
您可以通过您定义的slug列验证模型是否存在。这与调用现有的“exists”规则等效。
"post_id" => "exists:posts,slug"
但是,无需手动指定slug列(它将根据您是否自定义了名称自动获取)。
// app/Http/Controllers/PostController.php namespace App\Http\Controllers; use App\Models\Post; use Illuminate\Support\Facades\Validator; use Khalyomede\EloquentUuidSlug\Rules\ExistsBySlug; class PostController extends Controller { public function store(Request $request) { $validator = Validator::make($request->all(), [ "post_id" => ["required", new ExistsBySlug(Post::class)], ]); // ... } }
兼容性表
下表显示了Laravel、PHP和本包当前版本的兼容性。关于此包先前版本的兼容性,请浏览另一个标签。
为了验证这些结果,您可以使用Docker容器(参见docker-compose.yml文件)来运行测试部分中描述的测试。
替代品
我创建此包主要是为了练习创建测试过的laravel包,并尝试我的第一个Github Workflow。还有一些高质量包可供选择,请务必查看它们!
测试
composer run test
composer run analyse
composer run check
composer run lint
composer run scan
composer run updates
或者
composer run all