当向registerNodeClass注册新的基本节点类型时:看起来如果我对创建的元素重用变量名称,那么自定义属性将恢复为其默认值。我实际上正在尝试循环执行此操作,但这里有一个例子,我认为它清楚地表明了我的意思:
<?php
class myDOMElement extends DOMElement
{
public $myProp = 'Some default';
}
$doc = new DOMDocument();
$doc->registerNodeClass('DOMElement', 'myDOMElement');
$node = $doc->createElement('a');
$node->myProp = 'A';
$doc->appendChild($node);
# This seems to alter node A in $doc, not what I expected:
$node = $doc->createElement('b');
$node->myProp = 'B';
$doc->appendChild($node);
# Note: $nodeC instead of $node, this works fine.
$nodeC = $doc->createElement('c');
$nodeC->myProp = 'C';
$doc->appendChild($nodeC);
foreach ($doc->childNodes as $n) {
echo 'Tag ', $n->tagName, ' myProp:', PHP_EOL;
var_dump($n->myProp);
}
为什么我得到标签a
的"Some default"
而不是值"A"
?
Tag a myProp:
string(12) "Some default"
Tag b myProp:
string(1) "B"
Tag c myProp:
string(1) "C"
假设我们使用 PHP7(所描述的行为至少是 PHP 版本 5..7 特有的)。
DOMNode::appendChild
方法设置新 DOMNode
对象的内部结构,更新父节点的内部结构(在我们的例子中它是一个DOMDocument
对象),然后基于准备好的内部结构创建并返回一个新的DOMNode
对象。实际上,返回的对象和追加的子节点对象是相同的:
$ret_node = $doc->appendChild($node);
debug_zval_dump($node);
debug_zval_dump($ret_node);
var_dump(spl_object_hash($node));
var_dump(spl_object_hash($ret_node));
输出:
object(myDOMElement)#2 (18) refcount(3){
..
object(myDOMElement)#2 (18) refcount(3){
...
string(32) "00000000121277ac00000000658254f1"
string(32) "00000000121277ac00000000658254f1"
DOMNode::$childNodes
属性读取处理程序创建迭代器对象DOMNodeList
。当前迭代器值是从 php_dom_iterator_move_forward
准备的zval
中获取的。后者只是一个"创建新对象"(特别是DOMNode
)基于内部 XML 结构。
但是php_dom_create_object
创建对象的方式很棘手!如果对象是第一次构造的,它会通过以下方式保存指针php_libxml_increment_node_ptr
:
php_libxml_increment_node_ptr((php_libxml_node_object *)intern, obj, (void *)intern);
下次调用php_dom_create_object
时,它会检测保存的指针,递增引用计数,并返回以前创建的对象:
if ((intern = (dom_object *) php_dom_object_get_data((void *) obj))) {
GC_REFCOUNT(&intern->std)++;
ZVAL_OBJ(return_value, &intern->std);
return 1;
}
在自由对象处理程序(在销毁对象时调用)中,DOM 扩展调用 php_libxml_decrement_node_ptr
。
正如我们所看到的,DOM 对象实际上与任何 PHP 变量一样长。如果变量超出范围,则将其销毁。在这种情况下,DOM 扩展将为我们生成一个新对象。
现在让我们向 myDOMElement
类添加一个析构函数:
class myDOMElement extends DOMElement
{
public $myProp = 'Some default';
public function __destruct() {
echo __METHOD__, PHP_EOL;
}
}
然后下面的代码将显示DOMNode
对象在我们为其分配$doc->createElement('b')
的行中被销毁:
$node = $doc->createElement('a');
$node->myProp = 'A';
$doc->appendChild($node);
echo "Marker B-1'n";
$node = $doc->createElement('b');
echo "Marker B-2'n";
$node->myProp = 'B';
$doc->appendChild($node);
输出:
Marker B-1
myDOMElement::__destruct
Marker B-2
由于 DOM 扩展本身不存储zval
对象,因此存储在 $node
变量中的先前对象超出范围并自动销毁。从现在开始,我们不再引用 PHP 对象。myProp
财产也被摧毁。但是,如果我们在循环中请求 DOM 扩展,它将为 a
节点生成新实例:
foreach ($doc->childNodes as $n) {
var_dump($n->tagName);
}
因此,您的问题的答案
为什么标签 a 的"某些默认值"而不是值"A"?
is:带有 $myProp = "A"
的对象实际上被销毁了,因为当您将另一个对象分配给 $node
变量时,它会超出范围,并且 DOM 扩展不会为我们存储 PHP 对象 - 它将此责任委托给用户。但是,该节点仍然存在于内部 DOM 结构中。因此,当涉及到循环中的A
标记时,DOM 扩展会生成具有默认属性的新对象。
解决方法如下:
foreach (['a', 'b'] as $name) {
$nodes[] = $node = $doc->createElement($name);
$node->myProp = $name;
$doc->appendChild($node);
}
foreach ($doc->childNodes as $n) {
echo 'Tag ', $n->tagName, ' myProp:'; var_dump($n->myProp);
}
unset($nodes);
输出
Tag a myProp:string(1) "a"
Tag b myProp:string(1) "b"