In Symfony I'm writing an API for Angular2. I use the FOSRestBundle with the JMSSerializerBundle. Now, I have an entity "User" that has an entity field "address" with a OneToOne association. And I'm having trouble saving the address of the user.
So I first do a GET of the user object and it returns the whole object with the address as json. Then I do PUT request with that exact same object. In my PUT function I use a Symfony form to validate the data and there it returns an error:
{
"children": {
"address": {
"errors": [
"This value is not valid."
]
}
}
}
I have some other fields on my User entity and those are saved perfectly when I leave out the address field in my form-builder. BTW: I left out those other fields to not overload the amount of code.
I use these versions:
- symfony: 3.1
- jms/serializer: 1.1
- friendsofsymfony/rest-bundle: 2.0
I've been looking for 2 days now and I can't find anything that helps me with this issue. I use the date transformations like FOSRestBundle says: http://symfony.com/doc/current/bundles/FOSRestBundle/2-the-view-layer.html#data-transformation
I hope I formulated my question good enough and gave enough info.
Here is my simplified code:
User class:
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;
/**
* @ORM\Entity
*/
class User
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\OneToOne(targetEntity="Address", cascade={"persist", "remove"}, orphanRemoval=true)
* @JMS\Type("AppBundle\Entity\Address")
*/
private $address;
Address class:
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class Address
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\Column(type="string")
*/
private $street;
/**
* @ORM\Column(type="string")
*/
private $number;
/**
* @ORM\Column(type="string")
*/
private $postalCode;
/**
* @ORM\Column(type="string")
*/
private $city;
/**
* @ORM\Column(type="string")
*/
private $country;
UserType class:
use FOS\RestBundle\Form\Transformer\EntityToIdObjectTransformer;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class UserType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
// Data transformation needed for relationship entities
$addressTransformer = new EntityToIdObjectTransformer($options['em'], 'AppBundle:Address');
$builder
->add($builder->create('address', TextType::class)->addModelTransformer($addressTransformer))
;
}
/**
* @param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\User',
'csrf_protection' => false,
'allow_extra_fields' => true,
'em' => null
));
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'user';
}
}
UserController class:
use AppBundle\Entity\User;
use AppBundle\Form\UserType;
use FOS\RestBundle\Controller\Annotations as Rest;
use FOS\RestBundle\View\View;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
/**
* Class UserController
* @package AppBundle\Controller
*/
class UserController extends Controller
{
/**
* @Rest\View
* @Route("/users/{id}")
* @Method("PUT")
*/
public function putAction(Request $request, $id)
{
$user = $this->getEntity($id);
$form = $this->createForm(UserType::class, $user, array(
'method' => 'PUT',
'em' => $this->getDoctrine()->getManager()
));
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($user);
$em->flush();
$response = new Response();
$response->setStatusCode(204);
$response->setContent('User saved!');
return $response;
}
return View::create($form, 400);
}
/**
* @Rest\View
* @Route("/users/{id}", requirements={"id": "\d+"})
* @Method("GET")
*/
public function getAction($id)
{
$user = $this->getEntity($id);
return array('user' => $user);
}
/**
* Get the User entity object by the given ID and return it
*
* @param $id
*
* @return User
*/
private function getEntity($id)
{
$user = $this->getDoctrine()->getRepository('AppBundle:User')->find($id);
if (!$user instanceof User) {
throw new NotFoundHttpException('User not found');
}
return $user;
}
And the json-object that I GET and PUT looks like this:
{
"user":
{
"id":1,
"address": {
"id":1,
"street":"Teststreet",
"number":"1",
"postalCode":"9999",
"city":"Citytest",
"country":"Countrytest"
}
}
}
my config.yml:
fos_rest:
param_fetcher_listener: true
body_listener:
array_normalizer: fos_rest.normalizer.camel_keys
format_listener:
rules:
path: ^/
fallback_format: json
prefer_extension: false
priorities: [json, xml]
body_converter:
enabled: false
validate: false
view:
view_response_listener: force
formats:
json: true
xml: true
templating_formats:
html: true
force_redirects:
html: true
failed_validation: HTTP_BAD_REQUEST
default_engine: twig
mime_types:
json: ['application/json', 'application/json;version=1.0', 'application/json;version=1.1']
routing_loader:
default_format: json
serializer:
serialize_null: true
nelmio_api_doc: ~
jms_serializer:
metadata:
directories:
FOSUB:
namespace_prefix: "FOS\\UserBundle"
path: "%kernel.root_dir%/serializer/FOSUserBundle"