REST API and null values in XML

I have been working on a REST API using the excellent tools provided by Yii2. My problem was that I have to differentiate between empty values and null values. In other words, <elem></elem> is different from null as it represents an empty string. Also, although some use <elem/> to represent a null value it should still be interpreted as an empty string. In other cases, the absence of the element is taken to represent a null value, but this may create problem with some parsers.

After some research, it appears that the correct way of describing a null value is <elem xsi:nil="true"/>.

However this is not supported by the current implementation of XmlResponseFormatter because values are always appended as DOMText. This means that, even is I pass a PHP null value, I get <elem></elem>.

Therefore, I have extended XmlResponse Formatter as follows.

Firstly, the function format() must be modified because creating $root as DOMElement makes it immutable while I need to attach the xsi: namespace definition. Therefore I use:

...
$dom = new DOMDocument($this->version, $charset);
// A writeable element is created and the namespace added
$root = $dom->createElement($this->rootTag);
$root->setAttributeNS('http://www.w3.org/2000/xmlns/' ,'xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
$dom->appendChild($root);
...

Then I have modified the buildXml function as follows:

protected function buildXml($element, $data){
  if (is_array($data) ||
    ($data instanceof \Traversable && $this->useTraversableAsArray && !$data instanceof Arrayable)
  ) {
    foreach ($data as $name => $value) {
      if (is_int($name) && is_object($value)) {
        $this->buildXml($element, $value);
      } elseif (is_array($value) || is_object($value)) {
        $child = new DOMElement(is_int($name) ? $this->itemTag : $name);
        $element->appendChild($child);
        $this->buildXml($child, $value);
      } else {
        $child = new DOMElement(is_int($name) ? $this->itemTag : $name);
        $element->appendChild($child);
        // Checks if the value is null and creates a null MXL element
        if ($value === null) {
          $child->setAttributeNS('http://www.w3.org/2001/XMLSchema-instance','xsi:nil','true');
        } else {
          $child->appendChild(new DOMText((string) $value));
        }
      }
    }
  } elseif (is_object($data)) {
    $child = new DOMElement(StringHelper::basename(get_class($data)));
    $element->appendChild($child);
    if ($data instanceof Arrayable) {
      $this->buildXml($child, $data->toArray());
    } else {
      $array = [];
      foreach ($data as $name => $value) {
        $array[$name] = $value;
      }
      $this->buildXml($child, $array);
    }
  } else {
    // Checks if $data is null and adds xsi:nil to $element
    if ($data === null) {
      $element->setAttributeNS('http://www.w3.org/2001/XMLSchema-instance','xsi:nil','true');
    } else {
      $element->appendChild(new DOMText((string) $data));
    }
  }
}

This way, if the value of the XML element is null, I get <element xsi:nil="true"/> which is more correct while, if the value is an empty string, I get <element></element> as expected.

I hope this would be useful to somebody and maybe the Yii2 could consider this improvement in a future release.