DEV Community

Joël Harkes
Joël Harkes

Posted on

Make Laravel do type safe validation

Laravel has a nice validation framework, but a big catch is that it doesn't make sure your result has the right types.

here an example:

$data = ['isOk' => '1'];
$result = $this->validate($data, ['isOk' => 'boolean');

$isOkString = $result['isOk'];
$isOkBoolean = (boo;)  $result['isOk'];
if($isOkString === true){
    // wont fall here.
}
if($isOkBoolean === true){
    // but will go here.
}

wouldn't be wonderful to make sure you have no side affects.

Todo this we must create a nice interface to be able to make Rules allow to transform the result. You must subclass the Laravel Validator and extend the method: validateUsingCustomRule

interface RuleTransformsResult
{
    public function transformResult($attribute, $value);
}


class MyTypeSafeValidator extends \Illuminate\Validation\Validator {
    protected function validateUsingCustomRule($attribute, $value, $rule)
    {
        // below is just copy pasted laravel code of method
        if (!$rule->passes($attribute, $value)) {
            $this->failedRules[$attribute][get_class($rule)] = [];

            $messages = (array) $rule->message();

            foreach ($messages as $message) {
                $this->messages->add($attribute, $this->makeReplacements(
                    $message, $attribute, get_class($rule), []
                ));
            }

        // here is where we do our magic
        } elseif ($rule instanceof RuleTransformsResult) {
            $newValue = $rule->transformResult($attribute, $value, $result);
            if($newValue !== $value) {
                Arr::set($this->data, $attribute, $result);
            }
        }
    }
}

Now we can write our own Boolean validator:

class ValidBoolean implements Rule, RuleTransformsResult
{
    public static $validValuesMap = [
        '0' => false,
        '1' => true,
        'false' => false,
        'true' => true,
        'yes' => true,
        'no' => false,
    ];

    public function passes($attribute, $value)
    {
        if (is_bool($value)) {
            return true;
        }

        return array_key_exists((string) $value, self::$validValuesMap);
    }

    public function message()
    {
        return __('validation.boolean');
    }

    public function transformResult($attribute, $value)
    {
        return (bool) $value;
    }
}

Now we can always have a boolean as result:

$data = ['isOk' => '1'];
$result = $this->validate($data, ['isOk' => [new ValidBoolean()]);

if($result['isOk'] === true){
   // yeah we will make it here.
}

Top comments (0)