Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / PHP

Pro PHP: Chapter 9: Introduction to the Standard PHP Library

5.00/5 (3 votes)
28 Apr 2008CPOL14 min read 1  
The Standard PHP Library provides support for advanced OOP concepts in PHP. This sample chapter introduces SPL features such as indexers and iterators, the observer/reporter pattern, array overloading, and more.

Introduction

The Standard PHP Library (SPL) is where PHP 5's object-oriented capabilities truly shine. It improves the language in five key ways: iterators, exceptions, array overloading, XML, and file and data handling. It also provides a few other useful items, such as the observer pattern, counting, helper functions for object identification, and iterator processing. Additionally, it offers advanced functionality for autoloading classes and interfaces. This chapter introduces you to this very important library, and in the following chapters, you will learn more about some of the advanced SPL classes.

SPL is enabled by default and is available on most PHP 5 systems. However, because SPL's features were dramatically expanded with the PHP 5.2 release, I recommend using that version or newer if you want to truly take advantage of this library.

SPL Fundamentals

The SPL is a series of Zend Engine 2 additions, internal classes, and a set of PHP examples. At the engine level, the SPL implements a set of six classes and interfaces that provide all the magic.

These interfaces and the Exception class are special in that they are not really like a traditional interface. They have extra powers and allow the engine to hook into your code in a specific and special way. Here are brief descriptions of these elements:

ArrayAccess: The ArrayAccess interface allows you to create classes that can be treated as arrays. This ability is commonly provided by indexers in other languages.

Exception: The Exception class was introduced in Chapter 4. The SPL extension contains a series of enhancements and classifications for this built-in class.

Iterator: The Iterator interface makes your objects work with looping structures like foreach. This interface requires you to implement a series of methods that define which entries exist and the order in which they should be retrieved.

IteratorAggregate: The IteratorAggregate interface takes the Iterator concept a bit further and allows you to offload the methods required by the Iterator interface to another class. This lets you use some of the other SPL built-in iterator classes so you can gain iterator functionality without needing to implement Iterator's methods in your class directly.

Serializable: The Serializable interface hooks into the Serialize and Unserialize functions, as well as any other functionality, like sessions, that may automatically serialize your classes. Using this interface, you can ensure that your classes can be persisted and restored properly. Without it, storing object data in sessions can cause problems, especially where resource type variables are used.

Traversable: The Traversable interface is used by the Iterator and IteratorAggregate interfaces to determine if the class can be iterated with foreach. This is an internal interface and cannot be implemented by users; instead, you implement Iterator or IteratorAggregate.

In the rest of this chapter, we'll take a closer look at some of the SPL features, beginning with iterators.

Iterators

Iterators are classes that implement the Iterator interface. By implementing this interface, the class may be used in looping structures and can provide some advanced data-access patterns.

Iterator Interface

The Iterator interface is defined internally in C code, but if it were represented in PHP, it would look something like Listing 9-1.

Listing 9-1. The Iterator Interface

PHP
interface Iterator { 
  public function current(); 
  public function key(); 
  public function next(); 
  public function rewind(); 
  public function valid(); 
} 

Note
You do not need to declare Iterator or any other SPL interface yourself. These interfaces are automatically provided by PHP.

All iterable objects are responsible for keeping track of their current state. In a normal array, this would be called the array pointer. In your object, any type of variable could be used to track the current element. It is very important to remember the position of elements, as iteration requires stepping through elements one at a time.

Table 9-1 lists the methods in the Iterator interface. Figure 9-1 shows the flow of the Iterator interface methods in a foreach loop.

Table 9-1. Iterator Interface Methods

Method Description
current() Returns the value of the current element
key()Returns the current key name or index
next()Advances the array pointer forward one element
rewind()Moves the pointer to the beginning of the array
valid()Determines if there is a current element; called after calls to next() and rewind()

foreach Iterator method flow

Figure 9-1. foreach Iterator method flow

Uses for iterators range from looping over objects to looping over database result sets, and even looping around files. In the next chapter, you will learn about the types of built-in iterator classes and their uses.

Iterator Helper Functions

Several useful convenience functions can be used with iterators:

iterator_to_array($iterator): This function can take any iterator and return an array containing all the data in the iterator. It can save you some verbose iteration and array-building loops when working with iterators.

iterator_count($iterator): This function returns exactly how many elements are in the iterator, thereby exercising the iterator.


Caution
The iterator_to_array($iterator) and iterator_count($iterator) functions can cause some spooky action if you call them on an iterator that does not have a defined ending point. This is because they require an internal exercise of the entire iterator. So if your iterator's valid() method will never return false, do not use these functions, or you will create an infinite loop.

iterator_apply(iterator, callback, [user data]): This function is used to apply a function to every element of an iterator, in the same way array_walk() is used on arrays. Listing 9-2 shows a simple iterator_apply() application.

Listing 9-2. Using iterator_apply

PHP
function print_entry($iterator) {
print( $iterator->current() );
return true;
}
$array = array(1,2,3);

$iterator = new ArrayIterator($array);
iterator_apply($iterator, 'print_entry', array($iterator));

This code outputs the following:

123

While the callback function returns true, the loop will continue executing. Once false is returned, the loop is exited.

Array Overloading

Array overloading is the process of using an object as an array. This means allowing data access through the [] array syntax. The ArrayAccess interface is at the core of this process and provides the required hooks to the Zend Engine.

ArrayAccess Interface

The ArrayAccess interface is described in Listing 9-3.

Listing 9-3. The ArrayAccess Interface

PHP
interface ArrayAccess { 
  public function offsetExists($offset); 
  public function offsetSet($offset, $value); 
  public function offsetGet($offset); 
  public function offsetUnset($offset); 
} 

Table 9-2 lists the methods in the ArrayAccess interface.

Table 9-2. ArrayAccess Interface Methods

Method Description
offsetExistsDetermines if a given offset exists in the array
offsetSetSets or replaces the data at a given offset
offsetGetReturns the data at a given offset
offsetUnsetNullifies data at a given offset

In the following chapters, you will be introduced to some of the advanced uses for array overloading.

Counting and ArrayAccess

When working with objects acting as arrays, it is often advantageous to allow them to be used exactly as an array would be used. However, by itself, an ArrayAccess implementer does not define a counting function and cannot be used with the count() function. This is because not all ArrayAccess objects are of a finite length.

Fortunately, there is a solution: the Countable interface. This interface is provided for just this purpose and defines a single method, as shown in Listing 9-4.

Listing 9-4. The Countable Interface

PHP
interface Countable { 
  public function count(); 
} 

When implemented, the Countable interface's count() method must return the valid number of elements in the Array object. Once Countable is implemented, the PHP count() function may be used as normal.

The Observer Pattern

The observer pattern is a very simple event system that includes two or more interacting classes.

This pattern allows classes to observe the state of another class and to be notified when the observed class's state has changed.

In the observer pattern, the class that is being observed is called a subject, and the classes that are doing the observing are called observers. To represent these, SPL provides the SplSubject and SplObserver interfaces, as shown in Listings 9-5 and 9-6.

Listing 9-5. The SplSubject Interface

PHP
interface SplSubject {
  public function attach(SplObserver $observer); 
  public function detach(SplObserver $observer); 
  public function notify(); 
} 

Listing 9-6. The SplObserver Interface

PHP
interface SplObserver {
  public function update(SplSubject $subject);
}

The idea is that the SplSubject class maintains a certain state, and when that state is changed, it calls notify(). When notify() is called, any SplObserver instances that were previously registered with attach() will have their update() methods invoked.

Listing 9-7 shows an example of using SplSubject and SplObserver.

Listing 9-7. The Observer Pattern

PHP
class DemoSubject implements SplSubject {
 
 private $observers, $value;
 
 public function __construct() {
  $this->observers = array();

 }

 public function attach(SplObserver $observer) {
  $this->observers[] = $observer;
 }

 public function detach(SplObserver $observer) {
  if($idx = array_search($observer,$this->observers,true)) {
    unset($this->observers[$idx]);
  }
 }

 public function notify() {
  foreach($this->observers as $observer) {
   $observer->update($this);
  }
 }

 public function setValue($value) {
  $this->value = $value;
  $this->notify();
 }

 public function getValue() {
  return $this->value;

 }
}

class DemoObserver implements SplObserver {
  
  public function update(SplSubject $subject) {
   echo 'The new value is '. $subject->getValue();
 }
}

$subject = new DemoSubject();
$observer = new DemoObserver();
$subject->attach($observer);
$subject->setValue(5);

Listing 9-7 generates the following output:

The new value is 5

The benefits of the observer pattern are that there may be many or no observers attached to the subscriber and you don't need to have prior knowledge of which classes will consume events from your subject class.

PHP 6 introduces the SplObjectStorage class, which improves the verbosity of this pattern.

This class is similar to an array, except that it can store only unique objects and store only a reference to those objects. It offers a few benefits. One is that you cannot attach a class twice, as you can with the example in Listing 9-7, and because of this, you can prevent multipleupdate() calls to the same object. You can also remove objects from the collection without iterating/searching the collection, and this improves efficiency.

Since SplObjectStorage supports the Iterator interface, you can use it in foreach loops, just as a normal array can be used. Listing 9-8 shows the PHP 6 pattern using SplObjectStorage.

Listing 9-8. SplObjectStorage and the Observer Pattern

PHP
class DemoSubject implements SplSubject {

 private $observers, $value;
 public function __construct() {
  $this->observers = new SplObjectStorage();
 }

 public function attach(SplObserver $observer) {
  $this->observers->attach($observer);
 }

 public function detach(SplObserver $observer) {
  $this->observers->detach($observer);
 }

 public function notify() {
  foreach($this->observers as $observer) {
   $observer->update($this);
  }
 }

 public function setValue($value) {
  $this->value = $value;
  $this->notify();
 }

 public function getValue() {
  return $this->value;
 }
}

class DemoObserver implements SplObserver {

 public function update(SplSubject $subject) {
  echo 'The new value is '. $subject->getValue();
 }
}

$subject = new DemoSubject();
$observer = new DemoObserver();
$subject->attach($observer);
$subject->setValue(5);

Listing 9-8 generates the following output:

The new value is 5

Serialization

The SPL's Serializable interface provides for some advanced serialization scenarios. The non-SPL serialization magic method's __sleep and __wakeup have a couple of issues that are addressed by the SPL interface.

The magic methods cannot serialize private variables from a base class. The __sleep function you implement must return an array of variable names to include in the serialized output. Because of where the serialization occurs, private members of the base class are restricted.

Serializable lifts this restriction by allowing you to call serialize() on the parent class, returning the serialized private members of that class.

Listing 9-9 demonstrates a scenario that magic methods cannot handle.

Listing 9-9. Magic Method Serialization

PHP
error_reporting(E_ALL); //Ensure notices show 

class Base {
 private $baseVar;
 
 public function __construct() {
  $this->baseVar = 'foo';
 }

}

class Extender extends Base {
 private $extenderVar;

 public function __construct() {
  parent::__construct();
  $this->extenderVar = 'bar';
 }

 public function __sleep() {
  return array('extenderVar', 'baseVar');
 }
}
$instance = new Extender();
$serialized = serialize($instance);
echo $serialized . "\n";

$restored = unserialize($serialized);

Running the code in Listing 9-9 results in the following notice:

Notice: serialize(): "baseVar" returned as member variable from __sleep() 
but does not exist ...
O:8:"Extender":2:{s:21:"ExtenderextenderVar";s:3:"bar";

s:7:"baseVar";N;}

To solve this problem and properly serialize the baseVar member, you need to use SPL's Serializable interface. The interface is simple, as shown in Listing 9-10.

Listing 9-10. The Serializable Interface

PHP
interface Serializable {
 public function serialize();
 public function unserialize( $serialized );
}

The serialize() method, when you implement it, requires that you return the serialized string representing the object; this is usually provided by using the serialize() function.

The unserialize() function will allow you to reconstruct the object. It takes the serialized string as an input.

Listing 9-11 shows the serialization of a private member of a base class.

Listing 9-11. Serializing a Private Member in a Base Class

PHP
error_reporting(E_ALL); 
class Base implements Serializable { 
  private $baseVar; 
  public function __construct() { 
    $this->baseVar = 'foo'; 
  } 
  public function serialize() { 
    return serialize($this->baseVar); 
  } 
  public function unserialize($serialized) { 
    $this->baseVar = unserialize($serialized); 
  } 
  public function printMe() { 
    echo $this->baseVar . "\n"; 
  } 
} 

class Extender extends Base { 
  private $extenderVar; 
  public function __construct() { 
    parent::__construct(); 
    $this->extenderVar = 'bar'; 
  } 
  public function serialize() { 
   $baseSerialized = parent::serialize(); 
   return serialize(array($this->extenderVar, $baseSerialized)); 
  } 
  public function unserialize( $serialized ) { 
    $temp = unserialize($serialized); 
    $this->extenderVar = $temp[0]; 
    parent::unserialize($temp[1]); 
  } 
} 
$instance = new Extender(); 
$serialized = serialize($instance); 
echo $serialized . "\n"; 
$restored = unserialize($serialized); 
$restored->printMe(); 

Listing 9-11 has the following output:

C:8:"Extender":42:{a:2:{i:0;s:3:"bar";i:1;s:10:"s:3:"foo";";}}
foo 

As you can see, the foo value of the base class was properly remembered and restored. The code in Listing 9-11 is very simple, but you can combine the code with functions like get_object_vars() to serialize every member of an object.

The Serializable interface offers a few other benefits. Unlike with the __wakeup magic method, which is called after the object is constructed, the unserialize() method is a constructor of sorts and will give you the opportunity to properly construct the object by storing construction input in the serialized data. This is distinct from __wakeup, which is called after the class is constructed and does not take any inputs.

The Serializable interface offers a lot of advanced serialization functionality and has the ability to create more robust serialization scenarios than the magic method approach.

SPL Autoloading

The __autoload($classname) magic function, if defined, allows you to dynamically load classes on their first use. This lets you retire your require_once statements. When declared, this function is called every time an undefined class or interface is called. Listing 9-12 demonstrates the __autoload($classname) method.

Listing 9-12. The __autoload Magic Method

PHP
function __autoload($class) {
 require_once($class . '.inc');
}
$test = new SomeClass(); //Calls autoload to find SomeClass 

Now, this isn't SPL. However, SPL does take this concept to the next level, introducing the ability to declare multiple autoload functions.

If you have a large application consisting of several different smaller applications or libraries, each application may wish to declare an __autoload() function to find its files. The problem is that you cannot simply declare two __autoload() functions globally without getting redeclaration errors. Fortunately, the solution is simple.

The spl_autoload_register() function, provided by the SPL extension, gets rid of the magic abilities of __autoload(), replacing them with its own type of magic. Instead of automatically calling __autoload() once spl_autoload_register() has been called, calls to undefined classes will end up calling, in order, all the functions registered with spl_autoload_register().

The spl_autoload_register() function takes two arguments: a function to add to the autoload stack and whether to throw an exception if the loader cannot find the class. The first argument is optional and will default to the spl_autoload() function, which automatically searches the path for the lowercased class name, using either the .php or .inc extension, or any other extensions registered with the spl_autoload_extensions() function. You can also register a custom function to load the missing class.

Listing 9-13 shows the registration of the default methods, the configuration of file extensions for the default spl_autoload() function, and the registration of a custom loader.

Listing 9-13. SPL Autoload

PHP
spl_autoload_register(null,false);
spl_autoload_extensions('.php,.inc,.class,.interface');
function myLoader1($class) {
 //Do something to try to load the $class 
}
function myLoader2($class) {
 //Maybe load the class from another path 
}
spl_autoload_register('myLoader1',false);
spl_autoload_register('myLoader2',false);
$test = new SomeClass();

In Listing 9-13, the spl_autoload() function will search the include path for someclass.php, someclass.inc, someclass.class, and someclass.interface. After it does not find the definition in the path, it will invoke the myLoader() method to try to locate the class. If the class is not defined after myLoader() is called, an exception about the class not being properly declared will be thrown.

It is critical to remember that as soon as spl_autoload_register() is called, __autoload() functions elsewhere in the application may fail to be called. If this is not desired, a safer initial call to spl_autoload_register() would look like Listing 9-14.

Listing 9-14. Safe spl_autoload_register Call

PHP
if(false === spl_autoload_functions()) {
 if(function_exists('__autoload')) {
  spl_autoload_register('__autoload',false);
 }
}
//Continue to register autoload functions 

The initialization in Listing 9-14 first calls the spl_autoload_functions() function, which returns either an array of registered functions or if, as in this case, the SPL autoload stack has not been initialized, the Boolean value false. Then you check to see if a function called __autoload() exists; if so, you register that function as the first function in the autoload stack and preserve its abilities. After that, you are free to continue registering autoload functions, as shown in Listing 9-13.

You can also call spl_autoload_register() to register a callback instead of providing a string name for the function. For example, providing an array like array('class','method') would allow you to use a method of an object.

Next, you can manually invoke the loader without actually attempting to utilize the class, by calling the spl_autoload_call('className') function. This function could be combined with the function class_exists('className', false) to attempt to load a class and gracefully fail if none of the autoloaders can find the class.


Note
The second parameter to class_exists() controls whether or not it attempts to invoke the autoloading mechanism. The function spl_autoload_call() is already integrated with class_exists() when used in autoloading mode.

Listing 9-15 shows an example of a clean-failure load attempt using both spl_autoload_call() and class_exists() in non-autoloading mode.

Listing 9-15. Clean Loading

PHP
//Try to load className.php 
if(spl_autoload_call('className') 
  && class_exists('className',false) 
  ) { 
  
  echo 'className was loaded'; 
  
  //Safe to instantiate className 
  $instance = new className();
  
} else { 

  //Not safe to instantiate className 
  echo 'className was not found'; 

}

Object Identification

Sometimes it is advantageous to have a unique code for each instance of a class. For this purpose, SPL provides the spl_object_hash() function. Listing 9-16 shows its invocation.

Listing 9-16. Invoking spl_object_hash

PHP
class a {}

$instance = new a();
echo spl_object_hash($instance);

This code generates the following output:

c5e62b9f928ed0ca74013d3e85bbf0e9

Each hash is guaranteed to be unique for every object within the context of a single call.

Repeated execution will likely result in the same hashes being generated but is not guaranteed to produce duplicate hashes. References to the same object in the same call are guaranteed to be identical, as shown in Listing 9-17.

Listing 9-17. spl_object_hash and References

PHP
class a {}
$instance = new a();
$reference = $instance;
echo spl_object_hash($instance) . "\n";
echo spl_object_hash($reference) . "\n";

Listing 9-17 generates the following output:

c5e62b9f928ed0ca74013d3e85bbf0e9
c5e62b9f928ed0ca74013d3e85bbf0e9

This data is similar to the comparison === operator; however, some uses may benefit from a hash code approach. For example, when registering objects in an array, the hash code may be used as the key for easier access.

Just the Facts

In this chapter, you were introduced to the SPL. The following chapters will build on this introduction.

Iterators can be used in looping structures. SPL provides the Iterator interface, along with some iterator helper functions, including iterator_to_array(), iterator_count(), and iterator_apply(). Array overloading allows you to treat objects as arrays.

SPL includes the Countable interface. You can use it to hook into the global count() function for your custom array-like objects.

Using the SPL observer pattern and the PHP 6-specific SplObjectStorage class, you can make certain objects monitor other objects for changes.

SPL autoloading is provided by the spl_autoload(), spl_autoload_register(), spl_autoload_functions(), spl_autoload_extensions(), and spl_autoload_call() functions.

Object identification is provided by the spl_object_hash() function. References to the same object in the same call are guaranteed to be identical.

In the following chapter, you will be introduced to some of the more advanced iterator patterns, so be sure to keep the lessons learned about iterators and their helper functions in mind.


This sample content is an excerpt from the book, Pro PHP: Patterns, Frameworks, Testing and More, by Kevin McArthur, Copyright 2008, Apress, Inc.

The source code for this book is available to readers at http://apress.com/book/downloadfile/3934.

KEVIN MCARTHUR is an open source developer, residing in British Columbia, Canada. He is a self-taught entrepreneur and has been running a very successful PHP application development studio for more than eight years. His company, StormTide Digital Studios, has worked with industry in the United States and Canada to provide scaling solutions for web statistics, VoIP, and print automation. An avid IRC user, Kevin helps to administer one of the largest PHP support organizations, PHP EFnet. Kevin's contributions to open source projects, including the Zend Framework, have made him a well-known authority in the industry. He has written several articles for PHPRiot.com on topics such as reflection, the Standard PHP Library, object-oriented programming, and PostgreSQL.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)