chanmix51/parameter-juicer 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

chanmix51/parameter-juicer

Composer 安装命令:

composer require chanmix51/parameter-juicer

包简介

Parameter validator and cleaner

README 文档

README

Build Status Scrutinizer Code Quality License Monthly Downloads

This is the experimental branch of Parameter Juicer. If you want to use it in for your projects, it is advised to use the 1.x branches.

How to extract the juice from your parameters, CSV, forms etc. data. ParameterJuicer is a simple data validator and cleaner for PHP 8.x extensively unit tested.

It features:

  • cleaners and validators are any callable
  • default values can be scalars or callable
  • extra field strategies
  • one pass validation errors collection

Simple example of an anonymous 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
}

It is possible to create dedicated classes to validate and clean structures with embedded structures (see below).

Install

composer require chanmix51/parameter-juicer

Usage

Responsibilities

The cleaners are responsible for casting data in the expected type & format. If the casting is impossible, the cleaner can throw an exception and the field is discarded.

The validators are responsible for ensuring the business rules for the data are respected. Most of the time, it is about data being in a defined range of values.

Anonymous definition

Here is a fast and simple example of an anonymous juicer. It cleans and validates the data according to the given definition.

        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)
            }

Extra fields strategies

There are 3 strategies to handle extra data not defined in the plan:

  1. ParameterJuicer::STRATEGY_ACCEPT_EXTRA_VALUES (0) let the extra data untouched (be aware they ARE untrusted data).
  2. ParameterJuicer::STRATEGY_IGNORE_EXTRA_VALUES (1) discard extra data (this is the default strategy).
  3. ParameterJuicer::STRATEGY_REFUSE_EXTRA_VALUES (2) treat extra fields as anomalies and trigger the ValidationException.

Using form cleaners and validators

Each validator only sees the values it is responsible for, it makes the validation simple and easy to maintain. But there are some cases where validation rules must compare fields with other fields (like comparing password and password confirmation).

$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';
        }
    });

Form validation strategies

By default, form validation is not triggered if the fields validation fails. It is possible to always launch form validation using the setFormValidationStrategy method:

  1. ParameterJuicer::FORM_VALIDATORS_CONDITIONAL (default)
  2. ParameterJuicer::FORM_VALIDATORS_ALWAYS

Custom Juicer class

It is possible to embed cleaning & validation rules in a dedicated class:

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)
    ;

This is particularly useful because it makes cleaners and validators to be unit-testable in addition to the juicer being usable in different portions of the code.

Using a juicer class to clean & validate nested data.

It may happen a dataset embeds another dataset that already has its own Juicer class.

$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)
    ;

Local form validation or nested validation?

It is also possible to perform for cleaning & validation by adding validators to a nested 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) {
    …
}

Where to write such validations is a matter of context. If the rules are set in a Form(Cleaner|Validator) they belong to this type and they wille always be run whatever the outside context is. If the rules are in another juicer they are rules added to the original context.

Writing cleaners and validators.

Cleaners

Cleaners and validators can be everything callable. They have different purpose, the cleaners transform the data prior to validation. The validation indicates if the data is valid or not. Cleaners must return the value or throw a CleanerRemoveFieldException if the field is to be removed.

$cleaner = function($value) {
    $value = trim($value);

    if ($value === '') {
        throw new CleanerRemoveFieldException;
    }

    return $value;
}

In the example above, null or empty strings discard the field so a default value can be applied if set. If the field is unset with no default value and is mandatory, this will raise a validation exception in the end (see Validators below).

Validators

Validators follow a binary logic: either the data is good or not. When a value is considered acceptable by the rules, the validator returns null. In the case a validation rule fails, validators must throw a ValidationException with the error message. It is also possible to just return the error message, it is wrapped in a ValidationException. This exception is collected by the juicer and all the values are validated. At the end of the process, if exceptions have been collected, they are all grouped in the same ValidationException instance which is then thrown so users get all the validation messages at once.

$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;
}

The Juicer automatically cares about associating validation errors with field names, this name is prepend to the validation error message. Validation error message are kept short, with no starting uppercase and no final dot.

Validation exception

A juicer either produces clean values or a ValidationException in case of the validation fails. The ValidationException can store an nest exceptions. Every time a validation condition fail (mandatory field, extra field strategy or validator), it adds a ValidationException in a global exception. It is necessary to catch this exception to make it possible either to fetch the stored exception or to directly fetch the errors:

try {
    $my_juicer->squash($data);
} catch (ValidationException $e) {
    printf($e); // ← this calls $e->getFancyMessage()
}

Assuming a set is nesting a juicer in a field pikachu the output would be like the following:

validation failed
  [pikachu] - validation failed
      [pika] - must not be empty
      [chu] - missing mandatory field
  [me] - must be strictly positive (-1 given)

The getExceptions() method returns an array of the validation errors indexed by field name, values are arrays of ValidationException instances:

foreach ($exception->getExceptions() as $field_name => $exceptions) {
    printf("Field '%s' has %d errors.\n", $field_name, count($exceptions));
}

How to contribute

  1. Create a test case using the unit tests to ensure your interface is usable by average humans like me.
  2. Code your feature and make sure all your tests are green.
  3. Create a PR on Github and wait several years I care.

chanmix51/parameter-juicer 适用场景与选型建议

chanmix51/parameter-juicer 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 19.03k 次下载、GitHub Stars 达 13, 最近一次更新时间为 2017 年 02 月 08 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「validator」 「parameters」 「cleaner」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 chanmix51/parameter-juicer 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 chanmix51/parameter-juicer 我们能提供哪些服务?
定制开发 / 二次开发

基于 chanmix51/parameter-juicer 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 19.03k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 13
  • 点击次数: 8
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 13
  • Watchers: 2
  • Forks: 1
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2017-02-08