【问题标题】:How to serialize/save a DOMElement in $_SESSION?如何在 $_SESSION 中序列化/保存 DOMElement?
【发布时间】:2012-05-11 00:09:18
【问题描述】:

我对 PHP、DOM 和 PHP DOM 实现还很陌生。我要做的是将DOMDocument 的根元素保存在$_SESSION 变量中,以便我可以访问它并在后续页面加载时对其进行修改。

但是当我使用$_SESSION 保存 DOMElement 的状态时,我在 PHP 中遇到错误:

警告:DOMNode::appendChild() [domnode.appendchild]:无法获取 DOMElement

我读到 PHP DOMDocument 对象不能以原生方式保存到 $_SESSION。但是可以通过保存 DOMDocument 的序列化来保存它(例如$_SESSION['dom'] = $dom->saveXML())。

我不知道将DOMElement 保存到$_SESSION 变量是否也是如此,但这就是我正在尝试的。我想要这样做的原因是使用 DOMElement 的扩展类和一个附加属性。我希望通过将根 DOMElement 保存在 $_SESSION 中,以后可以检索元素并修改此附加属性并执行测试,例如 if (additionalProperty === false) { do something; }。我还通过保存 DOMDocument 并稍后检索它来阅读,所有元素都作为来自本机 DOM 类的对象返回。也就是说,即使我使用扩展类来创建元素,我随后需要的属性也将无法访问,因为持有对扩展类对象的引用的变量已经超出范围——这就是为什么我'我正在尝试其他事情。我首先尝试使用扩展类(不包括在下面),但出现错误......所以我恢复使用 DOMElement 对象来查看是否是问题所在,但我仍然遇到相同的错误。代码如下:

<?php
session_start();

$rootTag = 'root';
$doc = new DOMDocument;

if (!isset($_SESSION[$rootTag])) {
    $_SESSION[$rootTag] = new DOMElement($rootTag);
}

$root = $doc->appendChild($_SESSION[$rootTag]);
//$root = $doc->appendChild($doc->importNode($_SESSION[$rootTag], true));

$child = new DOMElement('child_element');
$n = $root->appendChild($child);

$ct = 0;
foreach ($root->childNodes as $ch) echo '<br/>'.$ch->tagName.' '.++$ct;

$_SESSION[$rootTag] = $doc->documentElement;
?>

此代码给出以下错误(取决于我是直接使用 appendChild 还是使用 importNode 的注释代码行):

Warning: DOMNode::appendChild() [domnode.appendchild]: Couldn't fetch DOMElement in C:\Program Files\wamp_server_2.2\www\test2.php on line 11

Warning: DOMDocument::importNode() [domdocument.importnode]: Couldn't fetch DOMElement in C:\Program Files\wamp_server_2.2\www\test2.php on line 12

我有几个问题。首先,是什么导致了这个错误,我该如何解决?另外,如果我试图做的事情是不可能的,那么我如何实现我的总体目标,即在为每个元素使用自定义属性的同时保存 DOM 树的“状态”?请注意,附加属性仅在程序中使用,而不是要保存在 XML 文件中的属性。此外,我不能每次都将 DOM 保存回文件,因为 DOMDocument 在修改后,根据我正在使用的模式可能无效,直到稍后对 DOMDocument 执行其他修改/添加。这就是为什么我需要保存一个暂时无效的 DOMDocument。感谢您的建议!

编辑: 在尝试了 hakre 的解决方案后,代码起作用了。然后我继续尝试使用 DOMElement 的扩展类,但正如我所怀疑的那样,它不起作用。这是新代码:

<?php
session_start();
//$_SESSION = array();
$rootTag = 'root';
$doc = new DOMDocument;

if (!isset($_SESSION[$rootTag])) {
    $root = new FreezableDOMElement($rootTag);
    $doc->appendChild($root);
} else {
    $doc->loadXML($_SESSION[$rootTag]);
    $root = $doc->documentElement;
}

$child = new FreezableDOMElement('child_element');
$n = $root->appendChild($child);

$ct = 0;
foreach ($root->childNodes as $ch) {
    $frozen = $ch->frozen ? 'is frozen' : 'is not frozen';
    echo '<br/>'.$ch->tagName.' '.++$ct.': '.$frozen;
    //echo '<br/>'.$ch->tagName.' '.++$ct;
}

$_SESSION[$rootTag] = $doc->saveXML();

/**********************************************************************************
 * FreezableDOMElement class
 *********************************************************************************/
class FreezableDOMElement extends DOMElement {
    public $frozen; // boolean value

    public function __construct($name) {
        parent::__construct($name);
        $this->frozen = false;
    }
}
?>

它给了我错误Undefined property: DOMElement::$frozen。就像我在原始帖子中提到的那样,在saveXMLloadXML 之后,最初用FreezableDOMElement 实例化的元素返回类型DOMElement,这就是无法识别frozen 属性的原因。有没有办法解决这个问题?

【问题讨论】:

    标签: php session dom serialization


    【解决方案1】:

    您不能在$_SESSION 中存储DOMElement 对象。一开始它会工作,但是在下一个请求时,它会被取消设置,因为它不能被序列化。

    这与您在问题中所写的 DOMDocument 相同。

    将其存储为 XML 或封装序列化机制。

    你在这里基本上面临三个问题:

    • 序列化DOMDocument(你这样做)
    • 序列化FreezableDOMElement(你这样做)
    • 将私人成员FreezableDOMElement::$frozen 保存在文档中。

    正如所写,序列化不是开箱即用的。此外,即使没有序列化,DOMDocument 也不会保留您的 FreezableDOMElement。下面的例子演示不自动保留实例,返回默认值FALSEDemo):

    class FreezableDOMElement extends DOMElement
    {
        private $frozen = FALSE;
    
        public function getFrozen()
        {
            return $this->frozen;
        }
    
        public function setFrozen($frozen)
        {
            $this->frozen = (bool)$frozen;
        }
    }
    
    class FreezableDOMDocument extends DOMDocument
    {
        public function __construct()
        {
            parent::__construct();
            $this->registerNodeClass('DOMElement', 'FreezableDOMElement');
        }
    }
    
    $doc = new FreezableDOMDocument();
    $doc->loadXML('<root><child></child></root>');
    
    # own objects do not persist
    $doc->documentElement->setFrozen(TRUE);
    printf("Element is frozen (should): %d\n", $doc->documentElement->getFrozen()); # it is not (0)
    

    由于 PHP 迄今为止不支持 setUserData(DOM 级别 3),一种方法是将附加信息存储在元素的命名空间属性中。这也可以通过在序列化对象时创建 XML 字符串并在反序列化时加载它来序列化(请参阅Serializable)。这样就解决了所有三个问题 (Demo):

    class FreezableDOMElement extends DOMElement
    {
        public function getFrozen()
        {
            return $this->getFrozenAttribute()->nodeValue === 'YES';
        }
    
        public function setFrozen($frozen)
        {
            $this->getFrozenAttribute()->nodeValue = $frozen ? 'YES' : 'NO';
        }
    
        private function getFrozenAttribute()
        {
            return $this->getSerializedAttribute('frozen');
        }
    
        protected function getSerializedAttribute($localName)
        {
            $namespaceURI = FreezableDOMDocument::NS_URI;
            $prefix = FreezableDOMDocument::NS_PREFIX;
    
            if ($this->hasAttributeNS($namespaceURI, $localName)) {
                $attrib = $this->getAttributeNodeNS($namespaceURI, $localName);
            } else {
                $this->ownerDocument->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:' . $prefix, $namespaceURI);
                $attrib = $this->ownerDocument->createAttributeNS($namespaceURI, $prefix . ':' . $localName);
                $attrib = $this->appendChild($attrib);
            }
            return $attrib;
        }
    }
    
    class FreezableDOMDocument extends DOMDocument implements Serializable
    {
        const NS_URI = '/frozen.org/freeze/2';
        const NS_PREFIX = 'freeze';
    
        public function __construct()
        {
            parent::__construct();
            $this->registerNodeClasses();
        }
    
        private function registerNodeClasses()
        {
            $this->registerNodeClass('DOMElement', 'FreezableDOMElement');
        }
    
        /**
         * @return DOMNodeList
         */
        private function getNodes()
        {
            $xp = new DOMXPath($this);
            return $xp->query('//*');
        }
    
        public function serialize()
        {
            return parent::saveXML();
        }
    
        public function unserialize($serialized)
        {
            parent::__construct();
            $this->registerNodeClasses();
            $this->loadXML($serialized);
        }
    
        public function saveBareXML()
        {
            $doc = new DOMDocument();
            $doc->loadXML(parent::saveXML());
            $xp = new DOMXPath($doc);
            foreach ($xp->query('//@*[namespace-uri()=\'' . self::NS_URI . '\']') as $attr) {
                /* @var $attr DOMAttr */
                $attr->parentNode->removeAttributeNode($attr);
            }
            $doc->documentElement->removeAttributeNS(self::NS_URI, self::NS_PREFIX);
            return $doc->saveXML();
        }
    
        public function saveXMLDirect()
        {
            return parent::saveXML();
        }
    }
    
    $doc = new FreezableDOMDocument();
    $doc->loadXML('<root><child></child></root>');
    $doc->documentElement->setFrozen(TRUE);
    $child = $doc->getElementsByTagName('child')->item(0);
    $child->setFrozen(TRUE);
    
    echo "Plain XML:\n", $doc->saveXML(), "\n";
    echo "Bare XML:\n", $doc->saveBareXML(), "\n";
    
    $serialized = serialize($doc);
    
    echo "Serialized:\n", $serialized, "\n";
    
    $newDoc = unserialize($serialized);
    
    printf("Document Element is frozen (should be): %s\n", $newDoc->documentElement->getFrozen() ? 'YES' : 'NO');
    printf("Child Element is frozen (should be): %s\n", $newDoc->getElementsByTagName('child')->item(0)->getFrozen() ? 'YES' : 'NO');
    

    它并不是真正的功能完整,而是一个工作演示。无需额外的“冻结”数据即可获得完整的 XML。

    【讨论】:

    • 感谢您的快速回复。我将尝试实施您描述的解决方案。但是,听起来我可能仍然会遇到在将 XML 字符串重新加载到 DOMDocument 时无法访问扩展类属性的问题,对吧?
    • @neizan:你在这里说什么?如果您自己处理序列化,那么您应该拥有您正在寻找的所有可能性。只是通常序列化 DOMDocument 是没有意义的,因为 XML 已经是一种序列化形式。
    • 对,当我说序列化时,我指的是使用saveXML 方法,很抱歉造成混淆。请查看我刚刚发布的其他示例代码,它试图按照您所说的去做。它使用DOMElement,但不适用于扩展类FreezableDOMElement
    • 从 DOMElement 扩展应该如何允许序列化(作为 PHP 会话)?可能如果你说出你想做什么,建议你做一些工作会更容易。
    • 我不太清楚你的意思,但让我更好地解释一下自己。照你说的做:创建DOMDocument并添加元素,然后使用DOMDocuemnt::saveXML将其保存到$_SESSION,然后在下一页加载时,DOMDocument::loadXML(来自$_SESSION)。伟大的。现在,我正在尝试使用扩展类FreezableDOMElement,它具有属性$frozen。但是,在随后的页面加载中,当调用loadXML 时,文档中的元素不是FreezableDOMElement 类型,而是DOMElement。我想这会发生。我的问题是如何解决这个问题?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-12-04
    • 1970-01-01
    • 2018-04-25
    • 1970-01-01
    • 2012-07-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多