Design patterns are just for Java architects - at least that's what you may have always thought. In fact, design patterns are useful for everyone. If these tools are not the preserve of “architectural astronauts,” then what are they? Why are they useful in PHP applications? This article explains these issues.
The book Design Patterns introduced design patterns to the software community. The authors of the book are Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides Design (commonly known as the "Gang of Four"). The core concepts behind the design patterns presented are very simple. After years of practicing software development, Gamma and others have discovered certain patterns with fixed designs, much like architects design houses and buildings, developing templates for where a bathroom should be or how a kitchen should be constructed. Using these templates, or design patterns, means designing better buildings faster. The same concept applies to software.
Design patterns not only represent a useful way to develop robust software faster, but they also provide a way to encapsulate large ideas in friendly terms. For example, you could say that you are writing a messaging system that provides loose coupling, or you could say that you are writing a pattern named Observer.
Demonstrating the value of patterns with smaller examples is very difficult. This often feels like overkill, since patterns actually work in large code bases. This article does not demonstrate a large application, so you need to think about ways to apply the principles of the example in your own large application—not the code itself demonstrated in this article. This is not to say that you shouldn't use patterns in small applications. Many good applications start out as small applications and progress to large applications, so there's no reason not to build on these types of solid coding practices. Now that you understand design patterns and why they are useful, let's take a look at the five commonly used patterns in PHP V5.
Factory pattern
Originally in the Design Patterns book, many design patterns encourage the use of loose coupling. To understand this concept, it's best to talk about the arduous journey that many developers go through working on large systems. When you change one piece of code, problems can occur, and cascading breaks can occur in other parts of the system—parts you once thought were completely unrelated.
The problem is tight coupling. Functions and classes in one part of the system are heavily dependent on the behavior and structure of functions and classes in other parts of the system. You want a set of patterns that allow these classes to communicate with each other, but you don't want to tie them tightly together to avoid interlocking. In large systems, a lot of code depends on a few key classes. Difficulties may arise when these classes need to be changed. For example, suppose you have a User class that reads from a file. You want to change it to a different class that reads from the database, however, all your code references the original class that reads from the file. At this time, it will be very convenient to use factory mode.
Factory pattern is a class that has certain methods that create objects for you. You can use factory classes to create objects without using new directly. This way, if you want to change the type of object created, you only need to change the factory. All code using this factory is automatically changed.
Listing 1 shows an example of a factory class. The server side of the equation consists of two parts: a database and a set of PHP pages that allow you to add feedback, request a list of feedback, and get articles related to a specific feedback.
Listing 1. Factory1.php
<?php interface IUser { function getName(); }
class User implements IUser { public function __construct( $id ) { }
public function getName() { return "Jack"; } }
class UserFactory { public static function Create( $id ) { return new User( $id ); } }
$uo = UserFactory::Create( 1 ); echo( $uo->getName()."n" ); ?> |
The IUser interface defines what actions a user object should perform. The implementation of IUser is called User, and the UserFactory factory class creates IUser objects. This relationship can be represented by UML in Figure 1.
Figure 1. Factory class and its related IUser interface and user class |
If you run this code on the command line using the php interpreter, you will get the following results:
The test code will request the User object from the factory and output the result of the getName method.
There is a variant of the factory pattern that uses factory methods. These public static methods in a class construct objects of that type. This method is useful if it is important to create objects of this type. For example, suppose you need to create an object and then set a number of properties. This version of the factory pattern encapsulates the process in a single location, so you don't have to copy complex initialization code and paste it all over the code base. Listing 2 shows an example of using a factory method.
Listing 2. Factory2.php
<?php interface IUser { function getName(); }
class User implements IUser { public static function Load( $id ) { return new User( $id ); }
public static function Create( ) { return new User( null ); }
public function __construct( $id ) { }
public function getName() { return "Jack"; } }
$uo = User::Load( 1 ); echo( $uo->getName()."n" ); ?> |
This code is much simpler. It has only one interface IUser and a User class that implements this interface. The User class has two static methods for creating objects. This relationship can be represented by UML in Figure 2.
Figure 2. IUser interface and user class with factory method |
Running the script on the command line produces the same results as Listing 1, as follows:
As mentioned above, sometimes such modes can seem overkill in smaller environments. However, it's best to learn this solid form of coding that you can apply to projects of any size.
Single element mode
Some application resources are exclusive because there is only one resource of this type. For example, connections to a database through a database handle are exclusive. You want to share the database handle across your application because it is an overhead when keeping the connection open or closed, even more so during the process of fetching a single page.
Single element mode satisfies this requirement. If the application contains one and only one object at a time, then this object is a singleton. The code in Listing 3 shows a database connection single element in PHP V5.
Listing 3. Singleton.php
<?php require_once("DB.php");
class DatabaseConnection { public static function get() { static $db = null; if ( $db == null ) $db = new DatabaseConnection(); return $db; }
private $_handle = null; private function __construct() { $dsn = 'mysql://root:password@localhost/photos'; $this->_handle =& DB::Connect( $dsn, array() ); }
public function handle() { return $this->_handle; } }
print( "Handle = ".DatabaseConnection::get()->handle()."n" ); print( "Handle = ".DatabaseConnection::get()->handle()."n" ); ?> |
This code shows a single class named DatabaseConnection. You cannot create your own DatabaseConnection because the constructor is private. But using the static get method, you can get and only get one DatabaseConnection object. The UML for this code is shown in Figure 3.
Figure 3. Database connection single element |
The best proof is that the database handle returned by the handle method is the same between the two calls. You can run the code in the command line to observe this.
% php singleton.php Handle = Object id #3 Handle = Object id #3 % |
The two handles returned are the same object. If you use a database connection single element throughout your application, you can reuse the same handle everywhere.
You can use global variables to store database handles, however, this approach is only suitable for smaller applications. In larger applications, avoid using global variables and use objects and methods to access resources. Observer pattern
The Observer pattern gives you another way to avoid tight coupling between components. The pattern is very simple: an object makes itself observable by adding a method that allows another object, the observer, to register itself. When an observable object changes, it sends messages to registered observers. These observers use this information to perform operations independent of the observable object. The result is that objects can talk to each other without having to understand why. A simple example is a list of users in the system. The code in Listing 4 displays a list of users, and when a user is added, it sends a message. This list can be observed via a log observer that sends messages when a user is added.
Listing 4. Observer.php
<?php interface IObserver { function onChanged( $sender, $args ); }
interface IObservable { function addObserver( $observer ); }
class UserList implements IObservable { private $_observers = array();
public function addCustomer( $name ) { foreach( $this->_observers as $obs ) $obs->onChanged( $this, $name ); }
public function addObserver( $observer ) { $this->_observers []= $observer; } }
class UserListLogger implements IObserver { public function onChanged( $sender, $args ) { echo( "'$args' added to user listn" ); } }
$ul = new UserList(); $ul->addObserver( new UserListLogger() ); $ul->addCustomer( "Jack" ); ?> |
This code defines four elements: two interfaces and two classes. The IObservable interface defines objects that can be observed, and UserList implements this interface in order to register itself as observable. The IObserver list defines how to become an observer. UserListLogger implements the IObserver interface. These elements are shown in the UML in Figure 4.
Figure 4. Observable user list and user list event logger |
If you run it from the command line, you will see the following output:
% php observer.php 'Jack' added to user list % |
The test code creates a UserList and adds the UserListLogger observer to it. Then add a consumer and notify the UserListLogger of this change.
It's critical to realize that UserList doesn't know what the logger will do. There may be one or more listeners that perform other operations. For example, you might have an observer that sends a message to new users, welcoming them to the system. The value of this approach is that UserList ignores all objects that depend on it and focuses primarily on maintaining the list of users and sending messages when the list changes.
This pattern is not limited to objects in memory. It is the basis for database-driven message query systems used in larger applications. chain of command mode
The chain of command pattern is based on loosely coupled topics, sending messages, commands, requests, or anything else through a set of handlers. Each handler makes its own judgment about whether it can handle the request. If it can, the request is processed and the process stops. You can add or remove handlers from the system without affecting other handlers. Listing 5 shows an example of this pattern.
Listing 5. Chain.php
<?php interfaceICommand { function onCommand( $name, $args ); }
class CommandChain { private $_commands = array();
public function addCommand( $cmd ) { $this->_commands []= $cmd; }
public function runCommand( $name, $args ) { foreach( $this->_commands as $cmd ) { if ( $cmd->onCommand( $name, $args ) ) return; } } }
class UserCommand implements ICommand { public function onCommand( $name, $args ) { if ( $name != 'addUser' ) return false; echo( "UserCommand handling 'addUser'n" ); return true; } }
class MailCommand implements ICommand { public function onCommand( $name, $args ) { if ( $name != 'mail' ) return false; echo( "MailCommand handling 'mail'n" ); return true; } }
$cc = new CommandChain(); $cc->addCommand( new UserCommand() ); $cc->addCommand( new MailCommand() ); $cc->runCommand( 'addUser', null ); $cc->runCommand( 'mail', null ); ?> |
This code defines the CommandChain class that maintains a list of ICommand objects. Both classes can implement the ICommand interface - one that responds to requests for mail, and the other that responds to adding users. Figure 5 shows the UML.
Figure 5. Command chain and its related commands |
If you run a script that contains some test code, you get the following output:
% php chain.php UserCommand handling 'addUser' MailCommand handling 'mail' % |
The code first creates the CommandChain object and adds two instances of the command object to it. Then run two commands to see who responded to the commands. If the command's name matches UserCommand or MailCommand, the code fails and no action occurs. The chain of command pattern is valuable when creating a scalable architecture for handling requests, and many problems can be solved using it. strategy pattern
The last design pattern we cover is the Strategy Pattern. In this pattern, algorithms are extracted from complex classes and can therefore be easily replaced. For example, if you want to change the way pages are ranked in search engines, Strategy mode is a good choice. Think about the parts of a search engine—one that traverses pages, one that ranks each page, and another that sorts the results based on the ranking. In complex examples, these parts are all in the same class. By using the Strategy pattern, you can put the arrangement part into another class to change the way the page is arranged without affecting the rest of the search engine's code.
As a simpler example, Listing 6 shows a user list class that provides a way to find a set of users based on a set of plug-and-play policies.
Listing 6. Strategy.php
<?php interface IStrategy { function filter( $record ); }
class FindAfterStrategy implements IStrategy { private $_name;
public function __construct( $name ) { $this->_name = $name; }
public function filter($record) { return strcmp( $this->_name, $record ) <= 0; } }
class RandomStrategy implements IStrategy { public function filter($record) { return rand( 0, 1 ) >= 0.5; } }
class UserList { private $_list = array();
public function __construct( $names ) { if ( $names != null ) { foreach( $names as $name ) { $this->_list []= $name; } } }
public function add( $name ) { $this->_list []= $name; }
public function find( $filter ) { $recs = array(); foreach( $this->_list as $user ) { if ( $filter->filter( $user ) ) $recs []= $user; } return $recs; } }
$ul = new UserList( array( "Andy", "Jack", "Lori", "Megan" ) ); $f1 = $ul->find( new FindAfterStrategy( "J" ) ); print_r( $f1 );
$f2 = $ul->find( new RandomStrategy() ); print_r( $f2 ); ?> |
Figure 6. User list and policy used to select users |
The UserList class is a wrapper around an array of names. It implements the find method, which uses one of several strategies to select a subset of these names. These strategies are defined by the IStrategy interface, which has two implementations: one that randomly selects the user, and another that selects all names after the specified name. When you run the test code, you get the following output:
%php strategy.php Array ( [0] => Jack [1] =>Lori [2] =>Megan ) Array ( [0] => Andy [1] =>Megan ) % |
The test code runs the same user list for both strategies and displays the results. In the first case, the strategy looks for any name that follows J, so you would get Jack, Lori, and Megan. The second strategy picks names at random, producing different results each time. In this case, the results are Andy and Megan.
The Strategy pattern is ideal for complex data management systems or data processing systems that require a high degree of flexibility in how data is filtered, searched, or processed.
Conclusion
This article introduces just a few of the most common design patterns used in PHP applications. More design patterns are demonstrated in the book Design Patterns. Don’t let the mystique of architecture put you off. Patterns are a wonderful idea that works in any programming language and at any skill level.