6.9 Redefining Class Iteration

 <  Day Day Up  >  

By default, when you iterate over an object, PHP serves up all of the object's properties. That's great for debugging, but it isn't what you want in every situation.

For example, you've mapped all property accesses in your class though the special _ _get( ) and _ _set( ) methods . In this implementation, you're storing all the data in a protected array named $data . It's logical that when someone iterates over the class, they're served up another element of $data . However, as things stand now, not only would they see other variables , but they're not going to see $data , because it's protected .

Fortunately, PHP 5 gives you the ability to control which items should appear during iteration. This lets you refine the default behavior to what's appropriate for the class.

Before getting into the specifics, here's a Person class for the later examples:

 class Person {     protected $data;     public function _ _construct($firstname, $lastname) {         $this->firstname = $firstname;           $this->lastname = $lastname;     }          public function _ _get($p) {         if (isset($this->data[$p])) {             return $this->data[$p];          } else {             return false;         }     }          public function _ _set($p, $v) {         $this->data[$p] = $v;     }          public function _ _toString( ) {         return "$this->firstname $this->lastname";     } } 

The Person class has a constructor that stores the person's firstname and lastname . Since property access has been overridden by _ _get( ) and _ _set( ) , $this->firstname really references $this->data['firstname'] .

There's also a _ _toString( ) method that defines how to print the person. That means you can use a Person object in a print statement and it'll look nice:

 $person = new Person('Rasmus', 'Lerdorf'); print $person;  Rasmus Lerdorf  

However, when you iterate over $person , nothing appears:

 $person = new Person('Rasmus', 'Lerdorf'); foreach($person as $property => $value) {     print "$property: $value\n"; } 

Since $data is protected , the foreach omits it. You could change $data 's visibility to public , but that defeats the whole purpose of using accessors.

Not surprisingly, this is the perfect opportunity for a custom iterator. This requires two steps. First, you need to implement the IteratorAggregate interface. This interface tells PHP that you're going to provide your own iterator for the class and also lets PHP know where to get it. Second, you must return an object that implements the Iterator interface containing the data for PHP to iterate over.

In essence, you're aggregating an Iterator object into your original class. If you remember from way back in Chapter 2, aggregation is when you embed one class inside another and arrange it so that when a person calls methods on the main class, they're actually handled by the embedded class.

6.9.1 Implementing IteratorAggregate

IteratorAggregate is a simple interface because you must add only a single method to your class: getIterator( ) .

When you iterate over a class that implements IteratorAggregate , instead of looping through the object's properties, PHP calls getIterator( ) and iterates through whatever is returned by the method.

Here's how to modify Person :

 class Person implements IteratorAggregate {          // Everything from before, plus:     public function getIterator( ) {         // insert code to return Iterator here...     } } 

Person now implements IteratorAggregate and has a getIterator( ) method, but there's still the question of what the method should return.

6.9.2 Turning Arrays and Objects into Iterators

At first glance, the solution seems trivial. Since $data is an array and PHP can iterate over arrays, $data is an iterator. Just return $data and you're done.

Unfortunately, that doesn't work. PHP requires an actual object that implements Iterator . It won't accept an array as a substitute even though it acts similarly. Therefore, you need to find a way to convert an array into an Iterator that behaves like an array.

You could solve this by writing your own Iterator , but the SPL extension provides an ArrayObject class that does exactly this for both arrays and objects:

 $data = array('firstname' => 'Rasmus',               'lastname' => 'Lerdorf'); foreach (new ArrayObject($data) as $key => $value) {     print "$key: $value\n"; }  firstname: Rasmus   lastname: Lerdorf  

From an external point of view, there's little difference between using ArrayObject and iterating over the array directly. When you pass ArrayObject an array, it allows you to loop over its elements. When you pass ArrayObject an object, it allows you to loop over the object's properties. Since that's how arrays and objects already work, the difference is subtle.

However, the ArrayObject class has the advantage of turning your variable into an Iterator . Since PHP requires that getIterator( ) return an Iterator instead of an array, this is an extraordinarily handy class to solve the problem of what to do with $data .

Armed with ArrayObject , the implementation is easy:

 public function getIterator( ) {         return new ArrayObject($this->data);     } 

Now you can iterate over the object:

 $person = new Person('Rasmus', 'Lerdorf'); foreach ($person as $property => $value) {     print "$property: $value\n"; }  firstname: Rasmus   lastname: Lerdorf  

That completes the work necessary to make Person iterable , but ArrayObject has one additional advantage worth mentioning. Although using foreach on a variable is easy, it's not particularly memory efficient, because PHP does not directly operate on the variable. Instead, it first makes a copy of the variable and loops through the duplicate.

ArrayObject optimizes array iteration because it makes fewer internal copies of the array. This speeds up code. Therefore, if speed is important in your application, make this switch:

 $person = array('firstname' => 'Rasmus',                 'lastname' => 'Lerdorf'); // FAST foreach ($person as $key => $value) {     print "$key: $value\n"; } // FASTER foreach (new ArrayObject($person) as $key => $value) {     print "$key: $value\n"; } 

6.9.3 Writing Dynamic Iterable Objects

The iterator returned by getIterator( ) doesn't need to return data already stored in the object. You can return any iterator, even one that accesses external information.

You've already briefly seen the SQLite iterator, which returns information from a database query:

 $db = new SQLiteDatabase($database); foreach ($db->query($sql) as $row) {     // operate on $row  } 

Since the SQLite result handle is also an object that implements Iterator , you can use it in your classes in place of ArrayIterator .

This example creates an object, addressBook . Pass addressBook a Person and it returns all the information in the database about that person:

 class addressBook implements IteratorAggregate {     protected $person;     protected $db;          public function _ _construct(Person $person) {         $this->person = $person;         $this->db = new SQLiteDatabase('address-book.db');     }     public function getIterator( ) {         $firstname = sqlite_escape_string($this->person->firstname);         $lastname  = sqlite_escape_string($this->person->lastname);         return $this->db->query("SELECT * FROM persons                                    WHERE firstname LIKE '$firstname'                                     AND lastname  LIKE '$lastname'");     } } $person = new Person('Rasmus', 'Lerdorf'); $ab = new addressBook($person); foreach($ab as $p) {     // $p contains an array of information about $person } 

When you create a new addressBook , it saves the $person in a protected property. Then, when you iterate over the object in a foreach , PHP calls getIterator( ) .

Inside getIterator( ) , you escape the SQL and make the query. Since the result object implements the Iterator interface, return it and you're done.

The $p variables are arrays with numerical keys. You can then operate on these fields as if you had called SQLite's sqlite_fetch_array( ) function.

For more on SQLite, see Chapter 5.

 <  Day Day Up  >  


Upgrading to PHP 5
Upgrading to PHP 5
ISBN: 0596006365
EAN: 2147483647
Year: 2004
Pages: 144

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net