Creating XML documents with the XML serializer

The XMLSerializer package enables you to create complex XML documents without needing to write a lot of lines of code. Instead of creating the document tag by tag, you pass the XMLSerializer any PHP data structure (like an array or any object) and it converts it to XML.

Basic usage

XMLSerializer is using the XMLStreamWriter package to create the XML documents. This means that it can use any XML extension supported by this package to create the XML documents.

To create an XML document, you always have to follow these simple steps:

  1. Create an instance of XMLStreamWriter
  2. Create an instance of XMLSerializer
  3. Pass your data and the stream writer to XMLSerializer
  4. Extract the created XML from the XMLStreamWriter

In code, this means:

stubClassLoader::load('net::stubbles::xml::stubDomXMLStreamWriter',
                      'net::stubbles::xml::serializer::stubXMLSerializer');
$serializer = new stubXMLSerializer();
$writer     = new stubDomXMLStreamWriter();
$data       = 'This is my data';

$serializer->serialize($data, $writer);
print $writer->asXML();

The output of this script is:

<?xml version="1.0" encoding="UTF-8"?>
<string>This is my data</string>

Instead of a string you could also pass an integer, boolean, array or even an object.

Handling of different data types

The serialize() method will accept any data-type that you pass in and will try to convert it to XML. The root tag of the document will depend on the type of the data; the following table will give you a short overview of how XMLSerializer handles the different types.

TypeRoot tag nameExample XML
NULL-valuenullA null value will always be serialized to an empty <null/> tag.
booleanbooleanA boolean value will be serialized to <boolean>true</boolean> or <boolean>true</boolean>
stringstring<string>This is a string</string>
integerinteger<integer>42</integer>
doubledouble<double>42.23</double
arrayarrayAn indexed array will be converted to <array><string>A string value</string><array>; the types will be used as tag names. When serializing an associative, the array keys will be used as tag names
objectName of the classSee below for examples

Options for the serialization

The serialize() method accepts a third, optional parameter, that can be used to specify options for the serialization. To set the options, you need to pass an array to this method, containing option names and their values:

$serializer = new stubXMLSerializer();
$writer     = new stubDomXMLStreamWriter();
$data       = 'This is my data';
$options    = array(
                stubXMLSerializer::OPT_ROOT_TAG => 'root'
              );
$serializer->serialize($data, $writer, $options);
print $writer->asXML();

Now, the script will output:

<?xml version="1.0" encoding="UTF-8"?>
<root>This is my data</root>

Currently, the following options are available:

OptionDescriptionDefault
OPT_ROOT_TAGDefines the name of the root tag for the documentnull, which results in autodetection
OPT_STRATEGYDefines the overall strategy for serializing objects. Possible values are stubXMLSerializer::STRATEGY_NONE, stubXMLSerializer::STRATEGY_PROPS, stubXMLSerializer::STRATEGY_METHODS and stubXMLSerializer::STRATEGY_ALL. The strategy can also be set using the @XMLStrategy annotation.stubXMLSerializer::STRATEGY_ALL

Serializing objects

Serializing objects to XML works exactly the same way as serializing any other data structure:

class MyClass {
    private $ignore = 'ignore';
    public $foo = 'foo';

    public function getBar() {
        return 'bar';
    }

    public function setBar($bar) {
        throw new Exception('setBar has been called');
    }
}

$obj = new MyClass();

$serializer = new stubXMLSerializer();
$writer     = new stubDomXMLStreamWriter();

$serializer->serialize($obj, $writer);

When serializing an instance of a class, XMLSerializer will export the following data:

  • All public properties
  • The return values of all public methods, that do not require any parameters. This does not include magic methods like __sleep() or the constructor of the class.

The above example will produce the following XML code:

<?xml version="1.0" encoding="UTF-8"?>
<MyClass>
    <foo>foo</foo>
    <getBar>bar</getBar>
</MyClass>

By default, XMLSerializer will also use the class name as the tag name.

Influence the serialization using annotations

XMLSerializer allows you to influence how it treats objects by adding annotations to your classes.

The @XMLTag annotation

The @XMLTag annotation can be added to any class to override the name of the XML tag that this class will be serialized to. This enables you to override the default behaviour, where the class name is used as a tag name:

/**
 * @XMLTag(tagName='user')
 */
class A_Long_User_Class {
    // ...
}

This enables you to choose any classname whithout forcing you use the same XML tag names.

You may also attach this annotation to any public property or Getter-method to define the name of the tag that will be used when serializing the property or return value:

/**
 * @XMLTag(tagName='user')
 */
class A_Long_User_Class {
    /**
     * @XMLTag(tagName='id')
     */
     public $userId;

    /**
     * @XMLTag(tagName='name')
     */
     public function getRealname() {
         // ...
     }
}

When serializing an instance of this class, XMLSerializer will produce the following XML:

<user>
  <id>schst</id>
  <name>Stephan Schmidt</name>
</user>

Without the annotation, the XML would be:

<A_Long_User_Class>
  <userId>schst</userId>
  <getRealnameStephan Schmidt</getRealname>
</A_Long_User_Class>

Serializing array properties

If a property contains an indexed array, the @XMLTag annotation can also be used to override the name of the tag that will be used for the elements in the indexed array:

/**
 * @XMLTag(tagName='user')
 */
class A_Long_User_Class {
    /**
     * @XMLTag(tagName='groups',elementTagName='groupId')
     */
     public $groups = array('users', 'admins');
}

An instance of this class will be serialized to:

<user>
  <groups>
    <group>users</group>
    <group>admins</group>
  </groups>
</user>

Without the elementTagName property, the same object would be serialized to:

<user>
  <groups>
    <string>users</string>
    <string>admins</string>
  </groups>
</user>

If you do not want to create a container tag, this can be accomplished by setting the tagName property to false:

/**
 * @XMLTag(tagName='user')
 */
class A_Long_User_Class {
    /**
     * @XMLTag(tagName=false,elementTagName='groupId')
     */
     public $groups = array('users', 'admins');
}

This will result in:

<user>
  <group>users</group>
  <group>admins</group>
</user>

The @XMLAttribute annotation

The @XMLAttribute annotation can be used to tell XMLSerializer that a scalar property must not be serialized as an XML tag, but an attribute of the current open tag:

/**
 * @XMLTag(tagName='user')
 */
class A_Long_User_Class {
    /**
     * @XMLAttribute(attributeName='id')
     */
     public $userId;

    /**
     * @XMLAttribute(attributeName='name')
     */
     public function getRealname() {
         // ...
     }
}

If you pass an instance of this class to XMLSerializer, it will produce XML like this:

<user id="schst" name="Stephan Schmidt"/>

This annotation cannot be applied to classes, as objects are no scalar types. By default, empty properties or method return values are not serialized to empty attributes. However, this can be changed by setting the skipEmpty property to false;

The @XMLFragment annotation

The @XMLFragement annotation is used to import an XML string, that is stored in a property or computed by a method into the generated XML document. This can be useful if you need to import XHTML or any other XML dialect, that is not created from an object structure into your document:

/**
 * @XMLTag(tagName='entry')
 */
class BlogEntry {
    /**
     * @XMLAttribute(attributeName='title');
     */
    public $title = 'Stubbles 0.1.0 released.';

    /**
     * @XMLFragement(tagName='content');
     */
    public $content = '<a href="http://www.stubbles.net">Stubbles 0.1.0</a> has been released today';
}

If you serialize an instance of this class to XML, you will get the following document:

<entry title="Stubbles 0.1.0 released.">
  <content>
    <a href="http://www.stubbles.net">
      Stubbles 0.1.0
    </a>
    has been released today
  <content>
</entry>

The value of the $content property is not treated as a string, but as a part of the XML document. If you do not want a container tag for the fragment, set the tagName property to false.

The @XMLIgnore annotation

By default, XMLSerializer will export all public properties and all public methods that do not require any parameters. If you do not want to export a property or method return value, you may attach the @XMLIgnore annotation:

/**
 * @XMLTag(tagName='user')
 */
class A_Long_User_Class {
    /**
     * @XMLAttribute(attributeName='id')
     */
     public $userId;

    /**
     * We do not want to export the e-mail address to XML
     * 
     * @XMLIgnore
     */
     public $email
}

When serializing an instance of this class, it will produce:

<user id="schst"/>

If you omit the @XMLIgnore annotation, the e-mail address will be exported as XML tag:

<user id="schst">
  <email>foo@bar.com</email>
</user>

The @XMLStrategy annotation

Using the @XMLStrategy annotation, you can define, whether XMLSerializer should serialize methods or properties that have not been annotated. To serialize only public properties of a class, you can use this annotation:

/**
 * @XMLStrategy(stubXMLSerializer::STRATEGY_PROPS)
 */
class User {
    public $id = 12;
    public $name = 'schst';
    public function getEmail() {
        return "me@example.com";
    }
}

If you serialize an instance of this class, XMLSerializer will only serialize public properties and ignore the public methods. The resulting XML document is:

<User>
  <id>12</id>
  <name>schst</name>
</User>

The following strategies are available:

strategydescription
stubXMLSerializer::STRATEGY_NONEDo not serialize any properties or methods, that are not annotated
stubXMLSerializer::STRATEGY_PROPSDo not serialize methods, that are not annotated
stubXMLSerializer::STRATEGY_METHODSDo not serialize properties, that are not annotated
stubXMLSerializer::STRATEGY_ALLSerialize any public properties and methods, even if they are not annotated

The @XMLProperties annotation

The @XMLProperties can be used to define, which properties of a class should be exported. This annotation is only an interface that requires you to implement the following methods:

interface stubXMLPropertiesAnnotation {
    public function getTagnameForProperty(stubReflectionProperty $property, $propertyValue);
}

Stubbles currently provides one implementation of this interface, the @XMLMatcher. This implementation allows you to specify a regular expression, that will be used to determine, whether a property will be exported as an XML tag:

/**
 * @XMLProperties[XMLMatcher(pattern='/^([a-zA-Z]{3})$/')
 */
class User {
    public $foo = 'foo';
    public $bar = 'bar';
    public $fooBar = 'fooBar';
}

When serializing an instance of this class, only the properties $foo and $bar will be exported to XML. The $fooBar property will not be exported, as it does not match the defined pattern.

You may use brackets in your regular expression to mark a sub-pattern, which will be used as the name of the XML tag.

The @XMLMethods annotation

The @XMLMethods is the counterpart to the XMLProperties annotations. It can be used to batch define which methods will be exported to XML. Like @XMLProperties this annotation is only an interface:

interface stubXMLMethodsAnnotation {
    public function getTagnameForMethod(stubReflectionMethod $method, $returnValue);
}

You may also use the @XMLMatcher implementation or implement your own solution. When using the @XMLMatcher the syntax is exactly the same as its usage in combination with @XMLProperties:

/**
 * @XMLMethods[XMLMatcher](pattern='/^get(.+)/')
 */
class User {
    public function getId() {
        return 'schst';
    }
    public function doSomething() {
        echo 'This should not happen.';
    }
}

When serializing an instance of this class, only the return value of getId() will be exported. The resulting XML will be:

<user>
  <id>schst</id>
</user>

As you probably already guessed after looking at the XML, the XMLMatcher also lowercases the first char of the tag name.