indexzer0/laravel-validation-provider

简单的可重用可组合验证提供者


README

Latest Version on Packagist GitHub Tests Action Status codecov Total Downloads

  • 在干净的、可重用的可组合提供者中编写所有验证规则。
  • 标准化您在表单请求和其他地方定义和使用验证的方式。
  • 轻松使用多个验证提供者组合验证规则。
  • 方便ValidationProvider直接创建和验证数据。

简单示例

  • 在数组中嵌套域模型规则。
use IndexZer0\LaravelValidationProvider\Facades\ValidationProvider;

class AuthorValidationProvider extends AbstractValidationProvider
{
    protected array $rules = ['name' => ['required']];
}

class BookValidationProvider extends AbstractValidationProvider
{
    protected array $rules = ['title' => ['required']];
}

$validationProvider = ValidationProvider::make([
    'author' => [
        AuthorValidationProvider::class,
        new ArrayValidationProvider('books', new BookValidationProvider()),
    ],
]);
$validationProvider->rules();
// [
//     'author.name'          => ['required'],
//     'author.books.*.title' => ['required'],
// ]

要求

安装

您可以通过composer安装此包

composer require indexzer0/laravel-validation-provider

使用

定义验证提供者

  • 在验证提供者类中创建域概念的细粒度表示。
    • 应扩展AbstractValidationProvider

通过属性

$rules$messages$attributes

class AddressValidationProvider extends AbstractValidationProvider
{
    protected array $rules = [
        'post_code' => ['required', 'string', 'between:1,20'],
    ];

    protected array $messages = [];
   
    protected array $attributes = [];
}

通过方法

  • 您还可以定义方法rules()messages()attributes()
    • 有时您需要动态地定义规则、消息和属性。
    • 您正在使用一个依赖规则
class AddressValidationProvider extends AbstractValidationProvider
{
    public function rules(): array
    {
        return [
            'post_code' => ['required', 'string', "regex:" . RegexHelper::getPostCodeRegex()],
            'zip_code' => ["same:{$this->dependentField('post_code')}"]
        ];
    }
    
    public function messages(): array { return []; }
    
    public function attributes(): array { return []; }
}

创建验证提供者

创建验证提供者有3种方式。外观、手动创建和流畅API。

在所有3个示例中,我们将使用以下两个定义的验证提供者以及此包的核心验证提供者来实现验证规则。

class AuthorValidationProvider extends AbstractValidationProvider
{
    protected array $rules = ['name' => ['required'],];
}

class BookValidationProvider extends AbstractValidationProvider
{
    protected array $rules = ['title' => ['required',],];
}

// Desired validation rules:
// [
//     'author.name'          => ['required'],
//     'author.books'         => ['required', 'array', 'min:1', 'max:2'],
//     'author.books.*.title' => ['required'],
// ]

外观

use IndexZer0\LaravelValidationProvider\Facades\ValidationProvider;

$validationProvider = ValidationProvider::make([
    'author' => [
        AuthorValidationProvider::class,
        new CustomValidationProvider(['books' => ['required', 'array', 'min:1', 'max:2']]),
        new ArrayValidationProvider('books', new BookValidationProvider()),
    ],
]);
$validationProvider->rules();
// [
//     'author.name'          => ['required'],
//     'author.books'         => ['required', 'array', 'min:1', 'max:2'],
//     'author.books.*.title' => ['required'],
// ]

手动创建

$validationProvider = new NestedValidationProvider(
    'author',
    new AggregateValidationProvider(
        new AuthorValidationProvider(),
        new CustomValidationProvider(['books' => ['required', 'array', 'min:1', 'max:2']]),
        new ArrayValidationProvider('books', new BookValidationProvider())
    )
);
$validationProvider->rules();
// [
//     'author.name'          => ['required'],
//     'author.books'         => ['required', 'array', 'min:1', 'max:2'],
//     'author.books.*.title' => ['required'],
// ]

流畅API

  • 对于流畅API,您从下往上组合验证提供者。
$validationProvider = (new BookValidationProvider())
    ->nestedArray('books')
    ->with(new CustomValidationProvider(['books' => ['required', 'array', 'min:1', 'max:2']]))
    ->with(AuthorValidationProvider::class)
    ->nested('author');
$validationProvider->rules();
// [
//     'author.name'          => ['required'],
//     'author.books'         => ['required', 'array', 'min:1', 'max:2'],
//     'author.books.*.title' => ['required'],
// ]

服务/操作类使用

在您的服务和动作中,提供了->createValidator()->validate()方法以方便使用。

$addressValidationProvider = new AddressValidationProvider();

/** @var Illuminate\Validation\Validator $validator */
$validator = $addressValidationProvider->createValidator($data);

/** @var array $validated */
$validated = $addressValidationProvider->validate($data);

表单请求使用

您可以通过两种方法在表单请求中使用验证提供者。

扩展抽象

提供了ValidationProviderFormRequest以扩展您的表单请求。

使用prepareForValidation钩子实例化验证提供者。

class StoreAddressRequest extends ValidationProviderFormRequest
{
    public function prepareForValidation()
    {
        $this->validationProvider = new AddressValidationProvider();
    }
}

或使用依赖注入。

// In a service provider.
$this->app->when(StoreAddressRequest::class)
    ->needs(ValidationProvider::class)
    ->give(AddressValidationProvider::class);
  
class StoreAddressRequest extends ValidationProviderFormRequest
{
    public function __construct(ValidationProvider $validationProvider)
    {
        $this->validationProvider = $validationProvider;
    }
}

使用特性装饰

提供了HasValidationProvider以装饰您的现有表单请求。

有时您没有扩展ValidationProviderFormRequest的能力。您可以在现有表单请求中使用HasValidationProvider特性。

class StoreAddressRequest extends YourOwnExistingFormRequest
{
    use HasValidationProvider;
    
    public function prepareForValidation()
    {
        $this->validationProvider = new AddressValidationProvider();
    }
}

可用的验证提供者

此包提供了核心类,使您能够组合验证提供者。

聚合验证提供者

  • 在相邻组合验证提供者时使用。
class AggregateValidationProvider extends AbstractValidationProvider {}
$validationProvider = new AggregateValidationProvider(
    new AuthorValidationProvider(),
    new BookValidationProvider(),
);
$validationProvider->rules();
// [
//     'name'  => ['required'], // From AuthorValidationProvider.
//     'title' => ['required'], // From BookValidationProvider.
// ]

嵌套验证提供者

  • 在想要在数组中嵌套验证提供者时使用。
class NestedValidationProvider extends AbstractValidationProvider {}
$validationProvider = new NestedValidationProvider(
    'author',
    new AuthorValidationProvider(),
);
$validationProvider->rules();
// [
//     'author.name'  => ['required'], // From AuthorValidationProvider.
// ]

数组验证提供者

class ArrayValidationProvider extends NestedValidationProvider {}
$validationProvider = new ArrayValidationProvider('books', new BookValidationProvider());
$validationProvider->rules();
// [
//     'books.*.title'  => ['required'], // From BookValidationProvider.
// ]

自定义验证提供者

  • 当需要验证数据而不创建专门的ValidationProvider类时使用。
class CustomValidationProvider extends AbstractValidationProvider {}
$customRules = [
    'books' => ['required', 'array', 'min:1', 'max:2'],
];
$customMessages = [
    'books.required' => 'Provide :attribute'
];
$customAttributes = [
    'books' => 'BOOKS'
];
$validationProvider = new CustomValidationProvider($customRules, $customMessages, $customAttributes);
$validationProvider->rules();
// [
//     'books' => ['required', 'array', 'min:1', 'max:2'],
// ]

排除属性验证提供者

  • 有时你可能想要从验证提供者中移除某些属性。
class ExcludeAttributesValidationProvider extends AbstractValidationProvider {}
$validationProvider = new ExcludeAttributesValidationProvider(
    ['one'],
    new CustomValidationProvider([
        'one' => ['required'],
        'two' => ['required']
    ])
);
$validationProvider->rules();
// [
//     'two' => ['required'],
// ]

映射属性验证提供者

  • 有时你可能想要重命名一个属性。
class MapAttributesValidationProvider extends AbstractValidationProvider {}
$validationProvider = new MapAttributesValidationProvider(
    ['one' => 'two'],
    new CustomValidationProvider([
        'one' => ['required'],
    ])
);
$validationProvider->rules();
// [
//     'two' => ['required'],
// ]

深入了解

使用流畅API

使用外观

ValidationProvider::make(ValidationProviderInterface|string|array $config): ValidationProviderInterface

  • 可以使用完全限定的类名字符串。
// Returns AuthorValidationProvider
$validationProvider = ValidationProvider::make(AuthorValidationProvider::class);
  • 无效的类名字符串会抛出异常。
// throws ValidationProviderExceptionInterface
try {
    $validationProvider = ValidationProvider::make('This is an invalid fqcn string');
} catch (\IndexZer0\LaravelValidationProvider\Contracts\ValidationProviderExceptionInterface $exception) {
    $exception->getMessage(); // Class must be a ValidationProvider
}
  • 可以使用验证提供者对象。本质上不起作用。
// Returns AuthorValidationProvider (same object)
$validationProvider = ValidationProvider::make(new AuthorValidationProvider());
  • 可以使用数组(完全限定的类名字符串和对象)。
// Returns AuthorValidationProvider
$validationProvider = ValidationProvider::make([
    AuthorValidationProvider::class,
]); 

// Returns AggregateValidationProvider
$validationProvider = ValidationProvider::make([
    AuthorValidationProvider::class,
    new BookValidationProvider()
]);
  • 数组字符串键创建NestedValidationProvider
// Returns NestedValidationProvider
$validationProvider = ValidationProvider::make([
    'author' => [
        AuthorValidationProvider::class,
    ],
]);
  • 空数组是无效的。
// throws ValidationProviderExceptionInterface
try {
    $validationProvider = ValidationProvider::make([]);
} catch (\IndexZer0\LaravelValidationProvider\Contracts\ValidationProviderExceptionInterface $exception) {
    $exception->getMessage(); // Empty array provided
}

组合验证提供者

用例

  • 你可能有一些需要为多个域概念验证数据的应用程序部分。
  • 你可能希望在嵌套数组中验证数据,而不在规则定义中引入重复。

示例

让我们看看3个路由示例以及如何重用你的Validation Providers。

  • 路由:address
    • 存储地址信息
    • 使用AddressValidationProvider
/*
 * ------------------
 * Address
 * ------------------
 */
Route::post('address', StoreAddress::class);
class StoreAddress extends Controller
{
    public function __invoke(StoreAddressRequest $request) {}
}
class StoreAddressRequest extends ValidationProviderFormRequest
{
    public function prepareForValidation()
    {
        $this->validationProvider = new AddressValidationProvider();
    }
}
  • 路由:contact-details
    • 存储联系信息
    • 使用ContactValidationProvider
/*
 * ------------------
 * Contact
 * ------------------
 */
Route::post('contact-details', StoreContactDetails::class);
class StoreContactDetails extends Controller
{
    public function __invoke(StoreContactDetailsRequest $request) {}
}
class StoreContactDetailsRequest extends ValidationProviderFormRequest
{
    public function prepareForValidation()
    {
        $this->validationProvider = new ContactValidationProvider();
    }
}
  • 路由:profile
    • 存储地址联系信息。
    • 使用
      • AddressValidationProvider
      • ContactValidationProvider
      • NestedValidationProvider(底层由Facade实现)
      • AggregateValidationProvider(底层由Facade实现)
/*
 * ------------------
 * Profile
 * ------------------
 */
Route::post('profile', StoreProfile::class);
class StoreProfile extends Controller
{
    public function __invoke(StoreProfileRequest $request) {}
}
class StoreProfileRequest extends ValidationProviderFormRequest
{
    public function prepareForValidation()
    {
        $this->validationProvider = ValidationProvider::make([
            'profile' => [
                'address' => AddressValidationProvider::class
                'contact' => ContactValidationProvider::class
            ]
        ]);
    }
}

依赖规则

  • 当使用任何相关规则时,应使用$this->dependentField()辅助函数。
    • 这确保了在使用NestedValidationProviderArrayValidationProvider时,相关字段将具有正确的嵌套。
class PriceRangeValidationProvider extends AbstractValidationProvider
{
    public function rules(): array
    {
        return [
            'min_price' => ["lt:{$this->dependentField('max_price')}"],
            'max_price' => ["gt:{$this->dependentField('min_price')}"],
        ];
    }
}

$validationProvider = new NestedValidationProvider(
    'product',
    new PriceRangeValidationProvider()
);

$validationProvider->rules();

//  [
//      "product.min_price" => [
//          "lt:product.max_price"
//      ]
//      "product.max_price" => [
//          "gt:product.min_price"
//      ]
//  ]

错误处理

该包抛出的所有异常都实现了\IndexZer0\LaravelValidationProvider\Contracts\ValidationProviderExceptionInterface

然而,也捕获\Throwable不会造成伤害。

try {
    $validationProvider = ValidationProvider::make('This is an invalid fqcn string');
} catch (\IndexZer0\LaravelValidationProvider\Contracts\ValidationProviderExceptionInterface $exception) {
    $exception->getMessage(); // Class must be a ValidationProvider
} catch (\Throwable $t) {
    // Shouldn't happen - but failsafe.
}

包提供

// Interface
interface ValidationProvider {}

// Validation Providers
abstract class AbstractValidationProvider implements ValidationProvider {}
class AggregateValidationProvider extends AbstractValidationProvider {}
class NestedValidationProvider extends AbstractValidationProvider {}
class ArrayValidationProvider extends NestedValidationProvider {}
class CustomValidationProvider extends AbstractValidationProvider {}
class ExcludeAttributesValidationProvider extends AbstractValidationProvider {}
class MapAttributesValidationProvider extends AbstractValidationProvider {}

// Form Request
class ValidationProviderFormRequest extends \Illuminate\Foundation\Http\FormRequest {}
trait HasValidationProvider {}

// Facade
class ValidationProvider extends \Illuminate\Support\Facades\Facade {}

测试

composer test

变更日志

请参阅变更日志以获取有关最近更改的更多信息。

贡献

请参阅贡献指南以获取详细信息。

致谢

许可

MIT许可证(MIT)。请参阅许可文件以获取更多信息。