How to serialize/save a DOMElement in $_SESSION?

后端 未结 1 511
半阙折子戏
半阙折子戏 2021-01-19 03:49

I\'m pretty new to PHP, DOM, and the PHP DOM implementation. What I\'m trying to do is save the root element of the DOMDocument in a $_SESSION var

相关标签:
1条回答
  • 2021-01-19 04:00

    You can not store a DOMElement object inside $_SESSION. It will work at first, but with the next request, it will be unset because it can not be serialized.

    That's the same like for DOMDocument as you write about in your question.

    Store it as XML instead or encapsulate the serialization mechanism.

    You are basically facing three problems here:

    • Serialize the DOMDocument (you do this to)
    • Serialize the FreezableDOMElement (you do this to)
    • Keep the private member FreezableDOMElement::$frozen with the document.

    As written, serialization is not available out of the box. Additionally, DOMDocument does not persist your FreezableDOMElement even w/o serialization. The following example demonstrates that the instance is not automatically kept, the default value FALSE is returned (Demo):

    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)
    

    As PHP does not so far support setUserData (DOM Level 3), one way could be to store the additional information inside a namespaced attribute with the element. This can also be serialized by creating the XML string when serializing the object and loading it when unserializing (see Serializable). This then solves all three problems (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');
    

    It's not really feature complete but a working demo. It's possible to obtain the full XML without the additional "freeze" data.

    0 讨论(0)
提交回复
热议问题