magicsunday / xmlmapper
Map PHP to XML
Fund package maintenance!
Requires
- php: ^8.3
- ext-dom: *
- ext-xml: *
- doctrine/inflector: ^2.0
- symfony/property-access: ^7.3 || ^8.0
- symfony/property-info: ^7.3 || ^8.0
- symfony/type-info: ^7.3 || ^8.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.65
- overtrue/phplint: ^9.0
- phpdocumentor/reflection-docblock: ^5.0 || ^6.0
- phpstan/phpstan: ^2.0
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- phpunit/phpunit: ^12.0 || ^13.0
- rector/rector: ^2.0
Suggests
- phpdocumentor/reflection-docblock: In order to use the PhpDocExtractor this library is required too.
README
Map strongly-typed PHP objects to XML using Symfony's PropertyInfo, PropertyAccess and TypeInfo components.
📌 Overview
XmlMapper is a PHP library that maps strongly-typed PHP objects (DTOs, value objects, entities) to XML using reflection and PHPDoc annotations. It leverages Symfony's PropertyInfo, PropertyAccess and TypeInfo components to derive each property's type and render a matching XML document.
| Key | Value |
|---|---|
| Package | magicsunday/xmlmapper |
| PHP | ^8.3 |
| Main API | MagicSunday\XmlEncoder |
| Output | XML string (string), or false on failure |
❓ What is this?
XmlMapper takes a PHP object implementing MagicSunday\XmlSerializable and renders it as XML, including nested objects, scalar and object collections, and custom types. Property values are routed to elements, attributes, raw text nodes or CDATA sections via a small set of annotations, and property names can be converted on the fly (e.g. snake_case to camelCase).
🎯 Why does this exist?
Serializing domain objects to XML usually means hand-writing DOMDocument/XMLWriter boilerplate that drifts from the underlying model. XmlMapper derives the XML structure from the object's typed properties and a few annotations, so the output follows the PHP model, with explicit hooks (attributes, CDATA, node values, custom type closures) where you need to deviate.
🚀 Usage
composer require magicsunday/xmlmapper
Quick start
Annotate the classes you want to serialize and let them implement XmlSerializable:
namespace App\Model; use MagicSunday\XmlSerializable; use MagicSunday\XmlMapper\Annotation\XmlAttribute; final class Author implements XmlSerializable { public string $name = 'Jane Doe'; } final class Book implements XmlSerializable { #[XmlAttribute] public string $isbn = '978-3-16-148410-0'; public string $title = 'The Title'; public Author $author; /** * @var string[] */ public array $tags = ['php', 'xml']; }
Build an encoder and map an instance:
require __DIR__ . '/vendor/autoload.php'; use App\Model\Author; use App\Model\Book; use MagicSunday\XmlEncoder; use MagicSunday\XmlMapper\Converter\CamelCasePropertyNameConverter; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\PropertyInfoExtractor; $extractor = new PropertyInfoExtractor( [new ReflectionExtractor()], [new PhpDocExtractor()] ); $book = new Book(); $book->author = new Author(); $encoder = new XmlEncoder($extractor, new CamelCasePropertyNameConverter()); echo $encoder->map($book);
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <book isbn="978-3-16-148410-0"> <title>The Title</title> <author> <name>Jane Doe</name> </author> <tags>php</tags> <tags>xml</tags> </book>
The name converter is optional; without it the raw class and property names are used verbatim. For collections, annotate the property with the phpDocumentor collection type so the value type can be resolved:
/** @var string[] $tags */ /** @var Chapter[] $chapters */ /** @var array<int, Author|Chapter> $members */
Property markers
Each marker is applied as a native PHP attribute (shown above).
| Marker | Effect |
|---|---|
XmlAttribute |
Render the value as an attribute of the surrounding element. |
XmlNodeValue |
Render the value as the raw text content of the surrounding element. |
XmlCDataSection |
Wrap the value in a <![CDATA[ … ]]> section (markup left intact). |
Custom types
Register a closure to transform every value of a given builtin type (bool, int, array, object, …) before it is written:
$encoder->addType('bool', static fn (string $name, mixed $value): string => $value === true ? 'yes' : 'no');
📚 Documentation
- API reference
- Recipes
- Manual instantiation — wiring the Symfony extractor and name converter
- Markers: attributes, node values and CDATA — native attribute syntax
- Custom types — transforming values with
addType() - Custom name converter — element naming
- Collections — scalar, object, nullable and union-typed collections
🛠️ Development
Prerequisites:
- PHP
^8.3 - Extensions:
dom,xml - Node.js (for the copy-paste detection gate, run via
npx)
Install dependencies:
composer install
Run the mandatory quality gate:
composer ci:test
ci:test includes:
- Linting (
phplint) - Unit tests (
phpunit) - Static analysis (
phpstan, max level) - Refactoring dry-run (
rector --dry-run) - Coding standards dry-run (
php-cs-fixer --dry-run) - Copy-paste detection (
jscpd)
🤝 Contributing
Contributions are welcome. Please run the full composer ci:test quality gate before submitting a pull request, and keep changes covered by tests.