Five more PHP design patterns

Two years ago, we introduced five design patterns -- here are five more that accelerate PHP application development

PHP V5's object-oriented features give you the ability to implement design patterns to improve your code's design. When you improve your code's design in this way, it becomes more readable, more maintainable, and more robust to absorb changes.

Share:

Nathan A. Good, Senior Information Engineer, Freelance Developer

Nathan Good lives in the Twin Cities area of Minnesota. Professionally, he does software development, software architecture, and systems administration. When he's not writing software, he enjoys building PCs and servers, reading about and working with new technologies, and trying to get his friends to make the move to open source software. He's written and co-written many books and articles, including Professional Red Hat Enterprise Linux 3, Regular Expression Recipes: A Problem-Solution Approach, and Foundations of PEAR: Rapid PHP Development.



25 March 2008

The book Design Patterns introduced me to the notion there could be such things. At the time, I was still learning object orientation (OO), so there were many concepts in the book I found difficult to grasp. However, as I became more comfortable with OO concepts — particularly the use of interfaces and inheritance — I began to see the real value in design patterns. As an application developer, you can have a lifelong career without ever knowing what any of the patterns are called or how or when they're used. However, I've found that a good working knowledge of these patterns, as well as those introduced in the developerWorks article "Five common PHP design patterns" (see Resources), allows you to do two things:

Enable high-bandwidth conversations
If you know about design patterns, you'll be able to build solid OO applications faster. But when your entire development team knows the various patterns, you can suddenly have very high-bandwidth conversations. You no longer need to talk about all the classes you would use here or there. Instead, you can talk in terms of patterns to each other. "Well, I'm referencing a singleton here, then using an iterator to go through a collection of my objects, and ... " is much, much faster than going through the classes, methods, and interfaces that make up these patterns. This efficiency in communication alone can be worth taking time to go through a couple of sessions together, as a team, to study patterns.
Reduce painful lessons
Each design pattern describes a proven way to solve a common problem. Therefore, you don't need to worry about whether your design is the right one, so long as you've chosen the pattern that provides the advantages you need.

Pitfalls

There is a saying that goes something like this: "When you're holding a hammer, everything looks like a nail." When you find a pattern you think is great, you may try to use it anywhere and everywhere, even in places you shouldn't. Remember that you must consider the usage goals of the patterns you're learning and try not to force patterns into parts of your application just for the sake of using them.

This article covers five patterns you can use to improve your PHP code. Each pattern covers a specific scenario. The PHP code for these patterns is available in the Download section.

Requirements

To get the most out of this article and use the examples, install the following on your computer:

  • PHP V5 or later (this article was written with PHP V5.2.4)
  • An extraction program, such as WinZIP (to extract the downloadable code archive)

Note: Although you don't absolutely need to have an editor other than a plain text editor, I find that it really helps to have syntax highlighting and syntax error-correcting. The examples in this article were written using the Eclipse PHP Development Tools (PDT).

The adapter pattern

Use the adapter pattern when you need to convert an object of one type to an object of another type. Typically, developers handle this process through a bunch of assignment code, as shown in Listing 1. The adapter pattern is a nice way to clean this type of code up and reuse all your assignment code in other places. Also, it hides the assignment code, which can simplify things quite a bit if you're also doing some formatting along the way.

Listing 1. Using code to assign values between objects
class AddressDisplay
{
    private $addressType;
    private $addressText;

    public function setAddressType($addressType)
    {
        $this->addressType = $addressType;
    }

    public function getAddressType()
    {
        return $this->addressType;
    }

    public function setAddressText($addressText)
    {
        $this->addressText = $addressText;
    }

    public function getAddressText()
    {
        return $this->addressText;
    }
}

class EmailAddress
{
    private $emailAddress;
    
    public function getEmailAddress()
    {
        return $this->emailAddress;
    }
    
    public function setEmailAddress($address)
    {
        $this->emailAddress = $address;
    }
}

$emailAddress = new EmailAddress();
/* Populate the EmailAddress object */
$address = new AddressDisplay();
/* Here's the assignment code, where I'm assigning values 
  from one object to another... */
$address->setAddressType("email");
$address->setAddressText($emailAddress->getEmailAddress());

This example uses an AddressDisplay object to display an address to a user. The AddressDisplay object has two parts: the type of address and a formatted address string.

After implementing the pattern (see Listing 2), the PHP script no longer needs to worry about exactly how the EmailAddress object is turned into the AddressDisplay object. That's a good thing, especially if the AddressDisplay object changes or the rules that govern how an EmailAddress object is turned into an AddressDisplay object change. Remember, one of the main benefits of designing your code in a modular fashion is to take advantage of having to change as little code as possible if something in the business domain changes or you need to add a new feature to the software. Think about this even when you're doing mundane tasks, such as assigning values from properties of one object to another.

Listing 2. Using the adapter pattern
class EmailAddressDisplayAdapter extends AddressDisplay
{
    public function __construct($emailAddr)
    {
        $this->setAddressType("email");
        $this->setAddressText($emailAddr->getEmailAddress());
    }
}	

$email = new EmailAddress();
$email->setEmailAddress("user@example.com");

$address = new EmailAddressDisplayAdapter($email);

echo($address->getAddressType() . "\n") ;
echo($address->getAddressText());

Figure 1 shows a class diagram of the adapter pattern.

Figure 1. Class diagram of the adapter pattern
Class diagram of the adapter pattern

Alternate method

An alternate method of writing an adapter — and one that some prefer — is to implement an interface to adapt behavior, rather than extending an object. This is a very clean way of creating an adapter and doesn't have the drawbacks of extending the object. One of the disadvantages of using the interface is that you need to add the implementation into the adapter class, as shown in Figure 2.

Figure 2. The adapter pattern (using an interface)
The adapter pattern (using an interface)

The iterator pattern

The iterator pattern provides a way to encapsulate looping through a collection or array of objects. It is particularly handy if you want to loop through different types of objects in the collection.

Look back at the e-mail and physical address example in Listing 1. Before adding an iterator pattern, if you're looping through the person's addresses, you might loop through the physical addresses and display them, then loop through the person's e-mail addresses and display them, then loop through the person's IM addresses and display those. That's some messy looping!

Instead, by implementing an iterator, all you have to do is call while($itr->hasNext()) and deal with the next item $itr->next() returns. An example of one of the iterators is shown in Listing 3. An iterator is powerful because you can add new types of items through which to iterate, and you don't have to change the code that loops through the items. In the Person example, for instance, you could add an array of IM addresses; simply by updating the iterator, you don't have to change any code that loops through the addresses for display.

Listing 3. Using the iterator pattern to loop through objects
class PersonAddressIterator implements AddressIterator
{
    private $emailAddresses;
    private $physicalAddresses;
    private $position;
    
    public function __construct($emailAddresses)
    {
        $this->emailAddresses = $emailAddresses;
        $this->position = 0;
    }
    
    public function hasNext()
    {
        if ($this->position >= count($this->emailAddresses) || 
            $this->emailAddresses[$this->position] == null) {
            return false;
        } else {
            return true;
        }
    }
    
    public function next()
    {
        $item = $this->emailAddresses[$this->position];
        $this->position = $this->position + 1;
        return $item;
    }
    
}

If the Person object is modified to return an implementation of the AddressIterator interface, the application code that uses the iterator doesn't need to be modified if the implementation is extended to loop through additional objects. You can use a compound iterator that wraps the iterators that loop through each type of address like the one listed in Listing 3. An example of this is available (see Download).

Figure 3 shows a class diagram of the iterator pattern.

Figure 3. Class diagram of the iterator pattern
Class diagram of the iterator pattern

The decorator pattern

Consider the code sample in Listing 4. The purpose of this code is to add a bunch of features onto a car for a Build Your Own Car site. Each car model has more features and an associated cost. With only two models, it would be fairly trivial to add these features with if then statements. However, if a new model came along, you'd have to go back through the code and make sure the statements worked for the new model.

Listing 4. Using the decorator pattern to add features
require('classes.php');

$auto = new Automobile();

$model = new BaseAutomobileModel();

$model = new SportAutomobileModel($model);

$model = new TouringAutomobileModel($model);

$auto->setModel($model);

$auto->printDescription();

Enter the decorator pattern, which allows you to add this functionality onto the AutomobileModel in a nice, clean class. Each class remains concerned only about its price and options and how they're added to the base model.

Figure 4 shows a class diagram for the decorator pattern.

Figure 4. Class diagram of the decorator pattern
Class diagram of the decorator pattern

An advantage of the decorator pattern is that you can easily tack on more than one decorator to the base at a time.

If you've done much work with stream objects, you have used a decorator. Most stream constructs, such as an output stream, are decorators that take a base input stream, then decorate it by adding additional functionality — like one that inputs streams from files, one that inputs streams from buffers, etc.


The delegate pattern

The delegate pattern provides a way of delegating behavior based on different criteria. Consider the code in Listing 5. This code contains several conditions. Based on the condition, the code selects the appropriate type of object to handle the request.

Listing 5. Using conditional statements to route shipping requests
pkg = new Package("Heavy Package");
$pkg->setWeight(100);

if ($pkg->getWeight() > 99)
{
	echo( "Shipping " . $pkg->getDescription() . " by rail.");
} else {
	echo("Shipping " . $pkg->getDescription() . " by truck");
}

With a delegate pattern, an object internalizes this routing process by setting an internal reference to the appropriate object when a method is called, like useRail() in Listing 6. This is especially handy if the criteria change for handling various packages or if a new type of shipping becomes available.

Listing 6. Using the delegate pattern to route shipping requests
require_once('classes.php');

$pkg = new Package("Heavy Package");
$pkg->setWeight(100);

$shipper = new ShippingDelegate();

if ($pkg->getWeight() > 99)
{
	$shipper->useRail();
}

$shipper->deliver($pkg);

The delegate provides the advantage that behavior can change dynamically by calling the useRail() or useTruck() method to switch which class handles the work.

Figure 5 shows a class diagram of the delegate pattern.

Figure 5. Class diagram of the delegate pattern
Class diagram of the delegate pattern

The state pattern

The state pattern is a similar to the command pattern, but the intent is quite different. Consider the code below.

Listing 7. Using code to build a robot
class Robot 
{

	private $state;

	public function powerUp()
	{
		if (strcmp($state, "poweredUp") == 0)
		{
			echo("Already powered up...\n");
			/* Implementation... */
		} else if ( strcmp($state, "powereddown") == 0) {
			echo("Powering up now...\n");
			/* Implementation... */
		}
	}

	public function powerDown()
	{
		if (strcmp($state, "poweredUp") == 0)
		{
			echo("Powering down now...\n");
			/* Implementation... */
		} else if ( strcmp($state, "powereddown") == 0) {
			echo("Already powered down...\n");
			/* Implementation... */
		}
	}

	/* etc... */

}

In this listing, the PHP code represents the operating system for a powerful robot that turns into a car. The robot can power up, power down, turn into a robot when it's a vehicle, and turn into a vehicle when it's a robot. The code is OK now, but you see that it can become complex if any of the rules change or if another state comes into the picture.

Now look at Listing 8, which has the same logic for handling the robot's states, but this time puts the logic into the state pattern. The code in Listing 8 does the same thing as the original code, but the logic for handling states has been put into one object for each state. To illustrate the advantages of using the design pattern, imagine that after a while, these robots have discovered that they shouldn't power down while being in robot mode. In fact, if they power down, they must change to vehicle mode first. If they're already in vehicle mode, the robot just powers down. With the state pattern, the changes are pretty trivial.

Listing 8. Using the state pattern to handle the robot's state
$robot = new Robot();
echo("\n");
$robot->powerUp();
echo("\n");
$robot->turnIntoRobot();
echo("\n");
$robot->turnIntoRobot(); /* This one will just give me a message */
echo("\n");
$robot->turnIntoVehicle();
echo("\n");
Listing 9. Small changes to one of the state objects
class NormalRobotState implements RobotState
{
    private $robot;

    public function __construct($robot)
    {
        $this->robot = $robot;
    }

    public function powerUp()
    {
        /* implementation... */
    }
    public function powerDown()  
    {
        /* First, turn into a vehicle */
        $this->robot->setState(new VehicleRobotState($this->robot));
        $this->robot->powerDown();
    }
    
    public function turnIntoVehicle()  
    {
        /* implementation... */
    }
    
    public function turnIntoRobot() 
    {
        /* implementation... */
    }
}

Something that doesn't appear obvious when looking at Figure 6 is that each object in the state pattern has a reference to the context object (the robot), so each object can advance the state onto the appropriate one.

Figure 6. Class diagram of the state pattern
Class diagram of the state pattern

Summary

Using design patterns in your PHP code is one way to make your code more readable and maintainable. By using established patterns, you benefit from common design constructs that allow other developers on a team to understand your code's purpose. It also allows you to benefit from the work done by other designers, so you don't have to learn the hard lessons of design ideas that don't work out.


Download

DescriptionNameSize
Sample codeos-php-designpatterns_morepatterns.zip4KB

Resources

Learn

Get products and technologies

  • Innovate your next open source development project with IBM trial software, available for download or on DVD.
  • Download IBM product evaluation versions, and get your hands on application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Open source on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=295932
ArticleTitle=Five more PHP design patterns
publish-date=03252008