1

In the Zend Framework 2 application I'm currently developing with Apigility there is a resource address, that provides following access points:

GET /address
    id <-- required

PATCH /address
    id <-- required
    street
    zip
    city
    type

OK. Now I started implementing a futher one for inserting items:

POST /address
    street
    zip
    city
    type

Since in the context of the POST method the id is not needed, it hence cannot be required. The problem is, that in Apigility there is no context dependent fields / fieldsets (yet?). So in order to implement the POST method, I have to make the id field not-required for all methods. But then I have to make them required for GET and PATCH manually (yet don't know how).

  1. Is it possible to define context dependent fields / fieldset in Apigility? (Maybe I simply didn't find this option.)

  2. If not: Which approaches are there to handle this?

automatix
  • 14,018
  • 26
  • 105
  • 230
  • 1
    The Apigility docu tells something about the [HTTP Method-Specific Validation](https://apigility.org/documentation/api-primer/content-validation) with `zf-content-validation`. It sounds like a solution this case. – automatix Sep 25 '14 at 10:04

1 Answers1

2

Apigility does support verb specific validators. Validators are only applied or needed on POST, PUT and PATCH though. DELETE and GET do not take any body and that's the only part of a request that Apigility supports validation on.

Additionally, it's probably unlikely that you want the user of your API to supply the id. This is pretty rare. As you've indicated in POST, the id would likely be generated. This means also that the id would be provided for PUT and PATCH, but it should be part of the URL.

For example:

PUT /address/4

PATCH /address/5

In your route, you should have something like /address[/:id]. Based on whether or not this id is provided will determine what method is called in your resource class. PUT without the id (PUT /address) would call replaceList, while PUT with the id would call update. PATCH with an id calls the patch method. It doesn't really make sense to have PATCH without an id.

If you want to constrain the values in the URL, you can add a constraints section to the route's options like so:


'router' => array(
        'routes' => array(
            'your-api.rest.address' => array(
                'type' => 'Segment',
                'options' => array(
                    'route' => '/address[/:address_id]',
                    'defaults' => array(
                        'controller' => 'YourApi\V1\Rest\Address\Controller',
                    ),
                    'constraints' => array(
                        'address_id' => '[0-9]+',
                    ),
                ),
            ),
        ),
    ),
)

The key for constraints should match the name of the id variable in your route. The value would be a regular expression that matches the possible legitimate values for the id. You don't include regex delimiters for this. It will make requests to something like /address/banana return a 404 and the request will not make it into your resource's code.

With this in place, I'd recommend removing the id field from your fields list. You'll likely be able to use the same set of validators for POST, PUT and PATCH. It's important to understand how the validators are applied depending on the verbs as well.

In all the verbs, if you've configured filters, those will be applied to the supplied fields before validation. This means, for instance, if you have a filter of \Zend\Filter\Digits, then all non-digits will be removed prior to validation. If your validator is something like \Zend\Validator\Digits, then as long as the field contains at least one digit, it will be valid.

There are slight differences in how the validators are applied based on verbs. In POST and PUT (with an id in the URL), you can include extra fields that are not specified in your validators. In PATCH, there will be an error if you send in any fields which are not specified in the validators.

For PUT without an id (routing to replaceList), the expected body will be an array of objects.

The final caveat with the validators is that if you have filters applied to any fields and the validation passes, the values in $data that are passed into any of the methods will be the values before filtering is applied. Going back to the earlier example with a field that has the Digits filter and the Digits validator, if you send in something like {'my_field': '1234banana56'}, it will pass the validation but the value in $data will not be 123456, it will be 1234banana56. If you want to get the filtered value, you need to do something like this:


$filteredData = $this->getInputFilter()->getValues();

This will give you back an array of the filtered and validated field values. Any fields that were not specified in your validator will not be returned in this array. There has been talk about making this behavior configurable so that $data would receive the filtered data values, but as of this writing, that's how it works.

If you do find that you need different validators based on different verbs, the answers are in the docs here: https://apigility.org/documentation/content-validation/advanced

Hope this all helps.

David Stockton
  • 2,261
  • 1
  • 14
  • 20
  • Thank you for a post! It does not give an answer to my auestion, but anyway, it's super informative. +1 – automatix May 14 '15 at 10:59
  • You are right, my example with the `id` as (required) parameter was not good. A better one would be one with `PUT` and `PATCH`. OK. – automatix May 14 '15 at 11:00
  • The approach with route constraints is a useful thing. But I would like to avoid misusage of the URI for the content. – automatix May 14 '15 at 11:00
  • Found a solution. I'll post it later, after testing it. In short: 1. No UI solution. 2. Over `zf-content-validation` in the config: a) Specify alternative validators (fields sets with filters and validators): `'input_filter_specs' => ..., [My\Alternative\Validator\V1 => [0 => ['name' => 'field_foo', 'filetrs' => [...], 'validators' => [...]], 0 => ['name' => 'field_bar', 'filetrs' => [...], 'validators' => [...]], ...]]`. b) Assign them to HTTP method keys: `zf-content-validation => ['MyAPI\\V1\\Rest\\MyEntity\\Controller' => ['input_filter' => ..., 'PATCH' => 'My\Alternative\Validator\V1']]`. – automatix May 14 '15 at 11:28
  • Unless you're allowing the user of the API to assign the id themselves, id is not content. It belongs in the URI for entity requests. – David Stockton May 14 '15 at 23:19
  • Exactly. It's why I say -- it was not a good example from me with `id` in the original question. – automatix May 15 '15 at 07:30