Search code examples
phpsymfonysymfony-forms

How to set default values for an API using Symfony Forms?


I have a very simple API. You can POST a price (value and currency) to the API. The default currency is EUR, so it's ok to omit the currency. The API returns the full price object:

$ curl -d '{"value":12.1}' http://localhost:8000/prices.json
{
    "value": 12.1,
    "currency": "EUR"
}

So I wanted to implement this using Symfony Forms. I've set up a small data model with some basic validation rules:

namespace AppBundle\Model;

use Symfony\Component\Validator\Constraints as Assert;

class Price
{
    /**
     * @Assert\NotBlank()
     * @Assert\GreaterThanOrEqual(0)
     */
    public $value;

    /**
     * @Assert\NotBlank()
     * @Assert\Length(min=3, max=3)
     */
    public $currency = 'EUR';
}

And a controller with a form:

namespace AppBundle\Controller;

use AppBundle\Model\Price;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;

class PriceController extends Controller
{
    /**
     * @Route("/prices.json")
     */
    public function apiAction(Request $request)
    {
        $product = new Price();

        $form = $this->createFormBuilder($product, [
                'csrf_protection' => false,
            ])
            ->add('value', 'number')
            ->add('currency')
            ->getForm();

        $form->submit(json_decode($request->getContent(), true));
        if ($form->isValid()) {
            return new JsonResponse($product);
        }

        return new JsonResponse($form->getErrorsAsString());
    }
}

This works only if I pass all fields in the request body. I cannot omit the currency. Also setting data or empty_data does not help.

I tried to toggle $clearMissing on the submit() method, but this disables the validation of the model:

$form->submit(json_decode($request->getContent(), true), false);

The best working idea I came up so far is an event listener merging the data:

$form = $this->createFormBuilder($product, [
        'csrf_protection' => false,
    ])
    ->add('value', 'number')
    ->add('currency')
    ->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $e) {
        $e->setData(array_merge((array) $e->getForm()->getData(), $e->getData()));
    })
    ->getForm();

This works for my simple example. But is this the best way to go? Or are there other / better alternatives?


Solution

  • Your solution looks good to me! I think adding the event listener like you did is the best way to go.

    I suggest using array_replace() instead of array_merge(), since it is dedicated to associative arrays.