Deserialize an entity with a relationship with Symfony Serializer Component Deserialize an entity with a relationship with Symfony Serializer Component symfony symfony

Deserialize an entity with a relationship with Symfony Serializer Component


Yes and no. First, you shouldn't re-create a new instance of the serializer in your controller but use the serializer service instead.

Second, no it's not possible out of the box with Symfony serializer. We are doing it in https://api-platform.com/ but there is a bit of magic there. That said, a PR has been made to support it: https://github.com/symfony/symfony/pull/19277


For anyone who is working on this in '18. I've managed to get this working using two different approaches.

The associated entities I'm working with.

class Category{     /**     * @ORM\Id     * @ORM\GeneratedValue     * @ORM\Column(type="integer")     */    private $id;    /**     * @ORM\Column(type="string", name="name", length=45, unique=true)     */    private $name;}class Item{     /**     * @ORM\Id     * @ORM\GeneratedValue     * @ORM\Column(type="integer")     */    private $id;    /**     * @ORM\Column(type="string", name="uuid", length=36, unique=true)     */    private $uuid;    /**     * @ORM\Column(type="string", name="name", length=100)     */    private $name;    /**     * @ORM\ManyToOne(targetEntity="App\Entity\Category", fetch="EAGER")     * @ORM\JoinColumn(name="category_id", referencedColumnName="id", nullable=false)     */    private $category;}

Method 1: Using Form Classes

#ItemType.phpnamespace App\Form;use Symfony\Component\Form\AbstractType;use Symfony\Component\Form\FormBuilderInterface;use Symfony\Component\Form\FormTypeInterface;use Symfony\Component\OptionsResolver\OptionsResolver;use Symfony\Bridge\Doctrine\Form\Type\EntityType;use App\Entity\Category;use App\Entity\Item;class ItemType extends AbstractType{    public function buildForm(FormBuilderInterface $builder, array $options)    {        $builder            ->add('name')            ->add('category', EntityType::class, [                'class' => Category::class,                'choice_label' => 'name',            ])        ;    }    public function configureOptions(OptionsResolver $resolver)    {        $resolver->setDefaults(array(            'data_class' => Item::class,        ));    }}#ItemController.phpnamespace App\Controller;use Symfony\Component\HttpFoundation\Request;use Symfony\Component\Routing\Annotation\Route;use Symfony\Component\Serializer\Exception\NotEncodableValueException;use App\Entity\Item;use App\Form\ItemType;class ItemController extends BaseEntityController{    protected $entityClass = Item::class;    /**     * @Route("/items", methods="POST")     */    public function createAction(Request $request)    {        $data = $request->getContent();        $item = new Item();        $form = $this->createForm(ItemType::class, $item);        $decoded = $this->get('serializer')->decode($data, 'json');        $form->submit($decoded);        $object = $form->getData();        $entityManager = $this->getDoctrine()->getManager();        $entityManager->persist($object);        $entityManager->flush();        return $this->generateDataResponse("response text", 201);    }}

Method 2: A Custom Normalizer

The PropertyInfo Component needs to be enabled.

#/config/packages/framework.yamlframework:    property_info:        enabled: true

Register the custom normalizer.

#/config/services.yamlservices:    entity_normalizer:        class: App\SupportClasses\EntityNormalizer        public: false        autowire: true        autoconfigure: true        tags: [serializer.normalizer]

The custom normalizer.

#EntityNormalizer.phpnamespace App\SupportClasses;use Doctrine\ORM\EntityManagerInterface;use Symfony\Component\PropertyAccess\PropertyAccessorInterface;use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;use Symfony\Component\Serializer\NameConverter\NameConverterInterface;use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;class EntityNormalizer extends ObjectNormalizer{    protected $entityManager;    public function __construct(        EntityManagerInterface $entityManager,        ?ClassMetadataFactoryInterface $classMetadataFactory = null,        ?NameConverterInterface $nameConverter = null,        ?PropertyAccessorInterface $propertyAccessor = null,        ?PropertyTypeExtractorInterface $propertyTypeExtractor = null    ) {        $this->entityManager = $entityManager;        parent::__construct($classMetadataFactory, $nameConverter, $propertyAccessor, $propertyTypeExtractor);    }    public function supportsDenormalization($data, $type, $format = null)    {        return (strpos($type, 'App\\Entity\\') === 0) &&         (is_numeric($data) || is_string($data) || (is_array($data) && isset($data['id'])));    }    public function denormalize($data, $class, $format = null, array $context = [])    {        return $this->entityManager->find($class, $data);    }}

Our controller's create action.

#ItemController.phpnamespace App\Controller;use Symfony\Component\HttpFoundation\Request;use Symfony\Component\Routing\Annotation\Route;use Symfony\Component\Serializer\Exception\NotEncodableValueException;use App\Entity\Item;use App\Form\ItemType;class ItemController extends BaseEntityController{    protected $entityClass = Item::class;    /**     * @Route("/items", methods="POST")     */    public function createAction(Request $request)    {        $data = $request->getContent();        $object = $this->get('serializer')->deserialize($data, $this->entityClass, 'json');        $entityManager = $this->getDoctrine()->getManager();        $entityManager->persist($object);        $entityManager->flush();        return $this->generateDataResponse('response text', 201);    }}

This has worked for me. I received inspiration from:https://medium.com/@maartendeboer/using-the-symfony-serializer-with-doctrine-relations-69ecb17e6ebd

I modified the normalizer to allow me to send the category as a child json object which is converted to a child array when the data is decoded from json. Hopefully this helps someone.


It works now.You have to enable property_info in config.yml:

  framework:            property_info:                    enabled: true