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?
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.