3

I'm building an API using Symfony2, Doctrine and the FOSRestBundle. I would like to use the forms component to validate API requests that create or modify records and am having a few problems. I have been using the validation component on it's own but would like to move to using the forms component as it moves all validation logic out of the controller, better handles binding the request data to my entities and better aggregates error messages.

The issues I'm having are to do with fields that are not required, either when a record is being created through POST or when it's being updated through PUT. Ideally I'd like it so that a non-required field does not have to be submitted at all through HTTP but this causes the form validation to fail. For example one of the fields on an entity I'm using is a DateTime field called endTime and this is not required. If a parameter called endTime is not present in the POST or PUT request Symfony2 binds the value null from the Request to the field. When this is converted to a DateTime instance it is cast to the current date time, which is not what I want at all.

Is there any way to tell Symfony to not bind values to the entity if they don't exist in the HTTP request? This should still be safe as validation would still fail based on the annotations in the entity class. I could override the bind method but this seems like a lot of work...

Thanks for any ideas.

Jeremy
  • 2,651
  • 1
  • 21
  • 28
  • I use same approach, and it works flawless: if you don't add a field in the form builder, it is not bound at all. – moonwave99 Sep 22 '12 at 13:21
  • I wasn't using the form builder but was creating a separate form class. This should allow me to use the form in the NelmioAPIDocBundle to automatically generate docs. Looks like removing elements from the form depending on what's submitted may be the way to go though... – Jeremy Sep 22 '12 at 13:25
  • Such bundle considers using your own `FormType`s, [`input="Your\Namespace\Form\Type\YourType"`] - just don't add unwanted fields in the `buildForm()` method. – moonwave99 Sep 22 '12 at 13:28
  • moonwave99 - not quite that simple. There are fields that are required for a POST request (creating a resource) which then become optional for a PUT request (updating a resource). Either of the other two solutions would work better... – Jeremy Sep 22 '12 at 17:08

3 Answers3

2

What about creating an array with the not-required fields as keys, then merge you request POST or PUT on top of that array and bind that to the form. That way the hint required fields are there for the form and would have the request data if provided.

DrFrow
  • 86
  • 1
  • 5
  • That looks like a possible solution. I could compare the parameters submitted with the form fields and remove any form fields that haven't been submitted. The validation should still be intact as that's separate from the form. I'll give this a spin on Monday: thanks! – Jeremy Sep 22 '12 at 13:27
  • Thanks-this idea works perfectly. I used AdrienBrault's idea of the event listener but used code to remove form elements depending on what's been submitted as you suggested. Thanks again. – Jeremy Sep 23 '12 at 13:15
2

You can create a Form EventSubscriber, that on the preBind event, replaces non submitted fields by their default value (ie, the values of the object being bound), instead of null.

Here's the EvenSubscriber I've created: https://gist.github.com/3766678 . By default with my implementation, only non required fields will have the default value instead of null ...

You, and anyone, are free to use this class, as long as you keep the author tag, indeed ;-)

AdrienBrault
  • 7,747
  • 4
  • 31
  • 42
  • That's another good solution. I may edit the code to use the event listener but to remove any non submitted fields from the form though. That way data won't be bound to the entity but validation will still pass/fail based on the annotations in the entity. Thanks for the idea and the code! – Jeremy Sep 22 '12 at 17:10
  • i believe this is the way to go ,event listener. Although it can be complex but propably in the end its the most clean solution maybe – GorillaApe Sep 23 '12 at 12:49
  • Thanks AdrienBrault. I wish I could mark your answer as the solution too as I used the event subscriber as you suggested but with DrFrow's idea of removing form elements depending on what's been submitted. You provided a good chunk of the answer too. :) – Jeremy Sep 23 '12 at 13:16
  • Removing form elements whose value where not submitted, will cause you trouble if you need to render some view of the form later. (In my case, in return an xml/json representation of the form) – AdrienBrault Sep 23 '12 at 13:56
  • Shouldn't be an issue in my case. I'm using the FOSRestBundle and return the form from the controller if validation fails. The FOSRestBundle handles setting a 400 response and gathering the errors from the form. Since the validation is separate from the form this doesn't seem to cause any issues if elements are removed from the form-the correct errors are still displayed. Do you know another way to achieve this? Is there a way to tell the form not to process elements that haven't been submittied? – Jeremy Sep 29 '12 at 18:31
2

You can achieve this using a combination of custom form classes and validation groups.

First, create the form (Your\Bundle\Form\CreateFormType), which extends Symfony\Component\Form\AbstractType builds the form with the appropriate elements in buildForm:


    $builder
        ->add('title')
        ->add('firstNames');

You can then add validation for each of your entity properties. Each of the properties that should be validated together will need to share the same groups.

E.g.


    Your\Bundle\Entity\User:
        properties:
            title:
                - NotBlank:
                    groups: [register]
                    message: Please provide your title
            firstNames:
                - NotBlank:
                    groups:  [register]
                    message: Please provide your first name(s)

You would also have another group (and custom form) for update, which would contain the properties that were present in that request.

In your form class, you configure which validation group(s) are used in setDefaultOptions():


    $resolver->setDefaults(array(
        // ...
        'validation_groups' => array('register'),
        // ...
    ));

StuBez
  • 1,346
  • 14
  • 21