Skip to content
Snippets Groups Projects
node.php 6.94 KiB
Newer Older
<?php
/**
 * Tree data structure, used for rendering purposes.
 * 
 * @author Taddeus Kroes
 * @version 1.0
 * @date 13-07-2012
 */

namespace WebBasics;

require_once 'base.php';

/**
 * Tree node.
 * 
 * Each tree node has a (non-unique) name, a list of variables, and zero or
 * more children.
 * 
 * @package WebBasics
 */
class Node extends Base {
	/**
	 * The number of Node instances, used to create unique node id's.
	 * 
	 * @var int
	 */
	private static $count = 0;
	
	/**
	 * The unique id of this Bloc.
	 * 
	 * @var int
	 */
	private $id;
	
	/**
	 * The node's name.
	 * 
	 * @var string
	 */
	private $name;
	
	/**
	 * An optional parent node.
	 * 
	 * If NULL, this node is the root of the data tree.
	 * 
	 * @var Node
	 */
	private $parent_node;
	
	/**
	 * Child nodes.
	 * 
	 * @var array
	 */
	private $children = array();
	
	/**
	 * Variables in this node.
	 * 
	 * All variables in a node are also available in its descendants through
	 * {@link get()}.
	 * 
	 * @var array
	 */
	private $variables = array();
	
	/**
	 * Constructor.
	 * 
	 * The id of the node is determined by the node counter.
	 * 
	 * @param string $name The node's name.
	 * @param Node|null &$parent_node A parent node (optional).
	 * @param int|null $id An id to assign. If none is specified, a new unique
	 *                     id is generated.
	 * @uses $count
	 */
	function __construct($name='', Node &$parent_node=null, $id=null) {
		$this->id = $id ? $id : ++self::$count;
		$this->name = $name;
		$this->parent_node = $parent_node;
	}
	
	/**
	 * Get the node's unique id.
	 * 
	 * @return int The node's id.
	 */
	function get_id() {
		return $this->id;
	}
	
	/**
	 * Get the node's name.
	 * 
	 * @return string The node's name.
	 */
	function get_name() {
		return $this->name;
	}
	
	/**
	 * Get the node's parent.
	 * 
	 * @return Node|null The parent node if any, NULL otherwise.
	 */
	function get_parent() {
		return $this->parent_node;
	}
	
	/**
	 * Get the node's children.
	 * 
	 * @return array A list of child nodes.
	 */
	function get_children() {
		return $this->children;
	}
	
	/**
	 * Check if a node is the same instance or a copy of this node.
	 * 
	 * @param Node $node The node to compare this node to.
	 * @return bool Whether the nodes have the same unique id.
	 */
	function is(Node $node) {
		return $node->get_id() == $this->id;
	}
	
	/**
	 * Check if this node is the root node of the tree.
	 * 
	 * A node is the root node if it has no parent.
	 * 
	 * @return bool Whether this node is the root node.
	 */
	function is_root() {
		return $this->parent_node === null;
	}
	
	/**
	 * Check if this node is a leaf node of the tree.
	 * 
	 * A node is a leaf if it has no children.
	 * 
	 * @return bool Whether this node is a leaf node.
	 */
	function is_leaf() {
		return !count($this->children);
	}
	
	/**
	 * Add a child node.
	 * 
	 * @param Node &$node The child node to add.
	 * @param bool $set_parent Whether to set this node as the child's parent
	 *                         (defaults to TRUE).
	 */
	function add_child(Node &$node, $set_parent=true) {
		$this->children[] = $node;
		$set_parent && $node->set_parent($this);
	}
	
	/**
	 * Add a child node.
	 * 
	 * @param string $name The name of the node to add.
	 * @param array $data Data to set in the created node (optional).
	 * @return Node The created node.
	 */
	function add($name, array $data=array()) {
		$node = new self($name, $this);
		$this->add_child($node, false);
		
		return $node->set($data);
	}
	
	/**
	 * Remove a child node.
	 * 
	 * @param Node &$child The node to remove.
	 */
	function remove_child(Node &$child) {
		foreach( $this->children as $i => $node )
			$node->is($child) && array_splice($this->children, $i, 1);
	}
	
	/**
	 * Remove this node from its parent.
	 * 
	 * @throws \RuntimeException If the node has no parent.
	 * @return Node This node.
	 */
	function remove() {
		if( $this->is_root() )
			throw new \RuntimeException('Cannot remove the root node of a tree.');
		
		$this->parent_node->remove_child($this);
		
		foreach( $this->children as $child )
			$child->set_parent(null);
		
		return $this;
	}
	
	/**
	 * Set the node's parent.
	 * 
	 * Removes this node as child of the original parent, if a parent was
	 * already set.
	 * 
	 * @param Node|null $parent The parent node to set.
	 * @return Node This node.
	 */
	function set_parent($parent) {
		if( $this->parent_node !== null )
			$this->parent_node->remove_child($this);
		
		$this->parent_node = &$parent;
		
		return $this;
	}
	
	/**
	 * Set the value of one or more variables in the node.
	 * 
	 * @param string|array $name Either a single variable name, or a set of name/value pairs.
	 * @param mixed $value The value of a single variable to set.
	 * @return Node This node.
	 */
	function set($name, $value=null) {
		if( is_array($name) ) {
			foreach( $name as $var => $val )
				$this->variables[$var] = $val;
		} else {
			$this->variables[$name] = $value;
		}
		
		return $this;
	}
	
	/**
	 * Get the value of a variable.
	 * 
	 * @param string $name The name of the variable to get the value of.
	 * @return mixed The value of the variable if it exists, NULL otherwise.
	 */
	function get($name) {
		// Variable inside this node?
		if( isset($this->variables[$name]) )
			return $this->variables[$name];
		
		// Variable in one of ancestors?
		if( $this->parent_node !== null )
			return $this->parent_node->get($name);
		
		// All nodes up to the tree's root node do not contain the variable
		return null;
	}
	
	/**
	 * Set the value of a variable.
	 * 
	 * This method provides a shortcut for {@link set()}.
	 * 
	 * @param string $name The name of the variable to set the value of.
	 * @param mixed $value The value to set.
	 */
	function __set($name, $value) {
		$this->set($name, $value);
	}
	
	/**
	 * Get the value of a variable.
	 * 
	 * This method provides a shortcut for {@link get()}.
	 * 
	 * @param string $name The name of the variable to get the value of.
	 * @return mixed The value of the variable if it exists, NULL otherwise.
	 */
	function __get($name) {
		return $this->get($name);
	}
	
	/**
	 * Find all child nodes that have the specified name.
	 * 
	 * @param string $name The name of the nodes to find.
	 * @return array The positively matched nodes.
	 */
	function find($name) {
		$has_name = function($child) use ($name) {
			return $child->get_name() == $name;
		};
		
		return array_values(array_filter($this->children, $has_name));
	}
	
	/**
	 * Create a copy of this node.
	 * 
	 * The copy will have the same list of children and variables. In case of
	 * a 'deep copy', the list of children is also cloned recursively.
	 * 
	 * @param bool $deep Whether to create a deep copy.
	 * @return Node A copy of this node.
	 */
	function copy($deep=false) {
		$copy = new self($this->name, $this->parent_node, $this->id);
		$copy->set($this->variables);
		
		foreach( $this->children as $child ) {
			if( $deep ) {
				$child_copy = $child->copy(true);
				$copy->add_child($child_copy);
			} else {
				$copy->add_child($child, false);
			}
		}
		
		return $copy;
	}
}

?>