PhpRiot
News Archive
PhpRiot Newsletter
Your Email Address:

More information

Sophisticated Object Iterators in PHP

Note: This article was originally published at Planet PHP on 19 April 9240.
Planet PHP

In my previous post, Simple Object Iterators in PHP, we discovered how to iterate over array items defined within an object using a foreach loop. However, what if you need to iterate over items which are not stored in an array, e.g. records from a database or lines of text read from a file?

For the following example, we'll create a class which:

  1. counts the number of users who are logged in to a web application by retrieving records from a database
  2. creates a collection of aousera objects which can be iterated with a foreach loop.

Ultimately, we want to be able to run simple code such as:

$loggedin = new LoggedIn(); // count users echo '

', count($loggedin), ' user(s) are currently logged in:

'; // iterate over users foreach ($loggedin as $rec = $user) { echo 'Record ', $rec, ': ID=', $user-id, ' Email=', $user-email, '
'; } note: Recordsets are traversable

PDO recordsets are already traversable so why wrap that functionality within a class?

The benefits are encapsulation and reuse. A developer using the LoggedIn class doesn't need to worry about implementation details such as database connections or table structures. If user or login details were moved to another system, you could change the LoggedIn class without affecting other code.

In addition, class functionality could be extended without affecting code which uses it, e.g. to fetch further user details, write to a log file, raise security alerts, etc.

First, we'll define a basic class which stores the ID and email address for a single user. An instance of this class will be returned when we iterate over logged in users:

class User { public $id, $email; }

We now require a LoggedIn class which handles a collection of users who have logged in. Note that the class will implement both the Countable and Iterator interfaces:

class LoggedIn implements Countable, Iterator { private $rec; // database recordset private $cursor; // number of current record private $user; // current user in the collection

The constructor will immediately call a private FetchLoggedIn() method which runs a database query:

// constructor public function __construct() { $this-FetchLoggedIn(); } // fetch logged in user records private function FetchLoggedIn() { $this-cursor = -1; $this-item = null; // find logged-in users $db = new PDO('mysql:host=localhost;dbname=dbname', 'dbuser', 'dbpass'); $this-rec = $db-Prepare('SELECT id, email FROM `user` WHERE loggedin=1;', array(PDO::ATTR_CURSOR = PDO::CURSOR_FWDONLY)); // convert returned records to a User object $this-rec-setFetchMode(PDO::FETCH_INTO, new User()); // run query $this-rec-execute(); }

Note:

  • $this-cursor is the current record number (zero based). It's initially set to -1 because we haven't retrieved any records.
  • $this-item will hold the current User object.
  • $this-rec is the set of records (if any) returned from our SQL query.
  • The $this-rec-setFetchMode line sets the default PDO fetch mode. When we fetch a row, a new instance of the User class is returned with the $id and $email properties mapped to the record.

Since our class implements Countable, we must create a public method named count() which returns the number items in the collection - in this case, the number of rows returned by our SQL query:

public function count() { return $this-rec-rowCount(); }

We now require 5 public methods which implement Iterator functionality. The first is confusingly named rewind(). It is called when the foreach loop starts and should aorewinda to the first item in the collection:

public function rewind() { if ($this-cursor = 0) $this-FetchLoggedIn(); $this-next(); }

This code checks whether one or more records has already been returned. If it has, we run the SQL query again since we've created a forward-only recordset.

The following line calls next() - our second Iterator method:

public function next() { $this-cursor++; $this-item = $this-rec-fetch(PDO::FETCH_ORI_NEXT); }

This method is called during each iteration of the foreach loop. It increments $this-cursor, fetches the next row from the recordset, and creates a new User obj

Truncated by Planet PHP, read more at the original (another 1613 bytes)