chanmix51/parameter-juicer

参数验证器和清理器

1.2.0 2020-12-17 11:15 UTC

This package is auto-updated.

Last update: 2024-08-24 19:36:12 UTC


README

Build Status Scrutinizer Code Quality License Monthly Downloads

这是 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 种策略来处理未在计划中定义的额外数据

  1. ParameterJuicer::STRATEGY_ACCEPT_EXTRA_VALUES (0) 不改变额外数据(请注意,这些数据是不可信的)。
  2. ParameterJuicer::STRATEGY_IGNORE_EXTRA_VALUES (1) 丢弃额外数据(这是默认策略)。
  3. 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 方法始终启动表单验证。

  1. ParameterJuicer::FORM_VALIDATORS_CONDITIONAL (默认)
  2. 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要么产生干净的值,要么在验证失败时产生ValidationExceptionValidationException可以存储嵌套的异常。每次验证条件失败(必填字段、额外字段策略或验证器)时,它都会在全局异常中添加一个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));
}

如何贡献

  1. 使用单元测试创建一个测试用例,以确保您的接口像我这样的人可以使用。
  2. 编写您的功能代码,并确保所有测试都通过。
  3. 在GitHub上创建一个PR,并等待几年,我会关注的。