How to update a Doctrine Entity from a serialized JSON?
Asked Answered
C

3

11

We are using Symfony2 to create an API. When updating a record, we expect the JSON input to represent a serialized updated entity. The JSON data will not contain some fields (for instance, CreatedAt should be set only once when the entity is created - and never updated). For instance, here is an example JSON PUT request:

{"id":"1","name":"anyname","description":"anydescription"}

Here is the PHP code on the Controller that should update the entity according to the JSON above (we are using JMS serializer Bundle):

$supplier = $serializer->deserialize(
    $this->get('request')->getContent(),
    'WhateverEntity',
    'json'
);

The EntityManger understands (correctly) that this is an update request (in fact, a SELECT query is implicitly triggered). The EntityManager also guess (not correctly) that CreatedAt property should be NULLified - it should instead keep the previous one.

How to fix this issue?

Cordillera answered 4/1, 2012 at 12:6 Comment(0)
I
11

I would use the Doctrine\ORM\Mapping\ClassMetadata API to discover existing fields in your entity. You can do following (I don't know how JMSSerializerBundle works):

//Unserialize data into $data
$metadata = $em->getMetadataFactory()->getMetadataFor($FQCN);
$id = array();
foreach ($metadata->getIdentifierFieldNames() as $identifier) {
    if (!isset($data[$identifier])) {
        throw new InvalidArgumentException('Missing identifier');
    }
    $id[$identifier] = $data[$identifier];
    unset($data[$identifier]);
}
$entity = $em->find($metadata->getName(), $id);
foreach ($metadata->getFieldNames() as $field) {
    //add necessary checks about field read/write operation feasibility here
    if (isset($data[$field])) {
        //careful! setters are not being called! Inflection is up to you if you need it!
        $metadata->setFieldValue($entity, $field, $data[$field]);
    }
}
$em->flush();
Infrequent answered 30/1, 2012 at 0:44 Comment(0)
I
17

It's possible as well to do it with Symfony Serializer using object_to_populate option.

Example: I receive JSON request. If record exists in database I want to update fields received in body, if it does not exist I want to create new one.

/**
 * @Route("/{id}", methods={"PUT"})
 */
public function upsert(string $id, Request $request, SerializerInterface $serializer)
{
  $content = $request->getContent(); // Get json from request

  $product = $this->getDoctrine()->getRepository(Product::class)->findOne($id); // Try to find product in database with provided id

  if (!$product) { // If product does not exist, create fresh entity
      $product = new Product();
  }

  $product = $serializer->deserialize(
            $content,
            Product::class,
            'json',
            ['object_to_populate' => $product] // Populate deserialized JSON content into existing/new entity
        );
  // validation, etc...
  $this->getDoctrine()->getManager()->persist($product); // Will produce update/instert statement 
  $this->getDoctrine()->getManager()->flush($product);

// (...)
Illegible answered 13/12, 2018 at 12:2 Comment(2)
There is a way to populate more objects? For request like: {"id":"1","name":"anyname", "otherObject": { "id": 3},} I want to populate other object which is in main object, so symfony should take two diffrent objects by id from databasePaint
yes, correctly create main object ($product in this case) but do not initialize otherObject. Because of that Doctrine try to create new object or nullify existing element.Paint
S
14

using the JMSSerializerBundle follow the install instructions at http://jmsyst.com/bundles/JMSSerializerBundle

either create your own serializer service or alter the JMSSerializerBundle to use the doctrine object constructor instead of the simple object constructor.

<service id="jms_serializer.object_constructor" alias="jms_serializer.doctrine_object_constructor" public="false"/>

This basically handles exactly what Ocramius solution does but using the JMSSerializerBundles deserialize.

Sandell answered 13/5, 2013 at 20:58 Comment(2)
I couldn't really anywhere documentation how to use the method you suggest. Can you suggest where I can read about it?Hoofbound
looks like they moved it under it's own section. configurationSandell
I
11

I would use the Doctrine\ORM\Mapping\ClassMetadata API to discover existing fields in your entity. You can do following (I don't know how JMSSerializerBundle works):

//Unserialize data into $data
$metadata = $em->getMetadataFactory()->getMetadataFor($FQCN);
$id = array();
foreach ($metadata->getIdentifierFieldNames() as $identifier) {
    if (!isset($data[$identifier])) {
        throw new InvalidArgumentException('Missing identifier');
    }
    $id[$identifier] = $data[$identifier];
    unset($data[$identifier]);
}
$entity = $em->find($metadata->getName(), $id);
foreach ($metadata->getFieldNames() as $field) {
    //add necessary checks about field read/write operation feasibility here
    if (isset($data[$field])) {
        //careful! setters are not being called! Inflection is up to you if you need it!
        $metadata->setFieldValue($entity, $field, $data[$field]);
    }
}
$em->flush();
Infrequent answered 30/1, 2012 at 0:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.