Web:Extend

Blog » Extending SimpleXML

PHP5 introduced the SimpleXML extension. It allows for easy manipulation of XML documents. This nice extension received a warm welcome in the community. But it unfortunately has its drawbacks.

The Problem

SimpleXML uses some little hacks to do its tricks. It overloads the -> and [] operators. What that means is that if you extend SimpleXML and create a property in the child class, you will never be able to access this property, because SimpleXML will return a child node and ignore your property. You cannot either use the ArrayAccess interface to store your properties since SimpleXML already overload the [] operator to allow you to access the node’s attributes.

Why would you extend SimpleXML if you can’t set any property? Well you can of course add a few methods, to add missing functionality for example. But that’s about it. No properties. If you need to associate a SimpleXML node with a variable you must either create a child node, or create an attribute. Problem is, it won’t work with objects, arrays or resources.

Someone already tried to do it. Sorry Oliver Brown, but I don’t like your solution. I shall thank you however, since you allowed me to find a better way to do it. I’m sorry about not telling the world sooner about all this.

The Solution

The solution is currently available in the LGPL licensed Web:Extend framework, in the form of the weeSimpleXMLHack class. Although LGPL licensed, I (the original author) allow you to copy and paste this specific class in your project whatever the license is. In return, please just refer to this post in your code.

For your convenience I will paste the code below.

/**
	Extends SimpleXMLIterator to allow the setting and getting of properties.
	Properties must be accessed using the function property since -> is already used by SimpleXMLIterator
	(and its parent class SimpleXMLElement).
*/

class weeSimpleXMLHack extends SimpleXMLIterator
{
	/**
		Return the uniqid string for this object.

		@return The uniqid string.
	*/

	protected function hack()
	{
		if (empty($this['weeuniqidhack']))
			$this['weeuniqidhack'] = uniqid();
		return (string)$this['weeuniqidhack'];
	}

	/**
		Set or get a property of a SimpleXMLIterator object.

		@param	$sName	The name of the property.
		@param	$mParam The variable to attach to this element. If null it is not attached.
		@return	mixed	The variable attached with the specified name.
	*/

	public function property($sName, $mParam = null)
	{
		static $aProperties = null;

		if (!is_null($mParam))
			$aProperties[$this->hack()][$sName] = $mParam;

		if (!array_key_exists($sName, $aProperties[$this->hack()]))
			throw new IllegalStateException("The property doesn't exist.");

		return $aProperties[$this->hack()][$sName];
	}
}

If you compare this code and the original code from the framework you will notice a few differences. I’ve edited it a bit to make it more readable for people that do not know how the fire function works.

So how does it work? Well as you know you can create a method. In this method you can create a static variable that will be accessible only by the code in the method. Make this variable an array of properties. By making the method usable both as a getter and a setter you can set and get properties from this static array.

But that’s not enough. As you might already know, a static variable declared in a method will keep the same value for all the objects of the class. That is, if object #1 sets a static variable value to 1, then object #2 sets it to 2, it will have a value of 2 when object #1 tries to access it again. We do not want that since we want to be able to associate objects and more to our SimpleXML objects.

That’s where uniqid comes. uniqid creates a completely unique identifier. Great! We can now assign an unique identifier to each of our SimpleXML objects, and use that identifier to retrieve the properties of that object. We only call uniqid once and store it as a reserved attribute named weeuniqidhack. When the property method is called we can just get that identifier and do a get or a set on our static array without fearing conflicts with other SimpleXML objects properties.

You can now use this class by calling simplexml_load_string or simplexml_load_file and giving the class name, 'weeSimpleXMLHack', as the second parameter:

$oNode = simplexml_load_file('/path/to/my/file.xml', 'weeSimpleXMLHack');

This hack was used for more than a year in the Web:Extend framework and worked since the early releases of PHP5, so you can use it without any fear. The only drawback is that an attribute must be sacrificed to store the unique identifier.

I have other tips to share but they will be for another day. You might find some by browsing the framework’s code.

Thanks for reading.

February 20th, 2008 by Loïc Hoguin · Tags: PHP5, Tips, Web:Extend

9 Comments

  1. Very SimpleXML - PHPUGFFM - PHP User Group Frankfurt am Main Says:

    [...] 2: Extending SimpleXML (Loïc Hoguin, [...]

  2. nhm tanveer hossain khan(hasan) Says:

    bit curious, what is the advantage of having class name in lower case ?
    and why didn’t you use php unit or other popular php unit testing framework ?

  3. information Says:

    Excellent function. It saved my day. Thanks!

  4. Loïc Hoguin Says:

    Hello, I’m like 6 months late in replying to comments but I didn’t see them filtered. There’s probably a problem with the mails that I’ll have to fix or something…

    I’ll just point out that the class name is a convention we decided and leave it at that. We prefix the classes with “wee” to protect from conflicts with other libraries.

    We did not use the PHPUnit framework for various reasons, some of which I’ll explain in a post soon. This is both a good thing and a bad thing. The good thing is to not have to write a lot of code that isn’t related to the tests themselves. We (and probably a lot of people) are pretty lazy so writing tests should be as simple as possible, eliminating all the annoying syntax. We intend to put a lot of tests, we already have hundreds, we need thousands to make sure the framework works as intended. It’s a lot of work, and we reduced the amount of work by a good chunk by writing this module. Note that it is designed specifically for our framework’s needs and is light enough to be redistributed with it, allowing anyone to just “make test” to check if it’ll work correctly without having to install PHPUnit or to redistribute it (it’s quite big). However, if you need to test your applications, then please use PHPUnit, as you probably already know it’s a great unit testing framework.

  5. niedziedz Says:

    In one word: LOL. Very low.

  6. How to Get Six Pack Fast Says:

    My friend on Orkut shared this link with me and I’m not dissapointed that I came here.

  7. Jane Goody Says:

    Not that I’m impressed a lot, but this is more than I expected for when I found a link on Digg telling that the info is awesome. Thanks.

  8. Nika Jones Says:

    One additional thing you could do is namespace the unique id attribute so that it will *never* overwrite an attribute with the same name (even though “weeuniqueidhack” is probably not going to be stumbled upon soon.)

  9. quazardous Says:

    have done a little mod to this hack because i wanted that all the xml dom can share the properties

    static protected $_propertyHackIdAttributeName='property:id';

    /**
    * Return the uniqid string for this object.
    * SimpleXmlElement extend classes cannot have normal properties.
    *
    * @return string
    */
    protected function getPropertyHackId()
    {
    $top=parent::xpath('/*');
    $top=$top[0];
    if (empty($top[self::$_propertyHackIdAttributeName]))
    {
    $top[self::$_propertyHackIdAttributeName] = uniqid();
    }
    return (string)$top[self::$_propertyHackIdAttributeName];
    }

    static protected $_properties=array();

    /**
    Set a property of a SimpleXMLIterator object.

    @param $name The name of the property.
    @param $value The variable to attach to this element. If null it is not attached.
    @return mixed The variable attached with the specified name.
    */
    public function setProperty($name, $value)
    {
    if (!isset(self::$_properties[$this->getPropertyHackId()]))
    {
    self::$_properties[$this->getPropertyHackId()] = array();
    }
    self::$_properties[$this->getPropertyHackId()][$name]=$value;
    }

    /**
    * Get a property of a SimpleXMLIterator object.
    *
    * @param $name The name of the property.
    * @return mixed The variable attached with the specified name.
    */
    public function getProperty($name)
    {
    if (!isset(self::$_properties[$this->getPropertyHackId()][$name]))
    {
    throw new Ea_Xml_Element_Exception("$name : property dos not exist !");
    }
    return self::$_properties[$this->getPropertyHackId()][$name];
    }

Leave a Reply

© 2008 Loïc Hoguin (essen), Anthony Ramine (nox)