chanmix51 / parameter-juicer
参数验证器和清理器
Requires
- php: >=7.0
Requires (Dev)
- atoum/atoum: ~3.0.0
- squizlabs/php_codesniffer: ~2.7
This package is auto-updated.
Last update: 2024-08-24 19:36:12 UTC
README
这是 Parameter Juicer 的实验分支。如果您想在项目中使用它,建议使用 1.x 分支。
如何从您的参数、CSV、表单等数据中提取精华。ParameterJuicer 是一个简单的 PHP 8.x 数据验证器和清理器,经过广泛的单元测试。
特点
- 清理器和验证器可以是任何可调用的函数
- 默认值可以是标量或可调用的
- 额外字段策略
- 单遍验证错误收集
匿名 juicer 的简单示例
$juicer = (new ParameterJuicer) ->addField('a_string') ->addCleaner('a_string', function($v) { return trim(strtolower($v)); }) ->addValidator('a_string', function($v) { return (strlen($v) !== 0) ? null : 'cannot be empty'; }); try { $juicer->squash(['a_string' => ' Pika CHU ']); // ↑ returns ['a_string' => 'pika chu'] $juicer->squash(['a_string' => ' ']); // ↑ throws a ValidationException } catch (ValidationException $e) { printf($e); // ↑ Validation failed // [a_string] - cannot be empty }
可以创建专用类来验证和清理包含嵌套结构的结构(见下文)。
安装
composer require chanmix51/parameter-juicer
用法
责任
清理器 负责将数据转换为期望的类型和格式。如果转换不可能,清理器可以抛出异常,并丢弃字段。
验证器 负责确保数据遵循业务规则。大多数情况下,这涉及到数据是否处于定义的值范围内。
匿名定义
以下是一个匿名 juicer 的快速简单示例。它根据给定的定义清理和验证数据。
use Chanmix51\ParameterJuicer\ParameterJuicer as Juicer; use Chanmix51\ParameterJuicer\Exception\ValidationException; $turn_to_integer = function($v):int { return (int) $v; }; $must_be_between_1_and_10 = function(int $value) { if (10 < $value || 1 > $value) { return sprintf( "must be between 1 and 10 (%d given).", $value ); }}; $juicer = (new Juicer) ->addField('pika') ->addCleaner('pika', $turn_to_integer) ->addValidator('pika', $must_be_between_1_and_10) ->setDefaultValue('pika', 9) // ← when not set ->addField('chu') ->addCleaner('chu', function($v) { return trim($v); }) ->setDefaultValue('chu', function() { return 10; }) // ↑ use a callable to have a lazy loaded default value ->addField('not mandatory', false) // ← not mandatory ->setStrategy(Juicer::STRATEGY_IGNORE_EXTRA_VALUES) ; // ↑ extra values are removed try { // ↓ return ["pika" => 9, "chu" => ''] $juicer->squash(['chu' => null, 'whatever' => 'a']); // ↓ return ["pika" => 3, "chu" => "a"] $juicer->squash(['pika' => '3', 'chu' => ' a ', 'whatever' => 'a']); // ↓ throw a ValidationException because "chu" is mandatory $juicer->squash(['pika' => '3', 'whatever' => 'a']); } catch (ValidationException $e) { // Get the validation errors from the exception (see below) }
额外字段策略
有 3 种策略来处理未在计划中定义的额外数据
ParameterJuicer::STRATEGY_ACCEPT_EXTRA_VALUES
(0) 不改变额外数据(请注意,这些数据是不可信的)。ParameterJuicer::STRATEGY_IGNORE_EXTRA_VALUES
(1) 丢弃额外数据(这是默认策略)。ParameterJuicer::STRATEGY_REFUSE_EXTRA_VALUES
(2) 将额外字段视为异常并触发ValidationException
。
使用表单清理器和验证器
每个验证器只能看到它负责的值,这使得验证简单且易于维护。但是,有些情况下,验证规则必须比较字段与其他字段(例如,比较密码和密码确认)。
$juicer = (new Juicer) ->addField('login') ->addCleaner('login', function($v) { return strtolower(trim($v)); }) ->addValidator('login', function($v) { return $v === '' ? 'must not be empty' : null; }) ->addField('password') ->addCleaner('password', 'trim') ->addValidator('password', function($v) { return strlen($v) < 3 ? 'must not be less than 3 chars' : null; }) ->addField('repeat_password') ->addCleaner('repeat_password', 'trim') ->addFormValidator(function($values) { if ($values['password'] != $values['repeat_password']) { return 'passwords do not match'; } });
表单验证策略
默认情况下,如果字段验证失败,则不会触发表单验证。可以通过使用 setFormValidationStrategy
方法始终启动表单验证。
ParameterJuicer::FORM_VALIDATORS_CONDITIONAL
(默认)ParameterJuicer::FORM_VALIDATORS_ALWAYS
自定义 Juicer 类
可以在专用类中嵌入清理和验证规则
class PikaChuJuicer extends ParameterJuicer { public function __construct() { $this ->addField('pika') ->addCleaner('pika', [$this, 'doTrimAndLowerString']) ->addValidator('pika', [$this, 'mustNotBeEmptyString']) ->addField('chu', false) ->addCleaner('chu', function($v) { return $v + 0; }) ->addValidator('chu', [$this, 'mustBeANumberStrictlyPositive']) ->setStrategy(ParameterJuicer::STRATEGY_REFUSE_EXTRA_VALUES) ; } public function doTrimAndLowerString($value): string { return strtolower(trim($value)); } public function mustNotBeEmptyString($value) { return (strlen($value) !== 0) ? null : 'must no be an empty string'; } public function mustBeANumberStrictlyPositive($value) { return ($value > 0) ? null : printf("must be strictly positive (%f given)", $value); } } $trusted_data = (new PikaChuJuicer) ->squash($untrusted_data) ;
这特别有用,因为它使得清理器和验证器可以单元测试,同时 juicer 也可以在不同的代码部分中使用。
使用 juicer 类清理和验证嵌套数据。
可能发生的情况是数据集嵌入另一个已经具有自己的 Juicer 类的数据集。
$juicer = (new Juicer) ->addField('pokemon_id') ->addField('pika_chu') ->addJuicer( 'pika_chu', // ↓ change this juicer’s strategy (new PikaChuJuicer)->setStrategy(Juicer::STRATEGY_IGNORE_EXTRA_VALUES) ) ->addValidator('pika_chu', … // ← add an extra validator on this field) ;
本地表单验证或嵌套验证?
也可以通过向嵌套 juicer 添加验证器来执行清理和验证
$juicer = (new ParameterJuicer) ->addField('my_form') ->addJuicer('my_form', (new PasswordFormJuicer) ->addValidator('my_form', function($val) { return $values['pass'] === $values['repass'] ? null : 'pass & repass do not match'; }); try { $clean_data = $juicer->squash(['my_form' => $form_data]); } catch (ValidationException $e) { … }
在哪里编写此类验证是一个上下文问题。如果规则在表单(清理器|验证器)中设置,则它们属于此类型,并且无论外部上下文如何,它们都将始终运行。如果规则在另一个 juicer 中,则它们是添加到原始上下文中的规则。
编写清理器和验证器。
清理器
清理器和验证器可以是任何可调用的对象。它们有不同的作用,清理器转换数据以便验证。验证指示数据是否有效。如果字段要删除,清理器必须返回值或抛出CleanerRemoveFieldException
。
$cleaner = function($value) { $value = trim($value); if ($value === '') { throw new CleanerRemoveFieldException; } return $value; }
在上面的例子中,null或空字符串会丢弃字段,因此可以应用默认值(如果已设置)。如果字段未设置且没有默认值且是必需的,最终将引发验证异常(见下文验证器)。
验证器
验证器遵循二进制逻辑:数据要么是好的,要么不是。当一个值符合规则时,验证器返回null。如果验证规则失败,验证器必须抛出带有错误信息的ValidationException
。也可以直接返回错误信息,它会被包裹在一个ValidationException
中。这个异常会被juicer收集,并验证所有值。在处理过程的最后,如果收集到异常,它们将被分组在同一个ValidationException
实例中,然后抛出,这样用户可以一次性获得所有验证消息。
$validator = function($value) { if (preg_match("/pika/", $value)) { throw new ValidationException("must NOT contain 'pika'"); } }
$validator = function($value) { return (preg_match("/pika/", $value)) ? "must NOT contain 'pika'" : null; }
Juicer会自动处理将验证错误与字段名称关联,这个名称会被添加到验证错误消息的开头。验证错误消息保持简短,没有首字母大写,没有句尾点。
验证异常
Juicer要么产生干净的值,要么在验证失败时产生ValidationException
。ValidationException
可以存储嵌套的异常。每次验证条件失败(必填字段、额外字段策略或验证器)时,它都会在全局异常中添加一个ValidationException
。必须捕获这个异常,以便能够获取存储的异常或直接获取错误。
try { $my_juicer->squash($data); } catch (ValidationException $e) { printf($e); // ← this calls $e->getFancyMessage() }
假设一个集合在字段pikachu
中嵌套了juicer,输出将如下所示
validation failed
[pikachu] - validation failed
[pika] - must not be empty
[chu] - missing mandatory field
[me] - must be strictly positive (-1 given)
getExceptions()
方法返回一个数组,其中包含按字段名称索引的验证错误,值是ValidationException
实例的数组
foreach ($exception->getExceptions() as $field_name => $exceptions) { printf("Field '%s' has %d errors.\n", $field_name, count($exceptions)); }
如何贡献
- 使用单元测试创建一个测试用例,以确保您的接口像我这样的人可以使用。
- 编写您的功能代码,并确保所有测试都通过。
- 在GitHub上创建一个PR,并等待几年,我会关注的。