Search code examples
phpxmlintellij-ideaphpstormphpdoc

How to document loaded xml structure with phpDOC


Consider the file 'cars.xml' with complex elements:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <car>
        <model>Volvo</model>
        <color>red</color>
    </car>
    <car>
        <model>BMW</model>
        <color>black</color>
    </car>
</root>

Once loaded in PHP how do I document the structure of the $xml variable via phpDoc, especially so that it can use in code completion in IntelliJ-IDEA?

/**
 * @var ?????????? $xml
 */
$xml=simplexml_load_file('cars.xml');

Solution

  • Don't :) It's more likely your XML is a serialization of one or more objects.

    Therefore in your software you don't pass the XML object along but you only use it when serializing or unserializing the objects.

    Then you pass the objects around which could be concrete types.

    So let's turn your problem top down. Why care about the XML when you want a concrete type. Let's say you want to have one or more cars, so let's create them!

    /**
     * Class Car
     */
    class Car
    {
        private $model;
        private $color;
    
        /**
         * Car constructor.
         *
         * @param $model
         * @param $color
         */
        public function __construct($model, $color)
        {
            $this->model = $model;
            $this->color = $color;
        }
    
        /**
         * @return mixed
         */
        public function getColor()
        {
            return $this->color;
        }
    
        /**
         * @return mixed
         */
        public function getModel()
        {
            return $this->model;
        }
    }
    

    Now that was easy. And right, here it goes:

    $car = new Car('Sahara', 'Blue');
    

    IntelliJ-IDEA will already type-hint the $car variable properly.

    But wait, didn't you ask about the XML? Well, true is that. Okay, so let's do this again. Let's pretend things are already there when we need them. For example to turn the XML into an array of Cars:

    $buffer = <<<XML
    <?xml version="1.0" encoding="UTF-8"?>
    <root>
        <car>
            <model>Volvo</model>
            <color>red</color>
        </car>
        <car>
            <model>BMW</model>
            <color>black</color>
        </car>
    </root>
    XML;
    
    $xml = new SimpleXMLElement($buffer);
    
    $carsArray = new CarsArray();
    
    $cars = $carsArray->fromXml($xml);
    

    Now that was easy again. Sure question remains what is CarsArray now? Well, let's say it's some kind of concept to translate the SimpleXMLElement into an array of Cars and vice-versa:

    /**
     * Class CarsArray
     */
    class CarsArray
    {
        /**
         * @param array $cars
         *
         * @return SimpleXMLElement
         */
        public function toXml(array $cars)
        {
            $xml = new SimpleXMLElement('<root/>');
            foreach ($cars as $car) {
                if ($car instanceof Car) {
                    $node = $xml->addChild('car');
                    $node->addChild('color', $car->getColor());
                    $node->addChild('model', $car->getModel());
                }
            }
    
            return $xml;
        }
    
        /**
         * @param SimpleXMLElement $xml
         *
         * @return array|Car[]
         */
        public function fromXml(SimpleXMLElement $xml)
        {
            $array = [];
            foreach ($xml->car as $car) {
                $array[] = new Car($car->model, $car->color);
            }
    
            return $array;
        }
    }
    

    As you can see, this is nothing very fancy. And already a working example. Here is the last part of it:

    foreach ($cars as $car) {
        echo "- ", $car->getModel(), "\n";
    }
    

    Doing the following output:

    - Volvo
    - BMW
    

    To profit from the vartype hinting here, it's important that you hint the type with the array for the return value. Let's take a look again into the fromXml() method:

        /**
         * @param SimpleXMLElement $xml
         *
         * @return array|Car[]
         */
        public function fromXml(SimpleXMLElement $xml)
        {
            $array = [];
            ...
    

    As you can see @return array|Car[] tells that the return value is an array and also (hence the pipe "|") an array of Cars, denoted by the square bracket pair behind it (standing for array).

         * @return array|Car[]
    

    This will give you code completition again. And not only that, within large part of your application you know how to deal with Car objects and that's independent to the XML and the relations between the XML and the cars are only interested for that part of your program which needs to translate/convert/map between the two.

    I hope this helps.

    You can find the code as well as an interactive online example here: https://eval.in/439670