Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
This class is used to map an XML document into PHP object
<?php
// namespace ShinSenter\Model;
/**
* Simple XML mapped model
*
* This class is used to map an XML document into PHP object, and you can
* re-generate the XML document from its data.
*
* @author Mai Nhut Tan <admin@shinsenter.com>
* @copyright 2015
* @license http://opensource.org/licenses/MIT The MIT License (MIT)
*
* The MIT License (MIT)
*
* Copyright (c) 2015 Mai Nhut Tan
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*
* Example:
* --------
*
* class Hotel extends XmlModel {
*
* // these properties will be shown in xml, even they are empty
* protected $properties = [
* 'HotelID' => '',
* 'HotelName' => '',
* 'CheckInDate' => '', // should be yyyy-mm-dd
* 'CheckOutDate' => '', // should be yyyy-mm-dd
* 'RoomClasses' => array(), // array of RoomClass objects
* ];
*
* // these properties will be hidden when they are empty
* protected $optional_properties = [
* 'Address' => '',
* 'Address2' => '',
* 'Fax' => '',
* 'Email' => '',
* ];
*
* protected $attributes = [
* 'Address' => [
* 'City' => '',
* 'Country' => '',
* ]
* ];
*
* // override the method parseValue()
* protected function parseValue(DOMNode $node) {
* switch ($node->nodeName) {
* case 'RoomClasses':
* $rooms = [];
* foreach ($node->childNodes as $room) {
* $rooms[] = new RoomClass($room); // another XmlModel
* }
* return $rooms;
* break;
*
* case 'CheckInDate':
* return date('Y-m-d', strtotime($node->nodeValue));
* break;
*
* case 'CheckOutDate':
* return date('Y-m-d', strtotime($node->nodeValue));
* break;
* }
*
* return $node->nodeValue;
* }
* }
*
*/
// use DOMDocument;
// use DOMNode;
class XmlModel {
// Properties will be mapped from XML
protected $properties = [];
protected $optional_properties = [];
protected $attributes = [];
// xml format
protected $xml = NULL;
protected $encoding = 'UTF-8';
protected $rootName = NULL;
/**
* Constructor
*
* @param mixed $data (array, xml string, DOMNode)
*/
public function __construct($data) {
$this->fill($data);
}
/*
|--------------------------------------------------------------------------
| Root tag of xml
|--------------------------------------------------------------------------
*/
/**
* Get root tag name of xml document
*
* @return string
*/
public function getRootName() {
if (!$this->rootName) {
$this->rootName = join('', array_slice(explode('\\', get_class($this)), -1));
}
return $this->rootName;
}
/**
* Set root tag name of xml document
*
* @param string $name
*/
public function setRootName($name) {
if ($this->isValidTagName($name)) {
$this->rootName = $name;
}
}
/*
|--------------------------------------------------------------------------
| Getters and setters
|--------------------------------------------------------------------------
*/
/**
* Getter
*
* @param string $name
* @return mixed
*/
public function __get($name) {
if (isset($this->properties[$name])) {
return $this->properties[$name];
}
if (isset($this->optional_properties[$name])) {
return $this->optional_properties[$name];
}
return NULL;
}
/**
* Setter
*
* @param string $name
* @param mixed $value
* @return mixed
*/
public function __set($name, $value) {
if (is_string($value)) {
$value = trim($value);
}
if (isset($this->properties[$name])) {
return $this->properties[$name] = $value;
}
if (isset($this->optional_properties[$name])) {
return $this->optional_properties[$name] = $value;
}
return NULL;
}
/**
* Set attributes for a property
*
* @param string $name
* @param array $attr
* @return mixed
*/
public function setAttributes($name, $attr = array()) {
if (isset($this->properties[$name])) {
return $this->attributes[$name] = $attr;
}
if (isset($this->optional_properties[$name])) {
return $this->attributes[$name] = $attr;
}
return FALSE;
}
/**
* Get attributes of a property
*
* @param string $name
* @return array or null
*/
public function getAttributes($name) {
if (isset($this->attributes[$name])) {
return $this->attributes[$name];
}
return NULL;
}
/*
|--------------------------------------------------------------------------
| Import values
|--------------------------------------------------------------------------
*/
/**
* Set model data from array, xml or DOMNode
*
* @param mixed $source (array, xml string, DOMNode)
*/
public function fill($source = array()) {
if (is_array($source)) {
foreach ($source as $property => $value) {
$this->__set($property, $value);
}
return;
}
if (is_string($source)) {
$string = $source;
$source = new DOMDocument();
$source->loadXML($string);
}
$this->fromXml($source);
}
/**
* Parse xml nodes into model data
*
* @param DOMNode $node
*/
protected function fromXml(DOMNode $node) {
if (XML_DOCUMENT_NODE == $node->nodeType) {
$node = $node->documentElement;
}
foreach ($node->childNodes as $childNode) {
$name = $childNode->nodeName;
// set values
$this->__set($name, $this->parseValue($childNode));
// set attributes
$attr = [];
foreach ($childNode->attributes as $item) {
$attr[$item->localName] = $item->nodeValue;
}
$this->setAttributes($name, $attr);
}
}
/**
* Parse xml values for model's properties.
* You can override this method.
*
* @param DOMNode $node
* @return mixed
*/
protected function parseValue(DOMNode $node) {
return $node->nodeValue;
}
/*
|--------------------------------------------------------------------------
| Output as xml
|--------------------------------------------------------------------------
*/
/**
* Convert model data to DOMDocument object
*
* @return DOMDocument
*/
public function &toXML() {
$xml = $this->getXMLRoot();
$root = $xml->createElement($this->getRootName());
foreach ($this->properties as $property => $value) {
$root->appendChild($this->convert($property, $value));
}
foreach ($this->optional_properties as $property => $value) {
if (empty($value)) {
continue;
}
$root->appendChild($this->convert($property, $value));
}
$xml->appendChild($root);
$this->xml = NULL;
return $xml;
}
/**
* Export model data to xml text
*
* @param boolean $format_output
* @param boolean $with_header
* @return string
*/
public function toString($format_output = TRUE, $with_header = FALSE) {
$xml = $this->toXML();
$xml->formatOutput = $format_output;
return $xml->saveXML($with_header ? NULL : $xml->documentElement);
}
/**
* Cast object to xml string
*
* @return string
*/
public function __toString() {
return $this->toString(TRUE, TRUE);
}
/*
|--------------------------------------------------------------------------
| Helper functions
|--------------------------------------------------------------------------
*/
/**
* Initialize the root XML node [optional]
* @param $version
* @param $encoding
*/
protected function init($version = '1.0', $encoding = 'UTF-8') {
$this->xml = new DOMDocument($version, $encoding);
$this->encoding = $encoding;
$this->xml->preserveWhiteSpace = FALSE;
}
/**
* Convert an Array to XML
* @param string $node_name - name of the root node to be converted
* @param array $arr - aray to be converterd
* @return DOMNode
*/
protected function &convert($node_name, $arr = array()) {
$xml = $this->getXMLRoot();
$parsed = FALSE;
if (is_a($arr, __CLASS__)) {
$node = $xml->importNode($arr->toXML()->documentElement, TRUE);
$parsed = true;
} else {
$node = $xml->createElement($node_name);
}
if (isset($this->attributes[$node_name])) {
foreach ($this->attributes[$node_name] as $key => $value) {
if (!$this->isValidTagName($key)) {
throw new Exception('Illegal character in attribute [' . $key . '] in ' . $node_name);
}
$node->setAttribute($key, $this->bool2str($value));
}
}
if ($parsed) {
return $node;
}
// we check if it has any text value, if yes, append it.
if (!is_array($arr)) {
$node->appendChild($xml->createTextNode($this->bool2str($arr)));
return $node;
}
// create subnodes using recursion
// recurse to get the node for that key
foreach ($arr as $key => $value) {
$node->appendChild($this->convert($key, $value));
}
return $node;
}
/*
* Get the root XML node, if there isn't one, create it.
*/
protected function getXMLRoot() {
if (empty($this->xml)) {
$this->init();
}
return $this->xml;
}
/*
* Get string representation of boolean value
*/
protected function bool2str($v) {
//convert boolean to text value.
$v = true === $v ? 'true' : $v;
$v = false === $v ? 'false' : $v;
return $v;
}
/*
* Check if the tag name or attribute name contains illegal characters
* Ref: http://www.w3.org/TR/xml/#sec-common-syn
*/
protected function isValidTagName($tag) {
$pattern = '/^[a-z_]+[a-z0-9\:\-\.\_]*[^:]*$/i';
return preg_match($pattern, $tag, $matches) && $matches[0] == $tag;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.