PHP registerNodeClass并重用变量名

Jac*_*uls 6 php dom

当注册一个新的基本节点类型与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);
}
Run Code Online (Sandbox Code Playgroud)

为什么我得到"Some default"标签a而不是价值"A"

Tag a myProp:
string(12) "Some default"
Tag b myProp:
string(1) "B"
Tag c myProp:
string(1) "C"
Run Code Online (Sandbox Code Playgroud)

Rus*_*nov 2

假设我们使用 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));
Run Code Online (Sandbox Code Playgroud)

输出:

object(myDOMElement)#2 (18) refcount(3){
..
object(myDOMElement)#2 (18) refcount(3){
...
string(32) "00000000121277ac00000000658254f1"
string(32) "00000000121277ac00000000658254f1"
Run Code Online (Sandbox Code Playgroud)

属性DOMNode::$childNodes读取处理程序创建DOMNodeList迭代器对象。当前迭代器值zval准备好的 中获取的php_dom_iterator_move_forward。后者只是基于内部 XML 结构“创建新对象”(特别是)。DOMNode

php_dom_create_object但创建对象的方法很棘手!如果对象是第一次构造的,它将通过以下方式保存指针php_libxml_increment_node_ptr

php_libxml_increment_node_ptr((php_libxml_node_object *)intern, obj, (void *)intern);
Run Code Online (Sandbox Code Playgroud)

下次调用时,php_dom_create_object会检测保存的指针,增加引用计数,并返回先前创建的对象

php_libxml_increment_node_ptr((php_libxml_node_object *)intern, obj, (void *)intern);
Run Code Online (Sandbox Code Playgroud)

在自由对象处理程序(当对象被销毁时调用)中,DOM 扩展调用 php_libxml_decrement_node_ptr.

正如我们所看到的,DOM 对象实际上与任何 PHP 变量一样长。如果变量超出范围,它将被销毁。在这种情况下,DOM 扩展将为我们生成一个新对象。

现在让我们向myDOMElement类添加一个析构函数:

if ((intern = (dom_object *) php_dom_object_get_data((void *) obj))) {
  GC_REFCOUNT(&intern->std)++;
  ZVAL_OBJ(return_value, &intern->std);
  return 1;
}
Run Code Online (Sandbox Code Playgroud)

然后下面的代码将显示该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);
Run Code Online (Sandbox Code Playgroud)

输出

class myDOMElement extends DOMElement
{
    public $myProp = 'Some default';

    public function __destruct() {
      echo __METHOD__, PHP_EOL;
    }
}
Run Code Online (Sandbox Code Playgroud)

由于 DOM 扩展本身不存储zval对象,因此变量中存储的先前对象$node会超出范围并自动销毁。从现在起我们就不再引用 PHP 对象了。其myProp财产也被毁坏。但是,如果我们在循环中请求, DOM 扩展将为节点生成新实例:a

foreach ($doc->childNodes as $n) {
  var_dump($n->tagName);
}
Run Code Online (Sandbox Code Playgroud)

因此,你的问题的答案

为什么标签 a 得到的是“Some default”而不是值“A”?

是: 的对象$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);
Run Code Online (Sandbox Code Playgroud)

输出

$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);
Run Code Online (Sandbox Code Playgroud)