Respect / Validation

The most awesome validation engine ever created for PHP
https://respect-validation.readthedocs.io
MIT License
5.76k stars 774 forks source link

Given a multirule validator, is there a usage pattern for testing a single rule? #1380

Closed paxperscientiam closed 1 year ago

paxperscientiam commented 2 years ago

Thanks for the awesome library!

So, here's what I'm trying to accomplish with use-case.

Goal: Given a multirule validator, with a mix of optional and required inputs, determine how to test a single arbitrary input.

Use case: presenting inline error text at

level immediately after an input field has changed. In such a scenario, the goal is not to validate the entire form, but just a single input field.

The below code is my attempt at this

// Injectable class for form validation

namespace App\Validations;

use Respect\Validation\Validator as V;

class UserSettingsFormValidator
{
    public readonly V $rules;

    public function __construct()
    {
        $this->pointsRule = (new V())->intVal();
        $this->planPointsRule = (new V())->number();
        $this->rules = (new V())
            ->key("plan-selection", $this->pointsRule)
            ->key("plan-points-goal", $this->planPointsRule, false);
    }

    public function getInlineError($data)
    {
        try {
            $this->rules->check($data);
        } catch (Respect\Validation\Exceptions\NestedValidationException $e) {
            return $e->getMessage();
        } catch (Respect\Validation\Exceptions\ValidationException $e) {
            return $e->getMessage();
        } catch (Respect\Validation\Exceptions\Exception $e) {
            return $e->getMessage();
        } catch (\Exception $e) {
            return $e->getMessage();
        }

    }
}
// pseudo code
// Handling POST request for single input fields of a given form
 if ("input-name-one" == $posted_input) {
   // graph associated rule from UserSettingsFormValidator and validate
 } else if ("input-name-two" == $posted_input) {
  // graph associated rule from UserSettingsFormValidator and validate
 } // etc
// etc

As you can see, this approach for validating a single input is cumbersome. I wonder, is it possible to setup a validator -- with a mix of required and optional fields -- that will validate a single input without worrying about omitted required inputs?

I hope that makes sense. Happy to clarify if it doesn't :)

alganet commented 1 year ago

If I got this right, you can use a combination of setName and setTemplate on individual validators. Then you can use $e->getId to figure out which field triggered the exception error.

If you want to reuse the original templates, there's an example with $pointsRule below:

<?php

declare(strict_types=1);

require 'vendor/autoload.php';

use Respect\Validation\Rules as r;
use Respect\Validation\Exceptions\IntValException;

$pointsRule = (new r\IntVal())
    ->setName('Points')
    ->setTemplate(IntValException::STANDARD);
$planPointsRule = (new r\Number())
    ->setName('Points')
    ->setTemplate('Plan Points must be a number');
$rules = new r\AllOf(
    (new r\Key("plan-selection", $pointsRule))
        ->setName('Plan Selection')
        ->setTemplate('You need to select a plan'),
    (new r\Key("plan-points-goal", $planPointsRule, false))
        ->setName('Goal')
        ->setTemplate('You need to select a goal')
);

$data = [];
try {
    $rules->check($data);
} catch (Respect\Validation\Exceptions\ValidationException $e) {
    // "Plan Selection"
    var_dump($e->getId());
    // "You need to select a plan"
    var_dump($e->getMessage());
}

$data = [
    'plan-selection' => 'notint'
];

try {
    $rules->check($data);
} catch (Respect\Validation\Exceptions\ValidationException $e) {
    //"Plan Selection"
    var_dump($e->getId());
    //"Plan Selection must be an integer number"
    var_dump($e->getMessage());
}

$data = [
    'plan-selection' => 123,
    'plan-points-goal' => 'dnjsndfn NOT A NUMBER'
];
try {
    $rules->check($data);
} catch (Respect\Validation\Exceptions\ValidationException $e) {
    //"Goal"
    var_dump($e->getId());
    //"Goal must be a number"
    var_dump($e->getMessage());
}

As you can see, it's possible to target and identify individual validator failures. This should also work with assert and getMessages to get them all at once.

You can also do ->setName and ->setTemplate in the AllOf rule, which is the same as new V in your snippet. In fact, any validator supports it.

There might be some tweaking and experimentation needed to get your use case right.

If this isn't enough, I'll need you to elaborate more. Please feel free to reopen this if you can't make it work for your case.