. */ namespace SP\Storage; use DOMDocument; use DOMElement; use DOMNode; use DOMNodeList; use ReflectionObject; use RuntimeException; use SP\Core\Exceptions\FileNotFoundException; /** * Class XmlHandler para manejo básico de documentos XML * * @package SMD\Storage */ class XmlHandler implements XmlFileStorageInterface { /** * @var mixed */ protected $items; /** * @var string */ protected $file; /** * @var DOMDocument */ private $Dom; /** * @var DOMElement */ private $root; /** * XmlHandler constructor. * * @param $file */ public function __construct($file) { $this->file = $file; } /** * Cargar un archivo XML * * @param string $node * @return XmlFileStorageInterface * @throws FileNotFoundException */ public function load($node = 'root') { if (!$this->checkSourceFile()) { throw new FileNotFoundException(sprintf(__('No es posible leer/escribir el archivo: %s'), $this->file)); } $this->setDOM(); $this->items = []; $this->Dom->load($this->file); $nodes = $this->Dom->getElementsByTagName($node)->item(0)->childNodes; $this->items = $this->readChildNodes($nodes); return $this; } /** * Comprobar que el archivo existe y se puede leer/escribir * * @return bool */ protected function checkSourceFile() { return (is_writable($this->file) && filesize($this->file) > 0); } /** * Crear un nuevo documento XML */ private function setDOM() { $this->Dom = new DOMDocument('1.0', 'utf-8'); } /** * Leer de forma recursiva los nodos hijos y devolver un array multidimensional * * @param DOMNodeList $nodeList * @return array */ protected function readChildNodes(DOMNodeList $nodeList) { $nodes = []; /** @var DOMElement $node */ foreach ($nodeList as $node) { if ($node->nodeType === XML_ELEMENT_NODE) { if (is_object($node->childNodes) && $node->childNodes->length > 1) { if ($node->hasAttribute('multiple') && (int)$node->getAttribute('multiple') === 1) { $nodes[] = $this->readChildNodes($node->childNodes); } else { $nodes[$node->nodeName] = $this->readChildNodes($node->childNodes); } } else { $val = is_numeric($node->nodeValue) && strpos($node->nodeValue, '.') === false ? (int)$node->nodeValue : $node->nodeValue; if ($node->nodeName === 'item') { $nodes[] = $val; } else { $nodes[$node->nodeName] = $val; } } } } return $nodes; } /** * Obtener un elemento del array * * @param $id * @return mixed */ public function __get($id) { return $this->items[$id]; } /** * Guardar el archivo XML * * @param string $node * @return XmlFileStorageInterface * @throws \RuntimeException */ public function save($node = 'root') { if (null === $this->items) { throw new RuntimeException(__u('No hay elementos para guardar')); } $this->setDOM(); $this->Dom->formatOutput = true; $this->root = $this->Dom->createElement($node); $this->Dom->appendChild($this->root); $this->writeChildNodes($this->items, $this->root); // $this->Dom->save($this->file); file_put_contents($this->file, $this->Dom->saveXML(), LOCK_EX); return $this; } /** * Crear los nodos hijos recursivamente a partir de un array multidimensional * * @param mixed $items * @param DOMNode $Node * @param null $type */ protected function writeChildNodes($items, DOMNode $Node, $type = null) { foreach ($this->analyzeItems($items) as $key => $value) { if (is_int($key)) { $newNode = $this->Dom->createElement('item'); $newNode->setAttribute('type', $type); } else { $newNode = $this->Dom->createElement($key); } if (is_array($value)) { $this->writeChildNodes($value, $newNode, $key); } else if (is_object($value)) { $newNode->setAttribute('class', get_class($value)); $newNode->appendChild($this->Dom->createTextNode(base64_encode(serialize($value)))); } else { $newNode->appendChild($this->Dom->createTextNode(trim($value))); } $Node->appendChild($newNode); } } /** * Analizar el tipo de elementos * * @param mixed $items * @param bool $serialize * @return array */ protected function analyzeItems($items, $serialize = false) { if (is_array($items)) { ksort($items); return $items; } if (is_object($items)) { return $serialize ? serialize($items) : $this->analyzeObject($items); } return []; } /** * Analizar un elemento del tipo objeto * * @param $object * @return array */ protected function analyzeObject($object) { $items = []; $Reflection = new ReflectionObject($object); foreach ($Reflection->getProperties() as $property) { $property->setAccessible(true); $value = $property->getValue($object); if (is_bool($value)) { $items[$property->getName()] = (int)$value; } elseif (is_numeric($value) && strpos($value, '.') === false) { $items[$property->getName()] = (int)$value; } else { $items[$property->getName()] = $value; } $property->setAccessible(false); } ksort($items); return $items; } /** * Devolver los elementos cargados * * @return mixed */ public function getItems() { return $this->items; } /** * Establecer los elementos * * @param $items * @return mixed */ public function setItems($items) { $this->items = $items; } }