3

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.

1
  • 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 flushing Commented Nov 6, 2015 at 7:10

2 Answers 2

3

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)

Sign up to request clarification or add additional context in comments.

5 Comments

If I understand correctly you completely abandoned standard Symfony2 Form objects? You don't use $form->submit() or $form->handleRequest() in the Controller? What about validation in $form->isValid()?
indeed, i abandoned the form completely, instead of $form->isValid() to check if the data is valid, we use the validator service, to validate the object (wich is sort of what the form would also do behind he scenes)
Another question. What happens if user submits extra data - that is not provided on the edit form? Does validationGroups handle that? For example there is edit form with "name" and "lastName" but user managed to send "gender" inside the request too. How to prevent this field from persisting (FormType builder has mechanism for that)?
if the field dies not exist in the model, it will be ignored, if it does exist in the model, it will be persisted, unless you add a readonly annotation, or set accessors annotation, without setting a setter, or overwrite it with a virtualproperty annotation, or block it some other way, otherwise, it WILL be persisted
edit, i have to re-check, but are you sure you cant just handle this with exclusion groups ? perhaps even by deserializing and merging it twice (so it ends up with a version wich doesnt have the extra field, if you understand what i mean ? ) deserialize, merge(wrong gender set), serialize(gender now gone), merge (with fresh, so correct gender) .? surely no good performance wise ..!!
2

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))

1 Comment

Wow, exactly my case was already in the Symfony2 documentation all along. Thanks for pointing this out. This is preferred solution since standard FormType is not abandoned as in @Sam's solution.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.