Do not pray for tasks equal to your powers. Pray for powers equal to your tasks.
Immense power is acquired by assuring yourself in your secret reveries that you were born to control affairs.
Ignorance is the curse of God; knowledge is the wing wherewith we fly to heaven.
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.
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.
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:
Does Timbuktu (aka Tombouctou or Timbuctu) exist? (Yes./No.) Answer = Yes.
Does Timbuktu have an airport? (Yes./No.) Answer = Yes, Airport Identifier = TOM.
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.
Are hostile rebels in control of Timbuktu now? (Yes./No.) If answer = Yes, there are no flights. If answer = No, there may be flights.
If flights are available, is Timbuktu safe for tourism or business? (Yes./No.) Answer = No.
Are visas from my country into Mali (country where Timbuktu is located) available? (Yes./No.) Answer = Yes.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
“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.
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.
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.
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.