A Composer library that provides an additional XML source for use with
the popular cuyz/valinor
library.
This allows to easily map XML files or contents to any signature supported
by Valinor, e.g. objects or special array shapes. It leverages the
mtownsend/xml-to-array
library to convert raw XML to a reusable array structure.
composer require eliashaeussler/valinor-xml
Given the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<person>
<name>Dr. Zane Stroman</name>
<address>
<street>439 Karley Loaf</street>
<postcode>17916</postcode>
<city>West Judge</city>
<country>Falkland Islands (Malvinas)</country>
</address>
<contact>
<phone>827-986-5852</phone>
</contact>
</person>
These are the resulting classes:
final readonly class Address
{
public function __construct(
public string $street,
public string $postcode,
public string $city,
public string $country,
) {}
}
final readonly class Contact
{
public function __construct(
public string $phone,
) {}
}
final readonly class Person
{
public function __construct(
public string $name,
public Address $address,
public Contact $contact,
) {}
}
In order to map the given XML to the Person
class, you need
to follow these three steps:
- Create a new mapper as written in Valinor's documentation
- Parse and prepare your XML using the shipped
XmlSource
- Use the mapper to map your XML to the
Person
class
use CuyZ\Valinor;
use EliasHaeussler\ValinorXml;
$mapper = (new Valinor\MapperBuilder())->mapper();
$source = ValinorXml\Mapper\Source\XmlSource::fromXmlString($xml);
$person = $mapper->map(Person::class, $source); // instanceof Person
The resulting object will look something like this:
object(Person)#180 (3) {
["name"]=>
string(16) "Dr. Zane Stroman"
["address"]=>
object(Address)#135 (4) {
["street"]=>
string(15) "439 Karley Loaf"
["postcode"]=>
string(5) "17916"
["city"]=>
string(10) "West Judge"
["country"]=>
string(27) "Falkland Islands (Malvinas)"
}
["contact"]=>
object(Contact)#205 (1) {
["phone"]=>
string(12) "827-986-5852"
}
}
The XML can also be read from an external file:
use CuyZ\Valinor;
use EliasHaeussler\ValinorXml;
$mapper = (new Valinor\MapperBuilder())->mapper();
$source = ValinorXml\Mapper\Source\XmlSource::fromXmlFile($file);
$person = $mapper->map(Person::class, $source); // instanceof Person
Sometimes it might be necessary to always convert XML nodes to collections. Given the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<community>
<member><!-- ... --></member> <!-- NOTE: There's only one member -->
</community>
Let's assume you want to map this XML to the following class:
final readonly class Community
{
/**
* @param list<Person> $member
*/
public function __construct(
public array $member,
) {}
}
You will recognize that this does not work as expected when using the above mapping method:
use CuyZ\Valinor;
use EliasHaeussler\ValinorXml;
$mapper = (new Valinor\MapperBuilder())->mapper();
$source = ValinorXml\Mapper\Source\XmlSource::fromXmlFile($file);
$person = $mapper->map(Community::class, $source); // throws exception
It will instead throw an exception like this:
CuyZ\Valinor\Mapper\TypeTreeMapperError: Could not map type `Community` with value array{member: array{…}}.
This is because the XML converter does not know whether <member>
should
be a collection or if it's just a "normal" node. That's why the XmlSource
provides an appropriate method to convert such nodes to collections:
use CuyZ\Valinor;
use EliasHaeussler\ValinorXml;
$mapper = (new Valinor\MapperBuilder())->mapper();
$source = ValinorXml\Mapper\Source\XmlSource::fromXmlFile($file)
->asCollection('member')
;
$person = $mapper->map(Community::class, $source); // instanceof Community
The resulting object will look something like this:
object(Community)#76 (1) {
["member"]=>
array(1) {
[0]=>
object(Person)#126 (3) {
["name"]=>
string(16) "Dr. Zane Stroman"
["address"]=>
object(Address)#170 (4) {
["street"]=>
string(15) "439 Karley Loaf"
["postcode"]=>
string(5) "17916"
["city"]=>
string(10) "West Judge"
["country"]=>
string(27) "Falkland Islands (Malvinas)"
}
["contact"]=>
object(Contact)#252 (1) {
["phone"]=>
string(12) "827-986-5852"
}
}
}
}
However, this is only relevant if only one node of the collection exists in your XML. If the XML contains more than one node, the XML converter properly converts them to a collection:
use CuyZ\Valinor;
use EliasHaeussler\ValinorXml;
$xml = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<community>
<member><!-- ... --></member>
<member><!-- ... --></member>
<member><!-- ... --></member>
</community>
XML;
$mapper = (new Valinor\MapperBuilder())->mapper();
$source = ValinorXml\Mapper\Source\XmlSource::fromXmlString($xml);
$person = $mapper->map(Community::class, $source); // instanceof Community
Please have a look at CONTRIBUTING.md
.
This project is licensed under GNU General Public License 3.0 (or later).