PHP registerNodeClass并重用变量名

时间:2016-02-26 14:46:58

标签: 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);
}

为什么我会为"Some default"而不是值a获取"A"

Tag a myProp:
string(12) "Some default"
Tag b myProp:
string(1) "B"
Tag c myProp:
string(1) "C"

1 个答案:

答案 0 :(得分:2)

让我们假设我们使用PHP7(描述的行为至少是PHP版本5..7的特性)。

DOMNode::appendChild方法设置新DOMNode对象的内部结构,更新父节点的内部结构(在我们的例子中是&#39; sa 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迭代器对象。 is fetched准备的zval中的当前迭代器值php_dom_iterator_move_forward。后者只有"creates new object"(特别是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 detects the saved pointer, increments reference count, and returns the previously created 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扩展calls 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);
}

因此,你的问题的答案

  

为什么我会得到&#34;有些默认&#34;标记a而不是值&#34; A&#34;?

是:具有$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"
相关问题