I\'m trying to deserialize an entity with a relationship using the symfony serializer component. This is my entity:
namespace AppBundle\\Entity;
use Doctrin
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.php
namespace 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.php
namespace 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.yaml
framework:
property_info:
enabled: true
Register the custom normalizer.
#/config/services.yaml
services:
entity_normalizer:
class: App\SupportClasses\EntityNormalizer
public: false
autowire: true
autoconfigure: true
tags: [serializer.normalizer]
The custom normalizer.
#EntityNormalizer.php
namespace 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.php
namespace 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.