Symfony Serializer десериализует XML, который содержит элемент с переменным количеством подэлементов

При десериализации XML-файла для объекта с использованием компонента Symfony Serializer возникает проблема, когда элемент в XML может содержать несколько элементов, но в этом случае содержит только один.

XML содержит несколько элементов, которые могут содержать ноль или более подэлементов. Например, «офисы» могут содержать ноль или более элементов «офис». Я представил пример кода, который упрощает проблему, с которой я столкнулся в реальности. См. Особенности вопроса ниже.

Пример кода:

sample.xml

<?xml version="1.0"?>
<buildings>
<restaurants>
<restaurant>
<name>Some restaurant name</name>
<type>Chinese</type>
</restaurant>
<restaurant>
<name>Another restaurant name</name>
<type>Italian</type>
</restaurant>
</restaurants>
<offices>
<office>
<company>Some company</company>
<owner>John</owner>
</office>
</offices>
</buildings>

serialize.php

<?php

use App\Entities\Buildings;
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
use Symfony\Component\Serializer\Serializer;

require 'vendor/autoload.php';

$xml = file_get_contents(__DIR__ . '/sample.xml');

$serializer = new Serializer(
[
new ArrayDenormalizer(),
new ObjectNormalizer(null, null, null, new PhpDocExtractor())
],
[new XmlEncoder()]
);

$data = $serializer->deserialize($xml, Buildings::class, 'xml');

var_dump($data);

Buildings.php

<?php

namespace App\Entities;

class Buildings
{
/**
* @var Restaurants
*/
private $restaurants;

/**
* @var Offices
*/
private $offices;

/**
* @return Restaurants
*/
public function getRestaurants(): Restaurants
{
return $this->restaurants;
}

/**
* @param Restaurants $restaurants
*/
public function setRestaurants(Restaurants $restaurants): void
{
$this->restaurants = $restaurants;
}

/**
* @return Offices
*/
public function getOffices(): Offices
{
return $this->offices;
}

/**
* @param Offices $offices
*/
public function setOffices(Offices $offices): void
{
$this->offices = $offices;
}
}

Restaurants.php

<?php

namespace App\Entities;

class Restaurants
{
/**
* @var Restaurant[]
*/
private $restaurant;

/**
* @return Restaurant[]
*/
public function getRestaurant(): array
{
return $this->restaurant;
}

/**
* @param Restaurant[] $restaurant
*/
public function setRestaurant(array $restaurant): void
{
$this->restaurant = $restaurant;
}
}

Restaurant.php

<?php

namespace App\Entities;

class Restaurant
{
/**
* @var string
*/
private $name;

/**
* @var string
*/
private $type;

/**
* @return string
*/
public function getName(): string
{
return $this->name;
}

/**
* @param string $name
*/
public function setName(string $name): void
{
$this->name = $name;
}

/**
* @return string
*/
public function getType(): string
{
return $this->type;
}

/**
* @param string $type
*/
public function setType(string $type): void
{
$this->type = $type;
}
}

Offices.php

<?php

namespace App\Entities;

class Offices
{
/**
* @var Office[]
*/
private $office;

/**
* @return Office[]
*/
public function getOffice()
{
return $this->office;
}

/**
* @param Office[] $office
*/
public function setOffice($office): void
{
$this->office = $office;
}
}

Office.php

<?php

namespace App\Entities;

class Office
{
/**
* @var string
*/
private $company;

/**
* @var string
*/
private $owner;

/**
* @return string
*/
public function getCompany(): string
{
return $this->company;
}

/**
* @param string $company
*/
public function setCompany(string $company): void
{
$this->company = $company;
}

/**
* @return string
*/
public function getOwner(): string
{
return $this->owner;
}

/**
* @param string $owner
*/
public function setOwner(string $owner): void
{
$this->owner = $owner;
}
}

При запуске serialize.php с sample.xml, как это предусмотрено, возникает следующая ошибка:

Fatal error: Uncaught Symfony\Component\Serializer\Exception\NotNormalizableValueException: The type of the key "company" must be "int" ("string" given). in /path/to/project/vendor/symfony/serializer/Normalizer/ArrayDenormalizer.php:57
Stack trace:
#0 /path/to/project/vendor/symfony/serializer/Serializer.php(172): Symfony\Component\Serializer\Normalizer\ArrayDenormalizer->denormalize(Array, 'App\\Entities\\Of...', 'xml', Array)
#1 /path/to/project/vendor/symfony/serializer/Normalizer/AbstractObjectNormalizer.php(271): Symfony\Component\Serializer\Serializer->denormalize(Array, 'App\\Entities\\Of...', 'xml', Array)
#2 /path/to/project/vendor/symfony/serializer/Normalizer/AbstractObjectNormalizer.php(202): Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer->validateAndDenormalize('App\\Entities\\Of...', 'office', Array, 'xml', Array)
#3 /path/to/project/vendor/symfony/serializer/Serializer.ph in /path/to/project/vendor/symfony/serializer/Normalizer/ArrayDenormalizer.php on line 57
PHP Fatal error:  Uncaught Symfony\Component\Serializer\Exception\NotNormalizableValueException: The type of the key "company" must be "int" ("string" given). in /path/to/project/vendor/symfony/serializer/Normalizer/ArrayDenormalizer.php:57
Stack trace:
#0 /path/to/project/vendor/symfony/serializer/Serializer.php(172): Symfony\Component\Serializer\Normalizer\ArrayDenormalizer->denormalize(Array, 'App\\Entities\\Of...', 'xml', Array)
#1 /path/to/project/vendor/symfony/serializer/Normalizer/AbstractObjectNormalizer.php(271): Symfony\Component\Serializer\Serializer->denormalize(Array, 'App\\Entities\\Of...', 'xml', Array)
#2 /path/to/project/vendor/symfony/serializer/Normalizer/AbstractObjectNormalizer.php(202): Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer->validateAndDenormalize('App\\Entities\\Of...', 'office', Array, 'xml', Array)
#3 /path/to/project/vendor/symfony/serializer/Serializer.ph in /path/to/project/vendor/symfony/serializer/Normalizer/ArrayDenormalizer.php on line 57

Process finished with exit code 255

Эта ошибка возникает из-за того, что в ArrayDenormalizer элемент Office в элементе Offices конвертируется в один элемент, а не в массив элементов. И поскольку PHPDoc указывает, что setOffice () в Offices.php ожидает массив Office, он ожидает, что значения ключа будут целыми числами.

Массив ArrayDenormalizer преобразует xml в:

array(2) {
[0]=>
array(2) {
["name"]=>
string(20) "Some restaurant name"["type"]=>
string(7) "Chinese"}
[1]=>
array(2) {
["name"]=>
string(23) "Another restaurant name"["type"]=>
string(7) "Italian"}
}
array(2) {
["company"]=>
string(12) "Some company"["owner"]=>
string(4) "John"}

Я ожидаю, что PHPDocExtractor и ObjectNormalizer поймут, что, хотя в массиве компаний есть только один элемент, он все равно должен быть массивом объектов, потому что я объявил это таким образом в PHPDoc в Offices.php.

Если я изменю PHPDoc в Offices.php на Office, а не на Office [], я получу один экземпляр Office, который работает нормально. Если я добавлю еще один элемент office в XML, два элемента Office преобразуются в объекты Office и добавляются в офисы в массиве, как и ожидалось. Это также работает для ресторанов.

Конечно, ни один из них не является решением моей проблемы, потому что это допустимый сценарий, когда в элементе офисов есть только один элемент office. То же самое касается ресторанов. Если я удалю один элемент Restaurant из элемента Restaurants в файле sample.xml, произойдет та же ошибка.

Я пробовал использовать разные комбинации нормализаторов и экстракторов, таких как ReflectionExtractor, но ни одна из них не достигла того, что мне нужно, что, на мой взгляд, не является необычной ситуацией.

Как я могу убедиться, что Symfony Serializer знает, что независимо от того, является ли он нулем, одним или несколькими элементами в родительском элементе, я всегда ожидаю, что массив объектов будет добавлен к свойству Office в моем объекте Offices.

0

Решение

Задача ещё не решена.

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

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

По вопросам рекламы [email protected]