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.
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.