Data Object that is a multi-dimensional Array and Iterable

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

Post Reply
santosj
Forum Contributor
Posts: 157
Joined: Sat Apr 29, 2006 7:06 pm

Data Object that is a multi-dimensional Array and Iterable

Post by santosj »

I've been pounding my head recently on how to create a system where I don't have to rewrite a ton of code over and over again that does the same thing. The concept of Data Objects is new to me, I've always encapulated functionality in a single class design pattern. Recently, I've been doing the whole Factory thing and loving it.

My thoughts however are if something of this complexity can be done in a better way.

Code: Select all

class Section implements ArrayAccess, Iterator
{
	/**
	 * Stores the Section Name for Iterator
	 * @name array $section
	 * @access private
	 */
	private $section = '';
	
	/**
	 * Stores the Name Data
	 * @name array $nameStorage
	 * @access private
	 */
	private $nameStorage = array();
	
	/**
	 * Stores the Section DataStore
	 * @name array $subsectionStorage
	 * @access private
	 */
	private $subsectionStorage = array();
	
	/**
	 * Stores the Visibility DataStore
	 * @name array $visibilityStorage
	 * @access private
	 */
	private $visibilityStorage = array();
	
	/**
	 * Stores the Restictions DataStore
	 * @name array $restrictionsStorage
	 * @access private
	 */
	private $restrictionsStorage = array();
	
	function __construct($section)
	{
		$this->section = $section;
	}
	
	public function name($name, $value = false)
	{
		if($value !== false)
		{
			// Test Restrictions
			$restrictions = $this->restrictions($name);
			if($restrictions !== false)
			{
				$accessdenied = false;
				if(array_search($value, $restrictions) === false)
				{
					$accessdenied = true;
				}
				
				// @todo Filters should use Security Filters Factory
				if(array_search('alpha_filter', $restrictions) !== false)
				{
					if(ctype_alpha($value) == false)
					{
						throw new RestrictedValueException();
					}
					else
					{
						$accessdenied = false;
					}
				}
				
				if(array_search('alnum_filter', $restrictions) !== false)
				{
					if(ctype_alnum($value) == false)
					{
						throw new RestrictedValueException();
					}
					else
					{
						$accessdenied = false;
					}
				}
				
				if(array_search('hex_filter', $restrictions) !== false)
				{
					if(ctype_xdigit($value) == false)
					{
						throw new RestrictedValueException();
					}
					else
					{
						$accessdenied = false;
					}
				}
				
				if(array_search('num_filter', $restrictions) !== false)
				{
					if(ctype_digit($value) == false)
					{
						throw new RestrictedValueException();
					}
					else
					{
						$accessdenied = false;
					}
				}
				
				if($accessdenied === true)
				{
					throw new RestrictedValueException();
				}
			}
			
			// If Restrictions past, then update value.
			if(array_key_exists($name, $this->nameStorage) and ($value != NULL))
			{
				$count = count($this->nameStorage[$name]);
				if($count > 1)
				{
					$this->nameStorage[$name][] = $value;
				}
				else if($count == 1)
				{
					$this->nameStorage[$name] = array($this->nameStorage[$name], $value);
				}
			}
			else if(array_key_exists($name, $this->nameStorage) and ($value == NULL))
			{
				unset($this->nameStorage[$name]);
			}
			else
			{
				$this->nameStorage[$name] = $value;
			}
		}
		else if(array_key_exists($name, $this->nameStorage) and $value === false)
		{
			return $this->nameStorage[$name];
		}
		
		return false;
	}
	
	public function section($section)
	{
		if(array_key_exists($section, $this->subsectionStorage))
		{
			return $this->subsectionStorage[$section];
		}
		else
		{
			$this->subsectionStorage[$section] = new ConfigurationSection;
			return $this->subsectionStorage[$section];
		}
	}
	
	public function restrictions($name, $value = false)
	{
		if($value !== false)
		{
			if(array_key_exists($name, $this->restrictionsStorage))
			{
				$count = count($this->restrictionsStorage[$name]);
				if($count > 1)
				{
					$this->restrictionsStorage[$name][] = $value;
				}
				else if($count == 1)
				{
					$this->restrictionsStorage[$name][] = $this->restrictionsStorage[$name];
					$this->restrictionsStorage[$name][] = $value;
				}
			}
			else
			{
				$this->restrictionsStorage[$name] = $value;
			}
		}
		else if(array_key_exists($name, $this->restrictionsStorage) and $value === false)
		{
			return $this->restrictionsStorage[$name];
		}
		
		return false;
	}
	
	public function visiblity($name, $value = false)
	{
		if($value !== false)
		{
			switch($value)
			{
				case 'show': break;
				case 'hide': break;
				default: $value = 'show'; break;
			}
			
			$this->restrictionsStorage[$name] = $value;
		}
		else if(array_key_exists($name, $this->visibilityStorage) and $value === false)
		{
			return $this->visibilityStorage[$name];
		}
		
		return false;
	}
	
	public function isUnique($name)
	{
		if(($this->nameExists($name) === true) and ($this->sectionExists($name) === true))
		{
			return false;
		}
		
		return true;
	}
	
	public function sectionExists($section)
	{
		if(array_key_exists($section, $this->subsectionStorage))
		{
			return true;
		}
		
		return false;
	}
	
	public function sectionUnset($section)
	{
		if(array_key_exists($section, $this->subsectionStorage))
		{
			unset($this->subsectionStorage[$section]);
		}
	}
	
	public function nameExists($name)
	{
		if(array_key_exists($name, $this->nameStorage))
		{
			return true;
		}
		
		return false;
	}
	
	// ArrayAccess
	public function offsetExists($name)
	{
		if(($this->sectionExists($name) === true) or ($this->nameExists($name) === true))
		{
			return true;
		}
		
		return false;
	}
	
	public function offsetUnset($name)
	{
		if(($this->offsetExists($name) === true) and ($this->isUnique($name) === true))
		{
			if($this->nameExists($name) === true)
			{
				$this->name($name, NULL);
				return true;
			}
			else if($this->sectionExists($name) === true)
			{
				$this->sectionUnset($name);
				return true;
			}
		}
		
		return false;
	}
	
	public function offsetSet($name, $value)
	{
		try
		{
			return $this->name($name, $value);
		}
		catch(RestrictedValueException $r)
		{
			throw $r;
		}
	}
	
	public function offsetGet($name)
	{
		if($this->isUnique($name) === false)
		{
			$array = array();
			$array['name'] = $this->name($name);
			$array['section'] = $this->section($name);
			
			$restrictions = $this->restrictions($name);
			if(is_array($restrictions))
			{
				$array['restrictions'] = &$restrictions;
			}
			
			$visibility = $this->visibility($name);
			if($visibility !== false)
			{
				$array['visibility'] = $visibility;
			}
		}
		else if($this->nameExists($name) === true)
		{
			return $this->name($name);
		}
		else if($this->sectionExists($name) === true)
		{
			return $this->section($name);
		}
		else
		{
			return new ConfigurationSection;
		}
	}
	// End ArrayAccess
}
?>
I've yet to add the Iterator functionality, my question is should I? I mean, there is ArrayObject, which would probably do a better job of it, but then I would have to recode the class to use ArrayObject and I just can't do that since the documentation on why you would extend it isn't yet out there.

The second question is that I've been banging my head against the wall trying to figure out an intelligent way to return either the name or section without seeking ahead. The issue that in the multi-dimensional array is that the sections don't know what is in the subsections or what I mean is.

Code: Select all

$section = new Section('start');
$section['score'] = 0;
$section['players'] = 8;

// Lap 1
$section['race']['lap1']['score'] = 4;
$section['race']['lap1']['players'] = 6;

// Lap 2
$section['race']['lap2']['score'] = 8;
$section['race']['lap2']['players'] = 4;
For instance if you add

Code: Select all

$section['race'] = 2; // amount of laps in example
Then try to access $sections['race'], then do I return 2 or Section? There is no possible way to know if they are assigning $sections['race'] to a variable or if they want to access $sections['race']['lap1'].

The issue is solved in the class by creating an array if both the name and section exist.

Code: Select all

// To access name
$laps = $section['race']['name'];

// To access the subsection
$section['race']['section']['lap1']
The next issue is that they may end up with something such as this:

$section['race']['section']['lap1']['section']['subsection']['section']['subsection']

and that defeats the purpose of the class.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

I am not quite sure what you Section class does, but it looks like a super Value Object that can be treated as a multi-dimensional associative array. Cool idea. Not sure whether it is a good solution. And, unfortunately you are going to run into a brick wall with the lack of support for true accessors in PHP.
(#10850)
santosj
Forum Contributor
Posts: 157
Joined: Sat Apr 29, 2006 7:06 pm

Post by santosj »

arborint wrote:I am not quite sure what you Section class does, but it looks like a super Value Object that can be treated as a multi-dimensional associative array. Cool idea. Not sure whether it is a good solution. And, unfortunately you are going to run into a brick wall with the lack of support for true accessors in PHP.
I'm sorry, I don't understand the part about the "lack of support for true accessors in PHP." Could you point me to a resource or explain what you mean about that statement. Keep in mind that this is a PHP 5 class and won't work on PHP 4.

Since it implements ArrayAccess, it allows the class to have array access. The ArrayAccess functions only use the class modifer methods.

Well, I was contemplating the class while at work and I realized that it is a custom SPL ArrayObject class. The only issue is that the ArrayObject does not return an instance of itself, but rather what is in the array it is handled.

Purpose of the Class

I needed to have a way to store and access configuration values in a simple matter that would be easy for other developers also. The first class used SQLite and an example from PHP.net SQLite comment section. Then it came up that I needed to support INI files and I decided if I was going to support INI, then I'm going to support XML for configuration also.

I found out early on that I would be writing a bunch of the same code over and over again if I didn't create the section as its own class. The first class had only sections and name, value pairs, so I knew that the first array was a section and the next one was the name so no problem or complexity there.

XML and SQLite allows for support for multi-dimension array and for XML, it is pretty much couldn't be implemented without it.

Also, it is pretty wicked sweet, I feel like I'm in Computer Science class again coding new and improved things and like I'm a child in a candy store, after hours all alone with the sweet goodness.

My Thought Out Solution

I've decided that it would be far easier to split the name details out of the Section class into its own class. In this way, I can maintain the Section as a Dictionary Model (?) and use it for caching purposes later by using another class other than the name one which is for configuration.

The solution comes out of the __get PHP 5 magic method, which would allow me to return the name class.

So the example would be:

Code: Select all

$section = section('config');
$section['parent']->name = value;
$section['parent']['child']->anothername = value;
$section['parent']['child']['child']->name = value;
In this way, the section and the name can have the same key name and I'll know that if it is in array syntax that it is a section to return, and if it is a get syntax that it is name value.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

santosj wrote:I'm sorry, I don't understand the part about the "lack of support for true accessors in PHP." Could you point me to a resource or explain what you mean about that statement. Keep in mind that this is a PHP 5 class and won't work on PHP 4.
I mean that the __get()/__set() functions are error handlers -- not accessor functions -- so you can really build full accessor functionality. You can even build a standard Container without resorting to get() and set() functions or limiting how you use it.
santosj wrote:I found out early on that I would be writing a bunch of the same code over and over again if I didn't create the section as its own class. The first class had only sections and name, value pairs, so I knew that the first array was a section and the next one was the name so no problem or complexity there.
I guess it is not clear to me what you mean by "Section"? Also, it seems like a lot of code and there is more functionality than you mention (such as visiblity).
(#10850)
santosj
Forum Contributor
Posts: 157
Joined: Sat Apr 29, 2006 7:06 pm

Post by santosj »

Visilibility is a feature of the name for hiding sensitive information from iterations.
Post Reply