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.
Be the first person to leave a comment
Please login to leave your comment.