laminas / laminas-validator

Validation classes for a wide range of domains, and the ability to chain validators to create complex validation criteria
https://docs.laminas.dev/laminas-validator/
BSD 3-Clause "New" or "Revised" License
126 stars 55 forks source link

New conditional validator #370

Closed froschdesign closed 1 month ago

froschdesign commented 1 month ago

The idea is to add a new validator that executes a validator when a rule is met.

Example

$validator = new Laminas\Validator\ConditionalValidator(
    [
        'validator' => [
            'name' => Laminas\Validator\NotEmpty::class,
        ],
        'rule'      => static function (array $context) {
            return (bool) ($context['confirm'] ?? false);
        },
    ]
);
$data = [
    'registration' => 'foo',
    'confirm'      => '1',
];

var_dump($validator->isValid($data['registration'], $data)); // true

$data = [
    'registration' => '',
    'confirm'      => '1',
];

var_dump($validator->isValid($data['registration'], $data)); // false

$data = [
    'registration' => '',
    'confirm'      => '',
];

var_dump($validator->isValid($data['registration'], $data)); // true

Implementation

The validator needs the plugin manager and a solution to allow a chain of validators, for example a third option – validators next to validator:

$validator = new Laminas\Validator\ConditionalValidator(
    [
        'validators' => [
            [
                'name'    => Laminas\Validator\StringLength::class,
                'options' => [
                    'min' => 3,
                    'max' => 255,
                ],
            ],
            [
                'name' => Laminas\I18n\Validator\Alnum::class,
            ],
        ],
        'rule'      => static function (array $context) {
            return (bool) ($context['confirm'] ?? false);
        },
    ]
);

Reference

https://book.cakephp.org/5/en/core-libraries/validation.html#conditional-validation

gsteel commented 1 month ago

Nice addition for v3 👍

froschdesign commented 1 month ago

@gsteel Looks like we need the following logic from laminas-inputfilter then also here:

    protected function populateValidators(ValidatorChain $chain, $validators)
    {
        foreach ($validators as $validator) {
            if ($validator instanceof ValidatorInterface) {
                $chain->attach($validator);
                continue;
            }

            /** @psalm-suppress RedundantConditionGivenDocblockType */
            if (is_array($validator)) {
                if (! isset($validator['name'])) {
                    throw new Exception\RuntimeException(
                        'Invalid validator specification provided; does not include "name" key'
                    );
                }
                $name    = $validator['name'];
                $options = [];
                if (isset($validator['options'])) {
                    $options = $validator['options'];
                }
                $breakChainOnFailure = false;
                if (isset($validator['break_chain_on_failure'])) {
                    $breakChainOnFailure = $validator['break_chain_on_failure'];
                }
                $priority = $validator['priority'] ?? ValidatorChain::DEFAULT_PRIORITY;
                $chain->attachByName($name, $options, $breakChainOnFailure, $priority);
                continue;
            }

            throw new Exception\RuntimeException(
                'Invalid validator specification provided; was neither a validator instance nor an array specification'
            );
        }
    }

https://github.com/laminas/laminas-inputfilter/blob/40591c1651c019e4d90a72d064ef5353f06cf61b/src/Factory.php#L419-L452

Should we create a factory for the validator chain in this package and then used in laminas-inputfilter?

gsteel commented 1 month ago

Yes… Perhaps a ValidatorChainFactory defined here would be better than having all the internals of ValidatorChain considered in inputfilter.

froschdesign commented 1 month ago

@gsteel It wasn't the plan for you to take over the implementation, but thank you very much! 👍🏻 😃