PHP SimpleXML 检查子节点是否存在

65

A->b->c 可能存在,但 c 可能不存在。我该如何检查它?


1
请选一个新答案。 - Tim Ogilvy
17个回答

133

将其包装在isset()中可能会更好。

if(isset($A->b->c)) { // c exists

这样如果$A$A->b不存在,它就不会出错。


14
这个方案似乎比其他答案更整洁,但实际上,对于任何不存在的请求节点,SimpleXMLElement返回一个空的SimpleXMLElement对象。因此,empty()方法似乎是最好的选择。 - spikyjt
此外,c 可能是可选的,但 Ab 可能是必需的,因此如果它们未定义,我实际上可能希望获得异常 - 这是检查文档完整性的简单方法。 - davka
@spikyjt isset() 是检查节点是否存在的最佳方法。如果您需要检查节点是否存在并且其内部有内容(即 >< 之间的文本或子节点),则可以使用 empty()。 如果您仅需要检查节点的存在,则 empty() 将无法完成任务。例如,对于此节点 <b a="zzz"></b>empty() 将返回 true - CITBL
2
@CITBL 您是正确的,empty() 没有太大用处。但需要注意的是,如果没有 c 元素,则 isset($A->c) === false,但 $A->c 返回一个空的 SimpleXMLElement。因此,使用 $c = $A->c;,则 isset($c) === true(即使可能会预期 $c !== null)。 - spikyjt

41

SimpleXML总是返回对象。如果没有子元素,则返回空对象。

if( !empty($a->b)){
  var_dump($a->b);
}

显然这是一个特性而不是错误。这一点非常重要。如果访问对象的子元素不存在,将会创建它。 - cosmin
3
这应该是被接受的答案,如果早些看到它就可以节省我一些时间和挫折 :-) - Niek Klein Kromhof
2
答案错误。因为empty()即使节点存在但没有内容也会返回true。例如,在您的代码示例中,如果$a包含此内容:<xml><b a="zzz"/></xml>,则empty($a->b)将返回true。因此,这不是一种可靠的检查节点是否存在的方法。但是,如果您需要节点内部的某些内容,即在><之间,而不关心没有内容的节点,则可以使用empty() - CITBL

9
@null的回答是正确的 - 最简单的方法是使用isset
if (isset($A->b->c)) { /* c exists */ }

然而,对我来说,它的原因并不明显,并且这个页面上有相当多的错误信息。我花了一些时间才真正理解它,所以我想分享我学到的东西。
正如一些人指出的那样,当您访问SimpleXMLElement的不存在子项时,实际上得到的是一个“空”的SimpleXMLElement,而不是false或null。
例如,如果b不存在:
$b = $A->b; // $b is now an empty SimpleXMLElement
$b->getName(); // returns an empty string
isset($b); // returns true

你可能认为使用 isset 来测试子元素是否存在是行不通的,因为如果子元素不存在,我们仍然会得到一个空的 SimpleXMLObject,所以 isset 绑定返回 true。

但实际上并不是这样:

isset($A->b); // returns false

这让我很惊讶!原因是isset不是普通函数,而是PHP语言结构。当你调用isset($A->b)时,PHP不会首先计算$A->b,然后将结果作为参数传递给isset()。相反,当在不可访问的对象属性上调用isset时,行为是调用类上的__isset()重载方法(如此解释:https://www.php.net/manual/en/language.oop5.overloading.php#object.isset
因此,类的作者可以独立于$b = $A->b的结果来控制isset($A->b)的结果。在SimpleXMLElement的情况下,他们设置了这样一个结果:如果b存在,isset($A->b)返回true,否则返回false,这正好符合我们测试子元素存在性所需的条件。
另外有一个注脚-原始问题询问了如何测试$A->b->c的存在性。即使中间的b不存在,使用isset($A->b->c)也完全有效。我认为这里发生的是,PHP首先执行$A->b,如果b不存在,则得到一个空的SimpleXMLElement,然后在该空SimpleXMLElement上调用__isset('c')以获取最终结果isset($A->b->c)

8

我使用children()函数并对其进行count()处理来解决这个问题,如果没有子元素,则在计数调用之前加上@以忽略PHP错误。虽然这很愚蠢,但它确实有效:

$identification = $xml->identification;
if (@count($identification->children()) == 0)
  $identification = $xml->Identification;

I hate this..


你是完全正确的(这就是为什么我说“我讨厌这个…”),但是当你知道无论发生什么错误,你都希望它被忽略时,那么这样做就没问题了。 - scippie

8

4
如果您使用的是PHP 5.3版本,您可以直接使用$a->count()。否则,scippie的解决方案@count($a->children())也能很好地工作。我发现我不需要@,但旧的PHP实现可能需要它。

这应该是被接受的答案,目前被接受的那个是错误的。 - Robbert van den Bogerd

3

4
不,文件说明中指出它返回FALSE是因为出现错误,而不是路径没有返回结果。 - Brian
经过测试,@Brian是正确的,只有在发生错误时它才会返回false。不要使用这个检查,它总是会返回一个(空)节点。 - Robbert van den Bogerd

3

使用if(isset($A->b){会出现问题,所以我尝试使用if($A->b){,它起作用了!


2
count和强制转换为布尔值是两种有意义的方法。按照您所描述的方式使用if是将其转换为布尔值的最短途径。 - Brilliand

3
只需简单地
var_dump(count($xml->node));

在PHP7.2中,当计算某些未实现可计数接口的内容时,会发出警告。因此,在使用count()时应考虑到这一点,最好使用isset()。 - shirkkan

2
使用xpath:
function has_child(?\SimpleXMLElement $parent, string $xpathToChild)
{
    return isset($parent) && !empty($parent->xpath('('.$xpathToChild.')[1]'));
}

在检查子节点时,$parent 是一个间接或直接的父节点,而 $xpathToChild 是相对于 $parent 的子节点的 xpath。

()[1] 是因为我们不想选择所有的子节点。一个就足够了。

要检查 $a->b->c 是否存在:

if(has_child($a,'b/c')) { //...

你还可以检查属性。检查节点c是否具有属性t
if(has_child($a,'b/c/@t')) { //...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接