0
votes

I have one thing I'm not sure how to solve in a good way: I'm trying to use ZF2 InputFilterManager for fetching my input-filters, which make use of some own validators.

I added the validators to service manager config, so they can be fetched by InputFilterManager and get injected by keyword reference inside the InputFilter definition. that works fine, as far as validators doesn't need anyting to get injected or some objects, that are available from the ServiceLocator.

But one validator needs to get an array injected to do an in_array check inside. It is not possible to inject that array inside the validator factory, because it depends on business logic that runs before InputFilter isValid() call happens.

Without using service manager I would inject that array with constructor of the validator and would initialize it after business logic was called. So the question is: If I remove injection by constructor to use InputFilterManager I would need to inject it later by using: $inputFilter->get('element')->getValidatorChain()->plugin(Validator\ArrayCheck::class)->setPossibleValues($array) - is this the way to go?

I quite like to have necessary dependencies getting injected into classes by using the constructor and it feels somehow dirty to rewrite validator to rely on a setter injection and add a check to verify the setter was used before running isValid() logic.

Some code to explain my concern:

return [
    'validators' => [
        'invokables' => [
            Validator\ArrayCheck::class => Validator\ArrayCheck::class
        ]
    ],
    'input_filters' => [
        'invokables' => [
            InputFilter\Foo::class => InputFilter\Foo::class,
        ],
    ],
];

final class Process extends InputFilter
{
    public function init()
    {
        $this->add(
            [
                'name' => 'foo',
                'required' => true,
                'allow_empty' => false,
                'validators' => [
                    ['name' => Validator\ArrayCheck::class]
                ],
            ]
        );
    }
}

class ArrayCheck extends AbstractValidator
{
    /**
     * @param array $possibleValues
     * @param array|Traversable $options
     */
    public function __construct(array $possibleValues = [], $options = null)
    {
        parent::__construct($options);
        $this->possibleValues = $possibleValues;
    }

    /**
     * @param array $possibleValues
     */
    public function setPossibleValues(array $possibleValues)
    {
        $this->possibleValues = $possibleValues;
    }

    ...

}

Any opinions on that?

1
You write It is not possible to inject that array inside the validator factory, can you explain why? Where does this business logic come from or depend on? If this runs before isValid it should be fine to inject it, right?Wilt
It def runs before the isValid call happens. But as I inject the InputFilter by constructor into the class, where business logic is placed, the initialization of the InputFilter already happend before the business logic kicks in. So there is no possibility to inject that array by constructor, only by using a setter and call it by using this ugly $inputFilter->get('element')->getValidatorChain()->plugin(Validator\ArrayCheck::class)->setPossibleValues($array) call. It's more a matter of bad design, as I def like to have dependencies getting injected by constructor.Mischosch
business logic happpens inside service and it is an array coming from a db query (depending on a value/id that is coming into service from a controller route param, so this value is not part of the data, that gets validated). I could inject the entitymanager to do query stuff inside my validator, but I still miss the value/id coming from the controller (and would need to inject that by setter, and this would make it not really a better design)Mischosch

1 Answers

0
votes

I think you should have a look at the Callback filter and Callback validator classes.

A callback filter runs a function or a class method when filtering your value. You can read on the Callback filter class here in the ZF2 documentation. You can find the class here on GitHUB.

A callback validator runs a function or a class method when validating your value. You can read on the Callback validator class here in the ZF2 documentation. You can find the class here on GitHUB.

They both use the PHP function call_user_func_array internally.

So let's say you need to filter using your dependent business logic in a class My\Filter\Dependency and your method is called filterLogic and/or you need to validate using your dependent business logic in a class My\Validator\Dependency and your method is called validateLogic. You do that like this:

$this->add([
    'name' => 'foo',
        'required' => true,
        'allow_empty' => false,
        `filters` => [
            [
                'name' => \Zend\Filter\Callback::class
                'callback' => [
                    'My\Filter\Dependency::filterLogic'
                ],
                'options'  => [
                    'argument1' => 'param1', //optional
                    'argument2' => 'param2'  //optional
                ]
            ]
        ],
        'validators' => [
            [
                'name' => \Zend\Validator\Callback::class
                'callback' => [
                    'My\Validator\Dependency::validateLogic'
                ],
                'options'  => [
                    'argument1' => 'param1', //optional
                    'argument2' => 'param2'  //optional
                ]
            ]
        ],
    ]
]);

Both the Callback classes will call call_user_func_array with your values for callback and options as arguments.

If you need the values from your input-filter in the callback functions then you will have to get them from your InputFilter instance manually ($inputFilter->getRawValues();) and pass them to the function as an argument.

If this is not solving your issue please try to explain better what you need. I can try to come up with something else :D