Symfony 4 Serializer:反序列化请求并将其与实体(包括客户端未完全传递的关系)合并

Oli*_*nes 1 php entity doctrine symfony symfony4

假设我有一个名为 User 的实体:

class User implements UserInterface
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
     private $id;

    /**
     * @ORM\Column(type="string", length=255, nullable=false)
     */
     private $username;

     /**
     * @ORM\OneToOne(targetEntity="App\Entity\Address", cascade={"persist", "remove"})
     * @ORM\JoinColumn(nullable=false)
     */
    private $address;
Run Code Online (Sandbox Code Playgroud)

地址字段与地址实体是一对一的关系:

class Address
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $street;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $name;
Run Code Online (Sandbox Code Playgroud)

我有一个用于更新用户及其地址的控制器:

...
 public function putUserSelf(Request $request)
    {
        $em = $this->getDoctrine()->getManager();
        $user = $this->getUser();

        $encoders = array(new JsonEncoder());
        $normalizers = array(new ObjectNormalizer(null, null, null, new ReflectionExtractor()));

        $serializer = new Serializer($normalizers, $encoders);
        $user = $serializer->deserialize($request->getContent(), User::class, "json", ['deep_object_to_populate' => $user]);
        $em->persist($user);
        $em->flush();
Run Code Online (Sandbox Code Playgroud)

理论上我现在应该可以像这样传递 json:

{
    "username": "foo",
    "address": {"name": "bar"}
}
Run Code Online (Sandbox Code Playgroud)

更新我的实体。但问题是,我收到一个 sql 错误:

An exception occurred while executing 'INSERT INTO address (street, name) VALUES (?, ?)' with params [null, "bar"]:

SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'street' cannot be null
Run Code Online (Sandbox Code Playgroud)

实体合并似乎不起作用。

Jak*_*umi 6

根据文档

当该AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE选项设置为true时,根的现有子级OBJECT_TO_POPULATE将从规范化数据中更新,而不是反规范化器重新创建它们。[...](强调我的)

所以你必须设置一个额外的选项,所以你的行应该是:

    $user = $serializer->deserialize($request->getContent(), User::class, "json", [
        'object_to_populate' => $user, // this still needs to be set, without the "deep_"
        'deep_object_to_populate' => true,
    ]);
Run Code Online (Sandbox Code Playgroud)

特定组件的源代码中有一些附加注释:

/**
 * Flag to tell the denormalizer to also populate existing objects on
 * attributes of the main object.
 *
 * Setting this to true is only useful if you also specify the root object
 * in OBJECT_TO_POPULATE.
 */
public const DEEP_OBJECT_TO_POPULATE = 'deep_object_to_populate';
Run Code Online (Sandbox Code Playgroud)

来源: https: //github.com/symfony/symfony/blob/d8a026bcadb46c9955beb25fc68080c54f2cbe1a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php#L82