O'Reilly logo

Learning PHP Design Patterns by William Sanders

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 1. PHP and Object-Oriented Programming

All the forces in the world are not so powerful as an idea whose time has come.

Victor Hugo

Do not pray for tasks equal to your powers. Pray for powers equal to your tasks.

Phillips Brooks

Immense power is acquired by assuring yourself in your secret reveries that you were born to control affairs.

Andrew Carnegie

Ignorance is the curse of God; knowledge is the wing wherewith we fly to heaven.

William Shakespeare

Entering into Intermediate and Advanced Programming

When we first learn to read, the stories, vocabularies, and words tend to be small and simple. Dealing with small and simple stories requires small and simple tools. However, when we are more advanced and introduced to the works of William Shakespeare, we need a more complex, larger, and more sophisticated toolset. If a kindergarten teacher handed her brood Hamlet, chances are the kids wouldn’t understand it, but if they are given an incremental set of reading tools over the years, by the time they reach high school, they can read, understand, and appreciate Hamlet. This book is for developers who are ready to read the PHP version of Hamlet.

To get what you need from this book, you need to begin with an understanding of and experience with PHP. Other books in this series, Learning PHP 5 by David Sklar and Learning PHP, MySQL, and JavaScript, 2nd Edition, by Robin Nixon (O’Reilly) are good places to start if you have no PHP experience. Of course, you may have learned PHP from any number of other books, courses, or online tutorials. What matters is that you know how to program in PHP. Further, we’re going to be dealing with PHP 5 and nothing earlier, like the last version of PHP 4 (PHP 4.4.9). That’s because just about everything we need for object-oriented programming (OOP) wasn’t implemented until PHP 5.

Why Object-Oriented Programming?

Although OOP has been around for more than 40 years, it was not until the last 15 years or so that it’s become more and more important. In large measure, this is due to the influence of Java, which includes built-in OOP structures. Newer languages associated with the Internet, such as JavaScript, ActionScript 3.0, and PHP 5, also have incorporated OOP in style or structure. In 1998, JavaScript Objects by Alexander Nakhimovsky and Tom Myers (Wrox), two Colgate University professors, showed that OOP could be incorporated into JavaScript. So OOP is nothing new, even for those whose main programming has been in the realm of Internet languages, and we can even say that it is a “tried and proven” method of programming in most languages designed to give instructions to computers.

Spending some time understanding OOP is important because understanding design patterns relies on understanding OOP. So while you may have substantial experience programming in PHP 5, if you do not have OOP experience, spend some time in Part I.

Making Problem Solving Easier

Computer programs are designed to solve human problems. A process called dynamic programming is a technique for breaking down larger problems into smaller ones. The plan is to solve each smaller problem and then put everything back together into a single, larger solution. Take, for example, planning a trip to Timbuktu. (It doesn’t sound like a complex problem, but see if you can find a flight from your town to Timbuktu on an online travel site.) Let’s break it down:

  1. Does Timbuktu (aka Tombouctou or Timbuctu) exist? (Yes./No.) Answer = Yes.

  2. Does Timbuktu have an airport? (Yes./No.) Answer = Yes, Airport Identifier = TOM.

  3. Are there flights into TOM? (Yes./No.) Answer = Maybe. Flights are available from both Bamako and Mopti, but Islamist rebels took control of Timbuktu as of July 1, 2012, and flights have been canceled until further notice.

  4. Are hostile rebels in control of Timbuktu now? (Yes./No.) If answer = Yes, there are no flights. If answer = No, there may be flights.

  5. If flights are available, is Timbuktu safe for tourism or business? (Yes./No.) Answer = No.

  6. Are visas from my country into Mali (country where Timbuktu is located) available? (Yes./No.) Answer = Yes.

  7. Are vaccinations required? (Yes./No.) Answer = Yes.

As you can see, getting to and from Timbuktu is a complex issue, but the list of simple questions can all be answered by yes or no. Lots more questions would be included in the list, but each can be answered in a binary fashion. The “maybe” answer means that more questions need to be asked to get a yes/no answer.

Modularization

The process of decomposing a problem into small subproblems is the process of modularization. Just like the complexities of getting from your home to Timbuktu can be modularized into a set of yes/no steps, any other complex problem also can be modularized. Figure 1-1 illustrates this process.

Even the most complex problem can be broken into modules
Figure 1-1. Even the most complex problem can be broken into modules

In looking at modularization, you may be thinking that it doesn’t look too difficult. You’d be absolutely right. The more complex the problem, the more it makes sense to modularize it. So, the initial reasoning in OOP programming, far from being complex, simplifies the complex. Even the most daunting programming problem can be solved by this divide-and-conquer strategy.

Classes and Objects

Once a problem is modularized, what are you going to do with the modules? As you saw, breaking down a complex problem can transform it into many simple subproblems, but you need a way to organize the modules and work with them in relation to each other to handle the larger problem being solved. One way to look at a module is as a collection of related functions. In programming, these modules are called classes.

A class itself is made up of parts called properties and methods. The properties are different types of data objects like numbers, strings, nulls, and Booleans. Generally, the data are stored as abstract data types known as variables, constants, and arrays. Methods, on the other hand, are functions that operate on the data.

Single Responsibility Principle

One way to think of a class is as a collection of objects with common characteristics. The “commonness” of characteristics does not mean that they are the same, but instead they deal with the common problem assigned to the module—the class. Keeping in mind that the purpose of a module is to solve some aspect of a more complex problem, we arrive at one of the first principles of object-oriented programming: the single responsibility principle, which states that a class should have only a single responsibility.

It’s not that a class cannot have multiple responsibilities, but keep in mind that we broke down a complex problem into simple modules so that we’d have several easy-to-solve problems. By limiting a class to a single responsibility, we not only remind ourselves of why we modularized the problem, but we also have an easier way of organizing the modules. Let’s look at a class with a single responsibility. Suppose you’re making a website for a client, and because the site is to be viewed by different devices ranging from desktops to tablets to smartphones, you want to have some way of determining what type of device and which browser is used to view your web page. With PHP, it’s easy to write a class that provides that information using the built-in array $_SERVER and the related element, HTTP_USER_AGENT. The TellAll class in the following listing demonstrates a class with a single responsibility—to provide information about the user agent viewing the PHP page:

<?php
//Saved as TellAll.php

class TellAll
{
    private $userAgent;

    public function __construct()
      {
        $this->userAgent=$_SERVER['HTTP_USER_AGENT'];
        echo $this->userAgent;
      }
}

$tellAll=new TellAll();

?>

Loading this class through a Safari browser on an iMac displays the following:

Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_4) AppleWebKit/ 534.57.2 (KHTML,
like Gecko) Version/5.1.7 Safari/534.57.2

When tested on an iPad using an Opera Mini browser, the results are different:

Opera/9.80 (iPad; Opera Mini/7.0.2/28.2051;U;en) Presto/2.8.119 Version/11.10

The class represents a module of a more complex operation, of which the class is only a single part. Like a good class in OOP, it has a single responsibility—finding information about the user agent.

Constructor Functions in PHP

A unique feature of PHP classes is the use of the __construct() statement as a constructor function. Most computer languages use the name of the class as the constructor function name; however, using the __construct() statement removes all doubt as to the function’s purpose.

The constructor function in a class automatically launches as soon as the class is instantiated. In the TellAll class, the results are immediately printed to the screen, whether you want them there or not. For demonstration purposes, that’s fine, but as a module, other modules may simply want to use the information about the device and/or the browser. So, as you will see, not all classes include a constructor function.

The Client as a Requester Class

In the TellAll class, I included a little trigger at the bottom to launch the class. With the exception of the Client class, self-launching is not recommended. In your experience with PHP, you most likely launched a PHP program from HTML using a form tag something like the following:

<form action="dataMonster.php" method="post">

So, you’re familiar with launching a PHP file from an external source. Similarly, PHP files containing classes should be used by other modules (classes) and not self-launched.

As we get more into design patterns, you’ll find a class named Client keeps appearing. The Client has different roles in the larger project, but the primary one is to make requests from the classes that make up the design pattern. Here, the Client is shown in relation to a revised version of the TellAll class. This new class used by the Client is different in several ways from TellAll that are more useful to an overall project and reusable in other projects. The MobileSniffer class begins with the same user agent information, but the class makes it available in more useful ways with its properties and methods. Using a Unified Modeling Language (UML) diagram you can see that the Client instantiates (dashed arrow) the MobileSniffer. Figure 1-2 illustrates a simple class diagram of the two classes.

Client class instantiates MobileSniffer class and can use its properties and methods
Figure 1-2. Client class instantiates MobileSniffer class and can use its properties and methods

Had a Client class instantiated automatically, the Client would have fewer options for how to use the MobileSniffer information. Take a look at the following listing to see how to create the class:

<?php
//User agent as property of object
class MobileSniffer
{
    private $userAgent;
    private $device;
    private $browser;
    private $deviceLength;
    private $browserLength;

    public function __construct()
      {
        $this->userAgent=$_SERVER['HTTP_USER_AGENT'];
        $this->userAgent=strtolower($this->userAgent);

        $this->device=array('iphone','ipad','android','silk','blackberry', 
          'touch');
        $this->browser= array('firefox','chrome','opera','msie','safari',
        'blackberry','trident');
        $this->deviceLength=count($this->device);
        $this->browserLength=count($this->browser);
      }
    public function findDevice()
    {
        for($uaSniff=0;$uaSniff < $this->deviceLength;$uaSniff ++)
        {
            if(strstr($this->userAgent,$this->device[$uaSniff]))
            {
                return $this->device[$uaSniff];
            }
        }
    }

    public function findBrowser()
    {
        for($uaSniff=0;$uaSniff < $this->browserLength;$uaSniff ++)
        {
            if(strstr($this->userAgent,$this->browser[$uaSniff]))
            {
                return $this->browser[$uaSniff];
            }
        }
    }
}

?>

In order to use the MobileSniffer, the Client instantiates the class and uses its methods as shown in the following listing:

<?php
ini_set("display_errors","1");
ERROR_REPORTING(E_ALL);
include_once('MobileSniffer.php');
class Client
{
    private $mobSniff;
    public function __construct()
    {
        $this->mobSniff=new MobileSniffer();
        echo "Device = " . $this->mobSniff->findDevice() . "<br/>";
        echo "Browser = " . $this->mobSniff->findBrowser() . "<br/>";
    }
}

$trigger=new Client();
?>

Using the Client class provides a way to make the MobileSniffer class more useful. The MobileSniffer does not have to launch itself, and using a return statement, any class that calls MobileSniffer just gets the data. The Client can then use that data in any way it wants. In this case, the Client formats the data to output it to the screen, as you can see in Figure 1-3.

The Client uses the MobileSniffer’s data to send to the screen
Figure 1-3. The Client uses the MobileSniffer’s data to send to the screen

We could have formatted the data in the MobileSniffer class, but then it would not have been as flexible or useful. By allowing the Client to use the data in the most general way, it could do any number of things with it. For example, instead of formatting the data for screen output, it could have used the data to call a CSS file that formats for the particular device and/or browser. Had the data been preformatted in the MobileSniffer class, using it for identifying a CSS file would require stripping away the unneeded formatting. Keep in mind that one of the most important features of design patterns is reuse of the objects.

At this point, you may be thinking, “I could write a better algorithm for sorting out devices and browsers.” You probably could, and in fact, you’ll probably have to because as new devices and browsers are introduced, they will have to be incorporated into a program that needs to use device/browser information. However, if you preserve the structure of the two methods, findDevice() and findBrowser(), you can make all the changes and improvements you want, and the larger program will not crash. You must imagine a much larger and more complex program and think about making changes. If you’ve ever had to revise a larger program, you know that a change can worm its way through the entire program and break it. Then your debugging becomes a nightmare. One of the main functions of OOP and design patterns is the capacity to change a module without breaking the entire program.

What About Speed?

Just about every programmer wants a program to run at optimum speed, and to do that, we look at the best algorithms. For now, though, we need to shift our attention to another kind of speed—the amount of time it takes to create and update a program. If a program cycles through an operation 100 million times, minor speed tweaks of that operation are important, but trying to squeeze a couple of microseconds from an operation that’s used only once can be an expensive use of time. Likewise, having to revise an entire program because of a few lines of added code is an equally expensive use of time.

The Speed of Development and Change

Consider a contract you have to update and maintain a program for a customer. You have negotiated a set amount for ongoing updates, and you want both to satisfy your client and to spend a fair but limited amount of time on updates. For example, suppose your customer has weekly sales on different products requiring ongoing text and image updates. One solution may be to set up a weekly update using the time() function, and then all you have to do is add the most current image URL and text to a database. In fact, if you had the text and images ahead of time, you could go on vacation and let the PHP do the work while you’re away. That would be a sweet maintenance deal, and you could keep several customers happy simultaneously.

Would you ever consider setting up a maintenance system where you had to rewrite the program every time you had to make a change? Probably not. That would be a very slow and expensive way of doing things. So where speed of revision is important, your program needs to consider the speed of both operation and development. Algorithms handle speed of operations, and design patterns handle speed of development.

The Speed of Teams

Another speed issue can be found in working with teams. When dealing with larger and more complex programs, teams need to agree on and understand a common plan and goal to effectively and efficiently create and maintain large programs. Among other things, OOP and design patterns provide a common language to speed along group work. References to “factories,” “state machines,” and “observers” all mean the same thing to those who understand OOP and design patterns.

Most importantly, design patterns provide a way of programming so that a team of programmers can work on separate parts that will go together. Think of an assembly line making automobiles—each team assembles a different part of the car. To do that, they need a pattern of development and an understanding of the relationship of one part to another. In this way, everyone can do their job knowing that someone else’s job will fit with their work. They don’t have to know the details of another worker’s job. They just need to know that they’re working from the same plan.

What’s Wrong with Sequential and Procedural Programming?

“If it ain’t broke, don’t fix it” is a widely believed sentiment, and you may immediately agree with it if a solution works. However, such a mindset is the antithesis of progress and improvement. After all, for getting from one place to another, walking works just fine. However, for getting from one side of the country to the other, flying in a jet works much better. OOP and design patterns are improvements over sequential and procedural programming in the same way as flying is to walking.

Sequential Programming

Most programmers begin programming by writing one statement after another to create a series of lines that will execute a program. For example, the following is a perfectly good PHP sequential program that works:

<?php

$firstNumber=20;
$secondNumber=40;
$total= $firstNumber + $secondNumber;
echo $total;

?>

The variables are abstract data types, and the arithmetic add operator (+) combines the values of two variables into a third variable. The echo statement prints out the total of the combined values to the screen.

Adding two numbers is a simple problem for PHP, and as long as you deal with simple problems, you can use simple solutions.

Procedural Programming

As programmers began to write longer and longer programs with more complex tasks, the sequences began to be entangled into what was called spaghetti code. A GOTO statement would allow sequential programmers to jump around in a program to complete a procedure, and so it was easy to become entangled.

With procedural programming came the function. A function is a little object where an operation can be called to perform a sequence with a single statement. For example, the following is a procedural version of the sequential program shown in the previous listing:

<?php

function addEmUp($first,$second)
{
    $total=$first + $second;
    echo $total;
}
addEmUp(20,40);

?>

The functions (or procedures) allow programmers to group sequences into modules that can be reused in a program. Further, by having parameters, the programmer can enter different arguments into a function so that it can be used with different concrete values.

Like OOP, procedural programming uses modularity and reuse. However, procedural programming does not provide for classes where programming tasks can be bundled into objects. Class objects (instances of classes) can operate on their own data structures, and that cannot be done by functions alone. As a result, procedural programming requires long sequences to accomplish large tasks. Further, working in teams is more difficult with procedural programming because different team members cannot easily work on independent but interrelated classes, as can be done with OOP.

Pay Me Now or Pay Me Later

A while ago, I published a blog post titled “No Time for OOP and Design Patterns.” The post was in reaction to a number of developers who said reasonably that they did not have time to incorporate OOP or design patterns into their work even though they wanted to do so. They explained that a project would come along with a clear deadline, and in an effort to get it done on time, they’d cobble together a working program using sequential and procedural programming. Maybe they’d include a class or two if they had one that met a particular goal, but that was it.

In learning OOP and design patterns for PHP, you need to remember a couple of points, which were first made by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides in Design Patterns: Elements of Reusable Object-Oriented Software:

  • Designing object-oriented software is hard.

  • Designing reusable object-oriented software is even harder.

Rather than looking at those statements as reasons not to learn OOP and design patterns, they stand as reasons why OOP and design patterns are so valuable. Knowledge adds value to any skillset. The more difficult the knowledge is to obtain, the more it is valued.

Don’t expect to pick up OOP and design patterns easily and quickly. Rather, incorporate them a little at a time into your PHP programming. At some point you will see the value. Over time, you will develop more skills and understanding, and you will run into a project where you can reuse most of the program structures from a previous project. In a recent project, I decided to use a Strategy design pattern. It included a table with 105 fields, and the customer wanted a certain set of functionalities. By using a Strategy design, each of the strategies was a class with an algorithm to handle a fairly mundane PHP problem—filtering, updating, and deleting data in a MySQL database. It took a while to set it up, but once it was configured, it was easy to change (customers always want change!). Some time later, I was asked to do a similar kind of project using frontend and backend PHP with a MySQL database. Rather than starting over from scratch, I just pulled out the Strategy pattern, changed the algorithms, and had it up and running in no time. I got paid the same, but having worked smart, my customer got a much better piece of software than had I worked longer and dumber.

At some point, we have to stop our old habits and upgrade our skills. At this point in time, many programmers are updating their skills to accommodate mobile devices. If they do not, they’ll lose out on many opportunities—eventually they may render their skills obsolete. Over time, we know that we’ll have to update our skills to incorporate the benefits of the latest PHP release, new technology, or direction the industry takes. OOP and design patterns contain concepts that transcend all of these changes, make us better programmers, and provide our customers with better software. It all starts with a first step. By taking the time now, you won’t be scrambling for time to get a project done in the future. Besides, you will come out of the process as a better programmer, and that in and of itself is reason enough to learn OOP and design patterns.

Above all, learning OOP and design patterns is the pleasure of doing something well.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required