Elements of Object-Oriented Design - Zend PHP 5 Certification Study Guide (2014)

Zend PHP 5 Certification Study Guide (2014)

Elements of Object-Oriented Design

The benchmark of a good programmer, regardless of what language he or she works with, is the ability to apply well-known and accepted design techniques to any given situation. Design Patterns are generally recognized as an excellent set of tried-and-true solutions to common problems that developers face every day.

In this chapter, we’ll focus on how we can make the development of applications easier using some common design patterns. While the exam is not strewn with complex examples of pattern development, it does require you to have a firm grasp of the basics behind design patterns and their use in everyday applications.

Designing Code

The changes to PHP’s object model in PHP 5.0 was just the start of being able to design better code. In the decade since PHP 5.0 was introduced, we’ve seen numerous major new features added that further enhance the object model and make our lives easier.

In particular, PHP 5.3 added Closures (read more in the Closures and Callbacks chapter), PHP 5.4 added Traits for horizontal reuse, and PHP 5.5 added Generators for simpler iterators and co-routines.

Traits

The biggest change that affects how we might design our code is Traits. Traits are intended to provide horizontal reuse, without the complexity (both for you and the language) of multiple inheritance.

Traits are best described as compiler assisted copy and paste—the code within a trait can be injected into one or more classes.

Defining a trait is very similar to defining a class, except that we use the trait keyword:

Listing 11.1: Defining a trait

namespace Ds\Util;

trait SecureDebugInfo

{

public function __debugInfo() {

$info = (array) $this;

foreach ($info as $key => $value) {

// Hide elements starting with '_'

if ($key[0] == '_') {

unset($info[$key]);

}

}

}

}

In our trait, Ds\Util\SecureDebugInfo we have defined the __debugInfo magic method, which can now be added to any class simply by using the trait. This is done by reusing the use keyword (which is also used for importing namespaces).

namespace Ds;

class MyClass

{

use Util\SecureDebugInfo;

}

Traits can define properties and methods, including static variants. Also, because they are almost literally copy-and-paste, keywords such as $this, parent, self, and static, will work exactly as you would expect.

You can use multiple traits in the same way you import multiple namespaces, either by using multiple use statements, or using a comma-separated list.

Namespaces

While you can define a trait within a namespace, unless you are within the traits namespace hierarchy, you must always specify the fully-qualified name.

So, with our Ds\Util\SecureDebugInfo trait , inside the Ds\Util namespace, you can simply use SecureDebugInfo. Within the Ds namespace, you can use Util\SecureDebugInfo. In any other namespace, you must always specify the fully-qualified name.

Inheritance and Precedence

While traits do not support inheritance from other traits, you can simply use a trait within another:

Listing 11.2: Using a trait in another trait

trait One

{

public function one() { }

}

trait Two

{

public function two() { }

}

trait Three

{

use One, Two; // Trait three now comprises of all three traits.

public function Three() { }

}

Whenever a trait is used within a class, any method that is defined within the class will override any method with the same name in the trait.

Additionally, when using a trait within a child class, any methods within the trait will override others with the same name that would normally be inherited.

Methods defined in traits that are used in the parent class are inherited identically to any other method in the parent class, and will resolve for parent::method() calls.

Listing 11.3: Trait inheritance

class Numbers

{

use One; // Adds One->one()

public function two() { }

public function three() { }

}

class MoreNumbers extends Numbers

{

use Two; // Two->two() overrides Numbers->two()

public function one() { } // Overrides Numbers->one()

// (Trait One->one)

}

class EvenMoreNumbers extends MoreNumbers {

use Three; // Three->one() overrides MoreNumbers->one()

// Three->two() overrides MoreNumbers->two()

// (Trait Two->two)

// Three->three() overrides inherited

// Numbers->three()

public function three() { } // Overrides Three->three()

}

Trait Requirements

It is also possible to specify methods that must be defined by the class that uses the trait, so that the trait’s methods can reliably call it. This is done using abstract methods.

Listing 11.4: Abstract trait methods

namespace Ds\Util;

trait SecureDebugInfo

{

public function __debugInfo() {

return $this->getData();

}

abstract public function getData();

}

Here we define an abstract method getData() which is used by our concrete method, __debugInfo().

Conflict Resolution

When using a trait, you may end up with two traits that have a conflicting method. When you do this, PHP will emit a fatal error:

Fatal error: Trait method <name> has not been applied, because there are collisions with other trait methods on <class>

To resolve this, we must use the insteadof keyword to explictly resolve this conflict.

Listing 11.5: Using insteadof

class MyClass

{

use Ds\Util\SecureDebugInfo, My\Framework\DebugHelper {

Ds\Util\SecureDebugInfo::__debugInfo

insteadof My\Framework\DebugHelper;

}

}

Note that we always use the static method syntax, with ::—and that we only need to specify the method itself on the left operand.

Aliases and Method Visibility

If we wish to still be able to access a conflicted method, we can use an alias, using the as keyword:

Listing 11.6: Aliasing a trait

class MyClass

{

use Ds\Util\SecureDebugInfo, My\Framework\DebugHelper {

Ds\Util\SecureDebugInfo::__debugInfo

insteadof My\Framework\DebugHelper;

My\Framework\DebugHelper::__debugInfo

as debugHelperInfo;

}

}

Aliases can be used regardless of conflicts to add a new name for a method. It’s important to understand that this does not rename the method, it simply adds a new alias.

It is also possible to change a method’s visibility with the as keyword, either on its own, or alongside an alias:

Listing 11.7: Aliasing a trait to change visibility

class MyClass

{

use MyTrait {

MyTrait::myMethod as protected;

MyTrait::myOtherMethod as private NewMethodName;

}

}

In the first case, we are changing the visibility of the method; in the second, we are adding a new alias with different visibility.

Detecting Traits

Because traits are like copy-and-paste, the instanceof operator does not work for detecting whether a class utilizes a trait. To check whether a class utilizes a trait, we use the class_uses() function.

The class_uses() function returns an array which has both its keys and values set to each of the traits in use:

class Numbers

{

use One, Two;

}

var_dump(class_uses('MoreNumbers'));

The code above will return:

array(2) {

["One"]=>

string(3) "One"

["Two"]=>

string(3) "Two"

}

With the new array derefencing in PHP 5.4, this means we can do:

if (class_uses('MyClass')['SomeTrait']) {

// MyClass uses the SomeTrait

}

However, using this feature is highly discouraged; utilizing a trait, unlike being an instanceof a class or interface, does not guarantee any API.

Design Pattern Theory

As we mentioned in the previous section, design patterns are nothing more than streamlined solutions to common problems. In fact, design patterns are not really about code at all—they simply provide guidelines that you, the developer, can translate into code for pretty much every possible language. In this chapter, we will provide a basic description of some of the simpler design patterns, but, as the exam concerns itself primarily with the theory behind them, we will, for the most part, stick to explaining how they work in principle.

Even though design patterns can be implemented using nothing more than procedural code, they are best illustrated using OOP. That’s why it’s only with PHP 5 that they have really become relevant to the PHP world: with a proper object-oriented architecture in place, building design patterns is easy and provides a tried-and-true method for developing robust code.

The Singleton Pattern

The Singleton is probably the simplest design pattern. Its goal is to provide access to a single resource that is never duplicated, but that is made available to any portion of an application that requests it without the need to keep track of its existence. The most typical example of this pattern is a database connection, which normally only needs to be created once at the beginning of a script and then used throughout its code. Here’s an example implementation:

Listing 11.8: Singleton example

class DB

{

private static $_singleton;

private $_connection;

private function __construct($dsn) {

$this->_connection = new PDO($dsn);

}

public static function getInstance() {

if (is_null(self::$_singleton)) {

self::$_singleton = new DB();

}

return self::$_singleton;

}

}

$db = DB::getInstance();

Our implementation of the DB class takes advantage of a few advanced OOP concepts that are available in PHP 5: we have made the constructor private, which effectively ensures that the class can only be instantiated from within itself. This is, in fact, done in the getInstance() method, which checks whether the static property $_singleton has been initialized and, if it hasn’t, sets it to a new instance of DB. From this point on, getInstance() will never attempt to create a new instance of DB, and instead will always return the initialized $_connection, thus ensuring that a database connection is not created more than once.

The Factory Pattern

The Factory pattern is used in scenarios where you have a generic class (the factory) that provides the facilities for creating instances of one or more separate “specialized” classes that handle the same task in different ways.

A good situation for which the Factory pattern provides an excellent solution is the management of multiple storage mechanisms for a given task. For example, consider configuration storage, which could be provided by data stores like INI files, databases or XML files interchangeably. The API that each of these classes provides is the same (ideally, implemented using an interface), but the underlying implementation changes. The Factory pattern provides us with an easy way to return a different data store class depending on either the user’s preference, or a set of environmental factors:

Listing 11.9: Factory example

class Configuration

{

const STORE_INI = 1;

const STORE_DB = 2;

const STORE_XML = 3;

public static function getStore($type = self::STORE_XML)

{

switch ($type) {

case self::STORE_INI:

return new Configuration_Ini();

case self::STORE_DB:

return new Configuration_DB();

case self::STORE_XML:

return new Configuration_XML();

default:

throw new Exception(

"Unknown Datastore Specified."

);

}

}

}

class Configuration_Ini

{

// ...

}

class Configuration_DB

{

// ...

}

class Configuration_XML

{

// ...

}

$config = Configuration::getStore(Configuration::STORE_XML);

The Registry Pattern

By taking the Singleton pattern a little further, we can implement the Registry pattern. This allows us to use any object as a Singleton without it being written specifically that way.

The Registry pattern can be useful, if, for example, for the bulk of your application, you use the same database connection, but need to connect to an alternate database to perform a small set of tasks every now and then. If your DB class is implemented as a Singleton, this is impossible (unless you implement two separate classes, that is)—but a Registry makes it very easy:

Listing 11.10: Registry example

class Registry

{

private static $_register;

public static function add(&$item, $name = null) {

if (is_object($item) && is_null($name)) {

$name = get_class($item);

} elseif (is_null($name)) {

$msg = "You must provide a name for non-objects";

throw new Exception($msg);

}

$name = strtolower($name);

self::$_register[$name] = $item;

}

public static function &get($name) {

$name = strtolower($name);

if (array_key_exists($name, self::$_register)) {

return self::$_register[$name];

} else {

$msg = "'$name' is not registered.";

throw new Exception($msg);

}

}

public static function exists($name) {

$name = strtolower($name);

if (array_key_exists($name, self::$_register)) {

return true;

} else {

return false;

}

}

}

$db = new DB();

Registry::add($db);

// Later on

if (Registry::exists('DB')) {

$db = Registry::get('DB');

} else {

die('We lost our Database connection somewhere. Bear with us.');

}

The Model-View-Controller Pattern

Unlike the patterns we have seen this far, the Model-View-Controller (MVC) pattern is actually quite complex. Its goal is to provide a methodology for separating the business logic (model) from the display logic (view) and the decisional controls (controller).

In a typical MVC setup, the user initiates an action (even a default one) by calling the Controller. The Controller, in turn, interfaces with the Model, causing it to perform some sort of action and, therefore, changing its state. Finally, the View is called, thus causing the user interface to be refreshed to reflect the changes in the Model and the action requested of the Controller, and the cycle begins anew.

The clear advantage of the MVC pattern is its clear-cut approach to separating each domain of an application into a separate container. This, in turn, makes your applications easier to maintain and to extend, particularly because you can easily modularize each element, minimizing the possibility of code duplication.

The ActiveRecord Pattern

The last pattern that we will examine is the Active Record pattern. This is used to encapsulate access to a data source so that the act of accessing its components—both for reading and for writing—is, in fact, hidden within the class that implements the pattern, allowing its callers to worry about using the data, as opposed to dealing with the database.

The concept behind Active Record is, therefore, quite simple, but its implementation can be very complicated, depending on the level of functionality that a class based on this pattern is to provide. This is usually caused by the fact that, while developers tend to deal with database fields individually and interactively, SQL deals with them as parts of rows that must be written back to the database atomically. In addition, the synchronization of data within your script to the data inside the database can be very challenging, because the data may change after you’ve fetched it from the database without giving your code any notice.

The Standard PHP Library

The Standard PHP Library (SPL) was added in PHP 5.0. 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 a foreach() construct to loop through an object as if it were an array, or even access its individual elements using the array operator [].

From PHP 5.3, the SPL extension is always enabled.

SPL works primarily by providing a number of interfaces that can be used to implement the functionality required to perform certain operations. By far, the largest number of patterns exposed by SPL are iterators; they allow, among other things:

· Array Access to objects

· Simple Iteration

· Seekable Iteration

· Recursive Iteration

· Filtered Iteration

Accessing Objects as Arrays

The ArrayAccess interface can be used to provide a means for your object to expose themselves as pseudo-arrays to PHP:

Listing 11.11: ArrayAccess interface

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:

Listing 11.12: Implementing ArrayAccess

class myArray implements ArrayAccess

{

protected $array = array();

function offsetSet ($offset, $value) {

if (!is_numeric ($offset)) {

throw new Exception ("Invalid key $offset");

}

$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 control over one of PHP’s most powerful (and most useful) data types. Used properly, ArrayAccess is a great tool for building applications that encapsulate complex behaviours in a data type that everyone is used to.

PHP provides a concrete implementation of ArrayAccess, the ArrayObject class, which can be used itself, or extended.

$obj = new ArrayObject($array);

While this doesn’t appear to do much, other than provide a very expensive way to perform $obj = (object) $array, its power lies in the other arguments for the constructor. The first of these arguments, flags, will allow you to use the actual object properties when iterating (ArrayObject::STD_PROP_LIST), or access the array keys as properties (ArrayObject::ARRAY_AS_PROPS).

The second argument, iterator_class, lets you specify a different type of iterator class to be used when iterating over the ArrayObject data.

Iterators

Iterators allow you to iterate, or loop, over data structures programmatically. Essentially, this allows you to have the ease of foreach ($array as $key => $value) over any data set, no matter what its structure.

There are two types of iterators, inner iterators, and outer iterators. Inner iterators are used to iterate over your data, and implement the Iterator, or IteratorAggregate interface, while outer iterators are used to iterate over other iterators, and implement the OuterIterator interface (which itself extends the Iterator interface!).

Simple Iteration

The Iterator interface is the simplest of the iterator family, providing simple iteration over any single-dimension array. It looks like this:

Listing 11.13: Iterator interface

interface Iterator

{

function current();

function next();

function rewind();

function key();

function valid();

}

You can see a simple implementation of the interface that allows iteration over a private property containing a simple array:

Listing 11.14: Implementing the Iterator interface

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 the myData private property in the exact same way that foreach() works on a standard array.

With PHP 5.5, there is another way to implement iterators, using the new Generators feature (covered later in this chapter).

Seekable Iterators

The next step up from a standard Iterator is the SeekableIterator, which extends the standard Iterator interface and adds a seek() method to enable the ability to retrieve a specific item from internal data store. Its interface looks like this:

Listing 11.15: SeekableIterator interface

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:

Listing 11.16: Array of tree data

$company = array(

array("Acme Anvil Co."),

array(

array(

"Human Resources",

array(

"Tom",

"Dick",

"Harry"

)

),

array(

"Accounting",

array(

"Zoe",

"Duncan",

"Jack",

"Jane"

)

)

)

);

Our goal is to print out something like this:

Listing 11.17: Desired tree output

<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 (an example of an OuterIterator), we can define the beginChildren() 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 example, keeping track of multiple nested levels of nesting). The example shown below defines two classes, our custom RecursiveIteratorIterator and a very simple RecursiveArrayObject:

Listing 11.18: Using the RecursiveIteratorIterator

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() {

return new RecursiveArrayIterator($this);

}

}

Then, to produce our desired end result, we simply use this code:

Listing 11.19: Output with the RecursiveIteratorIterator

$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

The abstract FilterIterator class is an OuterIterator that can be used to filter the items returned by an iteration. To use it, extend it with a concrete implementation that implements the accept() method:

Listing 11.20: Using the FilterIterator

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;

}

The accept() method simply determines whether any given element should be allowed in the iteration; note that FilterIterator already implements all of the methods of ArrayAccess, 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

PHP includes two concrete implementations of FilterIterator; the first, RegexIterator, was added in PHP 5.2, while CallbackFilterIterator was added in PHP 5.4.

We can use RegexIterator to filter the results of DirectoryIterator to only return markdown files:

Listing 11.21: Filtering with RegexIterator

$dir = new DirectoryIterator("./_posts");

$it = new RegexIterator($dir, '/^.*\.(md|markdown)$/');

foreach ($it as $file) {

// Only files ending in .md or .markdown

// will be returned

}

The CallbackFilterIterator allows us to specify a simple callback that performs the accept checking:

Listing 11.22: Filtering with CallbackFilterIterator

$dir = new DirectoryIterator("./_posts");

$it = new CallbackFilterIterator(

$dir,

function($value, $key, $iterator) {

$ext = pathinfo($value, PATHINFO_EXTENSION);

return in_array($ext, ['md', 'markdown']);

}

);

foreach ($it as $file) {

// Only files ending in .md will be returned

}

Other Iterators

PHP has a number of other concrete iterators, including LimitIterator, CachingIterator, MultipleIterator, and RecursiveTreeIterator.

There are so many iterators that we can’t possibly cover them all here. To explore all the options available, visit the documentation http://php.net/spl.iterators.

Data Structures

As of PHP 5.3, SPL also provides a number of data structure classes:

Structure

Description

SplDoublyLinkedList

An implementation of a doubly linked list

SplStack

A stack implementation. LIFO: Last In, First Out data structure (a backwards array)

SplQueue

A queue implementation, FIFO: First In, First Out data structure

SplPriorityQueue

A queue implementation that implements priorities, so that higher priority items will be returned first when iterated

SplHeap

An abstract heap implementation, used by SplMinHeap and SplMaxHeap

SplMinHeap

A heap implementation which is ordered by value in ascending order (smallest first)

SplMaxHeap

A heap implementation which is ordered by value in descending order (largest first)

SplFixedArray

A faster array implementation, but with a fixed size, and only allowing integer keys

Due to the specialized application of these data structures, we won’t cover how to use them; however, you should know they exist, and learn when they would best be used. For further reading, see http://php.net/spl.datastructures.

Generators

The major new feature for PHP 5.5 was Generators. Generators were intended to allow you to create simpler iterators, without needing to write a bunch of boilerplate code.

Generators are simply specialized functions that when called return an instance of the Generator class (which, much like the Closure class prior to PHP 5.4, is purely an implementation detail), which can then be iterated over with control being passed back and forth between the function and the foreach loop to send data back between.

A generator function is identified by the existence of one or more instances of the yield keyword. When this keyword exists within a function and that function is called, none of the code inside of the function is run. Instead a Generator is returned—the code inside is only run when the Generator is iterated over.

Listing 11.23: Sample generator function

function gen() {

echo "One";

yield "Two";

echo "Three";

yield "Four";

echo "Five";

}

$generator = gen();

At this point, although we have called our gen() function, no code has run inside the function. Instead, $generator is now a Generator.

If we then iterate over that object manually to inspect its 'line-height:normal'>The very thing that our iterator does is call rewind() (which in the case of a Generator does nothing) and then valid() to ensure that we even need to iterate.

Next, it calls current() to get the first value; with Generators we can manually do this like so:

$generator->rewind();

if ($generator->valid()) {

echo $generator->current();

}

The call to rewind() will enter into our generator function, and run all the code until it reaches a yield. It does not evaluate the yield expression at this point, just everything up to it. In our case, this means it would echo "One".

Because we have not reached the end of our generator yet, valid() returns true, so we call current() which will evaluate the yield expression ("Two"), returning the result to our echo, and it will then pause again.

To move on to the next iteration,call next(); this has a similar effect to rewind() and enters back into the function, this time right after the yield, and continues running until right before the next yield. This means it will echo "Three".

If we then continue the iteration, valid() is called, and if it returns true, we will again call current(), yielding "Four" to echo, and pause.

Moving on to the next iteration, we call next(), which runs echo "Five", and then we hit the end of the function; this causes valid() to return false, stopping the iterator.

Now, in reality, we would use a loop like foreach to accomplish this:

foreach ($generator as $value) {

echo $value;

}

While this is an obviously contrived example, Generators are perfect for any looping operation that operates on a data-set. Other examples of tasks you might perform are:

· Reading large files

· Handling database results

· Creating HTML tables

Listing 11.24: Generator for HTML tables

function tableGenerator($data) {

if (!is_object($data) && !is_array($data)) {

return; // bail

}

yield '<table>' . PHP_EOL;

foreach ($data as $values) {

$return = '<tr>' . PHP_EOL;

foreach ($values as $value) {

$return .= '<td>' .$value. '</td>' . PHP_EOL;

}

$return .= '</tr>' . PHP_EOL;

yield $return;

}

yield "</table>" . PHP_EOL;

}

This generator, when iterated, will output the opening and closing <table> tags around the table rows and columns:

Listing 11.25: Calling a generator to output an HTML table

$table = tableGenerator([

["One", "Two", "Three"],

[1, 2, 3],

["I", "II", "III"],

["a", "b", "c"]

]);

foreach ($table as $row) {

echo $row;

}

Will result in (indented for readability):

<table>

<tr>

<td>One</td>

<td>Two</td>

<td>Three</td>

</tr>

<tr>

<td>1</td>

<td>2</td>

<td>3</td>

</tr>

<tr>

<td>I</td>

<td>II</td>

<td>III</td>

</tr>

<tr>

<td>a</td>

<td>b</td>

<td>c</td>

</tr>

</table>

Closing a Generator

A generator is considered closed when one of the following conditions is met:

· It reaches a return statement.

· The end of the function is reached.

· An exception is thrown and not caught inside the generator.

· All references to the generator are removed (e.g. unset($generator)).

Keys

It is also possible to return keys with Generators, simply use the array notation on the yield:

function tagsAndSlugs(array $tags) {

foreach ($tags as $value) {

$slug = createSlug($value);

yield $slug => $value;

}

}

If you then iterate using the foreach ($generator as $key => $value) syntax then $key will be populated with the value of $slug.

References

A generator can also yield values by reference; this is done by simply prepending the generator function with an &, you can then use a by-ref foreach:

Listing 11.26: Yielding values by reference

class DataModel

{

protected $data = [];

function &getIterator() {

foreach ($this->data as $key => $value) {

yield $key => $value;

}

}

}

// $dm = instanceof DataModel

foreach ($dm->getIterator() as $key => &$value) {

$value = strtoupper($value); // $dm->data is updated

}

Reusing a Generator

Unlike regular iterators, or arrays, you cannot reuse a generator. If you call rewind() on a generator after its first yield, it will throw an exception. This means that you cannot iterate using the same generator more than once.

Additionally, you cannot clone a generator; trying to do so will result in a fatal error: Fatal error: Trying to clone an uncloneable object of class Generator.

If you wish to use a generator more than once, you should call the function again, creating a new instance each time instead.

Co-routines and Exceptions

It is also possible to send data into a Generator. This is known as a co-routine. When you do this, the yield is used as an expression—rather than as a statement—the result of which is the data sent in.

To send the data in, we use $generator->send():

Listing 11.27: Sending data to a generator

class Chat

{

public static function connect() {

$errno = $errstr = null;

$socket = stream_socket_client('tcp://localhost:5000', $errno, $errstr, 30);

try {

while (true) {

$data = yield . PHP_EOL;

if (!fputs($socket, $data)) {

throw new Exception("Unable to write to socket!");

}

}

} finally {

fclose($fp);

}

}

}

$chat = Chat::connect();

$chat->send("Hello");

$chat->send("World");

Here, rather than iterate over the generator, we instantiate it and then call the send() method.

As before, when we initially called the function, nothing runs inside the function.

The first time we call send(), it will implicitly call rewind(), which will advance the generator until it hits the first yield. This will create our socket, and enter into the while loop. We then retrieve the input using yield, append a newline, and assign to $data.

Next we try to write to our socket with fputs().

At this point the generator continues to execute until it hits a non-expression yield (e.g. yield $value;), or, as in our example, it reaches the expression again.

This means that when we call send(), it will execute everything after the yield, and everything before the next yield in our loop.

Exceptions

As already mentioned, if an exception is thrown and not caught, the generator will close—however, it is also possible to send exceptions into the generator.

In our example here, we can use this behavior to disconnect our socket using the throw() method:

$chat->throw(new Exception("Disconnect"));

Reading and Writing

It is also possible to read and write simultaneously, by changing our expression to also include a return value. By changing our yield to the following, we can also read from our socket and return that data:

$data = (yield fgets($socket)) . PHP_EOL;

The use of parentheses is required to indicate that this is both a statement, and an expression. The ability to have an interruptable function that can both output and receive data is quite a powerful addition.

Summary

Object-oriented programming, coupled with Design Patterns—including those provided by the SPL—is the key to reusable and highly modular code. The more forethought you give your design, the more likely you are to be able to reuse at least some of it, 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 mentioned, design patterns are, after all, largely language- and problem-independent.