Sophisticated Object Iterators in 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 “user” 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 '<p>', count($loggedin), ' user(s) are currently logged in:</p>';

// iterate over users
foreach ($loggedin as $rec => $user) {
	echo
		'Record ', $rec,
		':   ID=', $user->id, 
		' Email=', $user->email,
		'<br/>';
}
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 $item;		// 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 “rewind” 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 object which is assigned to $this->item.

The next two Iterator methods are named current() and key() which return the current collection item (a User object) and record number respectively:


	public function current() {
		return $this->item;
	}


	public function key() {
		return $this->cursor;
	}

Finally, we require a public valid() method. This is called immediately after the foreach loop executes rewind() or next() — it must return true if an item is available:


	public function valid() {
		return ($this->cursor < $this->count());
    }
// end of class definition
}

Our class is complete and we can count or iterate over logged-in users using the code at the top of this article.

I hope you found this series useful and consider Iterators the next time you create an object which contains a collection of items.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • aamonkey

    Maybe this is beyond the scope of the article, but I’m not a fan of putting all that implementation into the “LoggedIn” class. I would prefer to put everything into an abstract base class, and have LoggedIn extend that. Rename the FetchLoggedIn() method as Fetch(), make that abstract, and now you can easily get an “iteratable” collection of any type of object in the system just by creating a new class and defining the Fetch() method.

    • http://www.optimalworks.net/ Craig Buckler

      Absolutely. This is a simplified example which demonstrates iterating an object collection which isn’t an array. It’s not ‘real world’ code I’d suggest you use for managing users.

  • Frank Kiigan

    CodeMyConcept has helped me a lot with PHP for my Drupal website but thanks to this article I understand more of it.

    Thanks!

  • Adam

    You didn’t declare $item in the scope, how can you call $this->item?

    • http://www.optimalworks.net/ Craig Buckler

      Well spotted Adam. The private $user property should have been $item — it’s been fixed.

      Bizarrely, though, the code worked without errors? PHP appeared to use $item as a private property even though it wasn’t declared as such.

      • Derek Downey

        Interesting. Are you sure it was using item as a private property? Usually if you use a member variable that hasn’t been declared, it’s assumed public.

      • http://www.optimalworks.net/ Craig Buckler

        You’re suspect you’re right. PHP can be too damn forgiving of shoddy code!

  • Evert

    Be careful with cursors. They must be closed before you can do a second query.

  • kaf

    This is a great series. Iterators in php can be very handy. They are a very niche technique but can really simplify the architecture of your app and allow for some cool things.

    I use them in my framework. When I ask my model to return a list of results I return a result object. This allows me to tack other fields onto the object for stuff like pagination and populating select fields. ie:

    $result_object = $items->fetch();
    foreach($result_object as $item){
    $item->name;
    }
    $result_object->count; // number of records
    $result_object->options; // key and value pairs for select field

  • Brian Reich

    Excellent job on these last two articles… it’s nice to see this SPL stuff getting some attention!

  • rosegray

    thanks. This is a simplified example which demonstrates iterating an object collection which isn’t an array