2012-05-01 5 views
5

Soy bastante nuevo en PHP, DOM y en la implementación de PHP DOM. Lo que intento hacer es guardar el elemento raíz del DOMDocument en una variable $_SESSION para poder acceder a él y modificarlo en las siguientes cargas de página.

Pero me da un error en PHP cuando se utiliza $_SESSION para guardar el estado de DOMElement:

Advertencia: DOMNode :: appendChild() [domnode.appendchild]: No se pudo obtener DOMElement

He leído que un objeto PHP DOMDocument no puede guardarse en $_SESSION de forma nativa. Sin embargo, puede guardarse guardando la serialización del DOMDocument (por ejemplo, $_SESSION['dom'] = $dom->saveXML()).

No sé si lo mismo se aplica para guardar un DOMElement en una variable $_SESSION también, pero eso es lo que estaba intentando. Mi razón para querer hacer esto es usar una clase extendida de DOMElement con una propiedad adicional. Tenía la esperanza de que al guardar el elemento raíz DOMElement en $ _SESSION podría recuperar el elemento y modificar esta propiedad adicional y realizar una prueba como, if (additionalProperty === false) {hacer algo; }. También he leído que al guardar un DOMDocument y luego recuperarlo, todos los elementos se devuelven como objetos de las clases DOM nativas. Es decir, incluso si utilicé una clase extendida para crear elementos, la propiedad que posteriormente necesitaré no será accesible, porque la variable que contiene la referencia al objeto de clase extendida ha salido del alcance, que es la razón por la que ' Estoy intentando esta otra cosa. Intenté usar la clase extendida (no incluida a continuación) primero, pero obtuve errores ... así que volví a usar un objeto DOMElement para ver si ese era el problema, pero sigo recibiendo los mismos errores. Aquí está el código:

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

Este código da los siguientes errores (dependiendo de si utilizo appendChild directa o la línea comentada de código usando 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

I tiene varias preguntas Primero, ¿qué está causando este error y cómo lo soluciono? Además, si lo que trato de hacer no es posible, ¿cómo puedo lograr mi objetivo general de guardar el 'estado' de un árbol DOM mientras uso una propiedad personalizada para cada elemento? Tenga en cuenta que la propiedad adicional solo se usa en el programa y no es un atributo que se guardará en el archivo XML. Además, no puedo simplemente guardar el DOM de nuevo en el archivo cada vez, porque el DOMDocument, después de una modificación, puede no ser válido de acuerdo con un esquema que estoy utilizando hasta más adelante cuando se hayan realizado modificaciones/adiciones adicionales al DOMDocument. Es por eso que necesito guardar un DOMDocument temporalmente inválido. Gracias por cualquier consejo!

EDITADO: Después de probar la solución de hakre, el código funcionó. Luego pasé a tratar de usar una clase extendida de DOMElement, y, como sospechaba, no funcionó. Aquí está el nuevo código:

<?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; 
    } 
} 
?> 

Me da el error Undefined property: DOMElement::$frozen. Como mencioné en mi publicación original, después de saveXML y loadXML, un elemento originalmente instanciado con FreezableDOMElement está devolviendo el tipo DOMElement, por lo que la propiedad frozen no se reconoce. ¿Hay alguna forma de evitar esto?

Respuesta

4

No puede almacenar un objeto DOMElement dentro de $_SESSION. Funcionará al principio, pero con la próxima solicitud, se desactivará porque no se puede serializar.

Es lo mismo que para DOMDocument mientras escribe en su pregunta.

Almacénelo como XML en su lugar o encapsule el mecanismo de serialización.

Usted está enfrentando básicamente tres problemas aquí:

  • serializar el DOMDocument (haces esto)
  • serializar el FreezableDOMElement (haces esto)
  • Mantenga el miembro privado FreezableDOMElement::$frozen con el documento .

Como está escrito, la serialización no está disponible fuera de la caja. Además, DOMDocument no persiste su FreezableDOMElement incluso sin serialización. El siguiente ejemplo demuestra que la instancia no se mantiene de forma automática, se devuelve el valor predeterminado FALSE (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) 

al igual que PHP no lo que el apoyo ahora setUserData (DOM Nivel 3), una posible alternativa sería almacenar el información adicional dentro de un atributo de espacio de nombres con el elemento. Esto también se puede serializar creando la cadena XML al serializar el objeto y cargarlo cuando se deserializa (consulte Serializable). Esto luego resuelve los tres problemas (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'); 

en realidad no es completa, pero cuentan con una demostración de trabajo. Es posible obtener el XML completo sin los datos adicionales de "congelación".

+0

Gracias por la respuesta rápida. Voy a intentar implementar la solución que describes. Sin embargo, parece que probablemente me encuentre con el problema de no tener acceso a las propiedades de clase ampliadas cuando la cadena XML se vuelve a cargar en un DOMDocument, ¿verdad? – neizan

+0

@neizan: ¿Sobre qué hablas aquí? Si se ocupa de la serialización, debe tener todas las posibilidades que está buscando. Es solo que normalmente serializar 'DOMDocument' no tiene sentido, ya que XML ya es una forma de serialización. – hakre

+0

Correcto, cuando dije serialización me refería a utilizar el método 'saveXML', disculpe la confusión. Por favor, eche un vistazo al código de muestra adicional que acabo de publicar que intenta hacer lo que usted dice. Funcionó usando un 'DOMElement', pero no con la clase extendida' FreezableDOMElement'. – neizan

Cuestiones relacionadas