Symfonyシリアライザーコンポーネントを使用して、関係のあるエンティティを逆シリアル化しようとしています。これは私の実体です:
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Document
*
* @ORM\Table(name="document")
* @ORM\Entity(repositoryClass="AppBundle\Repository\DocumentRepository")
*/
class Document
{
/**
* @var int
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity="Genre", inversedBy="documents")
* @ORM\JoinColumn(name="id_genre", referencedColumnName="id")
*/
private $genre;
/**
* @var string
*
* @ORM\Column(name="name", type="string", length=100)
*/
private $name;
//getters and setters down here
...
}
そしてジャンルエンティティ:
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Genre
*
* @ORM\Table(name="genre")
* @ORM\Entity(repositoryClass="AppBundle\Repository\GenreRepository")
*/
class Genre
{
/**
* @var int
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var string
*
* @ORM\Column(name="name", type="string", length=50, nullable=true)
*/
private $name;
/**
* @ORM\OneToMany(targetEntity="Document", mappedBy="genre")
*/
private $documents;
public function __construct()
{
$this->documents= new ArrayCollection();
}
//getters and setters down here
....
}
私のコントローラーアクションで今私はこれを試しています:
$encoders = array(new JsonEncoder());
$normalizers = array(new ObjectNormalizer());
$serializer = new Serializer($normalizers, $encoders);
$document = $serializer->deserialize($request->getContent(), 'AppBundle\Entity\Document', 'json');
そして、私のjson data:
{"name": "My document", "genre": {"id": 1, "name": "My genre"}}
しかし、私は次のerrorを得ました:
タイプ「AppBundle\Entity\Genre」の予期された引数、「配列」が指定されました(500内部サーバーエラー)
内部に関係があるエンティティを使用してjsonリクエストをデシリアライズすることは可能ですか?
よろしくお願いします。
はいといいえ。まず、コントローラーでシリアライザーの新しいインスタンスを再作成するのではなく、代わりにserializer
サービスを使用してください。
第二に、Symfonyシリアライザーを使用した状態では、それは不可能ではありません。 https://api-platform.com/ でそれを行っていますが、少し魔法があります。とはいえ、それをサポートするPRが作成されています: https://github.com/symfony/symfony/pull/19277
18年にこれに取り組んでいる人のために。私は2つの異なるアプローチを使用してこれをうまく機能させることができました。
連携している関連エンティティ。
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;
}
方法1:フォームクラスを使用する
#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);
}
}
方法2:カスタムノーマライザ
PropertyInfoコンポーネントを有効にする必要があります。
#/config/packages/framework.yaml
framework:
property_info:
enabled: true
カスタムノーマライザーを登録します。
#/config/services.yaml
services:
entity_normalizer:
class: App\SupportClasses\EntityNormalizer
public: false
autowire: true
autoconfigure: true
tags: [serializer.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);
}
}
コントローラーのcreateアクション。
#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);
}
}
これでうまくいきました。 https://medium.com/@maartendeboer/using-the-symfony-serializer-with-doctrine-relations-69ecb17e6ebd からインスピレーションを受けました
ノーマライザを変更して、データをjsonからデコードしたときに子配列に変換される子jsonオブジェクトとしてカテゴリを送信できるようにしました。うまくいけば、これは誰かを助けます。
これで動作します.config.ymlでproperty_infoを有効にする必要があります:
framework:
property_info:
enabled: true
これは、Symfonyのドキュメントが " Recursive Denormalization "と呼んでいるバージョン3.3から実際のマスター4.0までです。
Symfonyがシリアル化されたオブジェクトのプロパティタイプを見つけるためには、PropertyInfoコンポーネントを使用する必要があります。これは、@ slk500が彼の回答で述べたように、 framework configuration でアクティブにする必要があります。
したがって、完全なフレームワークを使用している場合、ネストされたjsonオブジェクトを逆シリアル化するために必要なことは次のとおりです。
1. config.ymlでシリアライザとプロパティ情報コンポーネントを有効にします。
framework:
#...
serializer: { enabled: true }
property_info: { enabled: true }
<?php
// src/AppBundle/Controller/DefaultController.php
namespace AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\HttpFoundation\Request;
class DefaultController extends Controller
{
public function indexAction(SerializerInterface $serializer, Request $request)
{
$document = $serializer->deserialize($request->getContent(), 'AppBundle\Entity\Document', 'json');
// ...
}
}
これらのコンポーネントのデフォルト機能は、私のニーズには十分でした。
自動配線は基本的なサービス宣言を処理するので、特定のノーマライザが必要でない限り、services.yml
構成ファイルを編集する必要さえありません。ユースケースによっては、特定の機能を有効にする必要がある場合があります。シリアライザとPropertyInfoのドキュメントで、(できれば)より具体的な使用例を確認してください。
JMSシリアライザーを使用している場合、このコードを使用すると、シリアライザーはデータベース内の関係を検索します。
services.yml
services:
app.jms_doctrine_object_constructor:
class: AppBundle\Services\JMSDoctrineObjectConstructor
arguments: ['@doctrine', '@jms_serializer.unserialize_object_constructor']
jms_serializer.object_constructor:
alias: app.jms_doctrine_object_constructor
public: false
AppBundle\Services\JMSDoctrineObjectConstructor.php
<?php
namespace AppBundle\Services;
use Doctrine\Common\Persistence\ManagerRegistry;
use JMS\Serializer\DeserializationContext;
use JMS\Serializer\Metadata\ClassMetadata;
use JMS\Serializer\VisitorInterface;
use JMS\Serializer\Construction\ObjectConstructorInterface;
/**
* Doctrine object constructor for new (or existing) objects during deserialization.
*/
class JMSDoctrineObjectConstructor implements ObjectConstructorInterface
{
private $managerRegistry;
private $fallbackConstructor;
/**
* Constructor.
*
* @param ManagerRegistry $managerRegistry Manager registry
* @param ObjectConstructorInterface $fallbackConstructor Fallback object constructor
*/
public function __construct(ManagerRegistry $managerRegistry, ObjectConstructorInterface $fallbackConstructor)
{
$this->managerRegistry = $managerRegistry;
$this->fallbackConstructor = $fallbackConstructor;
}
/**
* {@inheritdoc}
*/
public function construct(VisitorInterface $visitor, ClassMetadata $metadata, $data, array $type, DeserializationContext $context)
{
// Locate possible ObjectManager
$objectManager = $this->managerRegistry->getManagerForClass($metadata->name);
if (!$objectManager) {
// No ObjectManager found, proceed with normal deserialization
return $this->fallbackConstructor->construct($visitor, $metadata, $data, $type, $context);
}
// Locate possible ClassMetadata
$classMetadataFactory = $objectManager->getMetadataFactory();
if ($classMetadataFactory->isTransient($metadata->name)) {
// No ClassMetadata found, proceed with normal deserialization
return $this->fallbackConstructor->construct($visitor, $metadata, $data, $type, $context);
}
// Managed entity, check for proxy load
if (!is_array($data)) {
// Single identifier, load proxy
return $objectManager->getReference($metadata->name, $data);
}
// Fallback to default constructor if missing identifier(s)
$classMetadata = $objectManager->getClassMetadata($metadata->name);
$identifierList = array();
foreach ($classMetadata->getIdentifierFieldNames() as $name) {
if (!array_key_exists($name, $data)) {
return $this->fallbackConstructor->construct($visitor, $metadata, $data, $type, $context);
}
$identifierList[$name] = $data[$name];
}
// Entity update, load it from database
if (array_key_exists('id', $identifierList) && $identifierList['id']) {
$object = $objectManager->find($metadata->name, $identifierList);
} else {
$object = new $metadata->name;
}
$objectManager->initializeObject($object);
return $object;
}
}