【问题标题】:Callback on serializer Symfony序列化器 Symfony 的回调
【发布时间】:2016-01-13 22:10:33
【问题描述】:

我正在运行 Symfony 2.7,我正在尝试将对象(Doctrine 实体)输出为 JSON。

当我规范化对象时,我想转换它的一些值。为此,我在the documentation 中找到了“setCallbacks”方法,但我对如何将其应用到我的案例中感到有些困惑。

有什么方法可以在调用 Symfonys 序列化服务时设置的规范化器上调用“setCallbacks”方法?

这是我想要实现的一个简短示例:

//ExampleController.php

public function getJSONOrderByIdAction($id) {
    $serializer = $this->get('serializer');
    $normalizer = $serializer->getNormalizer(); // <- This is what I'm unable to do

    $dateTimeToString = function ($dateTime) {
        return $dateTime instanceof \DateTime ? $dateTime->format(\DateTime::ISO8601) : '';
    };

    $normalizer->setCallbacks(['time' => $dateTimeToString]);


    $order = $this->getDoctrine()->find("AppBundle:Order", $id);

    return new JsonResponse(["order" => $serializer->normalize($order, null, ["groups" => ["public"]])]);
}

我知道大多数人已经切换到 JMS 序列化程序。似乎内置的序列化程序应该能够处理我想要实现的目标。

【问题讨论】:

    标签: php symfony serialization


    【解决方案1】:

    默认的 Serializer 服务是在依赖注入阶段创建的,并且 Serializer 接口不允许编辑(完整)检索规范化器。

    我认为您在这里(至少)有三个选择:

    1. 将您的自定义规范器添加到默认的序列化器服务
    2. 为您的实体添加 NormalizableInterface
    3. 按照您的尝试创建一个新的 Serializer 服务(或文档建议的本地对象)。

    我认为在您的场景中,案例 1 是首选(因为 2 很快就会变得无聊)。

    我会做这样的事情;首先创建一个自定义 Normalizer

    <?php
    namespace AppBundle; 
    
    class DateTimeNormalizer extends SerializerAwareNormalizer implements NormalizerInterface, DenormalizerInterface
    {
        /**
         * {@inheritdoc}
         */
        public function normalize($object, $format = null, array $context = array())
        {
            return $object->format(\DateTime::ISO8601);
        }
    
        /**
         * {@inheritdoc}
         */
        public function denormalize($data, $class, $format = null, array $context = array())
        {
            return new $class($data);
        }
    
        /**
         * Checks if the given class is a DateTime.
         *
         * @param mixed  $data   Data to normalize.
         * @param string $format The format being (de-)serialized from or into.
         *
         * @return bool
         */
        public function supportsNormalization($data, $format = null)
        {
            return $data instanceof \DateTime;
        }
    
        /**
         * Checks if the given class is a DateTime.
         *
         * @param mixed  $data   Data to denormalize from.
         * @param string $type   The class to which the data should be denormalized.
         * @param string $format The format being deserialized from.
         *
         * @return bool
         */
        public function supportsDenormalization($data, $type, $format = null)
        {
            $class = new \ReflectionClass($type);
    
            return $class->isSubclassOf('\DateTime');
        }
    }
    

    然后将其注册到您的服务中:

    # app/config/services.yml
    services:
        datetime_normalizer:
            class: AppBundle\DateTimeNormalizer
            tags:
                - { name: serializer.normalizer }
    

    【讨论】:

    • 我没有测试过代码,但应该可以。自己尝试一下,如果需要的话给我一些反馈;我会为社区更新答案
    • 这看起来可能是要走的路。 DateTime 示例正是他们在 Symfonys 文档中使用的示例——但是假设我的订单有一系列产品,我也需要对此做一些事情。我会创建一个 ProductNormalizer 还是需要以某种方式处理该集合?另外,我不完全确定如何将新的规范器应用于 Symfony 服务?我认为仅仅创建服务是不够的,并且序列化程序似乎没有“addNormalizer”方法。如果其中一些应该是给定的,我很抱歉。这对我来说是一个全新的领域。
    • @Daniel 默认规范化过程最好提供默认实现,但默认通常会转化为丑陋。没有“addNormalizer”方法(出于某种充分的理由),但标签“serializer.normalizer”可供您使用。您可以拥有一个通用的MyTweeksToNormalizer 类来处理多个类,但这可能会随着您的使用而变得复杂。作为替代方案,您可以对所有实体实施 NormalizableInterface。作为更好的解决方案,您可以切换到 jms-serializer 并受益于配置驱动的序列化。
    • 将 NormalizableInterface 应用于所有实体不是一个选项,因为我需要能够以不同数量的方式规范化同一个对象。我开始认为 JMS 序列化程序确实是要走的路。感谢您的详细解答。
    • @Daniel “对象内”总是快速简单,但灵活性较差。 JMS 序列化程序具有“序列化上下文”。通过使用不同的上下文,您可以在不同的情况下选择序列化的方式和内容;这一切都归结为写$serizalizer-&gt;serialize($object, 'my-context')。无论如何,不​​客气!只要记得 +1 或接受,如果它有帮助 ;)
    【解决方案2】:

    我自己的解决方案

    按照 giosh94mhz 的建议,我尝试切换到 JMS 序列化器,但最终又回到了 Symfonys 序列化器。

    JMS Serializer 提出了它自己的问题,在为那些寻找答案的过程中,我偶然发现了 blog post by Thomas Jarrand,它很好地解释了如何在 Symfony 中实现您自己的规范化器。

    【讨论】:

      【解决方案3】:

      您可以使用 K. Dunglas 组件的回调规范器。

      你可以在 ObjectNormalizer 中看到(在 normalize 方法中)

      if (isset($this->callbacks[$attribute])) {
          $attributeValue = call_user_func($this->callbacks[$attribute], $attributeValue);
       }
      

      这意味着您必须在回调数组键中使用您不想规范化的属性名称。

      例如,在我的实体中,我有一个名为“名称”的字段,类型为“pgarray”(如 postgresql 的数组)。我不想规范化这些数据。我想要一个字符串而不是数组。

      /**
           * $object represent the property "name" because callback is attached to name property (setCallback)
           */
          $nameCallback = function ($object, $outerObject = null) {
              return $object[0];      
          };
          $this->normalizer->setCallbacks(['name' => $dateCallback]);
      

      请记住,从 Symfony 4.2 开始,您必须在 DI 中使用 $context 才能使用回调。

      【讨论】:

        【解决方案4】:

        在我看来,您似乎试图使事情变得过于复杂。这是我需要将实体序列化为 JSON 时采用的方法:

        PHP 2.5 及更高版本允许您在对象上实现jsonSerialize 方法,只需直接在对象上调用json_encode

        如果您仍在使用 PHP 2.4,您只需在您的对象上手动调用 jsonSerialize()

        例如:

        /**
         * @ORM\Entity
         */
        class MyEntity {
            ...
            public function jsonSerialize() {
                $data = array("foo" => $this->bar());
                // add other data here ...
                return $data
            }
        }
        

        然后在调用代码中:

        // for PHP 2.5 and up:
        $normalized = json_encode($myEntityInstance);
        
        // for PHP 2.4 and below
        $normalized = json_encode($myEntityInstance->jsonSerialize());
        

        【讨论】:

        • 我希望我的实体尽可能地模块化。当前的控制器动作必须以某种方式转换对象,这在其他情况下是不需要的。
        • 哦,我明白了。是的,你不想用非标准的序列化来弄乱 Entity 类。
        猜你喜欢
        • 1970-01-01
        • 2017-02-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-09-27
        • 1970-01-01
        • 1970-01-01
        • 2021-09-02
        相关资源
        最近更新 更多