PHP Strategy Design Pattern: Encapsulating Algorithms

Strategy Design PatternOne of the most useful design patterns is the Strategy pattern. Where variation in algorithm use is key, the Strategy pattern encapsulates the algorithms and uses delegation to handle requests. You can easily change algorithms by re-writing the code just for the algorithm. Adding algorithms is just as easy when the requirements for your application change because all of the code for the algorithm is encapsulated in separate objects (classes). So instead of having your algorithms scattered all over the place, the Strategy pattern organizes and encapsulates them.

To get started, take a look at the class diagram for the Strategy pattern. If you are familiar with the State design pattern, you’ll see that the class diagrams are identical, but do not assume that the patterns are the same because they are very different. When we discuss the State pattern, we’ll look at those differences, but for now, just focus on the Strategy pattern itself.

Figure 1: Strategy class diagram

All requests from the Client (not shown in the diagram) go through the Context class to the Concrete Strategies through the Strategy interface.

Before continuing you need to know that not only do design patterns use a lot of classes, but they are typically saved in separate files. When it comes time to update and re-use materials, this arrangement works quite well. Further, you have to place all of the files in the same directory—this was done to keep it simple, but feel free to change that into multiple directories and re-code the materials so that the links are updated. Save some time by downloading all the code and get an idea of what the program does by clicking on the appropriate button below:

Algorithm Writers’ Heaven

I’ve never met two PHP programmers who agree on the same algorithm for…well just about anything. This is the beauty of the Strategy design pattern. All of the algorithms are encapsulated, and so if you don’t like the algorithms, you can change them to suit your own style and wisdom. Further, if you decide to add functionality to a program or to change the functionality, you can simply change one of the algorithms without the whole house come crashing down around your ears. Of course the Strategy pattern was not designed for cantankerous PHP programmers who don’t like other’s algorithms but rather for programmers who have to deal with changing functionality in an application or several different algorithms from the same “family.” In effect, you set up an interchangeable set of algorithms by establishing an interface with the desired method(s) and control the whole operation through a context class.

The algorithms can vary independent of the client (Client class) who uses it. Further, the strategies eliminate the need for conditional statement. Instead of using a conditional to call a different algorithm, the strategies are encapsulated in separate classes (implementations of a strategy interface), and so they can be called directly by the client through the context.

What’s Wrong with Conditional Statements?

Let me just blurt it out :

I avoid conditional statements whenever possible.

Such a claim may seem to be a little odd since the Chain of Responsibility pattern uses them extensively. The handleRequest() method in all of the concrete Handler classes use conditional statements. That’s true, but the conditionals are all the same and only check to see whether the request can be handled by the current concrete handler or not. These operations are little checkers or inspectors that either handle to request or pass it along the chain. They’re not the kind that have to think about several alternatives.

So what do I have against conditional statements? They can get in the way of revision and reuse. When making revisions, you have to check all of your conditional statements and see what may come unravelled if they are not all updated and rejiggered. The more you have, the bigger the problem for error.

The Encapsulating Context

To me the Context class is magic. A simple single class encapsulates all of the strategies. More precisely, the Context class encapsulates the point at which the strategy is performed. Further, the context accepts any of the algorithm family members. It doesn’t care which one is delegated a given chore to perform as long as they implement the strategy interface (IStrategy) that defines the family of algorithms.

You may well ask how that is possible given all of the different concrete strategies that can be called by the client. The answer lies in programming to the interface instead of the application. Fortunately in PHP 5 we have Type Hinting and the way that Type Hinting has been implemented allows the Interfaces as well as classes in the hint. For those of you not familiar with Type Hinting, it forces parameters to be objects of the typed hinted. Because we can use an interface as the hint and all of the concrete strategies (of the same family) implement the same interface, we can force them to be implementations of the desired interface. The following listing shows how Type Hinting is used in our Context class:

///
//////Save the following file as Context.php

 
class Context 
{
    private $strategy;
 
    public function __construct(IStrategy $strategy) 
        {
        $this->strategy = $strategy;
    }
 
    public function algorithm($elements) 
        {
        $this->strategy->algorithm($elements);
    }
}
 
?>

Notice that the Context class has a constructor function:

__construct(IStrategy $strategy)

The Type Hinting shows IStrategy to to be hint for the parameter ( $strategy) which forces the parameter to have the IStrategy interface. Further, because it is a constructor function, it is automatically launched when instantiated, and so any instance will have the concrete strategy named and ready to go when the algorithm($elements) method is called.

A Simple Strategy Pattern

One of the primary uses of PHP is to work with data and tables in databases. Each of the procedure sets used to enter, change, retrieve and delete records can be viewed as an algorithm. The type of algorithms used in this example can be understood as an SQL family of algorithms. The SQL family is nothing more than fairly standard SQL commands embedded in PHP classes that implement the same interface. It helps keep everything nice and neat and makes it insanely easy to make updates and changes to the algorithms without bringing the whole structure crashing down.

The following script is used to build the sample table.


        $server="your.server.com";
        $user="yourUserName";
        $pass="yourPassword";
        $currentDB="yourDataBaseName";
        $tableMaster="Strategy";
 
        $hookup=mysql_connect($server, $user, $pass) ;
 
        //Select the database
        $dbNow= mysql_select_db($currentDB, $hookup);
 
        //Drop table if it still exists
        $sql = "DROP TABLE IF EXISTS $tableMaster" or die("Master Table not dropped." . mysql_error());
        mysql_query( $sql, $hookup );
 
        //Create Master Table
        $sql = "CREATE TABLE $tableMaster (id INT NOT NULL AUTO_INCREMENT, fname VARCHAR(15), lname VARCHAR(20), email VARCHAR(50), PRIMARY KEY (id))";
 
        //Execute query
        $result= mysql_query($sql, $hookup) or die("Master Table not created." . mysql_error());
 
        if($result) {
                print "Master Table successfully created." . "
"
; } ?>

You can easily make the table in PHPMyAdmin or a similar table editor, but this one is ready to go.

Family of Algorithms and the Strategies

We now can set up the different strategies we’d like to implement along with a common interface. We’ll start of with the Strategy interface (IStrategy). It is an abstract version of a family of algorithms with a single parameter.

Interface


///
//////Save the following file as IStrategy.php
interface IStrategy 
{
    public function algorithm($elements);
}
 
?>

All of the concrete strategies will have to implement the algorithm($elements) method, but may have other methods as well. In this case none are required in the concrete strategy classes and none need constructor functions. The best part about this is that if you don’t like the way the algorithms are written you can easily substitute your own without having to worry about crashing the program.

Concrete Strategy Classes

//////////
//Save following as StratEnterData.php

class StratEnterData implements IStrategy 
{     
    public function algorithm($elements) 
        {
                $server="your.server.com";
                $user="yourUserName";
                $pass="yourPassword";
                $currentDB="yourDataBaseName";
                $currentTable=$elements[0];
 
                //Make the Connection
                $hookup=mysql_connect($server, $user, $pass);
 
                //Select the database
                $dbNow=@mysql_select_db($currentDB, $hookup) or die ("Rats!");
 
                //Create Query Statement
                $sql = "INSERT INTO $currentTable (fname,lname,email) VALUES ('$elements[1]','$elements[2]', '$elements[3]')";
                $result=@mysql_query($sql,$hookup) or die("Data not entered. " . mysql_error());
 
                print "Data entery completed:

"; } } ?> ///////// //Save following as StratRetrieveAll.php   class StratRetrieveAll implements IStrategy { public function algorithm($elements) { $server="your.server.com"; $user="yourUserName"; $pass="yourPassword"; $currentDB="yourDataBaseName"; $currentTable=$elements; $counter=0; $combo=" ";   //Make the Connection $hookup=mysql_connect($server, $user, $pass);   //Select the database $dbNow=@mysql_select_db($currentDB, $hookup) or die ("Rats!");   $result = mysql_query("SELECT * FROM $currentTable", $hookup);   //Each record is divided into an array with the //number of fields making up the array.   while($row = mysql_fetch_array($result, MYSQL_NUM)) { $combo= $combo . $row[0] . ": " . $row[1] . " " . $row[2] . " email address is " . $row[3] . "
"
; $counter++; } print $combo . '

'. $counter; } } ?> ///////// //Save following as: StratQuery.php class StratQuery implements IStrategy { public function algorithm($elements) { $server="your.server.com"; $user="yourUserName"; $pass="yourPassword"; $currentDB="yourDataBaseName";   $currentTable=$elements[0]; $counter=0; $combo=" "; //Make the Connection $hookup=mysql_connect($server, $user, $pass); //Select the database $dbNow=@mysql_select_db($currentDB, $hookup) or die ("Rats!"); $result = mysql_query("SELECT * FROM $currentTable WHERE lname='$elements[1]'", $hookup); //Each record is divided into an array with the number of fields making //up the array. while($row = mysql_fetch_array($result, MYSQL_NUM)) { $combo= $combo . $row[0] . ": " . $row[1] . " email address is " . $row[3] . "
"
; $counter++; } print $combo . '

'. $counter; } } ?> ///////// //Save following as: StratUpdate.php   class StratUpdate implements IStrategy { public function algorithm($elements) { $server="your.server.com"; $user="yourUserName"; $pass="yourPassword"; $currentDB="yourDataBaseName";   $currentTable=$elements[0];   //Make the Connection $hookup=mysql_connect($server, $user, $pass); //Select the database $dbNow=@mysql_select_db($currentDB, $hookup) or die ("Rats!"); $result = mysql_query("UPDATE $currentTable SET fname='$elements[2]' WHERE fname='$elements[1]'", $hookup); print "Update complete."; } } ?>

The Client and Trigger Files

The Client file (which most like to call Main) is key, but unlike some design patterns, it is not part of the pattern itself. Rather, like all client files, it just makes requests, but only through the Context class. This one is set up to respond to calls by what I’m calling trigger files. These are little files that instantiate a Client object and first Client method requested from the HTML user interface.

The algorithm method expects a parameter, and so the client uses an array to pass on all of the information required by the concrete strategy classes. The concrete strategies then use the array elements to pass on required data to the SQL commands.

Client

//Client

ini_set("display_errors","2");
ERROR_REPORTING(E_ALL);
include_once('Client.php');
include_once('Context.php');
include_once('IStrategy.php');
include_once('StratEnterData.php');
include_once('StratRetrieveAll.php');
include_once('StratUpdate.php');
include_once('StratQuery.php');
 
class Client 
{
                private $fnameNow;
                private $lnameNow;
                private $emailNow;
                private $tableNow;
                private $context;
                private $stuff;
                private $queryElements;
                private $upStuff;
                private $newFirst;
 
        public function enterData()
        {
                $this->fnameNow=$_POST['firstName'];
                $this->lnameNow=$_POST['lastName'];
                $this->emailNow=$_POST['email'];
                $this->tableNow=$_POST['sqlTable'];
 
                $this->stuff= array($this->tableNow, $this->fnameNow, $this->lnameNow, $this->emailNow);
                $this->context = new Context(new StratEnterData());
        $this->context->algorithm($this->stuff);
        }
 
        public function getAllData()
        {
                $this->tableNow=$_POST['allTable'];
                $this->context = new Context(new StratRetrieveAll());
        $this->context->algorithm($this->tableNow);
        }
 
        public function queryData()
        {
                $this->tableNow=$_POST['queryTable'];
                $this->lnameNow=$_POST['queryLast'];
 
                $this->queryElements= array($this->tableNow, $this->lnameNow);
 
                $this->context = new Context(new StratQuery());
        $this->context->algorithm($this->queryElements);
        }
 
        public function updateData()
        {
                $this->tableNow=$_POST['updateTable'];
                $this->fnameNow=$_POST['origFirst'];
                $this->newFirst=$_POST['updateFirst'];
 
                $this->upStuff= array($this->tableNow, $this->fnameNow, $this->newFirst);
                $this->context = new Context(new StratUpdate());
        $this->context->algorithm($this->upStuff);
        }
}
?>

The use of what I call trigger files is a way to further loosen up dependencies and make updates easier. Basically, the trigger files are used as targets to be called from the HTML UI. They in turn create a Client instance and then fire a Client method depending on what the user wants to do.

PHP Trigger Files

///
///Name the following program GoClient.php

ini_set("display_errors","2");
ERROR_REPORTING(E_ALL);
include_once('Client.php');
include_once('Context.php');
include_once('IStrategy.php');
include_once('StratEnterData.php');
include_once('StratRetrieveAll.php');
include_once('StratUpdate.php');
include_once('StratQuery.php');
$makeRequest = new Client();
$makeRequest->enterData();
?>
 
//
///Name the following program ShowAll.php

ini_set("display_errors","2");
ERROR_REPORTING(E_ALL);
include_once('Client.php');
include_once('Context.php');
include_once('IStrategy.php');
include_once('StratEnterData.php');
include_once('StratRetrieveAll.php');
include_once('StratUpdate.php');
include_once('StratQuery.php');
$makeRequest = new Client();
$makeRequest->getAllData();
?>
 
//
///Name the following program QueryClient.php

ini_set("display_errors","2");
ERROR_REPORTING(E_ALL);
include_once('Client.php');
include_once('Context.php');
include_once('IStrategy.php');
include_once('StratEnterData.php');
include_once('StratRetrieveAll.php');
include_once('StratUpdate.php');
include_once('StratQuery.php');
$makeRequest = new Client();
$makeRequest->queryData();
?>
 
//
///Name the following program UpdateTable.php

ini_set("display_errors","2");
ERROR_REPORTING(E_ALL);
include_once('Client.php');
include_once('Context.php');
include_once('IStrategy.php');
include_once('StratEnterData.php');
include_once('StratRetrieveAll.php');
include_once('StratUpdate.php');
include_once('StratQuery.php');
$makeRequest = new Client();
$makeRequest->updateData();
?>

HTML Data UI

Finally, the user needs a way to communicate with the database via PHP. As far as the user is concerned, this whole thing could have been done within a single HTML/PHP program. The following is a simple one for testing out the Strategy design pattern. (The output is very simple, but like all things done with a design pattern, feel free to change it.)

?View Code HTML





Strategy Data Input


 Strategy Design Pattern

Enter Name and Email:

Table Name:

First Name:

Last Name:

Email:

Show all Records and Fields:

Table Name:
 

Find Email by Last Name:

Table Name:

Last Name:

Update Records:

Table Name:

Current First Name:

New First Name:

 PHP Design Patterns: https://php5dp.com

Change and Update is the Essence of Programming

As noted at the outset, if you want to add or change something, doing so with design patterns is quite easy. For example, you might want to add an algorithm to delete a record. All you have to do is to add a new class that implements the IStrategy interface and change the algorithm() method so that it requests a record to bedeleted. Then add a request method in the Client class, the HTML UI as well as a trigger program, and you’re done! That was easy. Go ahead and try it, and I think you’ll appreciate Strategy design pattern in creating algorithms that can be encapsulated on request!

Copyright © 2010 William Sanders. All Rights Reserved.

2 Responses to “PHP Strategy Design Pattern: Encapsulating Algorithms”


  • Hiya, loved the atricle! It’s hard to find examples of design patterbs that are easy to understand! My next project involves a login system where three different types of user are to login, and once logged in, they have the choice to add job, edit job etc. I was wondering (because im not experienced enough to fore see problems) if the strategy pattern would work? As in… Each form for each user will be generated by a different algorithm and all ill have to do for each user is (for example) fetchForm();

  • Hello Alex,

    Please forgive me for not getting back to you sooner. I hope to spend more time on this blog and post new articles.

    Generally, when I think of “login” I think of security and so automatically consider the Proxy. However, that’s just for login. In your explanation of your project, the Strategy pattern does seem like a logical choice.

    Please let us know how it turns out.

    Kindest regards,
    Bill Sanders

Leave a Reply