3
votes

I'm using AngularJS with Restangular library to handle Rest Api written in Symfony2 + JMS Serializer + FOS Rest Bundle.

In order to perform CRUD operations on User Entity first I get particular User to display:

var User = Restangular.one('users', $routeParams.userId);

User.get()
    .then(function(response) {
        $scope.user = response;
    });

I recieve JSON response like this:

{
    id: 1,
    firstName: "Howell",
    lastName: "Weissnat",
    job: {
        id: 1
        name: "Job Name"
    }
}

Then I would like to modify job field inside User Entity. Jobs are basically separate Entities (more like Value Objects) in Symfony 2 backend with ORM relation to User Entity.

Then I modify User on the frontend via AngularJS, for example selecting Job from available options in the selectbox. JSON representation of $scope.user will look like this:

{
    id: 1,
    firstName: "Howell",
    lastName: "Weissnat",
    job: {
        id: 2
        name: "Job Second Name"
    }
}

Then I make a PUT request with Restangular:

$scope.user.save();

Unfortunately Symfony2 expects to receive object like this:

{
    id: 1,
    firstName: "Howell",
    lastName: "Weissnat",
    job: 2
}

Where job: 2 is basically pointing at ID of the Job Entity.

I managed to successfully deal with that problem creating ViewTransformer in the UserType class on the backend. Which will basically covert received job object into scalar integer with ID value. symfony.com/doc/current/cookbook/form/data_transformers.html

$builder->get('job')->addViewTransformer

However I feel like this is not correct approach and I can't think of any better idea right now. What is best practice to send more complex objects to the Symfony2 RESTful backend? This is only simple example but in my application job field in the User class could also contain collection of Job entities.

2
what we basically do is, Deserialize using jms (using doctrine object constructor, instead of jms's), merge it into doctrinemanager and flush .. sounds too easy, it is that easy !, and you can also simple use the validator service, to actually validate your entity, before flushingSam Janssens

2 Answers

3
votes

something along these lines is what we use:

$entity = $this->get('jms_serializer')->deserialize($request->getContent(), $entityName, 'json');
if (!$entity) {
     throw new ApiException(Codes::HTTP_NOT_FOUND, null, 'entity with that id not found');
}

$violations = $this->get('validator')->validate($entity, $validationGroups);

if (count($violations) > 0) {

     return $violations;
}


$this->getDoctrine()->getManager()->merge($entity);
$this->getDoctrine()->getManager()->flush();

And in your service :

services:
    jms_serializer.doctrine_object_constructor:
          class:        %jms_serializer.doctrine_object_constructor.class%
          public:       false
          arguments:    ["@doctrine", "@jms_serializer.unserialize_object_constructor"]

    jms_serializer.object_constructor:
      alias: jms_serializer.doctrine_object_constructor

What that basically does, is make sure, that jms fetches the entity from doctrine, and apply the values to that existing one, instead of creating a new one, and applying the values to that new one (and possibly ending up with some null fields)

2
votes

Try this solution in the your UserFormType

$jobTransformer = new EntityToIdObjectTransformer($this->om, "AcmeDemoBundle:Job");
$builder
    ->add('firstName', 'text')
    ...
    ->add($builder->create('job', 'text')->addModelTransformer($jobTransformer))