SimpleXML 子属性在有和没有命名空间时表现不同

SimpleXML children's attributes behaves different with and without namespace

SimpleXML examples page,“示例 #5 使用属性”部分指出:

Access attributes of an element just as you would elements of an array.

SimpleXMLElement::children() 中的示例 #1 使用 $element['attribute'] 语法访问子项的属性;

向该代码添加命名空间,将禁用对属性的访问:

$xml = new SimpleXMLElement(
'<person xmlns:a="foo:bar">
  <a:child role="son">
    <a:child role="daughter"/>
  </a:child>
  <a:child role="daughter">
    <a:child role="son">
      <a:child role="son"/>
    </a:child>
  </a:child>
</person>');
foreach ($xml->children('a', true) as $second_gen) {
    echo ' The person begot a ' . $second_gen['role'];
    foreach ($second_gen->children('a', true) as $third_gen) {
        echo ' who begot a ' . $third_gen['role'] . ';';
        foreach ($third_gen->children('a', true) as $fourth_gen) {
            echo ' and that ' . $third_gen['role'] . ' begot a ' . $fourth_gen['role'];
        }
    }
}
// results
// The person begot a who begot a ; The person begot a who begot a ; and that begot a 
// expected
// The person begot a son who begot a daughter; The person begot a daughter who begot a son; and that son begot a son

这里有很多问题指向相同的解决方案,使用 SimpleXMLElement::attributes() 函数而不是作为数组访问,但 none 答案解释了原因。

使用名称空间时的这种不同行为是一个错误吗?文档是否过时?我们是否应该始终使用 SimpleXMLElement::attributes() 并避免推荐的类似数组的语法?

注意:我正在使用PHP 5.5.9-1ubuntu4.9


相关问题

其原因实际上与 SimpleXML 无关,而是与 XML 命名空间如何根据标准工作的一些令人惊讶的细节有关。

在您的示例中,您有一个使用前缀 a 声明的名称空间,因此要声明该名称空间中的某个属性,您必须像您一样在其名称前添加前缀 a:包含元素:

<a:child a:role="daughter"/>

不带名称空间前缀的属性与其所在的元素位于同一名称空间中似乎是一种常见的假设,但事实并非如此。上面的例子等同于你的例子:

<a:child role="daughter"/>

您可能会看到的另一种情况是默认(无前缀)命名空间中的位置:

<person xmlns="http://example.com/foo.bar"><child role="daughter" /></person>

此处,child 元素在 http://example.com/foo.bar 命名空间中,role 属性仍然不在 中!正如 this related question, the relevant section of the XML Namespaces spec 中所讨论的那样,包括以下声明:

The namespace name for an unprefixed attribute name always has no value.

也就是说,没有命名空间前缀的属性永远不会出现在任何命名空间中,无论文档的其余部分是什么样子。

那么,这对 SimpleXML 有什么影响?

SimpleXML 的工作原理是在每次使用 ->children()->attributes() 方法时更改 "current namespace",并从此跟踪它。

所以当你写的时候:

$children = $xml->children('a', true);

或:

$children = $xml->children('http://example.com/foo.bar');

"current namespace" 是 foo:bar。随后使用 ->childElement['attribute'] 语法将在此命名空间中查找 - 您不需要每次都再次调用 children() - 但您的无前缀属性不会在那里可以找到,因为它们没有命名空间。

当您随后写下:

$attributes = $children->attributes();

这与以下内容的解释方式相同:

$attributes = $children->attributes(null);

所以现在 "current namespace" 是 null。现在当你寻找没有命名空间的属性时,你会找到它们。