【问题标题】:What is a proper way to convert a Doctrine Entity to array/JSON/XML?将 Doctrine Entity 转换为数组/JSON/XML 的正确方法是什么?
【发布时间】:2017-09-06 23:52:37
【问题描述】:

我正在将 Zend Framework 3 应用程序的 Zend\Db 驱动 DBAL 迁移到 Doctrine。一切正常,但现在我在导出数据时遇到了问题。

在迁移之前,它的工作方式如下:

有一个或多或少复杂的数据结构。 Mapper 执行了一些数据库请求,并根据这些数据构建了一个嵌套的DataObject。因此,导出的起点是一个对象,其中填充了所有数据并具有子对象以及它们的所有数据。所以我简单地将其转换为JSON

public function exportToJson(AbstractDataObject $dataObject)
{
    return json_encode($dataObject, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
}

public function exportToXml(AbstractDataObject $dataObject)
{
    $dataObjectVars = json_decode(json_encode($dataObject->jsonSerialize()), true);
    $xml = new \SimpleXMLElement('<' . self::XML_DEFAULT_ROOT_ELEMENT . ' />');
    $this->arrayToXml($dataObjectVars, $xml);
    $domxml = new \DOMDocument('1.0');
    $domxml->preserveWhiteSpace = false;
    $domxml->formatOutput = true;
    $domxml->loadXML($xml->asXML());
    $xmlString = $domxml->saveXML();
    return $xmlString;
}

protected function arrayToXml($array, &$xml)
{
    foreach ($array as $key => $value) {
        if(is_array($value)){
            if(is_int($key)){
                $key = self::XML_DEFAULT_ELEMENT_NAME;
            }
            $label = $xml->addChild($key);
            $this->arrayToXml($value, $label);
        }
        else {
            $xml->addChild($key, $value);
        }
    }
}

所有DataObjects 都扩展了AbstractDataObject,并提供了一种方法,可以轻松导出到JSON

class AbstractDataObject implements \JsonSerializable
{

    public function jsonSerialize()
    {
        $reflection = new \ReflectionClass($this);
        $properties = $reflection->getProperties();
        $members = [];
        foreach ($properties as $property) {
            $property->setAccessible(true);
            $members[$property->getName()] = $property->getValue($this);
        }
        $keys = array_keys($members);
        $values = array_values($members);
        $keysUnderscored = preg_replace_callback('/([A-Z])/', function($matches) {
            return '_' . strtolower($matches[1]);
        }, $keys);
        $varsUnderscored = array_combine($keysUnderscored, $values);
        return $varsUnderscored;
    }

}

现在要导出的对象是一个实体,它通常不会加载所有数据。这意味着,上述方法不再有效。

是否有/将嵌套实体(指具有子实体的实体)转换为结构化数据格式(array/JSON/XML)的正确方法?强>

【问题讨论】:

  • 您可以通过使用查询并在 select 子句中指定所有相关实体来加载实体。这基本上会覆盖延迟加载。或者您可以调整序列化程序以调用 getPropertyName() 而不是直接访问属性。最后,你可以看看 Symfony 序列化器组件,它可能是做这些事情的“官方”方式。现在我想,如果你真的不需要一个对象,那么 $query->getArrayResult() 可能就是你所需要的。
  • @Cerad 非常感谢您的评论!因此,您建议树方式:1. 具有所有依赖项的查询。 ——这会很大,我想避免这种情况。 2. 要“调整序列化程序以调用 getPropertyName() 而不是直接访问属性”。 什么意思? 3. Symfony 序列化组件。我会看看。感谢您的建议! 4. $query-&gt;getArrayResult() -- 这将是一个非常优雅的解决方案,但它不适用于嵌套结构。
  • 考虑使用缺少数据的实体的简单示例来更新您的问题。我认为您在延迟加载时遇到了问题,但我不确定。
  • 跟进@Cerad 评论,您可以创建一个custom hydration mode,它将返回一个包含所有嵌套对象的数组

标签: php arrays json doctrine-orm data-conversion


【解决方案1】:

我终于按照 Cerad 的 commentSymfony Serializer Component 的建议让它工作了。

我在编码方面遇到了一些问题:JSON_ERROR_UTF8 用于 JSON,而“Warning: DOMDocument::saveXML(): invalid character value”用于 XML。所以我必须“utf8ize”从Serializer 接收到的数组数据并使用dom_import_simplexml(...) 重新实现exportToXml(...),如图here 所示。但现在它开始工作了,我们开始吧:

namespace MyNamespace\DataExport;

use MyNamespace\DataObject\AbstractDataObject;
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Doctrine\Common\Annotations\AnnotationReader;
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;

class DataExporter
{

    /** @var string */
    const EXPORT_FORMAT_JSON = 'json';
    /** @var string */
    const EXPORT_FORMAT_XML = 'xml';
    /** @var string */
    const XML_DEFAULT_ROOT_ELEMENT = 'my_root_element_name';
    /** @var string */
    const XML_DEFAULT_ELEMENT_NAME = 'item';

    /** @var Serializer */
    protected $serializer;

    public function __construct()
    {
        $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
        $normalizer = new ObjectNormalizer($classMetadataFactory, new CamelCaseToSnakeCaseNameConverter());
        $normalizer->setCircularReferenceLimit(1);
        $normalizer->setIgnoredAttributes(['__initializer__', '__cloner__', '__isInitialized__']);
        $normalizer->setCircularReferenceHandler(function ($object) {
            // @todo A cleaner solution need.
            try {
                $return = $object->getId();
            } catch (\Error $exception) {
                $return = null;
            }
            $return = null;
            return $return;
        });
        $normalizers = [$normalizer];
        $this->serializer = new Serializer($normalizers);
    }

    public function exportToJson(AbstractDataObject $dataObject)
    {
        $data = $this->serializer->normalize($dataObject, null, ['groups' => ['export']]);
        $data = $this->utf8ize($data);
        return json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
    }

    public function exportToXml(AbstractDataObject $dataObject)
    {
        $data = $this->serializer->normalize($dataObject, null, ['groups' => ['export']]);
        $data = $this->utf8ize($data);
        $xml = new \SimpleXMLElement('<' . self::XML_DEFAULT_ROOT_ELEMENT . ' />');
        $this->arrayToXml($data, $xml);
        $domDocument = dom_import_simplexml($xml)->ownerDocument;
        $domDocument->formatOutput = true;
        $xmlString = $domDocument->saveXML();
        return $xmlString;
    }

    protected function utf8ize($data) {
        if (is_array($data)) {
            foreach ($data as $key => $value) {
                $data[$key] = $this->utf8ize($value);
            }
        } else if (is_string ($data)) {
            return utf8_encode($data);
        }
        return $data;
    }

    /**
     * Converts an $array to XML and
     * saves the result to the $xml argument.
     *
     * @param array $array
     * @param \SimpleXMLElement $xml
     * @return void
     */
    protected function arrayToXml($array, &$xml){
        foreach ($array as $key => $value) {
            if(is_array($value)){
                if(is_int($key)){
                    $key = self::XML_DEFAULT_ELEMENT_NAME;
                }
                $label = $xml->addChild($key);
                $this->arrayToXml($value, $label);
            }
            else {
                $xml->addChild($key, $value);
            }
        }
    }

}

【讨论】:

  • 您可能还想查看JMS Serializer。它将序列化为 json 或 xml。
  • 似乎是一个很好的工具,并且“与 Doctrine ORM 集成”。谢谢你的建议!
  • 是的,已经使用它完成了多个 REST api 项目。很棒的工具,我认为这是 symfony 组件的灵感。也可以很好地与注释一起使用。
猜你喜欢
  • 2014-03-18
  • 1970-01-01
  • 2012-08-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多