An abstract dataset class in PHP

Share on Google+0Share on Facebook0Tweet about this on TwitterEmail this to someone

From my Delphi days, I remember one of its most interesting features: The TDataset class. It was an abstract class used to implement data access from various data sources, including but not limited to databases. One had to derive a TDataset descendant, by implementing its abstract methods, in order to specify the access details of a particular data source. The latter could be an exotic (R)DBMS (all popular ones were already supported out-of-the-box), a csv file or even a dynamic set of data produced on-the-fly.

Having to code less was one obvious convenience, but the best part was the cooperation of your dataset with the rest of the framework. Other components (Delphi used to call them data-aware) worked smoothly with any TDataset descendant. Consequently, a number of third-party vendors produced a variety of data access components, making Delphi one of the best choices for developing database client applications. Finally Lazarus adopted the same functionality for its own implementation of TDataset.

I missed that kind of functionality in PHP, from day one. Sure, it comes with a variety of extensions for accessing several data sources, but it definitely lacks uniformality and, most important, extensibility. This article is an attempt to demonstrate the development of a very basic, abstract, dataset class. But first of all, lets define a dataset.

A dataset is an ordered set of tuples that have a predefined record organization corresponing to the dataset’s fieldset (an ordered set of field definitions).

This definition seems to describe something similar to a table of rows and columns. In both cases, to access the containing data, we have to locate the corresponding data cell. A cell belongs to exactly one tuple and one column (field) so the cell’s coordinates would be (1) the tuple’s order and (2) the field’s name. For example, the address of the 65th entry in a phonebook would be, in PHP terms, $phonebook->cell(65,’address’). In other words, if ‘address’ is 4th in the fieldset, the data in question would be the 4th part of $phonebook->tuple(65). Finally, supposing that $t=$phonebook->tuple_assoc(65) gives an associative array with field names as keys, we would need to do $addr=$t[‘address’].

Now, a field definition is something that really depends on the application. It’s a convention between the dataset derivative and the code that uses it, so I would prefer leaving it arbitary. All field definitions should be returned by a fields() method, provided that the return value is an associative array with field names as keys. Having said that, the field names are array_keys($this->fields()).

One feature that i’d like to have is being able to do foreach($phonebook as $num=>$entry) and then nest a foreach($entry as $field=>$data). In order to accomplish the first, our class should implement the Iterator interface. This means keeping an internal data pointer and implementing five methods: rewind(), current(), key(),next() and valid(). For the second, given that $this->position is the internal data pointer, making the current() method return $this->tuple_assoc($this->position) should be enough. As for the rest of the Iterator functionality, rewind() should set the internal pointer to zero, key() should return it, next() should advance it and valid() should check if it is less than $this->num_rows(), which apparently returns the total number of tuples.

Now let’s put it all together in a PHP class:

<?php
abstract class Dataset 
    implements Iterator
	{
	private $position=0;
	abstract function num_rows();
	abstract function cell($row,$field);
    // fields
	abstract function fields();
	final function field_names() { return array_keys($this->fields()); }
	// tuples
	final function tuple($row)
		{
		$r=array();
		foreach($this->field_names() as $f) $r[]=$this->cell($row,$f);
		return $r;
		}
	final function tuple_assoc($row)
		{
		return array_combine($this->field_names(),$this->tuple($row));
		}
	// iterator stuff
	final function rewind() { $this->position=0; }
	final function current() { return $this->tuple_assoc($this->position); }
	final function key() { return $this->position; }
	final function next() { ++$this->position; } 
	final function valid() { return $this->position<$this->num_rows(); }
	}

So a Dataset descendant should specify three things: (1) How many tuples are there (num_rows()), (2) the field definitions (fields()) and (3) how a cell content is retrieved (cell()). Note that no insert/update/delete functionality is provided, because not necessarily all datasets would need them.

Examples

A null dataset has no tuples and no fields, so:

<?php
class NullDataset extends Dataset
    {
	function fields() { return array(); }
	function num_rows() { return 0; }
	function cell() { return NULL; }
	}

If you wanted to view an array as a dataset, here’s how you would do it:

<?php
class SuitsDataset extends Dataset
    {
    private $arr(
    	array('♠','Spades','black'),
		array('♥','Hearts','red'),
		array('♦','Diamonds','red'),
		array('♣','Clubs','black'),
		);
	function fields() { return array('suit'=>array(),'suit_name'=>array(),'color'=>array()); }	
	function num_rows() { return count($this->arr); }
	function cell($n,$field) 
        { 
        $k=array_search($field,$this->field_names());
        return $this->arr[$n][$k]; 
        }
	}

The $_SESSION variable is an associative array containing user session data. Below is a one-tuple dataset containing the $_SESSION data:

<?php
class SessionDataset extends Dataset
    {
    function fields() 
        { 
        $r=array();
        foreach($_SESSION as $key=>$value) $r[$key]=array();
        return $r; 
        }
	function num_rows() { return 1; }
	function cell($n,$field) { return $_SESSION[$field]; }
	}

Finally, the following piece of code renders a dataset in a <table>:

<?php
echo "<table><tr>";
$dataset=new SuitsDataset();
foreach($dataset->field_names() as $f) echo "<th>$f</th>";
echo "</tr>"
foreach($dataset as $tuple)
    {
    echo "<tr>";
    foreach($row as $v) echo "<td>$v</td>";
    echo "</tr>";
    }
echo "</table>";

That’s it for now. See you next time!

VN:F [1.9.22_1171]
Rating: 0.0/5 (0 votes cast)
Share on Google+0Share on Facebook0Tweet about this on TwitterEmail this to someone

Submit comment

Allowed HTML tags: <a href="http://google.com">google</a> <strong>bold</strong> <em>emphasized</em> <code>code</code> <blockquote>
quote
</blockquote>