Extend Laravel validator multiple times

2019-02-26 07:25发布

Similar to the confirmation rule, I'm creating a validation rule that's based on the value of another attribute. From what I've found the only way to accomplish this is by extending the Validator class and get the value through $this->data.

The docs say to extend the Validator and then use Validator::resolver to register the new extension class. This works fine, but only in the case of a single resolver as it seems each subsequent defined resolver simply overrides the previous one.

How can this problem be solved? How can we define custom validators that still have access to other attributes without putting all methods in the same class...?

Thanks

//

Note: I'm asking this because I'd like to release a few validator packages, but following the reasoning above they'd just become useless if someone installs multiple packages...

2条回答
▲ chillily
2楼-- · 2019-02-26 07:31

I sub-classed Illuminate\Validation\Factory, Illuminate\Validation\ValidationServiceProvider, and Illuminate\Validation\Validator as (for example) MyFactory, MyValidationServiceProvider, and MyValidator, respectively.

MyFactory.php looked like this:

<?php

use Illuminate\Validation\Factory;

class MyFactory extends Factory {

    /**
     * {@inheritDoc}
     *
     * @param  array  $data
     * @param  array  $rules
     * @param  array  $messages
     * @param  array  $customAttributes
     * @return MyValidator
     */
    protected function resolve(array $data, array $rules, array $messages, array $customAttributes)
    {
        if (is_null($this->resolver))
        {
            return new MyValidator($this->translator, $data, $rules, $messages, $customAttributes);
        }
        else
        {
            return call_user_func($this->resolver, $this->translator, $data, $rules, $messages, $customAttributes);
        }
    }

}

MyValidationServiceProvider.php looked like this:

<?php

use Illuminate\Validation\ValidationServiceProvider;

class MyValidationServiceProvider extends ValidationServiceProvider {

    /**
     * {@inheritDoc}
     *
     * @return void
     */
    public function register()
    {
        $this->registerPresenceVerifier();

        $this->app->bindShared('validator', function($app)
        {
            $validator = new MyFactory($app['translator'], $app);

            // The validation presence verifier is responsible for determining the existence
            // of values in a given data collection, typically a relational database or
            // other persistent data stores. And it is used to check for uniqueness.
            if (isset($app['validation.presence']))
            {
                $validator->setPresenceVerifier($app['validation.presence']);
            }

            return $validator;
        });
    }

}

MyValidator.php looked like this:

<?php

namespace MuThetaTau\MuThetaTau\Validation;

use Illuminate\Validation\Validator;

class MyValidator extends Validator {

    // custom validation logic

}

Lastly, I had to update the service provider in app.php to point to MyValidationServiceProvider' instead ofIlluminate\Validation\ValidationServiceProvider`.

The facade Validator now points to MyValidator. You could even go to the extent of creating multiple factories, adding (and calling) methods to MyValidationServiceProvider to register other validator classes to map the new factories to new IoC bindings, creating new facade classes, and adding entries to the facades in app.php to have different facades for each validator that you make.

查看更多
姐就是有狂的资本
3楼-- · 2019-02-26 07:54

Yup, the reason it is getting overridden is the Validator\Factory class only stores one resolver. The code for the function is here:

public function resolver(Closure $resolver) {
    $this->resolver = $resolver;
}

I would assume that the point of the resolver method is to extend the base validation class with your own. This makes sense as you could instantiate a UserRegistrationValidator adding your own validation rules that have all the flexibility as the existing ones.

One issue with this is that it can be overridden with ease, this indicates to me that you should only call the resolver method just before calling make. Although more effort, it would stop rules from different packages potentially overriding other rules and even the base ones automatically.

But this does not work well for a package that only provides extra helpful rules. The simpler version to add a rule is:

Validator::extend('foo', 'FooValidator@validate');

This does not allow access to the input data, which is important for complex rules. The documentation example shows us this also:

class CustomValidator extends Illuminate\Validation\Validator 
{

    public function validateFoo($attribute, $value, $parameters) {
        return $value == 'foo';
    }

}

But wait! What the documentation doesn't tell you is that you can add on another parameter to get the instance of Validator. Which I just found out myself whilst writing this answer and getting deep into the classes!

class TestRulesValidator
{

    public function validateTestRule($attribute, $value, $params, $validator) {
        var_dump($validator->getData());
    }

}


Validator::extend('test_rule', 'TestRulesValidator@validateTestRule');

So to conclude, pass an extra parameter which will be the instance of the validator being used. I suspect this will also work with a callback too.

Hope this helps, it did for me!

查看更多
登录 后发表回答