The Standard PHP Library (SPL) is a great addition to PHP 5. It provides a number of very useful facilities that expose some of PHP’s internal functionality and allow the “userland” developer to write objects that are capable of behaving like arrays, or that transparently implement certain iterative design patterns to PHP’s own core functionality, so that you, for example, use aforeach()construct to loop through an object as if it were an array, or even access its individual elements using the array operator[].
SPL works primarily by providing a number of interfaces that can be used to im- plement the functionality required to perform certain operations. By far, the largest number of patterns exposed by SPL are iterators; they allow, among other things:
• Simple Iteration • Seekable Iteration • Recursive Iteration • Filtered Iteration
Accessing Objects as Arrays
TheArrayAccessinterface can be used to provide a means for your object to expose themselves as pseudo-arrays to PHP:
interface ArrayAccess {
function offsetSet($offset, $value); function offsetGet($offset);
function offsetUnset($offset); function offsetExists($offset); }
This interface provides the basic methods required by PHP to interact with an array: • offsetSet()sets a value in the array
• offsetGet()retrieves a value from the array • offsetUnset()removes a value from the array
• offsetExists()determines whether an element exists
As a very quick example, consider the following class, which “emulates” an array that only accepts elements with numeric keys:
class myArray implements ArrayAccess { protected $array = array();
function offsetSet ($offset, $value) {
if (!is_numeric ($offset)) {
throw new Exception ("Invalid key $offset"); }
164 ” Elements of Object-oriented Design
$this->array[$offset] = $value; }
function offsetGet ($offset) { return $this->array[$offset]; }
function offsetUnset ($offset) {
unset ($this->array[$offset]); }
function offsetExists ($offset) {
return array_key_exists ($this->array, $offset); }
}
$obj = new myArray(); $obj[1] = 2; // Works.
$obj[’a’] = 1; // Throws exception.
As you can see, this feature of SPL provides you with an enormous amount of con- trol over one of PHP’s most powerful (and most useful) data types. Used properly,
ArrayAccessis a great tool for building applications that encapsulate complex be- haviours in a data type that everyone is used to.
Simple Iteration
TheIteratorinterface is the simplest of the iterator family, providing simple itera- tion over any single-dimension array. It looks like this:
interface Iterator {} function current(); function next(); function rewind(); function key(); function valid(); function seek($key); }
You can see a simple implementation of the interface that allows iteration over a private property containing a simple array:
class myData implements iterator { private $_myData = array(
"foo", "bar", "baz", "bat"); private $_current = 0; function current() { return $this->myData[$this->current]; } function next() { $this->current += 1; } function rewind() { $this->current = 0; } function key() { return $this->current; } function valid() { return isset($this->myData[$this->current]); } }
$data = new myData();
foreach ($data as $key => $value) {
echo "$key: $value\n"; }
This example will iterate over each of the four elements in themyDataprivate property in the exact same wayforeach()works on a standard Array.
Seekable Iterators
The next step up from a standard Iterator is theSeekableIterator, which extends the standardIteratorinterface and adds aseek()method to enable the ability to retrieve a specific item from internal data store. Its interface looks like this:
166 ” Elements of Object-oriented Design interface SeekableIterator { function current(); function next(); function rewind(); function key(); function valid(); function seek($index); } Recursive Iteration
Recursive Iteration allows looping over multi-dimensional tree-like data structures. SimpleXML, for example, uses recursive iteration to allow looping through complex XML document trees.
To understand how this works, consider the following complex array:
$company = array(
array("Acme Anvil Co."),
array( array( "Human Resources", array( "Tom", "Dick", "Harry" ) ), array( "Accounting", array( "Zoe", "Duncan", "Jack", "Jane" ) ) ) );
<h1>Company: Acme Anvil Co.</h1> <h2>Department: Human Resources</h2> <ul> <li>Tom</li> <li>Dick</li> <li>Harry</li> </ul> <h2>Department: Accounting</h2> <ul> <li>Zoe</li> <li>Duncan</li> <li>Jack</li> <li>Jane</li> </ul>
By extending RecursiveIteratorIterator, we can define thebeginChildren() and
endChildren() methods so that our class can output the start and end <ul> tags
without any of the complexities normally associated with recursion (such as, for ex- ample, keeping track of multiple nested levels of nesting). The example shown be- low defines two classes, our customRecursiveIteratorIteratorand a very simple
RecursiveArrayObject:
class Company_Iterator extends RecursiveIteratorIterator { function beginChildren()
{
if ($this->getDepth() >= 3) {
echo str_repeat("\t", $this->getDepth() - 1);
echo "<ul>" . PHP_EOL; }
}
function endChildren() {
if ($this->getDepth() >= 3) {
echo str_repeat("\t", $this->getDepth() - 1);
echo "</ul>" . PHP_EOL; }
} }
class RecursiveArrayObject extends ArrayObject { function getIterator() {
168 ” Elements of Object-oriented Design
} }
Then, to produce our desired end result, we simply use this code:
$it = new Company_Iterator(new RecursiveArrayObject($company));
$in_list = false;
foreach ($it as $item) {
echo str_repeat("\t", $it->getDepth());
switch ($it->getDepth()) {
case 1:
echo "<h1>Company: $item</h1>" . PHP_EOL;
break;
case 2:
echo "<h2>Department: $item</h2>" . PHP_EOL;
break;
default:
echo "<li>$item</li>" . PHP_EOL; }
}
Filtering Iterators
TheFilterIteratorclass can be used to filter the items returned by an iteration:
class NumberFilter extends FilterIterator { const FILTER_EVEN = 1;
const FILTER_ODD = 2;
private $_type;
function __construct($iterator, $odd_or_even = self::FILTER_EVEN) { $this->_type = $odd_or_even; parent::__construct($iterator); } function accept() { if ($this->_type == self::FILTER_EVEN) { return ($this->current() % 2 == 0);
} else {
return ($this->current() % 2 == 1); }
} }
$numbers = new ArrayObject(range(0, 10)); $numbers_it = new ArrayIterator($numbers);
$it = new NumberFilter($numbers_it, NumberFilter::FILTER_ODD);
foreach ($it as $number) {
echo $number . PHP_EOL; }
Theaccept()method simply determines whether any given element should be al- lowed in the iteration; note thatFilterIteratoralready implements all of the meth- ods ofArrayAccess, so that, effectively, from the outside our class can still be used as an array.
This example outputs only the odd numbers stored in the array:
1 3 5 7 9
Summary
Object Oriented programming, coupled with Design Patterns—including those pro- vided by SPL—is the key to re-usable and highly modular code.
The more forethought you give your design, the more likely you are to be able to re-use at least some of it, later, saving time and effort in the future—not to mention that proper design techniques also make code easier to maintain and extend.
Bringing this experience to the table is what makes you truly versatile; as we men- tioned, design patterns are, after all, largely language- and problem-independent.