Nested data via DOM
Een oneindige diepte maken voor bijvoorbeeld een menu, mappenstructuur. Nu met DOM. Het voordeel met werken met DOM is dat je op een relatief gemakkelijke manier de parent node en en de child nodes kunt bereiken. In principe maakt het van 2 dimensionale data (bijvoorbeeld uit een database) een geneste DOMDocument. Aangezien DOMDocument af en toe toch nog best lastig is, heb ik er het e.e.a. omheengebouwd zodat alles simpel te benaderen is. Zoals je ziet heten mijn classes: - Nested - Nested_Child - Nested_Children - Nested_Child_Properties - Nested_Exception Deze komen dus overeen met de bestanden: - Nested.php - Nested/Child.php - Nested/Children.php - Nested/Child/Properties.php - Nested/Exception.php Het voorbeeld staat helemaal onderaan. Het komt er op neer dat je eerst de data in de class gooit met de Nested->addChild() method. De eerste parameter is het ID, de 2e is het id van het parent element. De derde parameter is de bijbehorende data bij het element (de 'properties'). Vervolgens kan je direct al met Nested->getElements() de elementen loopen, omdat getElements() een iteratable object terug geeft (Nested_Children) waar de elementen (Nested_Child) inzitten. Het Nested_Child element heeft een aantal methods, bijvoorbeeld: - getParent() - deze geeft het Nested_Child object terug van het parent element - getChildren() - deze geeft een Nested_Children object terug met een verzameling van Nested_Child objects - getProperties() - deze geeft het Nested_Child_Properties object terug. Deze kan je met de __get() magic method benaderen om de data snel op te vragen. Dit object kan je tevens loopen met bijvoorbeeld foreach en while. De Nested class zelf heeft naast addChild() en getElements() ook nog een method getElement() Met deze method ga je zoeken naar een bepaald element. Deze geeft dan ook een Nested_Child object terug. Je kunt ook gebruik maken van xPath. Bijvoorbeeld //*[@tekst="bla"] om alle children te selecteren die een property tekst hebben met bla. De Nested class heeft ook nog een debug() method, deze zet wat xml op het scherm, zodat je snel de structuur van je elementen boom kunt zien
<?php
// Nested.php
include 'Nested/Exception.php';
include 'Nested/Child.php';
include 'Nested/Children.php';
include 'Nested/Child/Properties.php';
class Nested {
/**
*
* @var DOMDocument
*/
protected $doc;
/**
* Properties stack
* Each instance of Nested has its own array in this collection. The key of that array is the
* instance id
* @var array
*/
static public $propertiesCollection = array();
/**
* The number of instances of Nested
* @var int
*/
static protected $instance_count = 0;
/**
* The instance number. this is the key for the properties array of self::$propertiesCollection
* @var int
*/
protected $instance_id;
/**
* If an element is added, when the parent element isn't already added, the element
* will stored (temporary) in this array
*
* @var array
*/
protected $noParentCollection = array();
/**
* Create the DOMDocument object
*/
public function __construct(){
$this->instance_id = ++self::$instance_count;
self::$propertiesCollection[$this->instance_id] = array();
$this->doc = new DOMDocument();
}
/**
*
* @param $id
* @param $parent_id
* @param $properties
* @return unknown_type
*/
public function addChild($id,$parent_id=0,$properties=array()){
if(!(ctype_digit($id) || is_int($id)) || $id <= 0 || isset(self::$propertiesCollection[$this->instance_id][$id])){
throw new Nested_Exception('The ID must be a unique positive integer');
}
// Properties
self::$propertiesCollection[$this->instance_id][$id] = new Nested_Child_Properties($properties);
// Create the element
$newElmt = $this->doc->createElement('child');
// Add ID attribute
$idAttr = $this->doc->createAttribute('id');
$idAttr->appendChild($this->doc->createTextNode('id'.$id));
$newElmt->appendChild($idAttr);
$newElmt->setIdAttribute('id',true);
// Add the other attributes. These attributes can you use to create xPath queries
foreach($properties as $key => $property){
if($key != 'id'){
$attr = $this->doc->createAttribute((string)$key);
$attr->appendChild($this->doc->createTextNode((string)$property));
$newElmt->appendChild($attr);
}
}
// If there's a parent element found, append this new element to the parent element
if($parent_id > 0){
$parentElmt = (int)$parent_id > 0 ? $this->doc->getElementById('id'.$parent_id) : null;
if($parentElmt instanceof DOMElement){
$parentElmt->appendChild($newElmt);
}else{
// The parent is not found, save the element and wait till the parent element
// gets added
$this->noParentCollection[$parent_id] = $newElmt;
}
}else{
$this->doc->appendChild($newElmt);
}
// There is a child element of this element who is already added
if(isset($this->noParentCollection[$id])){
$newElmt->appendChild($this->noParentCollection[$id]);
unset($this->noParentCollection[$id]);
}
}
/**
*
* @param int $id
* @return Nested_Child
*/
public function getElement($id){
$elmt = $this->doc->getElementById('id'.$id);
if($elmt instanceof DOMElement){
return new Nested_Child($elmt,$this->instance_id);
}
throw new Nested_Exception('This element does not exists');
}
/**
* Get the elements at the 'root' level
* @return Nested_Children
*/
public function getElements(){
$childElmts = $this->doc->childNodes;
$children = new Nested_Children();
foreach($childElmts as $elmt){
$children->addChild(new Nested_Child($elmt,$this->instance_id));
}
return $children;
}
/**
* You can run xPath queries too to select child elements
* @param string $query
* @return unknown_type
*/
public function xPath($query){
$xpath = new DOMXPath($this->doc);
$result = $xpath->evaluate($query);
if($result instanceof DOMNodeList){
$children = new Nested_Children();
foreach($result as $elmt){
if($elmt instanceof DOMElement){
$children->addChild(new Nested_Child($elmt,$this->instance_id));
}
}
return $children;
}
return $result;
}
/**
* Shows the hierarchy in XML
* @param int $id If you want to view the hierarchy of an element
* @return void
*/
public function debug($id=null){
$this->doc->formatOutput = true;
$node = null;
if(!empty($id)){
$node = $this->getElement($id)->getDOMElement();
}
echo '<pre>'.htmlentities($this->doc->saveXML($node),true).'</pre>';
}
/**
* Clear the memory
* @return void
*/
public function __destruct(){
unset($this->doc);
}
}
// Nested/Child.php
class Awf_Nested_Child {
/**
*
* @var DOMElement
*/
protected $elmt;
/**
*
* @var int
*/
protected $id;
protected $nested_instance_id;
/**
*
* @param DOMElement $elmt
*/
public function __construct(DOMElement $elmt,$nested_instance_id){
$this->elmt = $elmt;
$this->nested_instance_id = $nested_instance_id;
}
/**
* Get the element ID
* @return int
*/
public function getId(){
if(empty($this->id)){
$this->id = (int) substr($this->elmt->getAttribute('id'),2);
}
return $this->id;
}
/**
* Get the parent element
* @return Awf_Nested_Child
*/
public function getParent(){
$parent = $this->elmt->parentNode;
if($parent instanceof DOMElement){
return new Awf_Nested_Child($parent,$this->nested_instance_id);
}
throw new Awf_Nested_Exception('This parent element does not exists');
}
/**
* Get the element properties
* @return Awf_Nested_Child_Properties
*/
public function getProperties(){
$properties = isset(Awf_Nested::$propertiesCollection[$this->nested_instance_id][$this->getId()]) ?
Awf_Nested::$propertiesCollection[$this->nested_instance_id][$this->getId()] :
new Awf_Nested_Child_Properties(array());
return $properties;
}
/**
* Check if the element has child nodes
* @return bool
*/
public function hasChildren(){
return $this->elmt->hasChildNodes();
}
/**
* Get the element children
* @return Awf_Nested_Children
*/
public function getChildren(){
$childElmts = $this->elmt->childNodes;
$children = new Awf_Nested_Children();
foreach($childElmts as $elmt){
$children->addChild(new Awf_Nested_Child($elmt,$this->nested_instance_id));
}
return $children;
}
/**
*
* @return DOMElement
*/
public function getDOMElement(){
return $this->elmt;
}
}
// Nested/Children.php
class Nested_Children implements Iterator, Countable {
/**
* The children
*
* @var array
*/
protected $children = array();
/**
* Add a child
*
* @param Nested_Child $child
*/
public function addChild(Nested_Child $child){
$this->children[$child->getId()] = $child;
}
/**
* Get a child
*
* @param string $key The ID of the child
* @return Nested_Child
*/
public function __get($key){
if(isset($this->children[$key])){
return $this->children[$key];
}
return false;
}
/**
* If the Child is already added to the children
*
* @param string $key The ID of the child
* @return bool true if the child isset, otherwise false
*/
public function __isset($key){
return isset($this->children[$key]);
}
/**
* Count the children
*
* @return int
*/
public function count(){
return count($this->children);
}
/**
*
* @return bool
*/
public function rewind() {
reset($this->children);
}
/**
*
* @return Nested_Child
*/
public function current() {
return current($this->children);
}
/**
* @var string|int
*/
public function key() {
return key($this->children);
}
/**
*
* @return Nested_Child|false
*/
public function next() {
return next($this->children);
}
/**
*
* @return bool
*/
public function valid() {
return $this->current() !== false;
}
}
// Nested/Child/Properties.php
class Nested_Child_Properties implements Iterator, Countable {
/**
* The properties of a element
*
* @var array
*/
protected $properties = array();
/**
* Create a properties instance
*
* @param array $properties
*/
public function __construct(array $properties){
$this->properties = $properties;
}
/**
* Return a property
*
* @param string $key Property Key
* @return mixed The property value, if the property does not exists, it returns false
*/
public function __get($key){
if(isset($this->properties[$key])){
return $this->properties[$key];
}
return false;
}
/**
* Set a property
*
* @param string $key
* @param mixed $value
*/
public function __set($key,$value){
$this->properties[$key] = $value;
}
/**
* Checks if the property isset
*
* @param string $key
* @return bool True if the property isset, otherwise false
*/
public function __isset($key){
return isset($this->properties[(string)$key]);
}
public function count(){
return count($this->properties);
}
/**
* Make the properties iteratable
*/
public function rewind() {
reset($this->properties);
}
public function current() {
return current($this->properties);
}
public function key() {
return key($this->properties);
}
public function next() {
return next($this->properties);
}
public function valid() {
return $this->properties() !== false;
}
}
// Nested/Exception.php
class Nested_Exception extends Exception {
}
// Voorbeeld
$i = 1;
$nested = new Nested();
// Elementen toevoegen
$nested->addChild($i,0,array('id'=>$i++,'naam'=>'bar','tekst'=>'foo'));
$nested->addChild($i,0,array('id'=>$i++,'naam'=>'bar','tekst'=>'foo'));
// Zoals je ziet, heeft dit element de parent met id=5. Deze is helaas nog
//niet toegevoegd. Daarom zal dit element even tijdelijk worden opgeslagen
//en wordt alsnog toegevoegd als child van id=5 als het element met id=5
//toegevoegd wordt.
$nested->addChild($i,5,array('id'=>$i++,'naam'=>'bar','tekst'=>'foo'));
$nested->addChild($i,1,array('id'=>$i++,'naam'=>'bar','tekst'=>'foo'));
// Nu wordt dus het element met ID=3 toegevoegd aan dit element
$nested->addChild($i,2,array('id'=>$i++,'naam'=>'bar','tekst'=>'foo'));
$nested->addChild($i,2,array('id'=>$i++,'naam'=>'bar','tekst'=>'foo'));
$children = $nested->getElement(1)->getChildren();
foreach($children as $child){
echo $child->getProperties()->tekst.'<br />';
}
echo $nested->getElement(4)->getParent()->getProperties()->naam;
$nested->debug();
?>
Reacties
0