2016-02-26 23 views
6

当使用registerNodeClass注册新的基本节点类型时:它看起来像是我为创建的元素重新使用变量名称,然后自定义属性恢复为其默认值。实际上,我想这样做在一个循环,但在这里就是我想清楚地显示我的意思的例子:PHP 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" 
+0

这个问题可能涉及:http://stackoverflow.com/q/5473967 –

回答

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)); 

输出:

object(myDOMElement)#2 (18) refcount(3){ 
.. 
object(myDOMElement)#2 (18) refcount(3){ 
... 
string(32) "00000000121277ac00000000658254f1" 
string(32) "00000000121277ac00000000658254f1" 

DOMNode::$childNodes属性读取处理器创建DOMNodeList iterator对象。当前迭代器值is fetched来自zval,由php_dom_iterator_move_forward准备。后者仅基于内部XML结构的"creates new object"(特别是,DOMNode) 。

但方式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_objectdetects 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扩展callsphp_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“?

是:用$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" 
+0

意想不到的回答,非常感谢所有的细节。我为这个问题创建了一个PHP错误:https://bugs.php.net/bug.php?id = 71872 –