Symfony 4 序列化实体对象,不包括关联关系

7

我需要记录每个实体的更改。我有一个监听器,用于监听doctrine的事件:preRemovepostUpdatepostDelete

我的实体AccessModule有以下关系:

App\Entity\AccessModule.php

/**
 * @ORM\OneToMany(targetEntity="App\Entity\AccessModule", mappedBy="parent")
 * @ORM\OrderBy({"id" = "ASC"})
 */
private $children;

/**
 * @ORM\ManyToOne(targetEntity="App\Entity\AccessModule", inversedBy="children")
 * @ORM\JoinColumn(name="parent_id", referencedColumnName="id", nullable=true)
 */
private $parent;

/**
 * @ORM\ManyToMany(targetEntity="App\Entity\AccessModuleRoute", inversedBy="access_modules")
 * @ORM\JoinTable(name="access_routes",
 *     joinColumns={@ORM\JoinColumn(name="access_module_id", referencedColumnName="id")},
 *     inverseJoinColumns={@ORM\JoinColumn(name="route_id", referencedColumnName="id")})
 *
 */
private $routes;

在监听器中: App\EventListener\EntityListener.php

use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;


    $encoders = [new XmlEncoder(), new JsonEncoder()];
    $normalizer = new ObjectNormalizer();

        $normalizer->setCircularReferenceHandler(function ($object) {
            return $object->getId();
        });

    $this->serializer = new Serializer([$normalizer], $encoders);


public function createLog(LifecycleEventArgs $args, $action){
    $em = $args->getEntityManager();
    $entity = $args->getEntity();
    if ($this->tokenStorage->getToken()->getUser()) {
        $username = $this->tokenStorage->getToken()->getUser()->getUsername();
    } else {
        $username = 'anon'; // TODO Remove anon. set null value
    }

    $log = new Log();
//      $log->setData('dddd')
        $log->setData($this->serializer->serialize($entity, ''json)
            ->setAction($action)
            ->setActionTime(new \DateTime())
            ->setUser($username)
            ->setEntityClass(get_class($entity));
        $em->persist($log);
        $em->flush();
    }

我遇到了序列化的问题。当我使用$log->setData($entity)时,我会遇到循环引用的问题。当我使用$log->setData($this->serializer->serialize($entity, 'json')) 进行序列化时,结果会包含实体中所有关联数据,包括父级和子级,最终得到完整的树形结构:/ 我希望能够只得到...
[
 'id' => ID,
 'name' => NAME,
 'parent' => parent_id // ManyToOne, I'd like get its id
 'children' => [$child_id, $child_id, $child_id] // array of $id of children array collection
]

当然,在将其编码为json之前,这只是草稿。

如何在没有完整关系的情况下获取预期数据?


你可以尝试使用CustomPropertyAccessor::getValue()来实现这个。 - Anton Cvetaev
3个回答

9
您要查找的内容称为序列化组:这里这里

现在让我解释一下它是如何工作的。这很简单。假设您有一个帖子实体:

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

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\User\User")
     * @Groups({"default"})
     */
    private $author;
}

同时你也拥有一个用户实体(User Entity):

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

    /**
     * @ORM\Column(type="string", length=40)
     * @Groups({"default"})
     */
    private $firstName;

    /**
     * @ORM\Column(type="string", length=40)
     */
    private $lastName;
}

文章可以有作者(用户),但我不想每次都返回所有用户数据。我只对id和名字感兴趣。

请仔细查看@Groups注释。您可以指定所谓的序列化组。这只是告诉Symfony您希望在结果集中包含哪些数据的方便方式。

您必须通过在属性/获取器上方添加相关组的注释来告诉Symfony序列化器要保留哪些关系。您还必须指定您希望保留哪些关系的属性或获取器。

那么如何让Symfony知道这些内容呢?

当您准备/配置序列化服务时,只需提供定义的组即可:

return $this->serializer->serialize($data, 'json', ['groups' => ['default']]);

建议在原生的symfony序列化器周围构建一种包装服务,这样可以简化整个过程并使其更具可重用性。

还要确保序列化器被正确配置 - 否则它将不会考虑到这些组。

这也是“处理”循环引用的其中一种方式(其他方式之一)。

现在您只需要处理如何格式化结果集。


2
很棒的答案。我想再加上一点: 要告诉Symfony你想从注释中读取Groups,你必须这样做。$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));定义classMetadataFactory并将其作为参数传递给ObjectNormalizer。$normalizer = new ObjectNormalizer($classMetadataFactory);https://symfony.com/doc/current/components/serializer.html#configure-name-conversion-using-metadata - SpicyTacos23

6

ignored_attributes 提供了一种快捷且简单的方法来实现您所寻求的功能。

$serializer->serialize($object, 'json', ['ignored_attributes' => ['ignored_property']]); 

这是如何与序列化器一起使用的:
use Acme\Person;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;

$person = new Person();
$person->setName('foo');
$person->setAge(99);

$normalizer = new ObjectNormalizer();
$encoder = new JsonEncoder();

$serializer = new Serializer([$normalizer], [$encoder]);
$serializer->serialize($person, 'json', ['ignored_attributes' => ['age']]); 

文档:https://symfony.com/doc/current/components/serializer.html#ignoring-attributes

该文档介绍了Symfony的序列化组件中的忽略属性功能。

3
在Symfony 4.1中测试过,以下是实际可行的文档:https://symfony.com/blog/new-in-symfony-2-7-serialization-groups Robert的解释https://dev59.com/B6jka4cB1Zd3GeqPC7bJ#48756847缺少$classMetadataFactory才能正常使用。以下是我的代码:
    $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
    $encoders = [new JsonEncoder()];
    $normalizer = new ObjectNormalizer($classMetadataFactory);
    $normalizer->setCircularReferenceLimit(2);
    // Add Circular reference handler
    $normalizer->setCircularReferenceHandler(function ($object) {
        return $object->getId();
    });
    $normalizers = [$normalizer];
    $serializer = new Serializer($normalizers, $encoders);
    $jsonContent = $serializer->serialize($jobs, 'json', array('groups' => ['default']));

    return JsonResponse::fromJsonString($jsonContent);

很好的发现。我只是试图简要解释问题,而不涉及太多细节。但是,你可以用许多方式连接事物,并最小化以后需要编写的代码量。 - Robert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接