Reuse InputFilters Definition

I have a case, in which I want to reuse a set of rules for InputFilters: filtering and validation.

  • I am creating a REST API using apigility
  • I have a post request http://localhost/hasslefree with json body
{
  "name": "Hassle free development",
  "address": {
    "line": "3/1 online road",
    "suburb": "Point Cook",
    "state": "VIC",
    "country": "Australia"
  },
  "phone": "1300652000",
  "email": "foalford@gmail.com",
}
  • Would like to share the same InputFilter for address in PUT to http://localhost/hasslefree/address

What I want is to validate and filter the address by configuration. This is natural1) and should be met easily however I found it very hard using the existing zf-content-validation module.

Here is the dilemma:

  • InputFilter assume that the input is flat. No sub arrays.
  • CollectionInputFilter assume that input is array, whose values have same structure and are treated equally with the InputFilter definitions.

In my case, the address array isn't a collection. I cannot define a set of rules to measure agains address.line and address.state at the same time. Then I come up with a solution with my customized InputFilter.

class ComposedInputFilter extends CollectionInputFilter                                        
{                                                                                              
    public function setData($data)                                                             
    {                                                                                          
        $this->data = [$data];                                                                 
    }                                                                                          
 
    public function setCount($count)                                                           
    {                                                                                          
        if ($count > 1) {                                                                      
            throw new \RuntimeException("Count always equal or less than 1, $count");          
        }                                                                                      
        return parent::setCount($count);                                                       
    }                                                                                          
 
    public function setInputFilter($inputFilter)                                               
    {                                                                                          
        if (is_string($inputFilter)) {  //To reuse the InputFilter definition for other API request.                                                        
            $inputFilter = $this->getFactory()->getInputFilterManager()->get($inputFilter);    
        }                                                                                      
        return parent::setInputFilter($inputFilter);                                           
    }                                                                                          
 
    public function getValues()                                                                
    {                                                                                          
        return reset($this->collectionValues);                                                 
    }                                                                                          
 
    public function getRawValues()                                                             
    {                                                                                          
        return reset($this->collectionRawValues);                                              
    }                                                                                          
 
    public function getMessages()                                                              
    {                                                                                          
        return reset($this->collectionMessages);                                               
    }                                                                                          
}                                                                                              

And with this code, configuration file goes like this

....
    'zf-content-validation' => array(                                                          
        'GeoSearch\\V1\\Rest\\Supplier\\Controller' => array(                                  
            'input_filter' => 'GeoSearch\\V1\\Rest\\Hasslefree\\Validator',                      
        ), 
    ),      
    'input_filter_specs' => array(                                                             
        'GeoSearch\\V1\\Rest\\Hasslefree\\Validator' => array(                                   
            0 => array(            
                'name' => 'name',  
                'required' => true,  
            ),
            'address' => array(
                'name' => 'address',
                'type' => 'Geosearch\\InputFilter\\ComposedInputFilter',
                'input_filter' => 'GeoSearch\\V1\\Rest\\Supplier\\Address\\Validator'
                //'input_filter' => array(
                // Can be the same definition as normal inputfilters
                //)
...
       ), // GeoSearch\\V1\\Rest\\Hasslefree\\Validator
       GeoSearch\\V1\\Rest\\Hasslefree\\Address\\Validator' => array(
            0 => array(
                'name' => 'line',
                'required' => true,
                'filters' => array(
                    0 => array(
                        'name' => 'Zend\\Filter\\StringTrim',
                        'options' => array(),
                    ),
                ),
                'validators' => array(),
            ),
...
      )  //GeoSearch\\V1\\Rest\\Hasslefree\\Address\\Validator
   ) //input_filter_specs

However the code above doesn't work and complains about something like Expecting the InputFilter as class instead of a string. The reason is that the InputFilterPluginManager that respects the 'type' parameter within InputFilterSpec isn't the one registerred as ServiceManager. The culprit is because it wasn't injected into the factory. Changing it to construction injection would resolve it.

    protected function getInputFilterFactory(ServiceLocatorInterface $services)
    {
        if ($this->factory instanceof Factory) { 
            return $this->factory;
        }
 
        $this->factory = new Factory($services->get('InputFilterManager'));
...

Not sure if this is by design or not. An issue has been raised.

1) As a newbie to REST, I am not sure if my design of the API is a good practise. But I cannot find evidence against it so far.