illuminatech / model-rules
Laravel 模型验证规则的集合,用于检查模型是否存在或是否唯一
Requires
- illuminate/validation: ^5.8 || ^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0 || ^11.0
Requires (Dev)
- illuminate/config: *
- illuminate/database: *
- phpunit/phpunit: ^7.5 || ^8.0 || ^9.3 || ^10.5
This package is auto-updated.
Last update: 2024-08-25 12:31:48 UTC
README
Laravel 模型验证规则
此扩展提供了一套验证规则,用于检查 Laravel 中模型是否存在或是否唯一。
有关许可信息,请检查 LICENSE 文件。
安装
安装此扩展的首选方式是通过 composer。
运行
php composer.phar require --prefer-dist illuminatech/model-rules
或者将以下内容添加到您的 composer.json 文件的 "require" 部分中。
"illuminatech/model-rules": "*"
使用方法
此扩展提供了一套验证规则,用于检查 Laravel 中模型是否存在或是否唯一。它有两个主要用途
- 定义 Eloquent 模型存在/唯一验证的流畅接口,
- 消除 Eloquent 模型操作中的冗余数据库查询。
假设我们有一个数据库存储了一些按类别分组的条目。当创建一个新的 HTTP 请求处理器以保存新条目时,我们需要检查给定的类别记录是否存在,并将其关联到新条目。有一个已知的建议,即使用关系方法,如 associate()
在对象级别处理关系实例化。然而,如果我们遵循它并结合标准验证,我们的程序会执行冗余的 SQL 查询。例如
<?php namespace App\Http\Controllers; use App\Models\Category; use App\Models\Item; use Illuminate\Http\Request; class ItemController extends Controller { public function store(Request $request) { $validatedData = $this->validate($request, [ 'name' => [ 'required', 'string', ], 'category_id' => [ 'required', 'integer', 'exists:categories,id', // executes SQL query 'SELECT ... FROM categories WHERE id =...' ], ]); $item = new Item(); $item->name = $validatedData['name']; $category = Category::query()->find($validatedData['category_id']); // executes another SQL query 'SELECT ... FROM categories WHERE id =...' $item->category()->associate($category); $item->save(); // ... } }
要解决这个问题,您可以使用 Illuminatech\ModelRules\Exists
验证规则。在验证过程中,它会“记住”最后一次查询的模型实例,您可以使用 getModel()
方法检索它。例如
<?php namespace App\Http\Controllers; use App\Models\Category; use App\Models\Item; use Illuminate\Http\Request; use Illuminatech\ModelRules\Exists; class ItemController extends Controller { public function store(Request $request) { $validatedData = $this->validate($request, [ 'name' => [ 'required', 'string', ], 'category_id' => [ 'required', 'integer', $categoryRule = Exists::new(Category::class), // executes SQL query 'SELECT ... FROM categories WHERE id =...' only once ], ]); $item = new Item(); $item->name = $validatedData['name']; $category = $categoryRule->getModel(); // returns model fetched during validation without extra SQL query $item->category()->associate($category); $item->save(); // ... } }
您可以使用 Illuminatech\ModelRules\Unique
为唯一模型属性设置验证。例如
<?php namespace App\Http\Controllers; use App\Models\Item; use Illuminate\Http\Request; use Illuminatech\ModelRules\Unique; class ItemController extends Controller { public function update(Request $request, Item $item) { $validatedData = $this->validate($request, [ 'name' => [ 'required', 'string', ], 'slug' => [ 'required', 'string', Unique::new(Item::class, 'slug') // check 'slug' value is unique throughout the items ->ignore($item), // exclude current record from validation ], ]); $item = new Item(); $item->name = $validatedData['name']; $item->slug = $validatedData['slug']; $item->save(); // ... } }
自定义数据库查询
您可以直接将查询构建器实例传递给模型验证规则。这允许您指定任何自定义搜索条件或使用关系查询。例如
<?php namespace App\Http\Controllers; use App\Models\RefundRequest; use App\Models\RefundReason; use Illuminate\Http\Request; use Illuminatech\ModelRules\Exists; class RefundRequestController extends Controller { public function store(Request $request) { $user = $request->user(); $validatedData = $this->validate($request, [ 'reason_id' => [ 'required', 'integer', $reasonRule = Exists::new(RefundReason::query()->withoutTrashed()), // custom query condition ], 'order_id' => [ 'required', 'integer', $orderRule = Exists::new($user->orders()), // use a relation, e.g. `Order::query()->where('user_id', $user->id)` ], ]); $refundRequest = new RefundRequest(); $refundRequest->reason()->associate($reasonRule->getModel()); $refundRequest->order()->associate($orderRule->getModel()); $refundRequest->save(); // ... } }
注意:此扩展不对查询构建器对象类型施加显式限制 - 它仅期望匹配数据库查询构建器语法。因此,您可能创建一个自定义查询构建器类,该类适用于特殊数据存储,如 MongoDB 或 Redis,并将其实例作为数据源传递。如果其方法签名匹配
\Illuminate\Database\Query\Builder
- 它应该可以工作。尽管这并不保证。
自定义错误信息
您可以使用 withMessage()
方法为模型规则设置自定义错误信息。如果模型实例在验证失败后可用,则可以使用语法 model_{attribute}
将其属性用作错误信息中的占位符。例如
<?php namespace App\Http\Controllers; use App\Models\Item; use Illuminate\Http\Request; use Illuminatech\ModelRules\Unique; class ItemController extends Controller { public function update(Request $request, Item $item) { $validatedData = $this->validate($request, [ 'name' => [ 'required', 'string', ], 'slug' => [ 'required', 'string', Unique::new(Item::class, 'slug') ->ignore($item) ->withMessage('This slug is already in use at item ID=:model_id'), // on failure produces error "This slug is already in use at item ID=19" ], ]); // ... } }
验证多个模型
假设我们有一个数据库,其中条目和类别以 '多对多' 的方式链接。在这种情况下,存储条目的请求将包含一个类别 ID 列表,这些 ID 应与条目相关联。此请求处理器的普通外观可能如下所示
<?php namespace App\Http\Controllers; use App\Models\Category; use App\Models\Item; use Illuminate\Http\Request; class ItemController extends Controller { public function store(Request $request) { $validatedData = $this->validate($request, [ 'name' => [ 'required', 'string', ], 'category_ids' => [ 'required', 'array', ], 'category_ids.*' => [ 'exists:categories,id', // executes SQL query 'SELECT ... FROM categories WHERE id =...' in cycle multiple times! ], ]); $item = new Item(); $item->name = $validatedData['name']; $item->categories()->sync($validatedData['category_ids']); $item->save(); // ... } }
您可以使用 Illuminatech\ModelRules\MultiExist
验证规则减少此类场景中的数据库查询数量。例如
<?php namespace App\Http\Controllers; use App\Models\Category; use App\Models\Item; use Illuminate\Http\Request; use Illuminatech\ModelRules\MultiExist; class ItemController extends Controller { public function store(Request $request) { $validatedData = $this->validate($request, [ 'name' => [ 'required', 'string', ], 'category_ids' => [ 'required', 'array', $categoryRule = MultiExist::new(Category::class), // executes single SQL query 'SELECT ... FROM categories WHERE id IN (...)' ], ]); $item = new Item(); $item->name = $validatedData['name']; $item->categories()->sync($categoryRule->getModels()); $item->save(); // ... } }
注意:如您所猜测的,还有一个
Illuminatech\ModelRules\MultiUnique
验证规则,但其实际应用相当有限。
请注意!请记住,模型验证规则不是累积的,每个规则只记住最后一次查询的模型。因此,在嵌套数组验证(例如,您希望将多个条目作为单个 HTTP 请求的一批存储)的情况下,它可能无法为您提供良好的服务。对于此类情况,最好将验证分成多个步骤。例如
<?php namespace App\Http\Controllers; use App\Models\Category; use App\Models\Item; use Illuminate\Http\Request; use Illuminatech\ModelRules\Exists; class ItemController extends Controller { public function storeBatch(Request $request) { $firstRoundValidated = $this->validate($request, [ 'items' => ['required', 'array'], 'items.*.name' => ['required', 'string'], 'items.*.category_id' => ['required', 'integer'], ]); $items = []; $categoryRule = Exists::new(Category::class); foreach ($firstRoundValidated['items'] as $key => $item) { $this->validate($request, [ "items.{$key}.category_id" => $categoryRule, // validate single item at once ]); // create item draft: $item = new Item(); $item->name = $item['name']; $item->category()->associatte($categoryRule->getModel()); $items[] = $item; } foreach ($items as $item) { $item->save(); // save item drafts } // ... } }
与表单请求一起工作
您可以使用模型规则与表单请求验证一起使用。例如
<?php namespace App\Http\Controllers; use App\Models\Category; use App\Models\Item; use Illuminate\Http\Request; use Illuminate\Foundation\Http\FormRequest; use Illuminatech\ModelRules\Exists; class ItemStoreRequest extends FormRequest { private $categoryRule; public function rules() { return [ 'name' => [ 'required', 'string', ], 'category_id' => [ 'required', 'integer', $this->categoryRule = Exists::new(Category::class), ], ]; } public function validatedCategory(): Category { return $this->categoryRule->getModel(); } } class ItemController extends Controller { public function store(ItemStoreRequest $request) { $validatedData = $request->validated(); $item = new Item(); $item->name = $validatedData['name']; $item->category()->associate($request->validatedCategory()); $item->save(); // ... } }