Как правильно преобразовать Doctrine Entity в массив / JSON / XML?

Я переношу Zend\Db управляемый DBAL приложения Zend Framework 3 для 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);
}
}
}

Все DataObjectс продлен 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)?

0

Решение

Наконец-то у меня все работает так, как предлагалось в Cerad’s комментарий с Symfony Serializer Component.

У меня возникли проблемы с кодировкой: JSON_ERROR_UTF8 для JSON и «Warning: DOMDocument::saveXML(): invalid character value«Для XML. Так что мне пришлось»utf8ize«массив данных, полученных от Serializer и переопределить exportToXml(...) используя dom_import_simplexml(...) как показано Вот. Но теперь это работает, здесь мы идем:

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);
}
}
}

}
0

Другие решения

Других решений пока нет …