如何以编程方式更改Doctrine Entity属性的序列化

时间:2017-04-18 12:38:12

标签: symfony jmsserializerbundle symfony-2.8 jms-serializer

我有一个Dashboard实体被JMSSerializer正确序列化/反序列化(通过JMSSerializerBundle):

/**
 * @ORM\Table(name="dashboard", schema="myappID")
 * @ORM\Entity(repositoryClass="Belka\MyBundle\Entity\Repository\DashboardRepository")
 */
class Dashboard
{
    /**
     * @Id
     * @Column(type="integer")
     * @GeneratedValue("SEQUENCE")
     *
     * @Serializer\Groups({"o-all", "o-all-getCDashboard", "i-p2-editDashboard"})
     */
    protected $id;

    /**
     * @ORM\ManyToMany(targetEntity="Belka\MyBundle\Entity\User")
     *
     * @ORM\JoinTable(name="users_dashboards_associated",
     *      schema="myAppID",
     *      joinColumns={@ORM\JoinColumn(name="dashboard_id", referencedColumnName="id")},
     *      inverseJoinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")}
     *      )
     *
     * @Serializer\groups({
     *     "o-p2-create",
     *     "i-p2-create",
     *     "o-p2-patch",
     *     "i-p2-editDashboard"
     * })
     */
    protected $users;
}

我正在使用JMSSerializer的jms_serializer.doctrine_object_constructor作为对象构造函数。 一切都像魅力一样,但我有以下几个案例:有时我必须将Dashboard::$users设置为一个字符串(即当客户端发送一个语义不正确的users属性时,在我检查后我返回对象以及一个字符串,以便通知它。这对前端应用程序非常方便)。 JMSSerializer takes advantage of the Doctrine's annotation,但在这种情况下我真的想以编程方式覆盖它,因为这是一个非常大的问题。我想到了两个方法:

  1. 有没有办法将SerializationContext设置为将Dashboard::$users映射为字符串属性?
  2. 是否有办法在序列化之前更改Doctrine的元数据?
  3. 我没有意识到的其他选择?
  4. 任何建议都非常受欢迎

1 个答案:

答案 0 :(得分:2)

我找到了一个解决方案,虽然它没有考虑嵌套实体的属性(has-a relations)。这意味着访问整个图表,但我没有时间研究优秀的JMSSSerializer的胆量。它非常适合强制实现第一级实体的属性:

首先,需要pre-serialize订阅者。它将遍历受保护的属性并检查它们是否包含字符串。是这样,序列化的类型将被覆盖。

class SerializationSubscriber implements EventSubscriberInterface
{

    /**
     * @inheritdoc
     */
    static public function getSubscribedEvents()
    {
        return array(
            array('event' => 'serializer.pre_serialize', 'method' => 'onPreserialize'),
        );
    }

    public function onPreSerialize(PreSerializeEvent $event)
    {
        $entity = $event->getObject();
        $metadata = $event->getContext()->getMetadataFactory()->getMetadataForClass($event->getType()['name']);
        $reflect = new \ReflectionClass($entity);
        $props = $reflect->getProperties(\ReflectionProperty::IS_PROTECTED);

        foreach ($props as $prop) {
            $prop->setAccessible(true);

            if (is_string($prop->getValue($entity))) {
                // here is the magic
                $metadata->propertyMetadata[$prop->name]->type = array('name' => 'string', 'params' => array());
            }
        }
    }
}

接下来,每次我序列化某些内容时,我都不想听这个。这是我的一项服务中的一个极端案例。尽管JMS\Serializer\EventDispatcher\EventDispatcher::addSubscriber服务已声明为EventDispatcher,但我们可以利用private。 因此,让我们通过编译器传递将该服务转换为public,以便利用addSubscriber

class MyBundle extends Bundle
{
    public function build(ContainerBuilder $container)
    {
        parent::build($container);

        $container->addCompilerPass(new OverrideJmsSerializerEventDispatcherDefPass());
    }
}

...让我们将该服务转换为public

class OverrideJmsSerializerEventDispatcherDefPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        $definition = $container->getDefinition('jms_serializer.event_dispatcher');
        $definition->setPublic(true);
    }
}

因此,我们可以将其注入我们的服务中。即在我的services.yml

  belka.mybundle.dashboardhandler:
      class: Belka\MyBundle\Handlers\DashboardHandler
      calls:
          - [setEventDispatcher, ["@jms_serializer.event_dispatcher"]]

好的,现在我们可以在需要的时候轻松添加我们的用户,每次我的应用程序执行序列化时都没有其他监听器的负担:

$serializationSubscriber = new SerializationSubscriber();
$this->eventDispatcher->addSubscriber($serializationSubscriber);

随意使用访问整个实体的解决方案来完成答案。图形。那会很棒。