Guide To PHP Design Patterns

User Manual:

Open the PDF directly: View PDF PDF.
Page Count: 337 [warning: Documents this large are best viewed by clicking the View PDF Link!]

.309
7.50 x 9.25 7.50 x 9.25
php|architects
Guide to
PHP Design Patterns
A Practical Approach to Design Patterns
for the PHP 4 and PHP 5 Developer
Jason E. Sweat
USA $21.99
Canada $29.99
U.K. £16.99 Net
php|architects
Guide to
PHP Design Patterns
Design patterns are comprehensive, well-tested solutions to common problems
that developers everywhere encounter each day. Although designed for solving
general programming issues, some of them have been successfully adapted to
the specific needs of Web development.
php|architect’s Guide to PHP Design Patterns is the first comprehensive guide
to the application of design patterns to the PHP development language.
Designed to satisfy the need of enterprise-strength development, you will find
this book both an excellent way to learn about design pattern and an
irreplaceable reference for your day-to-day programming
With coverage of more than XXX different types of patterns, including BLAH,
BLAH, BLAH, BLAH and much more, this book is the ideal resource for your
enterprise development with PHP 4 and PHP 5.
NanoBooks are excellent, in-depth resources created by the publishers of
php|architect (http://www.phparch.com), the world’s premier magazine dedicated
to PHP professionals.
NanoBooks focus on delivering high-quality content with in-depth analysis and
expertise, centered around a single, well-defined topic and without any of the fluff
of larger, more expensive books.
Shelve under PHP/Web Development/Internet Programming
From the publishers of
php|architects Guide to PHP Design Patterns
Jason E. Sweat
GREETS FLYS OUT TO KOOBE COMMUNITY!
PHP|ARCHITECTSGUIDE TO
PHP DESIGN PATTERNS
by Jason E. Sweat
php|architects Guide to PHP Design Patterns
Contents Copyright © 2004-2005 Jason E. Sweat - All Right Reserved
Book and cover layout, design and text Copyright © 2004-2005 Marco Tabini & Associates, Inc. - All Rights Reserved
First Edition: July 2005
ISBN 0-9735898-2-5
Produced in Canada
Printed in the United States
No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means,
without the prior written permission of the publisher, except in the case of brief quotations embedded in critical
reviews or articles.
Disclaimer
Although every effort has been made in the preparation of this book to ensure the accuracy of the information con-
tained therein, this book is provided "as-is" and the publisher, the author(s), their distributors and retailers, as well
as all affiliated, related or subsidiary parties take no responsibility for any inaccuracy and any and all damages
caused, either directly or indirectly, by the use of such information.
We have endeavoured to properly provide trademark information on all companies and products mentioned in this
book by the appropriate use of capitals. However, we cannot guarantee the accuracy of such information.
Marco Tabini & Associates, The MTA logo, php|architect, the php|architect logo, NanoBook and NanoBook logo are
trademarks or registered trademarks of Marco Tabini & Associates Inc.
Bulk Copies
Marco Tabini & Associates, Inc. offers trade discounts on purchases of ten or more copies of this book. For more
information, please contact our sales offices at the address or numbers below.
Credits
Written by Jason E. Sweat
Published by Marco Tabini & Associates, Inc.
28 Bombay Ave.
Toronto, ON M3H 1B7
Canada
(416) 630-6202
(877) 630-6202 toll free within North America
info@phparch.com / www.phparch.com
Edited By Martin Streicher
Technical Reviewer Marcus Baker
Layout and Design Arbi Arzoumani
Managing Editor Emanuela Corso
Biography
Jason E. Sweat
Jason graduated from Colorado State University in 1992 as a University Honor Scholar with a Bachelors of Science
in Business Administration, concentrations in Computer Information Systems and Finance & Real Estate, and a
minor in Mathematics.
He spent seven years working for a small engineering firm doing process control work in the steel industry. This let
to extensive SQL development and Jason's first web development experience creating ASP pages. He changed
employers and worked as a Senior Project Leader for a Fortune 100 industrial manufacturer, leading a team of
developers for commercial applications, and acting as the web master for his business unit. His role changed again
in January 2005, and Jason is now the Manager of eBusiness/Commercial Systems for the same business unit.
Jason has used PHP since 2001, where he was searching for a free‹as in beer ;) ‹substitute for IIS/ASP to create
an accounting system for a home business. His Unix administrator pointed him towards Linux, Apache and PHP. He
has since adopted PHP as an intranet development standard at work, as well as using PHP in a Unix shell script-
ing environment.
He was a co-author of PHP Graphics Handbook (Wrox 2003), has published several articles for the Zend website
and for php|architect magazine, and has presented numerous talks on PHP at various conferences. Jason is a Zend
Certified Engineer, and maintains a blog at http://blog.casey-sweat.us/.
Jason currently resides in Iowa with his wife and two children. He enjoys many activities with his family including
camping, hiking and swimming. He also enjoys practicing the Japanese martial art of Aikido.
To my wife, Vicki, and to my children, Madeline and Caleb,
for putting up with even more time with Daddy on the computer.
Thank you for your support and love.
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .17
The Goal of This Book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .19
Object Oriented Programming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .19
Assumed Reader Skill Set . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .19
PHP4 and PHP5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .19
Object Handles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .20
Additional Resources and References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .22
Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .22
1 Programming Practices . . . . . . . . . . . . . . . . . . . . . .25
Testing Your Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .25
Refactoring . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .30
Other Practices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .34
UML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .34
Source Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .35
Source Code Documentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .35
2 The Value Object Pattern . . . . . . . . . . . . . . . . . . . . .39
The Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .41
The Solution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .41
PHP 5 Sample Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .42
In Context Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .43
PHP 4 Sample Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .47
Business Logic in ValueObjects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .49
3 The Factory Pattern . . . . . . . . . . . . . . . . . . . . . . . . . .53
The Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .53
CONTENTS
The Solution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .53
Sample Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .54
Adding a Little Color . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .57
Factories to Hide Object State Setup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .61
Factories to Promote Polymorphism . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .63
Factories for Lazy Loading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .71
Issues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .72
4 The Singleton Pattern . . . . . . . . . . . . . . . . . . . . . . . .75
The Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .75
The Solution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .76
Sample Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .77
A “Global” Approach . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .77
A Static Approach . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .79
The Singleton in PHP5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .80
Issues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .81
The Monostate Pattern: Stealth Singletons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .81
5 The Registry Pattern . . . . . . . . . . . . . . . . . . . . . . . . .85
The Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .85
The Solution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .85
Sample Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .86
An Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .90
Implementing the Registry as a MonoState Object . . . . . . . . . . . . . . . . . . . . . . .92
Implementing with Class Static Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .95
Issues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .98
Embedded Registry . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .98
6 The MockObject Pattern . . . . . . . . . . . . . . . . . . . . .101
The Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .101
The Solution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .102
Sample Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .102
A Legacy Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .106
Issues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .119
Resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .120
Table of Contents10
7 The Strategy Pattern . . . . . . . . . . . . . . . . . . . . . . . .123
The Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .124
The Solution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .124
An Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .124
Sample Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .131
Issues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .134
Related Patterns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .134
8 The Iterator Pattern . . . . . . . . . . . . . . . . . . . . . . . . .137
The Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .137
The Solution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .138
Sample Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .142
A Variant Iterator API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .148
Filtering Iterator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .150
Sorting Iterator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .151
SPL Iterator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .153
Issues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .158
9 The Observer Pattern . . . . . . . . . . . . . . . . . . . . . . .161
The Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .161
The Solution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .161
Sample Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .162
Issues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .170
10 The Specification Pattern . . . . . . . . . . . . . . . . . . .173
The Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .174
The Solution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .174
Traveling to Warm Destinations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .174
Sample Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .176
Parameterized Specification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .178
11 The Proxy Pattern . . . . . . . . . . . . . . . . . . . . . . . . . .191
The Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .191
The Solution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .191
Sample Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .194
Table of Contents 11
RemoteProxy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .194
Lazy Proxy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .198
Dynamic Proxy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .200
Issues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .201
12 The Decorator Pattern . . . . . . . . . . . . . . . . . . . . .203
The Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .203
The Solution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .204
Sample Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .206
Issues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .216
13 The Adapter Pattern . . . . . . . . . . . . . . . . . . . . . . .219
The Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .219
The Solution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .219
Sample Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .220
Issues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .224
14 The Active Record Pattern . . . . . . . . . . . . . . . . . .227
The Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .227
The Solution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .227
Sample Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .228
Test Independence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .230
Record Creation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .231
Testing Database Failure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .236
Active Record Instance ID . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .237
Searching for Records . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .238
Updating Records . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .240
Issues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .243
15 The Table Data Gateway Pattern . . . . . . . . . . . .247
The Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .247
The Solution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .247
Sample Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .248
Test Case Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .249
Returning Recordsets as Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .250
Table of Contents12
Returning Iterable Object Collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .252
Updating Rows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .255
Issues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .258
16 The Data Mapper Pattern . . . . . . . . . . . . . . . . . . .261
The Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .261
The Solution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .262
Sample Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .263
Issues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .281
17 The Model-View-Controller Pattern . . . . . . . . .283
The Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .283
The Solution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .283
The Model-View-Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .284
The Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .285
Domain Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .285
The View . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .286
Template View . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .286
The Transform View . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .290
The Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .291
Front Controllers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .291
Application Controllers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .292
Cross-Cutting MVC Concerns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .295
Non-MVC Frameworks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .296
Event Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .296
Inversion of Control Containers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .296
Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .296
18 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .299
A Pattern Quick Reference . . . . . . . . . . . . . . . . . . . .303
Book References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .310
B SimpleTest Testing Practices . . . . . . . . . . . . . . . . .313
Best Practices for Using SimpleTest . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .314
Table of Contents 13
Mock Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .317
Web Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .319
Our Legacy Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .320
Partial Mock Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .325
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .331
Table of Contents14
Introduction
HAVE YOU EVER STARTED to tackle a new feature in your application only to realize that its
solution is strikingly similar to something that youve already implemented? If youve been a
programmer for even a short time, the answer is probably “Yes” and it’s likely that you’ll reach
for some existing code to bootstrap your new development. You might even realize that your solution
is fundamental, an approach that can be applied widely and repeatedly, not just by you, but by all pro-
fessional developers.
In fact, many programming problems are faced over and over again, and many fundamental solu-
tions—or design patterns—have emerged to address them. Design patterns are a template for how to
organize your code so you can take advantage of a tried-and-true design.
All design patterns have several common characteristics: a name, a problem statement, and a solu-
tion.
• The name of a design pattern is important, because it allows you to instantly communi-
cate the intent of your code with other programmers—at least programmers familiar
with patterns—without going into too much detail.
• The problem is the domain where the pattern can be applied.
• The solution describes the implementation of the pattern. Good coverage of a pattern
should discuss the pros and cons of the patterns use.
A pattern is a useful technique to solve a given problem. A design pattern isnt a library—code
to be included and used directly in your project—but rather a template for how your code can be
structured. Indeed, a code library and a design pattern are applied much differently.
For example, a shirt you buy off the rack at a department store is a code library. Its color, style,
and size were determined by the designer and manufacturer, but it meets your needs.
However, if nothing in the store suits you, you can create your own shirt—designing its form,
choosing a fabric, and stitching it together. But unless you are a tailor, you may find it easier to sim-
ply find and follow an appropriate pattern. Using a pattern, you get an expertly-designed shirt in far
less time.
Returning the discussion to software, a database abstraction later or a content management
system is a library—it’s pre-designed and already coded, and a good choice if it meets your require-
ments exactly. But if youre reading this book, chances are that off-the-shelf solutions dont always
work for you. Yet you know what you want and are capable of realizing it; you just need a pattern to
guide you.
One last thought: like a sewing pattern, a design is of little use on its own. After all, you cant wear
a pattern—it’s just a patchwork of thin paper. Similarly, a software design pattern is just a guide. It
must still be tailored specifically to a programming language and your applications features and
requirements.
Design Pattern History
The term design pattern” was originally coined in the field of architecture. Christopher Alexander, in
his 1977 work, A Pattern Language: Towns/Building/Construction,” describes common issues of archi-
tectural design and explains how new, effective designs can be created through the aggregation of exist-
ing, well-known patterns. Alexander’s concepts translate well into software development, where it’s long
been desirable to construct solutions from previously existing components.
Introduction18
L
L
The Goal of This Book
The goal of this book is not to present a comprehensive catalog of software design patterns or to
develop any new design patterns or terminology, but rather to highlight a few of the existing, well-
known design patterns. In particular, the book presents those patterns that I’ve found most useful
for development of dynamic web applications and shows reference implementations for these pat-
terns in PHP.
Object Oriented Programming
By the very nature of design patterns, a good deal of this book is based on the concepts and prac-
tices of Object Oriented Programming (OOP).
If youre not familiar with OOP, there are many resources—books, web sites, magazines, and
classes—to help you learn more about it. Much of the OOP materials extol the benefits of code reuse,
robustness, encapsulation, polymorphism, and extensibility, each of which is important and valuable.
However, I believe the main benefit of OOP is how it encourages you to distill the problem at hand
into manageable pieces. Designed and implemented in focused, small pieces, your code can be test-
ed more thoroughly and is easier to understand and maintain.
Assumed Reader Skill Set
This book assumes that youre already fluent with PHP. In particular, it presupposes that you have a
working knowledge of PHP and its syntax and understand the fundamentals of PHP’s implementa-
tion of OOP. This book isnt intended to be an introduction to PHP programming, nor to OOP in PHP.
Because not all practitioners of OOP use the same terminology, where new terminology is intro-
duced, it’s defined in the text or in a sidebar.
PHP4 and PHP5
As I write this book, PHP5 has been released for some time but has yet to be widely adopted in the
hosting community. In my own job, I’ve started to migrate new development of applications to PHP
5.0.3 and am very pleased so far with both its backwards compatibility with PHP4 code and its new
object model, which is one of the significant new features of PHP5 and the main driver for my adop-
tion.
There are many fine articles and tutorials dealing with the nuances of the change in the object
model between PHP versions, but the short story is that PHP5 offers:
• Object handles (explained below, and further in Chapter 2: The Value Object Pattern)
• Better constructors (uniform name, changing
$this
not allowed)
Introduction 19
• Destructors now exist
Visibility (public, protected, private for methods and attributes)
• Exceptions (an alternative to triggering errors using the new
try{} catch{}
syntax)
• Class constants (defines using the class for a name space)
• Reflection (dynamic examination of classes, methods and arguments)
Type hinting (specifying expected classes or interfaces for method arguments)
PHP5 also offers a few more obscure features:
• New magic methods (
__get()
and
__set()
allow you to control attribute access;
__call()
lets you dynamically intercept all method calls to the object;
__sleep()
and
__wakeup()
let you override serialization behavior; and
__toString()
lets you control
how an object represents itself when cast as a string)
• Autoloading (allows the end user to try to automatically load the class the first time a ref-
erence to it is made)
• Final (do not allow a method or a class to be overridden by subclasses)
Object Handles
The best news in PHP5 is all objects are now defined by handles, similar to a system resource like a
file or a database handle. Passing an object to a PHP function no longer implicitly makes a copy of
the object.
To see the difference, consider the following two examples:
// PHP4 class
class ExampleP1 {
var $foo;
function setFoo($foo) {
$this->foo = $foo`;
}
function getFoo() {
return $this->foo;
}
}
function changeExample($param) {
$param->setFoo(‘blah’);
return $param->getFoo();
}
$obj = new ExampleP1;
$obj->setFoo(‘bar’);
echo $obj->getFoo(); // bar
echo ChangeExample($obj); //blah
echo $obj->getFoo(); // bar
Introduction20
In PHP4, the variable
$param
in
changeExample()
contains a copy of
$obj
. So, the function doesnt
alter the value of
$foo
in the original object and the final
$obj->getFoo()
prints “bar.
In PHP5, because $obj is passed as a handle, the same
changeExample()
function does effect the
original object. In other words, using handles, a copy isnt made and
$param
is the instance
$obj
.
// PHP5 class
class ExampleP2 {
protected $foo;
function setFoo($foo) {
$this->foo = $foo;
}
function getFoo() {
return $this->foo;
}
}
$obj = new ExampleP2;
$obj->setFoo(‘bar’);
echo $obj->getFoo(); // bar
echo ChangeExample($obj); //blah
echo $obj->getFoo(); // IMPORTANT, produces blah
This issue becomes even more complicated when you pass the
$this
variable to other objects or
functions inside of the object constructor.
What this boils down to is that in PHP4 you need to (nearly) always:
• Create an object by reference, as in
$obj =& new Class;
• Pass an object by reference, like
function funct(&$obj_param) {}
• Catch an object by reference
function &some_funct() {} $returned_obj =&
some_funct()
Now, there are some cases where you actually want to have a copy of the original object. In my PHP4
code, I always comment any non-reference assignment of an object as an intentional copy. In the
long run, such a brief comment can save you or anyone else maintaining your code a great deal of
headaches. Reference passing, object handles, and object copies are explored in greater detail in
Chapter 2, “The Value Object Pattern.
Despite my personal preference to move towards PHP5 development, my feeling is that PHP4
will continue to be with us for quite some time and existing public projects should continue to sup-
port it. To that end, this book tries to provide equal footing to both versions of PHP. Whenever pos-
sible, both PHP4 and PHP5 versions of example code are provided and explained. Within each chap-
ter, each code block that changes from one version of PHP to another has a comment of
// PHP4
or
Introduction 21
// PHP5
to indicate the change. Subsequent blocks of code are in the same version of PHP, until the
next switch is indicated.
Additional Resources and References
There are a number of great references available to help you learn more about design patterns. The
“bible” of design patterns is Design Patterns: Elements of Reusable Object-Oriented Software by Erich
Gamma, Richard Helm, Ralph Johnson and John Vlissides (his seminal work is often referred to as
the “Gang of Four” or simply “GoF,” in reference to the four authors). Throughout this book, the GoF
names of patterns are used as the canonical source.
Following “Design Patterns,” the next most useful book on design patterns for PHP web appli-
cation developers is Patterns of Enterprise Application Architecture by Martin Fowler. Fowler’s book
details many patterns that are of use specifically in the task of developing web application, in con-
trast with the broader coverage of general patterns in GoF.
The Web offers many good resources for information on design patterns. One particular stand-
out is the Portland Pattern Repository at http://c2.com/ppr/.
A good site for reference patterns implemented in PHP is ::phpPatterns(), located online at
http://www.phppatterns.com/.
Acknowledgments
I would like to thank my employer, where my role and responsibilities allow me to spend a portion
of my time in this area I love, providing me with the knowledge and experience to have the confi-
dence to write this book.
Another source of inspiration, ideas, and experience is the SitePoint
(http://www.sitepoint.com/) forums. In particular, the regular contributors to the Advanced PHP
Forum” have a great wealth of experience and knowledge, which they regularly share in one of the
most generous and helpful communities I’ve found on the Internet. It was through this resource I
located SimpleTest (http://simpletest.sf.net/), WACT (http://wact.sf.net/) and numerous other
PHP projects that I’ve found invaluable. I hope SitePoint continues to be a great resource for PHP
developers for many years to come.
This book clearly could not have come into existence without the significant efforts and dedica-
tion of the PHP team, who developed a useful, easy to learn, and versatile language that’s very well-
suited to the ecological niche of web applications.
Finally, I’d like to thank Marco Tabini and the staff of php|architect. The magazine has been a
source of many varied PHP topics, presented by professional developers with extensive knowledge
to share. The conferences organized by Marco and company have been great as well.
Introduction22
LEARNING A NEW TECHNIQUE means adopting new practices. This chapter introduces, or per-
haps reinforces, several practices that you’ll likely find very useful as you implement design pat-
terns in your code.
Many of the practices summarized here are worthy of an individual chapter, even an entire book.
You should consider this chapter an introduction to pattern-related practices with a PHP spin and
look at the references listed throughout to investigate a topic further.
Testing Your Code
Probably no other coding practice is as important as testing your code. With good testing comes great
freedom.
At first, that motto” might strike you as counter-intuitive. If anything, you might assert, testing
seems an impediment to freedom. To the contrary: if you can run tests that completely exercise your
softwares public interface, you can change the internals of your implementation without changing (or
1
Programming
Practices
worse, breaking) existing applications. Testing validates the veracity and accuracy of your published
interface, letting you readily change the inner workings of your code with complete confidence that
it remains accurate and bug-free — that youve not introduced new bugs or reintroduced old bugs.
Before talking more about the benefits of testing, lets look at an example. All of the tests in this
book use the
SimpleTest
PHP testing framework, available at http://simpletest.org/.
Consider this code:
<?php
// PHP4
// the subject code
define(‘TAX_RATE’, 0.07);
function calculate_sales_tax($amount) {
round($amount * TAX_RATE,2);
}
// include test library
require_once ‘simpletest/unit_tester.php’;require_once ‘simpletest/reporter.php’;
// the test
class TestingTestCase extends UnitTestCase {
function TestingTestCase($name=’’) {
$this->UnitTestCase($name);
}
function TestSalesTax() {
$this->assertEqual(7, calculate_sales_tax(100));
}
}
// run the test
$test = new TestingTestCase(‘Testing Unit Test’);
$test->run(new HtmlReporter());
The code defines a constant,
TAX_RATE
, and defines a function that calculates the amount of sales tax
owed. Next, the code includes the required
SimpleTest
components: the unit tester itself and a
reporter” module that displays the results of the test.
The test itself,
TestingTestCase
, is a class that extends
SimpleTest
’s
UnitTestCase
class. By
extending
UnitTestCase
, all of the methods (except the constructor) within
TestingTestCase
that
begin with the word
Test
are used as test cases — code that creates conditions to exercise your code
and makes assertions about the results.
TestingTestCase
defines one test,
TestSalesTax()
, which contains an
assertEqual()
assertion.
This assertion passes if its first two arguments are equal and fails otherwise. (If youd like to display
an informative message if
assertEqual()
fails, pass a third argument, as in
$this->assertEqual(7,
calculate_sales_tax(100), “The sales tax calculation failed”)
).
Programming Practices26
The last two lines in the code create an instance of the test case and run it with an
HtmlReporter
.
You can run this test case simply by browsing to its web page.
Running the test shows the test name, the details of any assertions that failed, and a summary
“bar”. (A green bar indicates success (all assertions passed), while a red bar indicates failure (at least
one assertion did not pass).
The code above has an (intentional) error, so running it yields a failure such as this:
What went wrong in
calculate_sales_tax()
, a simple, one-line function? You may have noticed that
the function doesnt return a result. Heres the corrected function:
function calculate_sales_tax($amount) {
return round($amount * TAX_RATE,2);
}
Rerunning the test with the corrected code passes. “If the bar is green, the code is clean.
But a single test does not guarantee that the code is robust. For example, if you rewrote
calculate_sales_tax()
as
function calculate_sales_tax($amount) { return 7; }
, the test would
pass, but would be correct only for the single dollar amount of 100. You can add additional
Test
methods to test other static values...
Programming Practices 27
function TestSomeMoreSalesTax() {
$this->assertEqual(3.5, calculate_sales_tax(50));
}
... or change
TestSalesTax()
to validate the results of a second (and third, and so on) value:
function TestSalesTax() {
$this->assertEqual(7, calculate_sales_tax(100));
$this->assertEqual(3.5, calculate_sales_tax(50));
}
Better yet, you might add another test that chooses values at random to give you more confidence
in your code:
function TestRandomValuesSalesTax() {
$amount = rand(500,1000);
$this->assertTrue(defined(‘TAX_RATE’));
$tax = round($amount*TAX_RATE*100)/100;
$this->assertEqual($tax, calculate_sales_tax($amount));
}
TestRandomValuesSalesTax()
introduces
assertTrue()
, which passes if the first parameter evalu-
ates to the boolean value true. (Like the
assertEqual()
assertion,
assertTrue()
also takes an
optional, additional argument to present an informative failure message.) So,
TestRandomValuesSalesTax()
asserts that the constant
TAX_RATE
has been defined and then uses
that constant to calculate what the tax should be on the randomly selected amount.
TestRandomValuesSalesTax()
has a problem, though: it depends greatly on significant details
from the actual implementation of the
calculate_sales_tax()
function, probably more than what’s
ideal for testing. Tests should be insensitive to the specifics of an implementation. Perhaps a better
test might just be to establish a reasonable boundary and test for it. The following test assumes and
asserts that sales tax rate will never be more than 20%:
function TestRandomValuesSalesTax() {
$amount = rand(500,1000);
Programming Practices28
$this->assertTrue(calculate_sales_tax($amount)<$amount*0.20);
}
Making sure your code works is the primary benefit of testing, but there are additional, secondary
benefits that you can realize by thoroughly testing your code:
Testing forces you to write code that is easily testable. This leads to looser coupling, flex-
ible designs, and good modularity.
Writing tests forces you to explicitly clarify your expectations of how your code is to
behave, distilling your design into sharper focus from the beginning. Writing tests forces
you to consider the universe of possible inputs and the corresponding results.
Tests are a very explicit way of communicating the intent of your code. In other words,
test cases act as examples and documentation, showing exactly how a given class,
method, or function should behave. In this book, I sometimes demonstrate the desired
effect of code via a test case. By reading a test methods assertions, you can see how the
code is intended to operate. A test case defines how code works in a non-ambiguous
way.
Finally, if your test suite—your set of test cases—is very thorough, you can say your code is com-
plete when all of your tests pass. Interestingly, that notion is one of the hallmarks of Test Driven
Development.
Test Driven Development (TDD), also referred to as Test First Coding, is a
methodology that takes testing one step further: you write your tests before you ever
write any code. A nice, brief summary of the tenants of TDD is available at
http://xprogramming.com/xpmag/testFirstGuidelines.htm, and a good introductory book on the
strategy is “Test Driven Development: By Example” by Kent Beck. (The books examples are in Java,
but it’s a quick read and gives you a very good overview and introduction to the subject.)
Agile Development
Recently, unit testing — in particular Test Driven Development — has been associated with agile devel-
opment methodologies such as Extreme Programming (XP) that focus on rapid iterations of releasing
functional code to customers and welcoming changing customer requirements as a natural part of the
development process. Some good online resources for learning about agile development include:
• http://en.wikipedia.org/wiki/Agile_software_development
• http://agilemanifesto.org/
• http://www.extremeprogramming.org/
Programming Practices 29
L
L
I hope you get infected after this discussion—“Test infected!” (This term, coined by Erich Gamma,
is detailed in the article at http://junit.sourceforge.net/doc/testinfected/testing.htm.) As Gamma
writes, you may feel that testing is cumbersome at first, but after you begin to build an extensive test
suite for your software, you’ll begin to have more confidence in all of your code.
Refactoring
Even the most thoughtful and skilled programmer cannot anticipate every nuance and subtlety of a
software project. Problems crop up unexpectedly, requirements can and do change, and as a result,
code is refined, shared, and obsoleted.
Refactoring is the practice of examining all of your code, looking for commonalities and similar-
ities that can be unified and simplified to make the code easier to maintain and extend. Refactoring
also includes recognizing when a design pattern can be applied to a problem—again to make solu-
tions simpler.
Refactoring can be a simple as renaming an attribute or method, or can be as complex as col-
lapsing an existing class. Changing your code to make it match one or more design patterns is
another kind of refactoring—something you may do after reading this book.
Nothing explains refactoring better than an example.
Let’s take two simple classes,
CartLine
and
Cart
.
CartLine
records the per unit price and the
quantity of each item added to a shopping cart. For example,
CartLine
might record “four red polo
shirts at $19.99 each.” Cart is a container for one or more
CartLine
objects and performs calculations
such as the total cost of all items in the cart.
Here is a simple implementation of
Cartline
and
Cart
:
// PHP5
class CartLine {
public $price = 0;
public $qty = 0;
}
class Cart {
protected $lines = array();
Testing Works for Functions, Too
Most of the examples of testing shown in this book test object-oriented code, but all forms of program-
ming can benefit. Unit testing frameworks, like PHPUnits or SimpleTest, can very easily test functions,
too. Consider the SimpleTest example above, which tested the
ccaallccuullaattee__ssaalleess__ttaaxx(())
function.
Procedural programmers of the world: include unit tests cases with your function libraries, too!
Programming Practices30
L
L
public function addLine($line) {
$this->lines[] = $line;
}
public function calcTotal() {
$total = 0;
// add totals for each line
foreach($this->lines as $line) {
$total += $line->price * $line->qty;
}
// add sales tax
$total *= 1.07;
return $total;
}
}
The first step in refactoring is to have adequate test coverage for your code. That ensures that your
modified code does not produce different results from your original code. By the way, unless you
change a requirement (the intended result of your code) or find a bug in a test case, your tests should
not change.
Here is a sample test for
CartLine
and
Cart
, which wont change during refactoring:
function TestCart() {
$line1 = new CartLine;
$line1->price = 12; $line1->qty = 2;
$line2 = new CartLine;
$line2->price = 7.5; $line2->qty = 3;
$line3 = new CartLine;
$line3->price = 8.25; $line3->qty = 1;
$cart = new Cart;
$cart->addLine($line1);
$cart->addLine($line2);
$cart->addLine($line3);
$this->assertEqual(
(12*2 + 7.5*3 + 8.25) * 1.07,
$cart->calcTotal());
}
Looking at the code for
CartLine
and
Cart
, there are several code smells”—curious looking and
seemingly problematic code—that are likely candidates for refactoring. (Point your nose at
http://c2.com/cgi/wiki?CodeSmell for more telltale code smells.) Two immediate candidates for
refactoring are the comments and calculations related to line totals and sales tax. One form of refac-
toring, Extract Method, would pull these uglier pieces of code out of the flow of
Cart::calcTotal()
Programming Practices 31
and replace them with appropriately named methods that make the overall flow clearer.
For example, you might add two calculation methods,
lineTotal()
and
calcSalesTax()
:
protected function lineTotal($line) {
return $line->price * $line->qty;
}
protected function calcSalesTax($amount) {
return $amount * 0.07;
}
Now, you can rewrite
calcTotal()
as:
public function calcTotal() {
$total = 0;
foreach($this->lines as $line) {
$total += $this->lineTotal($line);
}
$total += $this->calcSalesTax($total);
return $total;
}
Since the changes made so far are significant (at least in the context of this example), it’s beneficial
to pause and run the test again to verify that the results are still correct. Remember, a green bar indi-
cates success!
However, there are still some nagging doubts about the current code. One is the access of pub-
lic properties in the new
lineTotal()
method. Its clear that the responsibility for calculating the line
total doesnt belong in the
Cart
class, but should be in
CartLine
instead.
Refactoring again, add a
total()
method to
CartLine
to calculate the extended price of an item
in the order ...
public function total() {
return $this->price * $this->qty;
}
... then remove
lineTotal()
method from
Cart
, and change the
calcTotal()
method to use the new
Programming Practices32
CartLine::total()
method. Then run the test again, looking for the green bar.
The newly refactored code is thus:
class CartLine {
public $price = 0;
public $qty = 0;
public function total() {
return $this->price * $this->qty;
}
}
class Cart {
protected $lines = array();
public function addLine($line) {
$this->lines[] = $line;
}
public function calcTotal() {
$total = 0;
foreach($this->lines as $line) {
$total += $line->total();
}
$total += $this->calcSalesTax($total);
return $total;
}
protected function calcSalesTax($amount) {
return $amount * 0.07;
}
}
Now the code no longer requires inline comments, because the code itself documents what is hap-
pening much better. The new methods better encapsulate the calculation, allowing more flexibility
in the future if the calculation must change (say, to consider different sales tax rates). In addition, the
classes are now more balanced, maintaining code in better alignment with each classes role.
This example is obviously trivial, but hopefully you can extrapolate and envision what this can
do for your own code.
When coding, you should be in one of two modes: adding features or refactoring. When adding
features, write tests and add code. When refactoring, change only existing code, making sure all that
all relevant tests still run correctly.
The primary reference on refactoring is Refactoring: Improving the Design of Existing Code by
Martin Fowler. To be so bold as to summarize Fowlers book in a few bullet points, the steps in refac-
toring are:
Programming Practices 33
• Identify the code in need of refactoring.
• Have test coverage for the code.
• Work in small steps.
• Run your tests after each step. Code and test in quick iterations — which is much easier
in an interpreted language like PHP as compared with compiled languages.
• Use refactoring to make your more readable and to improve performance.
Other Practices
There are several other practices that are worthy of mention and valuable to incorporate into your
own coding habits.
UML
The Unified Modeling Language (UML, a synthesis of the notations of Booch, Rumbaugh, and
Jacobson) is a programming language- and vendor-independent notation for describing object ori-
ented programming concepts. General information on UML can be found at http://www.uml.org/.
There are many aspects to UML, but the two most relevant for PHP developers are the class dia-
gram and the sequence diagram.
The class diagram describes one or more classes and how the classes relate to each other in your
program. Each class is represented by a box with up to three divisions: the first division is the name
of the class; the second division enumerates the classes attributes (variables); and the last division
lists the classs methods. The visibility of attributes and methods are designated with
+
for public, —
for private, and
#
for protected.
The sequence diagram illustrates the typical interaction of objects in the code for a particular task
or event. A sequence diagram conveys when different methods are called, by whom, and in what
order (hence the name, “sequence diagram”), and are incredibly useful instruments to communicate
interactions between sets of objects to other developers.
Programming Practices34
In my own work, I typically use
both kinds of diagrams to sketch
out designs, but rarely formalize
them into project documentation.
Often, the relationships between
objects change as your knowledge
of the system evolves and as user
requirements change, and the dia-
grams can age quickly. That being
said, A picture is worth a thousand
words.” These diagrams can be
very useful in communicating the
design of a system to new develop-
ers and can serve as documenta-
tion for developers that use your
software.
Source Control
“Save code early and often” is
another valuable developer
mantra. Even if you’re the sole
developer on a project, you should
maintain everything under source
control.
While there are many source control solutions available, two are standouts: CVS
(https://www.cvshome.org/) and Subversion (http://subversion.tigris.org/). CVS is a very popular
solution, used by both the PHP and Apache projects. Meanwhile, Subversion is rapidly becoming a
popular alternative, because the projects design has overcome several of CVSs shortcomings (par-
ticularly in the areas of atomic commits and moving/renaming directories and files). However, fewer
projects run Subversion servers.
I have adopted use of CVS for projects at work, and the chapters and code in this book were
maintained in a Subversion repository.
Source Code Documentation
If you flip through the pages of this book, you may notice some distinctly formatted comment blocks
similar to:
Programming Practices 35
/**
* funny multi-line comments
* @something what is this?
*/
These are docblocks and are used by programs like phpDocumentor (http://phpdocu.sf.net/) to
automatically generate application programming interface (API) documentation for your PHP proj-
ects.
Docblocks are specifically formatted multi-line comments that start with
/**
, continue on each
subsequent line with a leading
*
, and are terminated by
*/
, with white space allowed before each
prefix (which allows docblocks to be indented at the same level as your code).
The
@something
represents a “tag,” which clarifies information when the documentation is con-
verted to the parsed format. An example of a tag is
@private
, which was used in PHP4 to mark a
method or attribute of a class as private, since the language did not provide that capability natively
(all functions and variables are public in PHP4).
Source code documentation such as docblocks serve both as a useful reference and as an adver-
tisement for open source projects. One example (that I help to maintain) is the
SimpleTest
API doc-
umentation at http://simpletest.org/.
Programming Practices36
2
The Value
Object Pattern
In all but the simplest applications, most objects have an “identity.” An important business object,
such as a
Customer
or a
SKU
, will have one or more attributes—an ID, or a name and an email
address, say—that differentiate it from other instances of the same class. Moreover, an object with
an identity persists”: its a singularity that exists across the entire application. To you, the program-
mer, “Customer A” is “Customer A” everywhere, and changes to “Customer A” endure for as long as
your application is running.
But an object need not have an identity. Some objects merely describe the characteristics of other
objects.
For example, it’s common to use an object to represent a date, a number, or money. A
Date
,
Integer
, or
Dollar
class is a handy—and inexpensive—encapsulation, easily copied, compared, or
created when needed.
At first blush, small descriptive objects may seem a cinch to implement: they’re just (tiny or small)
classes, no different in structure than a
Customer
or
SKU
. That’s almost right, but “almost right” leads to
bugs.
Consider the following implementation of a dollar that’s almost right (the class is named
BadDollar
because it’s not an ideal implementation). See if you can find the bug.
// PHP5
class BadDollar {
protected $amount;
public function __construct($amount=0) {
$this->amount = (float)$amount;
}
public function getAmount() {
return $this->amount;
}
public function add($dollar) {
$this->amount += $dollar->getAmount();
}
}
class Work {
protected $salary;
public function __construct() {
$this->salary = new BadDollar(200);
}
public function payDay() {
return $this->salary;
}
}
class Person {
public $wallet;
}
function testBadDollarWorking() {
$job = new Work;
$p1 = new Person;
$p2 = new Person;
$p1->wallet = $job->payDay();
$this->assertEqual(200, $p1->wallet->getAmount());
$p2->wallet = $job->payDay();
$this->assertEqual(200, $p2->wallet->getAmount());
$p1->wallet->add($job->payDay());
The Value Object Pattern40
$this->assertEqual(400, $p1->wallet->getAmount());
//this is bad — actually 400
$this->assertEqual(200, $p2->wallet->getAmount());
//this is really bad — actually 400
$this->assertEqual(200, $job->payDay()->getAmount());
}
So, what’s the bug? If the test case didnt make the problem apparent, heres a hint: employees
$p1
and
$p2
share the same
BadDollar
.
First, instances of
Work
and
Person
are created. Then, assuming that each person initially has an
empty wallet,
Person::wallet
is set to the
BadDollar
object returned by
Work::payDay()
.
Remember your friend” the PHP 5 object handle? Because of it,
$job::salary
,
$p1::wallet
,
and
$p2::wallet
, three conceptually different objects with different “identities,” actually all refer to
the same object.
So, the second pay day,
$job->payDay()
, which was intended just to fatten the wallet of
$p1
,
inadvertently pays
$p2
again and changes the base
$salary
of
$job
. Hence, the last two assertions
fail:
Value Object PHP5 Unit Test
1) Equal expectation fails because [Integer: 200] differs from [Float: 400] by 200
in testBadDollarWorking
in ValueObjTestCase
2) Equal expectation fails because [Integer: 200] differs from [Float: 400] by 200
in testBadDollarWorking
in ValueObjTestCase
FAILURES!!!
The Problem
So, how do you implement a lightweight, or easy to construct, descriptive object like
Date
or
Dollar
?
The Solution
Lightweight objects should behave like PHP integers: if you assign the same object to two different
variables and then change one of the variables, the other variable should remain unaffected. And
indeed, this is the goal of the Value Object pattern.
Implementing Value Object differs between PHP 4 and PHP 5.
The Value Object Pattern 41
As you saw above, PHP 5’s (new) method of referring to objects via a handle—a paradigm we
typically try to emulate with references in PHP 4—is an issue. To solve that problem and implement
a proper
Dollar
Value Object, make the
$amount
attribute—and, in the general case, all attributes of
a Value Objectimmutable, or unchangeable. While PHP does not provide immutability as a facili-
ty of the language, you can combine attribute visibility and getter and setter methods to simulate it
adequately.
In contrast, PHP4 (almost) treats all objects like Value Objects, because the PHP4 assignment
operator
=
makes a copy of the object if you omit the reference operator
&
. To implement Value
Objects in PHP 4, simply break your carefully-cultivated habit of always creating, passing and catch-
ing objects by reference.
PHP 5 Sample Code
Since we started with PHP 5 code, lets flesh out a PHP 5 Value Object implementation and build a
better
Dollar
class. Naming is very important in OOP: selecting a single currency type as the name
of this class explicitly declares that it doesnt handle multiple forms of currency.
class Dollar {
protected $amount;
public function __construct($amount=0) {
$this->amount = (float)$amount;
}
public function getAmount() {
return $this->amount;
}
public function add($dollar) {
return new Dollar($this->amount + $dollar->getAmount());
}
}
Using
protected $amount
so the attribute
Dollar::amount
is not accessible from outside of the class
itself is the first step towards making
Dollar::amount
immutable.
protected
(and
private
) denies
direct access to the attribute.
Terminology - Immutable
The dictionary definition of immutable is “not capable of or susceptible to change”. In programming, the
term denotes a value that does not change once it’s been set.
The Value Object Pattern42
L
L
Normally, when you use this OOP idiom, you create a setter” function like
public
setAmount($amount) { $this->amount = $amount; }
. In this case, no setter function has been
defined since
Dollar::amount
is set during the instantiation of the object.
Dollar::getAmount()
is
an accessor method, giving public access to the
Dollar
objects amount as a
float
.
The most interesting change is in the
Dollar::add()
method. Instead of changing the value of
$this->amount
, thereby altering the state of the existing
Dollar
instance, the method creates and
returns a new instance of
Dollar
. Now, even if you assign this object to multiple variables, each is
insulated from changes made to any other.
Immutability is key to the Value Object pattern. Any change to the amount of a Value Object is
accomplished by creating a new instance of the class with the different desired value. Above,
$this->amount
never changes.
To review briefly, the fundamentals of the ValueObject pattern in PHP 5 are:
1. Protect the attributes of a Value Object so direct access is forbidden.
2. Set the objects attributes in the constructor.
3. Provide no setter” functions, which otherwise allow attributes to be altered.
These three steps create an immutable value—one that can not change after it’s initially set. Of
course, you should also provide “getters,” or methods to access a Value Object’s attributes and pro-
vide any functions that are germane to the class. A Value Object need not be a simple structure,
either; it can hold important business logic as well. Let’s look at that next.
In Context Example
Let’s explore the Value Object pattern in the context of a larger example. Let’s begin an implementa-
tion of a game of Monopoly, building upon the PHP 5
Dollar
class created above.
The first class is
Monopoly
, a frame to build on:
class Monopoly {
protected $go_amount;
/**
* game constructor
* @return void
*/
public function __construct() {
$this->go_amount = new Dollar(200);
}
/**
* pay a player for passing “Go”
* @param Player $player the player to pay
The Value Object Pattern 43
* @return void
*/
public function passGo($player) {
$player->collect($this->go_amount);
}
}
So far, the
Monopoly
class is very minimal. The constructor creates
$go_amount
, an instance of the
Dollar
Value Object class, set to $200.
$go_amount
is used by
passGo()
, which takes a
Player
as an
argument and tells the
Player
to
collect()
$200.
Player
should be next. The
Monopoly
class calls a
Player::collect()
method with one argu-
ment, a
Dollar
, to add that
Dollar
amount to the players cash balance. In addition to that method,
let’s add the method
Player::getBalance()
to access a players cash reserve current to validate that
the
Player
and
Monopoly
objects are working,
class Player {
protected $name;
protected $savings;
/**
* constructor
* set name and initial balance
* @param string $name the players name
* @return void
*/
public function __construct($name) {
$this->name = $name;
$this->savings = new Dollar(1500);
}
/**
* receive a payment
* @param Dollar $amount the amount received
* @return void
*/
public function collect($amount) {
$this->savings = $this->savings->add($amount);
}
* return player balance
* @return float
*/
public function getBalance() {
return $this->savings->getAmount();
}
}
The Value Object Pattern44
Given
Monopoly
and
Player
, you can now write a test case for what’s been implemented so far.
MonopolyTestCase
might look like:
class MonopolyTestCase extends UnitTestCase {
function TestGame() {
$game = new Monopoly;
$player1 = new Player(‘Jason’);
$this->assertEqual(1500, $player1->getBalance());
$game->passGo($player1);
$this->assertEqual(1700, $player1->getBalance());
$game->passGo($player1);
$this->assertEqual(1900, $player1->getBalance());
}
}
If you run
MonopolyTestCase
, you should get a green bar. Time to continue adding features.
Another important concept in Monopoly is paying rent. Let’s write a test case first (a la Test
Driven Development) to set the goals for the next round of coding:
function TestRent() {
$game = new Monopoly;
$player1 = new Player(‘Madeline’);
$player2 = new Player(‘Caleb’);
$this->assertEqual(1500, $player1->getBalance());
$this->assertEqual(1500, $player2->getBalance());
$game->payRent($player1, $player2, new Dollar(26));
$this->assertEqual(1474, $player1->getBalance());
$this->assertEqual(1526, $player2->getBalance());
}
Looking at the test, the
payRent()
method needs to be added to the
Monopoly
class to allow one play-
er to pay rent to another.
Class Monopoly {
// ...
/**
* pay rent from one player to another
* @param Player $from the player paying rent
* @param Player $to the player collecting rent
* @param Dollar $rent the amount of the rent
The Value Object Pattern 45
* @return void
*/
public function payRent($from, $to, $rent) {
$to->collect($from->pay($rent));
}
}
payRent()
effectuates the transaction between two players,
$from
and
$to
.
Player::collect()
already exists, but the
Player::pay()
method must be added to let
$from pay()
a
Dollar
amount to
$to
.
Player::pay()
might look like:
class Player {
// ...
public function pay($amount) {
$this->savings = $this->savings->add(-1 * $amount);
}
}
Unfortunately, you cant multiply an object by a number in PHP (unlike some programming lan-
guages, PHP does not allow for the overloading of operators, which might allow for a construct like
this). Instead, add a
debit()
method to
Dollar
to perform subtraction.
class Dollar {
protected $amount;
public function __construct($amount=0) {
$this->amount = (float)$amount;
}
public function getAmount() {
return $this->amount;
}
public function add($dollar) {
return new Dollar($this->amount + $dollar->getAmount());
}
ppuubblliicc
ffuunnccttiioonn
ddeebbiitt(($$ddoollllaarr))
{{
rreettuurrnn
nneeww
DDoollllaarr(($$tthhiiss->>aammoouunntt
-
$$ddoollllaarr->>ggeettA
Ammoouunntt(())));;
}}
}
Given
Dollar::debit()
,
Player::pay()
remains simple:
The Value Object Pattern46
class Player {
// ...
/**
* make a payment
* @param Dollar $amount the amount to pay
* @return Dollar the amount payed
*/
public function pay($amount) {
$this->savings = $this->savings->debit($amount);
return $amount;
}
}
Player::pay()
returns the
$amount
paid so the statement in
Monopoly::payRent()
of
$to->collect($from->pay($rent))
works properly. This can help in the future if you refine the
“business logic” to not allow a payment greater than the player’s balance. (Such a circumstance
would then return the players balance and perhaps raise a “BankruptException” to calculate a mod-
ified payment instead of the full amount. The
$to
player would still want to collect as much as pos-
sible from player
$from
.)
PHP 4 Sample Code
Unlike PHP 5, PHP 4’s copy-by-value object semantics work naturally with the Value Object pattern.
However, because PHP 4 does not support property or method visibility, implementing a Value
Object in PHP 4 has its nuances as well.
If you recall, the “Object Handles” section of the Preface of this book presented three rules” to
nearly always” apply when working with objects in PHP 4 to simulate PHP 5’s object handles:
1. Create objects by reference (
$obj =& new Class;
)
2. Pass objects by reference (
function funct(&$obj_param) {}
)
Terminology — Business Logic
Mentioning “business logic” in the context of modeling a board game may seem odd. The business here
does not refer to companies engaged in the act of commerce, but rather to the concept of application-
specific requirements in the domain the application is addressing. Think of the definition of business as
an immediate task or objective,” as in “What is your business here?”,
Of course, given the problem domain for Monopoly, perhaps the connotations of “business logic” apply
just the same.
The Value Object Pattern 47
L
L
3. Catch by reference (
function &some_funct() {} $returned_obj =& some_funct()
)
The Value Object pattern is one significant exception to the nearly always” part of these rules. Just
ignore the rules and you’ll always get a copy of the PHP 4 object (the equivalent of the PHP5 clone
operation, described at http://www.php.net/manual/en/language.oop5.cloning.php.
While PHP 4 makes object copying a breeze—its an inherent behavior in the language—
immutability can only be realized by convention. To create Value Objects in PHP 4, never create or
catch Value Objects by reference, and prefix all “private” property or method names prefixed with an
underscore (
_
). By convention then, variables that hold Value Object attributes should be with an
underscore to indicate its private.
Here is the
Dollar
class in PHP 4:
// PHP4
class Dollar {
var $_amount;
function Dollar($amount=0) {
$this->_amount = (float)$amount;
}
function getAmount() {
return $this->_amount;
}
function add($dollar) {
return new Dollar($this->_amount + $dollar->getAmount());
}
function debit($dollar) {
return new Dollar($this->_amount - $dollar->getAmount());
}
}
And here is a test case that demonstrates you can not make an immutable property in PHP4:
function TestChangeAmount() {
$d = new Dollar(5);
$this->assertEqual(5, $d->getAmount());
//only possible in php4 by not respecting the _private convention
$d->_amount = 10;
$this->assertEqual(10, $d->getAmount());
}
Again, in all PHP 4 objects, prefix private variables with an underscore, and do access such private
properties and methods directly.
The Value Object Pattern48
Business Logic in ValueObjects
Value Objects need not be restricted to be simple structures of data with minimal accessor methods;
they can contain valuable business logic as well. Consider the case where you want to divide money
equally among a number of people.
If the amount is divisible exactly, you might return an array of
Dollar
objects, with each contain-
ing one of the equal portions. But what happens when the amount to be divided does not divide
equally into round numbers of dollars and cents?
Let’s start coding with a few simple test cases:
// PHP5
function testDollarDivideReturnsArrayOfDivisorSize() {
$full_amount = new Dollar(8);
$parts = 4;
$this->assertIsA(
$result = $full_amount->divide($parts)
,’array’);
$this->assertEqual($parts, count($result));
}
A
Dollar::divide()
method could pass this test by being coded as...
public function divide($divisor) {
return array_fill(0,$divisor,null);
}
... so it’d be better to add more specifics:
function testDollarDrivesEquallyForExactMultiple() {
$test_amount = 1.25;
$parts = 4;
$dollar = new Dollar($test_amount*$parts);
foreach($dollar->divide($parts) as $part) {
assertIsA
The
aasssseerrttIIssAA(())
assertion lets you test if a particular variable is an instance (or descendant) of a named
class. You can also use this assertion to validate against PHP base types like
ssttrriinngg
,
nnuummbbeerr
, or
aarrrraayy
as
well.
The Value Object Pattern 49
L
L
$this->assertIsA($part, ‘Dollar’);
$this->assertEqual($test_amount, $part->getAmount());
}
}
Now, instead of just being the correct size array, the returned array must be populated with
Dollar
objects of the correct amount. The implementation can still be a one liner:
public function divide($divisor) {
return array_fill(0,$divisor,new Dollar($this->amount / $divisor));
The last feature to code is the possibility of rounding errors caused by a divisor that does not divide
evenly into the
Dollar
amount. That’s a sticky point: does the first portion or the last portion get the
extra penny if theres a rounding issue? How can that be tested independent of the implementation?
One means is to specify the end goal of the code explicitly: the size of the array should be equal
to the number of parts, no part should differ more than $0.01 from any other part, and the sum of all
the part’s amounts should equal the value of the amount being dividing.
This expressed as a test case is:
function testDollarDivideImmuneToRoundingErrors() {
$test_amount = 7;
$parts = 3;
$this->assertNotEqual( round($test_amount/$parts,2),
$test_amount/$parts,
’Make sure we are testing a non-trivial case %s’);
$total = new Dollar($test_amount);
$last_amount = false;
$sum = new Dollar(0);
foreach($total->divide($parts) as $part) {
if ($last_amount) {
$difference = abs($last_amount-$part->getAmount());
$this->assertTrue($difference <= 0.01);
}
$last_amount = $part->getAmount();
$sum = $sum->add($part);
}
$this->assertEqual($sum->getAmount(), $test_amount);
}
The Value Object Pattern50
With some test cases in hand, how does
Dollar::divide()
shape up?
class Dollar {
protected $amount;
public function __construct($amount=0) {
$this->amount = (float)$amount;
}
public function getAmount() {
return $this->amount;
}
public function add($dollar) {
return new Dollar($this->amount + $dollar->getAmount());
}
public function debit($dollar) {
return new Dollar($this->amount - $dollar->getAmount());
}
ppuubblliicc
ffuunnccttiioonn
ddiivviiddee(($$ddiivviissoorr))
{{
$$rreett
==
aarrrraayy(());;
$$aalllloocc
==
rroouunndd(($$tthhiiss->>aammo
ouunntt
//
$$ddiivviissoorr,,22));;
$$ccuummmm__aalllloocc
==
00..00;;
ffoorreeaacchh((rraannggee((11,,$$ddiivviissoorr-11))
aass
$$ii))
{{
$$rreett[[]]
==
nneeww
DDoollllaarr(($$aalllloocc));;
$$ccuummmm__aalllloocc
++==
$$aalllloocc;;
}}
$$rreett[[]]
==
nneeww
DDoollllaarr((rroouunndd(($$tthhiiss->>aammoouunntt
-
$$ccuummmm__aalllloocc,,22))));;
rreettuurrnn
$$rreett;;
}}
}
This code works, but still has some issues. Consider boundary conditions like changing the begin-
ning of
testDollarDivide()
to
$test_amount = 0.02; $num_parts = 5;
. Or consider what happens
when you dont provide an integer divisor?
The methodology to solve issues like these? Use the Test Driven development cycle: add a test
case, observe for failure, code to allow the new test case to pass, and refactor if needed. Repeat as
necessary.
assertNotEqual
The
aasssseerrttNNoottEEqquuaall(())
assertion fails if the first two arguments passed to it satisfy a PHP
====
conditional
test. You can use it in test cases whenever you need to make sure two values are different.
The Value Object Pattern 51
L
L
3
The Factory
Pattern
IN OBJECT-ORIENTED PROGRAMMING, the most common way to create an object is with the
new
operator, the language construct provided to do just that. But in some cases,
new
can be problem-
atic. For instance, the creation of many kind of objects requires a series of steps: you may need to
compute or fetch the object’s initial settings; you might have to choose which of many sub classes to
instantiate; or perhaps you have to create a batch of other helper objects before you can create the
object you need. In those cases,
new
is a process” more than an operation—a cog in a bigger machine.
The Problem
How can you create such complex” objects easily and conveniently—without cut-and-paste pro-
gramming?
The Solution
Create a factory”—a function or a class method— to manufacture” new objects. To understand the
value of a factgory, think about the difference between ...
$connection =& new MySqlConnection($user, $password, $database);
... spread throughout your code, and the more concise ...
$connection =& create_connection();
The latter code snippet centralizes the code to create a database connection in the
create_connec-
tion()
factory,” and, following the analogy earlier, transforms the process of creating the database
connection to a simple operation—an operation just like
new
. The Factory pattern injects “intelli-
gence” to object creation. It encapsulates the creation of an object and returns the new object to the
caller.
Need to change the structure of an object and how it’s created? Just go to the object’s factory and
change the code once. (The Factory pattern is so useful, it’s foundational, meaning that it appears
again and again in many other complex patterns and applications.)
Sample Code
The Factory pattern encapsulates the creation of objects. You can create a Factory within the object
itself or in an external Factory class—the exact implementation depends on the needs of your appli-
cation. Let’s look at an example of a Factory.
The application code below repeats the same code to create a database connection in multiple
places:
// PHP4
class Product {
function getList() { $$ddbb
==&&
nneeww
MMyyssqqllCCoonnnneeccttiioonn((DDBB__UUSSEERR,,
DDBB__PPWW,,
DDBB__NNAAMMEE));;
//...
}
function getByName($name) { $$ddbb
==&&
nneeww
MMyyssqqllCCoonnnneeccttiioon
n((DDBB__UUSSEERR,,
DDBB__PPWW,,
DDBB__NNAAMMEE));;
//...
}
//...
}
The Factory Pattern54
Why is this bad? Connection parameters are spread all over, and while I’ve shown the parameters as
constants, implying you have a way to define them centrally and globally, the solution is obviously
not optimal:
While you can change the values of the parameters easily, you cannot add or change the
order of parameters without changing (at least) two sections of code.
You cannot easily instantiate a new class to use another kind of database connection, say
a
PostgresqlConnection
.
• It is difficult to separately test and validate the behavior of the connection object.
The code would be much improved with the use of a Factory:
class Product {
function getList() {
$$ddbb
==&&
$$tthhiiss->>__ggeettCCoonnnneeccttiioonn(());;
////......
}}
ffuunnccttiioonn
&&__ggeettCCoonnnneeccttiioonn(())
{{
rreettuurrnn
nneeww
MMyyssqqllC
Coonnnneeccttiioonn((DDBB__UUSSEERR,,
DDBB__PPWW,,
DDBB__NNAAMMEE));;
}}
}}
The class method
_getConnection()
centralizes the otherwise repetitious
new
MysqlConnection(DB_USER, DB_PW, DB_NAME)
calls found in the classs other methods.
Heres another variation of a Factory, this one a static call to a Factory class:
class Product {
function getList() {
$$ddbb
==&&
DDbbCCoonnnneeccttiioonnBBrrookkeerr::::ggeettCCoonnnneeccttiioonn(());;
////......
}}
}}
ccllaassss
DDbbCCoonnnneeccttiioonnBBrrookkeerr
{{
ffuunnccttiioonn
&&ggeettCCoonnnneeccttiioonn(())
{{
rreettuurrnn
nneeww
MMyyssqqllCCoonnnneeccttiioonn((DDBB__UUSSEERR,,
DDBB__PPWW,,
DDBB__NNAAMMEE));;
}}
}}
DbConnectionBroker::getConnection()
produces the same result as the previous Factory, but has a
The Factory Pattern 55
distinct advantage: it replaces the repeated
new MysqlConnection(DB_USER, DB_PW, DB_NAME)
calls
in every method in every class that uses the database.
Yet another variation is a call to a Factory class that’s been previously associated with the object:
class Product {
var $_db_maker;
function setDbFactory(&$connection_factory) {
$this->_db_maker =& $connection_factory;
}
function getList() {
$db =& $this->_db_maker->getConnection();
//...
}
}
Lastly, a Factory can be implemented as a procedural function, a reasonable way to achieve global
visibility for the Factory:
function &make_db_conn() {
return new MysqlConnection(DB_USER, DB_PW, DB_NAME);
}
class Product {
function getList() {
$bar =& make_db_conn();
//...
}
}
Heres a UML class diagram for an idealized implementation of the Factory:
The Factory Pattern56
Adding a Little Color
To go into the Factory pattern in more detail, let’s take a small segue and build a simple class that can
serve as an example for the rest of the chapter. Let’s build a class to output an HTML RGB color in
hex. The R, G, and B values are passed in as three arguments to the constructor and a function
getRgb()
returns a string of the hex color value.
As before lets follow the Test Driven Development (TDD) methodology: write a test, write the
code to satisfy the test, refactor if needed, and repeat.
Heres a very simple initial test:
function TestInstantiate() {
$this->assertIsA($color = new Color, ‘Color’);
$this->assertTrue(method_exists($color, ‘getRgb’));
}
The code to satisfy this test looks just like the pseudo-code you might sketch out on a white board
while designing the class:
class Color {
function getRgb() {}
}
(This
Color
class might look like a baby step, but TDD is an iterative process. Code in very small
increments when necessary—perhaps when youre initially learning a new concept or when youre
struggling with a particular implementation.)
Next, the
getRgb()
method should return the hex string based on the red, green, and blue val-
ues passed when the
Color
object is created. Specify that with a test:
function TestGetRgbWhite() {
$white =& new Color(255,255,255);
$this->assertEqual(‘#FFFFFF’, $white->getRgb());
}
Per TDD, you write the simplest possible code to satisfy your test, not necessarily the code that sat-
isfies your sense of aesthetic or the code you think is the proper implementation.
The Factory Pattern 57
The simplest implementation of the
Color
class that passes this test is:
class Color {
function getRgb() { rreettuurrnn
##FFFFFFFFFFFF;;
}
}
This
Color
isnt very satisfying, but it does represent incremental progress.
Next, let’s add an additional test to force the
Color
to save some state information inside the
object for a more realistic implementation:
function TestGetRgbRed() {
$red =& new Color(255,0,0);
$this->assertEqual(‘#FF0000’, $red->getRgb());
}
So what must change in
Color
? The constructor must take the red, green, and blue arguments and
store them in instance variables.
Color
also requires a method to convert decimal integer numbers
to hexadecimal. Some code to implement those requirements might look like:
class Color {
vvaarr
$$rr==00;;
vvaarr
$$gg==00;;
vvaarr
$$bb==00;;
ffuunnccttiioonn
CCoolloorr(($$rreedd==00,,
$$ggrreeeenn==00,,
$$bblluuee==00))
{{
$$tthhiiss->>rr
=
=$$rreedd;;
$$tthhiiss->>gg
==
$$ggrreeeenn;;
$$tthhiiss->>bb
==
$$bblluuee;;
}
function getRgb() {
rreettuurrnn
sspprriinnttff((##%%0022XX%%0022XX%%0022XX,,
$$tthhiiss->>rr,,
$$tthhiiss->>gg,,
$$tthhiiss->>bb));;
}
}
The constructor is very simple: collect the red, green, and blue values passed into the constructor
and store them in instance variables. The
getRgb()
method uses
sprintf()
to convert the values to
hexadecimal.
The Factory Pattern58
To gain still more confidence in the code, you can test it with more values. This test runs with
the code as-is:
function TestGetRgbRandom() {
$color =& new Color(rand(0,255), rand(0,255), rand(0,255));
$this->assertWantedPattern(
‘/^#[0-9A-F]{6}$/’,
$color->getRgb());
$color2 =& new Color($t = rand(0,255), $t, $t);
$this->assertWantedPattern(
‘/^#([0-9A-F]{2})\1\1$/’,
$color2->getRgb());
}
All of these tests detail how the
Color
class behaves under normal, expected circumstances. But
every well-designed class should also account for boundary conditions. For example, what should
happen if a negative number is passed into the constructor as a color value? What happens for num-
bers greater than 255? What happens for non-numeric data? A good test suite for
Color
would
account for these boundary conditions in the tests.
function testColorBoundaries() {
$color =& new Color(-1);
$this->assertErrorPattern(‘/out.*0.*255/i’);
$color =& new Color(1111);
$this->assertErrorPattern(‘/out.*0.*255/i’);
}
assertErrorPattern
The
aasssseerrttEErrrroorrPPaatttteerrnn(())
assertion allows you to specify a PCRE expression that should match a PHP
error. If the error doesnt materialize or doesnt match the specified pattern, the assertion fails.
assertWantedPattern
The
aasssseerrttWWaanntteeddPPaatttteerrnn(())
assertion tries to match its second parameter to the PCRE expression in the
first parameter. If theres a match, the assertion passes; otherwise it fails.
Building on the power of regular expression matching, the
aasssseerrttWWaanntteeddPPaatttteerrnn(())
assertion can allow for
flexible tests.
The Factory Pattern 59
L
L
L
L
With those tests in place,
Color
could be further implemented as:
class Color {
var $r=0;
var $g=0;
var $b=0;
function Color($red=0, $green=0, $blue=0) {
$$rreedd
==
((iinntt))$$rreedd;;
iiff
(($$rreedd
<<
00
||||
$$rreedd
>>
225555))
{{
ttrriiggggeerr__eerrrroorr((ccoolloorr
$$ccoollo
orr
oouutt
ooff
bboouunnddss,,
..pplleeaassee
ssppeecciiffyy
aa
nnuummbbeerr
bbeettwweeeenn
00
aanndd
225555));;
}}
$this->r = $red;
$$ggrreeeenn
==
((iinntt))$$ggrreeeenn;;
iiff
(($$ggrreeeenn
<<
00
||||
$$ggrreeeenn
>>
225555))
{{
ttrriiggggeerr__eerrrroorr((ccoolloorr
$$ccoolloorr
oouutt
ooff
bboouunnddss,,
..pplleeaassee
ssppeecciiffyy
aa
nnuummbbeerr
bbeettwweeeenn
00
aanndd
225555));;
}}
$this->g = $green;
$$bblluuee
==
((iinntt))$$bblluuee;;
iiff
(($$bblluuee
<<
00
||||
$$bblluuee
>>
225555))
{{
ttrriiggggeerr__eerrrroorr((ccoolloorr
$$ccoolloorr
oouutt
oof
f
bboouunnddss,,
..pplleeaassee
ssppeecciiffyy
aa
nnuummbbeerr
bbeettwweeeenn
00
aanndd
225555));;
}}
$this->b = $blue;
}
function getRgb() {
return sprintf(‘#%02X%02X%02X’, $this->r, $this->g, $this->b);
}
}
This code passes the test, but the cut-and-paste” style of the code should smell bad to you. In TDD,
a rule of thumb is to code the simplest possible solution and if you need the same code twice
wince—but duplicate the code. However, if you need the same a third or more times, then refactor.
So,
Color
is a great candidate for Extract Method refactoring.
Refactoring — Extract Method
When you have two or more sections of code that can be assimilated, combine the sections of code into
a separate method named according to its purpose. Extract method refactoring is most powerful when
the same section of code is repeated several times in one or more methods in your class.
The Factory Pattern60
L
L
class Color {
var $r=0;
var $g=0;
var $b=0;
function Color($red=0, $green=0, $blue=0) {
$$tthhiiss->>rr
==
$$tthhiiss->>vvaalliiddaatteeCCoolloorr(($$rreedd));;
$$tthhiiss->>gg
==
$$tthhiiss->>vvaalliiddaatteeCCoolloorr(($$ggrreeeenn));;
$$tthhiiss->>bb
==
$$tthhiiss->>vvaalliiddaatteeCCoolloorr(($$bblluuee));;
}
ffuunnccttiioonn
vvaalliiddaatteeCCoolloorr(($$ccoolloorr))
{{
$$cchheecckk
==
((iinntt))$$ccoolloorr;;
iiff
(($$cchheecckk
<<
00
||||
$$cchheecckk
>>
225555))
{{
ttrriiggggeerr__eerrrroorr((ccoolloorr
$$ccoolloorr
oouutt
ooff
bboouunnddss,,
..pplleeaassee
ssppeecciiffyy
aa
nnuummbbeerr
bbeettwweeeenn
00
aanndd
225555));;
}}
eellssee
{{
rreettuurrnn
$$cchheecckk;;
}}
}}
function getRgb() {
return sprintf(‘#%02X%02X%02X’, $this->r, $this->g, $this->b);
}
}
Factories to Hide Object State Setup
Let’s add a Factory to
Color
that makes creating new instances easy. Really easy. Let’s add a method
that creates a
Color
given a name—after all, who can remember the RGB values of his or her favorite
color?
Factory objects or functions dont have to be named “Factory.” Factories are pretty obvious
whenever you read code. Instead, it’s better to use a meaningful name that expresses how the
Factory corresponds to the problem youre solving.
In this example code, I am going to call the
Color
Factory
CrayonBox.
The static method
CrayonBox::getColor()
takes a text string containing the name of a color and returns a
Color
object
with the appropriate values set.
Heres the desired behavior as a test case:
function TestGetColor() {
$this->assertIsA($o =& CrayonBox::getColor(‘red’), ‘Color’);
$this->assertEqual(‘#FF0000’, $o->getRgb());
$this->assertIsA($o =& CrayonBox::getColor(‘LIME’), ‘Color’);
$this->assertEqual(‘#00FF00’, $o->getRgb());
}
The Factory Pattern 61
The test case validates that each returned object is an instance of the class
Color
and that its
getRgb()
method responds with the correct string. The red” used for the test was all lowercase, so
the second case, “LIME,” is passed as all uppercase to make sure the code is case-insensitive.
To be safe, let’s also add an additional test to explore boundary conditions that should not work.
The
TestBadColor()
method expects an invalid color name to trigger a PHP error containing the
name of the bad color and expects the Factory to return the color black instead.
function TestBadColor() {
$this->assertIsA($o =& CrayonBox::getColor(‘Lemon’), ‘Color’);
$this->assertErrorPattern(‘/lemon/i’);
// got black instead
$this->assertEqual(‘#000000’, $o->getRgb());
}
A sample implementation of a
CrayonBox
class to fulfill these tests might be:
class CrayonBox {
/**
* Return valid colors as color name => array(red, green, blue)
*
* Note the array is returned from function call
* because we want to have getColor able to be called statically
* so we can’t have instance variables to store the array
* @return array
*/
function colorList() {
return array(
‘black’ => array(0, 0, 0)
,’green’ => array(0, 128, 0)
// the rest of the colors ...
,’aqua’ => array(0, 255, 255)
);
}
/**
* Factory method to return a Color
* @param string $color_name the name of the desired color
* @return Color
*/
function &getColor($color_name) {
$color_name = strtolower($color_name);
if (array_key_exists($color_name,
$colors = CrayonBox::colorList())) {
$color = $colors[$color_name];
return new Color($color[0], $color[1], $color[2]);
}
trigger_error(“No color ‘$color_name’ available”);
The Factory Pattern62
// default to black
return new Color;
}
}
This is obviously a very simple factory, but it does simplify object creation (using text names for col-
ors rather than RGB values) and shows how the internal state of an object can be established at the
time the object is created but before the client code calling the factory receives the new object.
Factories to Promote Polymorphism
Controlling the internal state of returned objects is important, but promoting polymorphism
returning objects of varying classes with the same interface—is an even more powerful capability of
the Factory pattern.
Let’s revisit the Monopoly example and implement the games real estate properties. In the
game, you get a deed when you purchase a property; the deed contains a number of basic facts
about the property that are used throughout game play. Further, there are three different types of
properties: streets, railroads, and utilities. All three kinds of properties have some aspects in com-
mon: each can be owned by a player; each has a price; and each generates rent for its owner when-
ever other players land on it. But some aspects of each kind of real estate are very different. For
example, the formula for calculating rent depends on the type of property.
The following code can act as a base real estate property class:
// PHP5
abstract class Property {
protected $name;
protected $price;
protected $game;
function __construct($game, $name, $price) {
$this->game = $game;
$this->name = $name;
$this->price = new Dollar($price);
}
aabbssttrraacctt
pprrootteecctteedd
ffuunnccttiioonn
ccaallccRReenntt(());;
public function purchase($player) {
$player->pay($this->price);
$this->owner = $player;
}
public function rent($player) {
if ($this->owner
&& $this->owner != $player) {
$this->owner->collect(
The Factory Pattern 63
$player($this->calcRent())
);
}
}
}
Here, the Property class and the method
CalcRent()
are declared abstract.
calcRent()
must be overridden in a subclass to make a concrete class. Hence, each subclass of
Property
,
Street
,
Utility
, and
Railroad
, must define a
calcRent()
method.
An implementation of those latter three (sub)classes might be:
class Street extends Property {
protected $base_rent;
public $color;
public function setRent($rent) {
$this->base_rent = new Dollar($rent);
}
protected function calcRent() {
if ($this->game->hasMonopoly($this->owner, $this->color)) {
return $this->base_rent->add($this->base_rent);
}
return $this->base_rent;
}
}
class RailRoad extends Property {
protected function calcRent() {
switch($this->game->railRoadCount($this->owner)) {
case 1: return new Dollar(25);
case 2: return new Dollar(50);
case 3: return new Dollar(100);
case 4: return new Dollar(200);
default: return new Dollar;
}
}
}
class Utility extends Property {
Terminology — Abstract Class
An abstract class is a class that cannot be instantiated directly. An abstract class contains one or more
abstract methods that must be overridden in a subclass. Once all of the abstract methods have been real-
ized by actual methods, the subclass can be instantiated.
Abstract classes make good prototypes for families of similar classes.
The Factory Pattern64
L
L
protected function calcRent() {
switch ($this->game->utilityCount($this->owner)) {
case 1: return new Dollar(4*$this->game->lastRoll());
case 2: return new Dollar(10*$this->game->lastRoll());
default: return new Dollar;
}
}
}
Each subclass
extends
the
Property
class and includes its own
protected ClacRent()
method. Since
all of the abstract methods are defined, each subclass can be instantiated.
To set up the game, all of the Monopoly properties have to be created. Since this is the chapter
on the Factory design pattern—and because the property types in Monopoly have much in com-
mon—you should be thinking about a polymorphic Factory to create all of the necessary objects.
Start by creating a
Property
factory class. Where I live, the County Assessor handles property
taxes and deeds, so I named my
Property
factory
Assessor
. Next, the factory has to manufacture all
of the Monopoly properties. In a real application, all of the Monopoly assets might come from a
database or a configuration file, but for this example, lets just hard code an array with the relevant
data:
class Assessor {
protected $prop_info = array(
// streets
‘Mediterranean Ave.’ => array(‘Street’, 60, ‘Purple’, 2)
,’Baltic Ave.’ => array(‘Street’, 60, ‘Purple’, 2)
//more of the streets...
,’Boardwalk’ => array(‘Street’, 400, ‘Blue’, 50)
// railroads
,’Short Line R.R.’ => array(‘RailRoad’, 200)
//the rest of the railroads...
// utilities
,’Electric Company’ => array(‘Utility’, 150)
,’Water Works’ => array(‘Utility’, 150)
);
}
The
Property
subclasses require an instance of
Monopoly
as part of the constructor. For now, simply
make a setter function and define an instance variable,
$game
, to hold it in the
Assessor
class.
class Assessor {
pprrootteecctteedd
$$ggaammee;;
ppuubblliicc
ffuunnccttiioonn
sseettGGaammee(($$ggaammee))
{{
$$tthhiiss->>ggaammee
==
$$ggaammee;;
}}
The Factory Pattern 65
protected $prop_info = array(/* ... */);
}
Although youd likely prefer a database of records over such an array, there are times when long lists
of parameters are unavoidable. If you run into such an occasion—such as here—consider the
“Introduce Parameter Object” refactoring.
In the case of Monopoly, what might a parameter object for the real estate properties, say,
PropertyInfo
, look like? The intent is to pass each properties array into the constructor of the
PropertyInfo
class and receive a new object. Intent implies design, and according to TDD, that
means a test case.
Here is a sample test that begins to sketch a
PropertyInfo
class:
function testPropertyInfo() {
$list = array(‘type’,’price’,’color’,’rent’);
$this->assertIsA(
$testprop = new PropertyInfo($list), ‘PropertyInfo’);
foreach($list as $prop) {
$this->assertEqual($prop, $testprop->$prop);
}
}
This test verifies that each
PropertyInfo
has four public attributes and validates the exact order of
the array parameters.
But because the
RailRoad
and
Utility
classes dont require color or rent information when
instantiated, another test is needed to verify that
PropertyInfo
can also be instantiated given a
shorter list of parameters:
function testPropertyInfoMissingColorRent() {
$list = array(‘type’,’price’);
$this->assertIsA(
Refactoring — Introduce Parameter Object
Methods with long lists of parameters are complex and therefore prone to error. You can replace natu-
rally grouped sets of parameters with an object encapsulating those parameters. For example, “start
date” and “end date” parameters could be replaced with a
DDaatteeRRaannggee
object.
The Factory Pattern66
L
L
$testprop = new PropertyInfo($list), ‘PropertyInfo’);
$this->assertNoErrors();
foreach($list as $prop) {
$this->assertEqual($prop, $testprop->$prop);
}
$this->assertNull($testprop->color);
$this->assertNull($testprop->rent);
}
A
PropertyInfo
class to satisfy the two previous tests might look like:
class PropertyInfo {
const TYPE_KEY = 0;
const PRICE_KEY = 1;
const COLOR_KEY = 2;
const RENT_KEY = 3;
public $type;
public $price;
public $color;
public $rent;
public function __construct($props) {
$this->type =
$this->propValue($props, ‘type’, self::TYPE_KEY);
$this->price =
$this->propValue($props, ‘price’, self::PRICE_KEY);
$this->color =
$this->propValue($props, ‘color’, self::COLOR_KEY);
$this->rent =
$this->propValue($props, ‘rent’, self::RENT_KEY);
}
protected function propValue($props, $prop, $key) {
if (array_key_exists($key, $props)) {
return $this->$prop = $props[$key];
}
}
}
So,
PropertyInfo
can now act as a parameter object for the various
Property
classes, and
Assessor
assertNoErrors()
aasssseerrttNNooEErrrroorrss(())
validates that no PHP errors have occured. If any errors are present, the assertion fails.
assertNull()
aasssseerrttNNuullll(())
passes if the first parameter passed is null. Any other valid PHP value causes the assertion
to fail. Like most other SimpleTest assertions, you can optionally pass a failure message as a second
parameter.
The Factory Pattern 67
L
L
has the data needed to create valid
PropertyInfo
objects.
Its time to creates new instances of the
PropertyInfo
class based on the data from our
Assessor->$prop_info
array.
Such code might look like:
class Assessor {
protected $game;
public function setGame($game) { $this->game = $game; }
ppuubblliicc
ffuunnccttiioonn
ggeettPPrrooppeerrttyy(($$nnaammee))
{{
$$pprroopp__iinnffoo
==
nneeww
PPrrooppeerrttyyIInnffoo(($$tthhiiss->>pprroopp__iinnffoo[[$$n
naammee]]));;
sswwiittcchh(($$pprroopp__iinnffoo->>ttyyppee))
{{
ccaassee
SSttrreeeett::
$$pprroopp
==
nneeww
SSttrreeeett(($$tthhiiss->>ggaammee,,
$$nnaammee,,
$$pprroopp__iinnffoo->>pprriiccee));;
$$pprroopp->>ccoolloorr
==
$$pprroopp__iinnffoo->>ccoolloorr;;
$$pprroopp->>sseettRReenntt(($$pprroopp__iinnffoo->>rreenntt));;
rreettuurrnn
$$pprroopp;;
ccaassee
RRaaiillRRooaadd::
rreettuurrnn
nneeww
RRaaiillRRooaadd(($$tthhiiss->>ggaammee,,
$$nnaammee,,
$$pprroopp__iinnffoo->>pprriiccee));;
bbrreeaak
k;;
ccaassee
UUttiilliittyy::
rreettuurrnn
nneeww
UUttiilliittyy(($$tthhiiss->>ggaammee,,
$$nnaammee,,
$$pprroopp__iinnffoo->>pprriiccee));;
bbrreeaakk;;
ddeeffaauul
ltt::
////sshhoouulldd
nnoott
bbee
aabbllee
ttoo
ggeett
hheerree
}}
}}
protected $prop_info = array(/* ... */);
}
This code is functional, but brittle. Consider what happens if you pass a key that doesnt exist in the
$this->prop_info
array. Because the instantiation of the
PropertyInfo
object is embedded in the
code, there is no effective way to test the created object. A better solution is to create a Factory
method to facilitate creation of the
PropertyInfo
objects. Hence, the next step is to write a test for
the
PropertyInfo
factory method in the
Assessor
class.
There is a problem, however: this method shouldnt be a part of the public API of the
Assessor
class. How then can it be tested?
There are a couple of approaches here, and delving into any requires a fair amount of testing
theory. Briefly, you can perform black box testing or white box testing.
Black Box Testing
Black Box Testing treats the tested object as a “black box,” where the specification (the published API) is
known, but nothing of the actual implementation of the object is known. Testing therefore focuses only
on the inputs and outputs to the public methods of the object.
The Factory Pattern68
L
L
To avoid straying too far off topic, though, is there a compromise between Black Box and White Box
Testing to enable use of TDD? One option is to make the method public during development and
protected upon release (commenting out any effected tests). This is not a very satisfying approach,
so an alternative is to subclass the object and make the method public in the testing subclass.
Heres the subclass approach:
class TestableAssessor extends Assessor {
public function getPropInfo($name) {
return Assessor::getPropInfo($name);
}
}
The advantage of this solution is you can have the correct
Assessor
public API, but still allow for test
coverage through the
TestableAssessor
subclass. Additionally, any other code you might introduce
specifically for test coverage would not be present in your normal run-time version of
Assessor
.
The disadvantages include testing an additional class, which could introduce additional prob-
lems due to the additional complexity. And since youre specifying the behavior for the object’s inter-
nal API, your tests become brittle if you ever refactor this internal structure again.
Weighing the pros and cons, a test case is the correct way to go for this example, so let’s get
started.
function testGetPropInfoReturn() {
$assessor = new TestableAssessor;
$this->assertIsA(
$assessor->getPropInfo(‘Boardwalk’), ‘PropertyInfo’);
}
To ensure that all calling code passes valid key values, use an exception. SimpleTest is currently a
PHP4 based testing framework, so it doesnt have any built-in features to test for exceptions, but you
White Box Testing
White Box Testing is the opposite of Black Box Testing, in that it assumes the tester has both
knowledge of and access to all of the code for the tested object. The goal of this style of
testing is typically complete code coverage and extensive failure condition testing. See
http://c2.com/cgi/wiki?WhiteBoxTesting for a good introduction to this style of testing.
The Factory Pattern 69
L
L
can easily work around this in a test case.
function testBadPropNameReturnsException() {
$assessor = new TestableAssessor;
$exception_caught = false;
try { $assessor->getPropInfo(‘Main Street’); }
catch (InvalidPropertyNameException $e) {
$exception_caught = true;
}
$this->assertTrue($exception_caught);
$this->assertNoErrors();
}
Finally, the implementation of Assessor can be completed:
class Assessor {
protected $game;
public function setGame($game) { $this->game = $game; }
public function getProperty($name) {
$$pprroopp__iinnffoo
==
$$tthhiiss->>ggeettPPrrooppIInnffoo(($$nnaammee));;
switch($prop_info->type) {
case ‘Street’:
$prop = new Street($this->game, $name, $prop_info->price);
$prop->color = $prop_info->color;
$prop->setRent($prop_info->rent);
return $prop;
case ‘RailRoad’:
return new RailRoad($this->game, $name, $prop_info->price);
break;
case ‘Utility’:
return new Utility($this->game, $name, $prop_info->price);
break;
default: //should not be able to get here
}
}
protected $prop_info = array(/* ... */);
pprrootteecctteedd
ffuunnccttiioonn
ggeettPPrrooppIInnffoo(($$nnaammee))
{{
iiff
((!!aarrrraayy__kkeeyy__eexxiissttss(($$nnaammee,,
$$tthhiiss->>pprroopp__iinnffoo))))
{{
tthhrrooww
nneeww
IInnvvaalliiddPPrrooppeerrttyyNNaammeeEExxcceeppttiioon
n(($$nnaammee));;
}}
rreettuurrnn
nneeww
PPrrooppeerrttyyIInnffoo(($$tthhiiss->>pprroopp__iinnffoo[[$$nnaammee]]));;
}}
}
The Factory Pattern70
The method
Assessor::getPropInfo()
represents the logical introduction of a
PropertyInfo
factory as a protected method of the
Assessor
class. The
Assessor::getProperty()
method is the
public factory that returns one of our three
Property
subclasses, depending on what property name
is requested.
Factories for Lazy Loading
Another significant benefit to using a Factory is the ability to perform lazy loading. Where this sce-
nario comes into play most often is when a factory can instantiate a number of subclasses that are
defined in separate PHP source files.
A common technique with web sites is to have multiple web pages dynamically controlled through
a single script. Consider blog software that might have different pages for viewing the recent entries,
a single entry with comments, a comment submitting page, an archive navigation page, a page for
the administrator to edit page, and so forth. You might encapsulate the logic to generate each of
these in a class, and use a Factory to load both the class definition and the object. Each of these class-
es might be stored in a separate file in a pages’ subdirectory of your application.
The code to implement a lazy loading page factory might look like:
class PageFactory {
function &getPage() {
$page = (array_key_exists(‘page’, $_REQUEST))
? strtolower($_REQUEST[‘page’])
: ‘’;
switch ($page) {
case ‘entry’: $pageclass = ‘Detail’; break;
case ‘edit’: $pageclass = ‘Edit’; break;
case ‘comment’: $pageclass = ‘Comment’; break;
default:
$pageclass = ‘Index’;
}
if (!class_exists($pageclass)) {
rreeqquuiirree__oonnccee
ppaaggeess//..$$ppaaggeeccllaassss....pphhpp;;
}
return new $pageclass;
}
}
Terminology — Lazy Loading
The term lazy loading refers to not performing expensive operations (generally IO operations like includ-
ing PHP files or querying a database) before they are absolutely required by the script.
The Factory Pattern 71
L
L
You can take advantage of PHP’s dynamic nature and use run-time logic to determine the class name
you wish to create. In this case, an HTTP request parameter, page is evaluated to determine which
page has been requested. You can implement lazy loading by not loading all possible “page” classes
during every script execution, but instead including the class definition only when you are about to
create the new object. This occurs in the conditional
require_once
above. This technique is not as
important on a system with a PHP accelerator—a byte code cache—because the cost of including
the additional source code is negligible there. Otherwise; it’s a good performance enhancer for most
typical PHP hosted environments.
For a more detailed look at lazy loading, read Chapter 11—The Proxy Pattern.
Issues
The Factory pattern is reasonably simple and very powerful. You may have examples of this pattern
in your code already, and you will soon notice many more. The GoF book includes several addition-
al related construction patterns: AbstractFactory and Builder. An AbstractFactory handles families of
related components and the Builder pattern is designed to facilitate construction of complex
objects.
In many of this chapters examples, a parameter was passed to the Factory method (e.g.
CrayonBox::getColor(‘red’);
). The GoF refer to this as a parameterized factory” and it is fairly
typical of the Factory methods I have seen in PHP web applications.
You have now been introduced to the Factory pattern, a technique for managing creation of new
objects within your code. You have seen how the Factory pattern can centralize the creation of com-
plex objects or even substitute objects of different classes. Factories support the very important
principal of polymorphism in OOP.
The Factory Pattern72
4
The Singleton
Pattern
IN NEARLY EVERY OBJECT-oriented program, there are usually one or two resources that are cre-
ated once and shared for the duration of the entire application. For example, a database connec-
tion in an e-commerce application is one such resource: it’s initialized when the application
launches, is used to effectuate all transactions, and is finally disconnected and destroyed when the
program ends. In your code, theres no need to conjure a database connection each and every time;
that’s a hassle and very inefficient. Instead, your code can simply re-use the connection that’s already
been established. The challenge then is how do you refer to the connection (or to any other unique
perennial resource, such as an open file or a queue).
The Problem
How do you ensure that an instance of a particular class is exclusive (it’s always the lone instance of
that class) yet is also readily-accessible?
The Solution
Of course, a global variable is an obvious solution, but its also a Pandoras Box (The saying, Good
judgment comes from experience, but experience usually comes from poor judgment” comes to
mind.) Any portion of your code can modify a global variable, causing endless aggravation debug-
ging any number of serendipitous problems. In other words, the state of a global variable is
always questionable. (A good description of the global variable dilemma can be found at
http://c2.com/cgi/wiki?GlobalVariablesAreBad.)
When you need an exclusive instance of a particular class, use the aptly-named Singleton pat-
tern. A class based on the Singleton pattern properly instantiates and initializes one instance of the
class and provides access to the exact same object every time, typically through a static method
named
getInstance()
.
Getting the exact same instance every time is critical and worthy of a test:
// PHP4
function TestGetInstance() {
$this->assertIsA(
$obj1 =& DbConn::getInstance(),
‘DbConn’,
‘The returned object is an instance of DbConn’);
$this->assertReference(
$obj1,
$obj2 =& DbConn::getInstance(),
‘Two calls to getInstance() return the same object’);
}
This test method makes two assertions: that the value returned from calling the static
DbConn::getInstance()
method is an instance of the
DbConn
class and that a second call to
getInstance()
returns the same reference, which implies its the very same object.
Besides asserting the expected behavior of the code, the test also demonstrates the proper
(PHP4) usage of
getInstance()
:
$local_conn_var =& DbConn::getInstance();
. The local variable is
assigned the result of the static method call by reference (
=&
).
Theres one other test to write, at least for now: verify that instantiating a Singleton class
assertReference
aasssseerrttRReeffeerreennccee(())
ensures that the two passed parameters are references to the same PHP variable.
In PHP4, this asserts the two tested parameters are in fact the same object.
aasssseerrttRReeffeerreennccee(())
may be
deprecated as SimpleTest is migrated to PHP 5.
The Singleton Pattern76
L
L
directly via
new
causes an error of some kind. Heres that test:
function TestBadInstantiate() {
$obj =& new DbConn;
$this->assertErrorPattern(
‘/(bad|nasty|evil|do not|don\’t|warn).*’.
‘(instance|create|new|direct)/i’);
}
The code creates an instance of the
DbConn
class by using
new
directly, which should cause a PHP
error. To make the code less brittle, a PCRE pattern is provided to match the error message. (The
exact wording of the error message is relatively unimportant.)
Sample Code
The Singleton is an interesting pattern. Lets explore its implementation in both PHP4 and PHP5,
starting with PHP4.
A “Global” Approach
Conceptually, a global variable makes an ideal Singleton, but a global variable is unpredictable:
theres no guarantee that it contains the exact same object over the entire course of your script.
However, you can mitigate the problem of “unrestrainted access” to a global variable by never refer-
encing the global directly. For instance, this code “hides” the reference in a global variable with a
very long unique and descriptive name.
class DbConn {
function DbConn($fromGetInstance=false) {
if (M_E != $fromGetInstance) {
trigger_error(‘The DbConn class is a Singleton,’
.’ please do not instantiate directly.’);
}
}
function &getInstance() {
$key = ‘__some_unique_key_for_the_DbConn_instance__’;
if (!(array_key_exists($key, $GLOBALS) && is_object($GLOBALS[$key])
&& ‘dbconn’ == get_class($GLOBALS[$key]) )) {
$GLOBALS[$key] =& new DbConn(M_E);
}
return $GLOBALS[$key];
}
}
The Singleton Pattern 77
You may be wondering about the default parameter
$fromGetInstance
in the
DbConn
constructor. It
provides (pretty weak) protection from instantiating the object directly: unless the default value is
changed to e(the PHP math constant
M_E
= 2.718281828459), the code triggers an error. The
getInstance()
method calls
new DbConn(M_E)
, creating the object in the correct manner.
Expressed as a UML class diagram, the solution looks like this:
If you dont care for this secret parameter”-style guard, another option is to create a global token to
validate youre creating the object from the
getInstance()
method. This moves the guard condition
from something you know” to something in the environment.
Heres a sample of how the constructor guard code might look like with a global semaphore:
class DbConn {
function DbConn() {
$token = ‘__some_DbConn_instance_create_semaphore__’;
if (!array_key_exists($token, $GLOBALS)) {
trigger_error(‘The DbConn class is a Singleton,’
.’ please do not instantiate directly.’);
}
}
function &getInstance() {
static $instance = array();
if (!$instance) {
$token = ‘__some_DbConn_instance_create_semaphore__’;
$GLOBALS[$token] = true;
$instance[0] =& new DbConn;
unset($GLOBALS[$token]);
}
return $instance[0];
}
}
The Singleton Pattern78
Another important aspect of the code is the use of the reference operator,
&
. There are two locations
where the use of
&
is required. The first is in the function definition, prior to the function name,
which indicates the
return
is a reference. The second is the assignment of the new
DbConn
object to
the
$GLOBALS
array. (Both uses emphasize the point mentioned in the preface and the ValueObject
chapter: in PHP4 code, you nearly always want to create, pass, and return objects by reference, lead-
ing to a proliferation of
&
reference operators in your code.)
The conditional check in the
getInstance()
method is written to always run without warnings,
even at the
E_ALL
error reporting level. It verifies theres an object of the class
DbConn
in the appro-
priate spot in the
$GLOBALS
array, else it creates the object there. The method then returns the object
that may or may not have been created on this iteration through the method, but by the time the
method is finished, youre sure you have the one valid instance of the class, and that it’s been initial-
ized correctly.
A Static Approach
One problem with the global variable solution, even with the global variable access hidden within
getInstance()
, is you still have the potential to corrupt the global variable inadvertently, simply
because the variable is potentially in scope anywhere in your script.
A cleaner solution is to use a static variable inside of the getInstance() method to store the
Singleton. A first cut at the code might look like:
class DbConn {
// ...
function &getInstance() {
static $instance = false;
if (!$instance) $instance =& new DbConn(M_E);
return $instance;
}
}
Alas, the Zend 1 engine in PHP4 doesnt store references in static variables (see
http://www.php.net/manual/en/language.variables.scope.php#AEN3609). A workaround is to store a
Tip
PHP4 allows you to change the value of
$$tthhiiss
in the constructor. In the past, I have used
$$tthhiiss
==
nnuullll;;
when I had a construction error, ensuring the invalid object could not be used by further code. While
useful in PHP4, it’s not compatible with PHP5, so in the interest of future-proofing your code, this tech-
nique is no longer recommended.
The Singleton Pattern 79
L
L
static array, and place the reference to your Singleton instance in a known index of that array.
getInstance()
method might then look like:
class DbConn {
function DbConn($fromGetInstance=false) {
if (M_E != $fromGetInstance) {
trigger_error(‘The DbConn class is a Singleton,’
.’ please do not instantiate directly.’);
}
}
function &getInstance() {
static $instance = array();
if (!$instance) $instance0 =& new DbConn(M_E);
return $instance0;
}
}
This code simply chooses the first element of the static
$instance
array to hold the reference to our
Singleton
DbConn
instance.
This code is much tighter than the global version, though it does it does rely on a bit of PHP
boolean magic: an empty array evaluates to false in a conditional check. As in the previous version
of the DbConn class, reference operators are required in the function definition and in the assign-
ment.
The Singleton in PHP5
A Singleton in PHP5 is much simpler to implement, because PHP5 provides and enforces visibility
for variables and functions inside of classes. By making the
DbConn::__construct()
method
pri-
vate
, no code can directly instantiate the class. Expressed in a UML diagram, a PHP5
DbConn
singleton looks like:
The Singleton Pattern80
Combining the static method variable to hold the instance and the private constructor to prevent
inadvertent construction, you now have a class like:
class DbConn {
/**
* static property to hold singleton instance
*/
static $instance = false;
/**
* constructor
* private so only getInstance() method can instantiate
* @return void
*/
private function __construct() {}
/**
* factory method to return the singleton instance
* @return DbConn
*/
public function getInstance() {
if (!DbConn::$instance) {
DbConn::$instance = new DbConn;
}
return DbConn::$instance;
}
}
Issues
Now that you’ve seen several possible implementations of the Singleton design pattern, let’s look at
some of the tradeoffs you should consider when looking at implementing this design pattern.
First, a Singletons isnt a “better” global variable. For example, if a method requires a Singleton,
pass it as a parameter to make its usage plainly obvious.
Also, you may be tempted to lump “utility functions” of all sorts into a Singleton class because
of its global” availability to your application. Avoid this, limiting the Singletons methods to the
functions that are purposeful for the class.
More discussion related to these issues is available online at:
http://c2.com/cgi/wiki?SingletonGlobalProblems
http://c2.com/cgi/wiki?GlobalVariablesAreBad
The Monostate Pattern: Stealth Singletons
Occasionally, I’ve wanted a class where all of the instances of that class share a global state — in
other words, any instance of the class returns the exact same information. Similar in behavior to a
The Singleton Pattern 81
Singleton, this is a design pattern called the MonoState (http://c2.com/cgi/wiki?MonostatePattern).
In PHP, you can use a neat trick with references to bind global data to an instance variable to
achieve a MonoState.
As an example, lets create a class to provide global application configuration. No matter what
instance of this MonoState class you access, you get the same values.
Here are those requirements expressed as a test:
// PHP4
function TestApplConfig() {
$this->assertIsA(
$obj1 =& new ApplicationConfig, ‘ApplicationConfig’);
$this->assertIsA(
$obj2 =& new ApplicationConfig, ‘ApplicationConfig’);
$test_val = ‘/path/to/cache’.rand(1,100);
$obj1->set(‘cache_path’, $test_val);
$this->assertEqual($test_val, $obj2->get(‘cache_path’));
}
The test creates two different instances of the MonoState class, changes one, and then verifies that
the other instance was indeed affected by the change.
Here is the code to implement the MonoState:
class ApplicationConfig {
var $_state;
function ApplicationConfig() {
$key = ‘__stealth_singleton_state_index__’;
if (!(array_key_exists($key, $GLOBALS)
&& is_array($GLOBALS[$key]))) {
$GLOBALS[$key] = array();
}
$this->_state =& $GLOBALS[$key];
}
function set($key, $val) {
$this->_state[$key] = $val;
}
function get($key) {
if (array_key_exists($key, $this->_state)) {
return $this->_state[$key];
}
}
}
The core of this trick is
$this->state =& $GLOBALS[$key;]
. After making sure
$GLOBALS[$key]
is an
The Singleton Pattern82
array, the code binds a reference to the global array to the class variable
$this->state
. From then
on, any changes to
$this->state
are seamlessly reflected in the global array and therefore in any
other instance of the class.
This trick can be used with any of PHP’s superglobal arrays and is particularly effective with
$_SESSION
for the user notification queue. A MonoState can store a series of messages to present to
the user for use throughout your code (but you might redirect to another page prior to actually dis-
playing the messages).
$_SESSION
is a good place to store these messages so that the messages per-
sist after redirection.
The Singleton Pattern 83
5
The Registry
Pattern
Because it’s generally considered good form” to avoid the use of global variables, objects are
usually passed from one code segment to another as parameters. But the problem with pass-
ing instances is that objects sometimes end up as “tramp data,” passed into one function only
to be passed again to another function which truly needs the object.
To make writing, reading, and consuming code simpler, it’s best to minimize the number of dif-
ferent objects and consolidate knowledge of how to get to a myriad of other widely-used objects into
a single, well-known object.
The Problem
How can you get references to objects through a single, well-known, object?
The Solution
The Registry design pattern is like an object phone book”—a directory—that stores and retrieves
references to objects. (PHP associative arrays perform a similar phone book” function, and in fact,
the heart of a Registry implementation can center around PHP’s powerful arrays.) The features of a
Registry are most often encapsulated in a Singleton (see Chapter 4), making the Registry a definitive
source of information for your entire application.
Sample Code
As Martin Fowler mentions in his chapter on the Registry pattern, you can implement the pattern in
a number of ways and offer a variety of interfaces. Lets explore that notion and build several varia-
tions of the Registry pattern in PHP4.
Let’s start with writing code to store and retrieve instances of objects and provide global access
to the Registry. An instance variable caches the objects, and the Registry itself is a Singleton.
As always, tests capture the requirements. This first test verifies that the Registry is a Singleton.
// PHP4
class RegistryPHP4TestCase extends UnitTestCase {
function testRegistryIsSingleton() {
$this->assertIsA($reg =& Registry::getInstance(), ‘Registry’);
$this->assertReference($reg, Registry::getInstance());
}
}
Given what youve learned from the previous chapter on the Singleton pattern, you should be able
to quickly write a
Registry
class that passes this test. Heres a
Registry
class that satisfies the test
(ignoring the code required to enforce no direct object creation):
class Registry {
function &getInstance() {
static $instance = array();
The primary reference on the Registry pattern is Patterns of Enterprise Application Architecture, where
Martin Fowler describes the pattern using Java as the implementation language.
Marcus Baker wrote a detailed article on using the Registry pattern in PHP, which is available on the
phpPatterns.com site at (http://www.phppatterns.com/index.php/article/articleview/75/1/1/). Baker
also focuses on testing considerations and demonstrates more of the Test Driven Development method-
ology.
The Registry Pattern86
L
L
if (!$instance) $instance[0] =& new Registry;
return $instance[0];
}
}
A simple static array is sufficient to record the single instance.
Next, let’s turn to the specific features of the Registry. A registry should provide
get()
and
set()
methods to store and retrieve objects using some key and should also offer an
isValid()
method to
determine if a specific key has been set.
The very easiest of these three methods is the latter. Here are two test cases for
isValid()
:
class RegistryPHP4TestCase extends UnitTestCase {
function testRegistryIsSingleton() { /*...*/ }
ffuunnccttiioonn
tteessttEEmmppttyyRReeggiissttrryyKKeeyyIIssIInnvvaalliidd(())
{{
$$rreegg
==&&
RReeggiissttrryy::::ggeettIInnssttaannccee(());;
$$tthhiiss->>aasssseerrttF
Faallssee(($$rreegg->>iissVVaalliidd((kkeeyy))));;
}}
ffuunnccttiioonn
tteessttEEmmppttyyRReeggiissttrryyKKeeyyRReettuurrnnssNNuullll(())
{{
$$rreegg
==&&
RReeggiissttrryy:
:::ggeettIInnssttaannccee(());;
$$tthhiiss->>aasssseerrttNNuullll(($$rreegg->>ggeett((kkeeyy))));;
}}
}
Per Test Driven Development, do the minimum coding possible to satisfy your existing tests and
then add more tests if you havent satisfied all of the class’ requirements. Heres the simplest amount
of code that satisfies the previous test:
class Registry {
ffuunnccttiioonn
iissVVaalliidd(())
{{
rreettuurrnn
ffaallssee;;
}}
ffuunnccttiioonn
ggeett(())
{{
}}
function &getInstance() {
static $instance = array();
if (!$instance) $instance[0] =& new Registry;
assertFalse()
aasssseerrttFFaallssee(())
is simply the negation of
aasssseerrttTTrruuee(())
: it passes if the first parameter evaluates to a PHP
boolean false.
The Registry Pattern 87
L
L
return $instance[0];
}
}
Admittedly, the code snippets for
isValid()
and
get()
arent very inspired, but all of the tests do
pass. Time to add some more meaty tests.
class RegistryPHP4TestCase extends UnitTestCase {
function testRegistryIsSingleton() { /*...*/ }
function testEmptyRegistryKeyIsInvalid() { /*...*/ }
function testEmptyRegistryKeyReturnsNull() { /*...*/ }
ffuunnccttiioonn
tteessttSSeettRReeggiissttrryyKKeeyyBBeeccoommeessVVaalliidd(())
{{
$$rreegg
==&&
RReeggiissttrryy::::ggeettIInnssttaannccee(());;
$$tteesstt__vvaalluuee
==
ssoommeetthhiinngg;;
$$rreegg->>sseett((kkeeyy,,
$$tteesstt__vvaalluuee));;
$$tthhiiss->>aasssseerrttTTrruuee(($$rreegg->>iissVVaalliidd((kkeeyy))));;
}}
}
To satisfy
testSetRegistryKeyBecomesValid()
, the
Registry
class must have some means of track-
ing if a particular key has been stored using
set()
. The obvious implementation is to use a PHP asso-
ciative array as an instance variable and use PHP’s
array_key_exists()
function to determine if the
index of interest has been created yet. Heres a possible next step for
Registry
:
class Registry {
vvaarr
$$__ssttoorree
==
aarrrraayy(());;
ffuunnccttiioonn
iissVVaalliidd(($$kkeeyy))
{{
rreettuurrnn
aarrrraayy__kkeeyy__eexxiissttss(($$kkeeyy,,
$$tthhiiss->>__ssttoorree));;
}}
ffuunnccttiioonn
sseett(($$kkeeyy,,
$$oobbjj))
{{
$$tthhiiss->>__ssttoorree[[$$kkeeyy]]
==
$$oobbjj;;
}}
function get() {
}
function &getInstance() {
static $instance = array();
if (!$instance) $instance[0] =& new Registry;
return $instance[0];
}
}
By initializing the
$_store
variable when it’s declared, theres no need for a constructor method.
The Registry Pattern88
(With no proper visibility in PHP4, the code follows the convention of prefixing a private variable
with an underscore.)
The tests pass again; time to move on to the final feature: given a key, the
Registry::get()
oper-
ation needs to return a reference to the specified object. Heres a test that captures that intent:
class RegistryPHP4TestCase extends UnitTestCase {
function testRegistryIsSingleton() { /*...*/ }
function testEmptyRegistryKeyIsInvalid() { /*...*/ }
function testEmptyRegistryKeyReturnsNull() { /*...*/ }
function testSetRegistryKeyBecomesValid() { /*...*/ }
ffuunnccttiioonn
tteessttSSeettRReeggiissttrryyVVaalluueeIIssRReeffeerreennccee(())
{{
$$rreegg
==&&
RReeggiissttrryy::::ggeettIInnssttaannccee(());;
$$tteesstt__vvaalluuee
==
ssoommeetthhiinngg;;
$$rreegg->>sseett((kkeeyy,,
$$tteesstt__vvaalluuee));;
$$tthhiiss->>aasssseerrttRReeffeerreennccee(($$tteesstt__vvaalluuee,,
$$rreegg->>ggeett((kkeeyy))));;
////aannootthheerr
wwaayy
ttoo
tteesstt
tthhee
rreeffeerreennccee
$$tteesstt__vvaalluuee
..==
eellssee;;
$$tthhiiss->>aasssseerrttEEqqu
uaall((
ssoommeetthhiinngg
eellssee
,,$$rreegg->>ggeett((kkeeyy))
));;
}}
}
And here is a complete implementation of the
Registry
class:
class Registry {
var $_store = array();
function isValid($key) {
return array_key_exists($key, $this->_store);
}
ffuunnccttiioonn
&&ggeett(($$kkeeyy))
{{
iiff
((aarrrraayy__kkeeyy__eexxiissttss(($$kkeeyy,,
$$tthhiiss->>__ssttoorree))))
rreettuurrnn
$$tthhiiss->>__ssttoor
ree[[$$kkeeyy]];;
}}
ffuunnccttiioonn
sseett(($$kkeeyy,,
&&$$oobbjj))
{{
$$tthhiiss->>__ssttoorree[[$$kkeeyy]]
==&&
$$oobbjj;;
}}
function &getInstance() {
static $instance = array();
if (!$instance) $instance[0] =& new Registry;
return $instance[0];
}
}
The Registry Pattern 89
The
Registry::get()
method returns a reference. Similarly, the
$obj
parameter of the
Registry::set()
method is defined to be pass by reference and a reference is assigned to
$this-
>_store[$key
]. The combination of these
get()
and
set()
methods and the proper use of reference
allows the
assertReference()
assertion in
testRegistry()
test to pass.
In PHP5, object handles come to the rescue again, saving you from the hassle of object reference
passing. In fact, Registry implementations become trivial because you can access associative arrays
without worrying about the possibility of a fatal error from not passing the object by reference. Using
PHP5, you can also mix objects and literals in Registry.
An Example
So what might a Registry look like in action? In web application development, it’s fairly typical to
have a single database connection (hence the widespread use of a Singleton for managing that con-
nection). But, say, for legacy reasons, that your applications customer database is separate from
your online orders database and that your database analyst (DBA) has moved older orders to an
archive database, again, completely separate from your customer database and the (current and
recent) orders databases. How can you manage those three database connections easily, without
coding three different Singletons? Use a Registry.
class DbConnections extends Registry {}
Tip
When you integrate a design pattern into your code, the name of your class should still reflect its role or
function in your application, not necessarily the patterns name.
Referring to code using a pattern name is good for communication with programmers outside of your
project; within your project, however, the names of your classes should be appropriate to the domain of
your application and be well understood by your colleagues.
Continued ...
The
RReeggiissttrryy::::ggeett(())
code could be written
rreettuurrnn@@$$tthhiiss->>__ssttoorree[[$$kkeeyy;;]]
, however, it’s best to avoid the
error suppression operator. Moreover, the code using the error suppression operator would be ambigu-
ous, requiring more time to digest if you have to revisit the code again later. The
aarrrraayy__kkeeyy__eexxiissttss(())
function makes it clear what error is being avoided.
The Registry Pattern90
L
L
L
L
DbConnections
is a Singleton and since it inherits from the
Registry
class,
DbConnections
combines
all of the benefits of the two patterns.
The following code snippet creates and stores a connection to each of the databases in the
Registry.
// initial setup, somewhere near the start of your script
$dbc =& DbConnections::getInstance();
$dbc->set(
‘contacts’,
new MysqlConnection(‘user1’, ‘pass1’, ‘db1’, ‘host1’));
$dbc->set(
‘orders’,
new MysqlConnection(‘user2’, ‘pass2’, ‘db2’, ‘host2’));
$dbc->set(
‘archives’,
new MysqlConnection(‘user3’, ‘pass3’, ‘db3’, ‘host3’));
With the Registry loaded with data, it’s ready to be used.
// domain model classes
class Customer {
var $db;
function Customer() {
$dbc =& DbConnections::getInstance();
$this->db =& $dbc->get(‘contacts’);
}
//...
}
class Orders {
var $db_cur;
var $db_hist;
function Contact() {
$dbc =& DbConnections::getInstance();
$this->db_cur =& $dbc->get(‘orders’);
$this->db_hist =& $dbc->get(‘archive’);
}
//...
}
Tip: Continued...
Throughout the rest of this chapter the example class names reflect the patterns name and the specific
implementation being developed, not a role in an application. This is done for clarity of the example, not
as an example of a good naming convention.
The Registry Pattern 91
L
L
One class models the customer database and the other class models both the historical and current
orders databases. Obtaining the right connection is two lookups: one to find the Registry and one to
find the object associated with the key.
Implementing the Registry as a MonoState Object
As mentioned earlier, there are a number of possible implementations for the Registry pattern.
The first variation realizes the Registry as a MonoState object (the MonoState pattern was cov-
ered briefly at the end of Chapter 4—The Singleton Pattern). With this design, any instance of the
Registry would need access to the same array. Lets call the new class
RegistryGlobal
to distinguish
it from the
Registry
class that was just developed and to reflect the nature of the implementation.
Heres a test to flesh out the idea (it should look very familiar):
class RegistryGlobalPHP4TestCase extends UnitTestCase {
function testRegistryGlobal() {
$reg =& new RegistryGlobal;
$this->assertFalse($reg->isValid(‘key’));
$this->assertNull($reg->get(‘key’));
$test_value = ‘something’;
$reg->set(‘key’, $test_value);
$this->assertReference($test_value, $reg->get(‘key’));
}
}
The implementation should look reasonably familiar as well:
class RegistryGlobal {
var $_store = array();
function isValid($key) {
return array_key_exists($key, $this->_store);
}
function &get($key) {
if (array_key_exists($key, $this->_store))
return $this->_store[$key];
}
function set($key, &$obj) {
$this->_store[$key] =& $obj;
}
}
The
isValid()
,
get(),
and
set()
methods are identical to the methods of the
Registry
class devel-
oped earlier.
The Registry Pattern92
Next, let’s write a test to verify that the
RegistryGlobal
class functions as a MonoState:
class RegistryGlobalPHP4TestCase extends UnitTestCase {
function testRegistryGlobal() { /*...*/ }
function testRegistryGlobalIsMonoState() {
$reg =& new RegistryGlobal;
$reg2 =& new RegistryGlobal;
$this->assertCopy($reg, $reg2);
$test_value = ‘something’;
$reg->set(‘test’, $test_value);
$this->assertReference(
$reg->get(‘test’)
,$reg2->get(‘test’));
}
}
Here, the test creates two instances of the
RegistryGlobal
class, verifies they’re not references to the
same object, sets a value in one Registry, and finally validates that the same object is returned by
both instances. If the tests pass, the
RegistryGlobal
class exhibits MonoState behavior.
ddeeffiinnee((RREEGGIISSTTRRYY__GGLLOOBBAALL__SSTTOORREE,,
____rreeggiissttrryy__gglloobbaall__ssttoorree__kkeeyy____));;
class RegistryGlobal {
vvaarr
$$__ssttoorree;;
ffuunnccttiioonn
RReeggiisst
trryyGGlloobbaall(())
{{
iiff
((!!aarrrraayy__kkeeyy__eexxiissttss((RREEGGIISSTTRRYY__GGLLOOBBAALL__SSTTOORREE,,
$$GGLLOOBBAALLSS))
||||
!!iiss__aarrrraayy(($$GGLLOOBBAALLSS[[RRE
EGGIISSTTRRYY__GGLLOOBBAALL__SSTTOORREE]]))))
{{
$$GGLLOOBBAALLSS[[RREEGGIISSTTRRYY__GGLLOOBBAALL__SSTTOORREE]]
==
aarrrraayy(());;
}}
$$tthhiiss->>__ssttoor
ree
==&&
$$GGLLOOBBAALLSS[[RREEGGIISSTTRRYY__GGLLOOBBAALL__SSTTOORREE]];;
}}
function isValid($key) {
return array_key_exists($key, $this->_store);
}
function &get($key) {
if (array_key_exists($key, $this->_store))
return $this->_store[$key];
}
function set($key, &$obj) {
$this->_store[$key] =& $obj;
}
}
assertCopy()
The
aasssseerrttCCooppyy(())
assertion is the negation of
aasssseerrttRReeffeerreennccee(())
, so if the two variables passed are not
references, the assertion passes.
The Registry Pattern 93
L
L
The real magic in this alternative is the line
$this->_store =& $GLOBALS[REGISTRY_GLOBAL_STORE
;],
where the reference operator binds the global array to the instance variable
$_store
. This is the key
to MonoState implementations: each time
$this->_store
is used in the object, the actual effect is
mirrored to the global variable.
But it hardly makes sense to recommend a solution based on global variables. A static class vari-
able would be a better solution, if only PHP4 provided such a feature. Yet, is there a way to use refer-
ences to implement a static class variable in your own code?
The tests can be similar to the
RegistryGlobal
tests:
class RegistryMonoStatePHP4TestCase extends UnitTestCase {
function testRegistryMonoState() {
$this->assertCopy(
$reg =& new RegistryMonoState
,$reg2 =& new RegistryMonoState);
$this->assertFalse($reg->isValid(‘key’));
$this->assertNull($reg->get(‘key’));
$test_value = ‘something’;
$reg->set(‘key’, $test_value);
$this->assertReference($reg->get(‘key’), $reg2->get(‘key’));
}
}
To make your own class static variable, bind a reference to a function static variable to a class
instance variable.
class RegistryMonoState {
var $_store;
ffuunnccttiioonn
&&__iinniittRReeggiissttrryy(())
{{
ssttaattiicc
$$ssttoorree
==
aarrrraayy(());;
rreettuurrnn
$$ssttoorree;;
}}
ffuunnccttiioonn
RReeggiis
sttrryyMMoonnooSSttaattee(())
{{
$$tthhiiss->>__ssttoorree
==&&
$$tthhiiss->>__iinniittRReeggiissttrryy(());;
}}
function isValid($key) {
return array_key_exists($key, $this->_store);
}
function &get($key) {
if (array_key_exists($key, $this->_store))
return $this->_store[$key];
}
The Registry Pattern94
function set($key, &$obj) {
$this->_store[$key] =& $obj;
}
}
The
initRegistry()
method contains a static variable,
$store
, initialized to an array. This static vari-
able is returned by reference. In the constructor, the
$_store
instance variable is set to the returned
reference from the
initRegistry()
method and thus to the static array. Voila! A PHP4 class static
variable.
Implementing with Class Static Variables
In PHP5, theres no need to implement your own class static variables, because the language sup-
ports the concept of static class variables directly. Thus, PHP5 simplifies the implementation a bit.
Also, reference and objects no longer have the meaning they had in PHP4, but
assertReference()
handles this distinction, passing the test if two variables refer to the same object handle.
Heres the familiar Registry test case modified for PHP5:
// PHP5
class RegistryMonoStatePHP5TestCase extends UnitTestCase {
function testRegistryMonoState() {
$this->assertCopy(
$reg = new RegistryMonoState
,$reg2 = new RegistryMonoState);
$this->assertFalse($reg->isValid(‘key’));
$this->assertNull($reg->get(‘key’));
$test_value = new TestObj;
$reg->set(‘key’, $test_value);
$this->assertReference($test_value, $reg2->get(‘key’));
}
}
And heres the PHP5 version of the Registry class using static class variables.
class RegistryMonoState {
protected static $store = array();
function isValid($key) {
return array_key_exists($key, RegistryMonoState::$store);
}
function get($key) {
if (array_key_exists($key, RegistryMonoState::$store))
The Registry Pattern 95
return RegistryMonoState::$store[$key];
}
function set($key, $obj) {
RegistryMonoState::$store[$key] = $obj;
}
}
An interesting side effect of coding the Registry in PHP5 this way is you can actually use both
instance and static method calls with the same set of code. Here is a test case that proves that—it
uses static method calls only.
class RegistryMonoStatePHP5TestCase extends UnitTestCase {
function testRegistryMonoState() { /*...*/ }
function testRegistryMonoStateStaticCalls() {
$this->assertFalse(RegistryMonoState::isValid(‘key’));
$this->assertNull(RegistryMonoState::get(‘key’));
$test_value = new TestObj;
RegistryMonoState::set(‘key’, $test_value);
$this->assertIdentical($test_value,
RegistryMonoState::get(‘key’));
}
Now that you’ve seen how the static call interface looks in PHP5, let’s code the same interface in
PHP4. As in the previous PHP4 static class variable” emulation, this implementation needs to use
the function static returning a reference” trick.
The test for PHP4 static call interface looks similar to the PHP5 version of the test.
// PHP4
class RegistryStaticPHP4TestCase extends UnitTestCase {
function testRegistryStatic() {
$this->assertFalse(RegistryStatic::isValid(‘key’));
$this->assertNull(RegistryStatic::get(‘key’));
$test_value = ‘something’;
RegistryStatic::set(‘key’, $test_value);
$this->assertReference($test_value, RegistryStatic::get(‘key’));
}
}
And here is an implementation that satisfies the test:
The Registry Pattern96
class RegistryStatic {
ffuunnccttiioonn
&&__ggeettRReeggiissttrryy(())
{{
ssttaattiicc
$$ssttoorree
==
aarrrraayy(());;
rreettuurrnn
$$ssttoorree;;
}}
function isValid($key) {
$$ssttoorree
==&&
RReeg
giissttrryySSttaattiicc::::__ggeettRReeggiissttrryy(());;
return array_key_exists($key, $store);
}
function &get($key) {
$store =& RegistryStatic::_getRegistry();
if (array_key_exists($key, $store))
return $store[$key];
}
function set($key, &$obj) {
$store =& RegistryStatic::_getRegistry();
$store[$key] =& $obj;
}
}
The key to this implementation is having the
getRegistry()
method return a reference to a static
array. The line
$store =& RegistryStatic::_getRegistry();
in subsequent functions sets the local
variable
$store
by reference to this static array, granting all of the functions static access to the array
and allowing all of the methods to be called statically.
There is another way to achieve the same effect without using the PHP4 static class variable
trick: combine the original Singleton-based
Registry
class with a wrapper class to allow for static
method calls. This class has an identical test to the
testRegistryStatic()
, but is implemented like
this:
class RegistryStatic {
function isValid($key) {
$reg =& Registry::getInstance();
return $reg->isValid($key);
}
function &get($key) {
$reg =& Registry::getInstance();
return $reg->get($key);
}
function set($key, &$obj) {
$reg =& Registry::getInstance();
$reg->set($key, $obj);
}
}
The Registry Pattern 97
Issues
While the Registry simplifies access to a number of objects, it still has many of the problems associ-
ated with global variables. You have to make sure the requested key is initialized before you access
it, and because theres global access to the setter method, your object can still be replaced in anoth-
er portion of your code unexpectedly. Obviously there are benefits and reasons for global data, but
you should remember that any global data is always a bit suspect.
Embedded Registry
Rather than using the Registry pattern standalone, as has been shown in this chapter, the Registry
can be very powerful when combined as a feature of another object. Consider a situation where
object creation is somewhat expensive (perhaps due to the number of database calls required to ini-
tialize the object) and where the object may be requested one or more times in any given execution
of the program, if ever. Could you create a “Finder” class combining aspects of the Factory (see
Chapter 3) and Registry patterns to maintain a cache of objects that have already been created
instead of creating them again?
Here’s a
Contact
class, where
AddressBook
is the Factory.
class AddressBook {
function &findById($id) {
return new Contact($id);
}
}
class Contact {
function Contact($id) {
// expensive queries to create object using $id
}
// ... other methods
}
You could embed the Registry within the
AddressBook
class to seamlessly provide caching. That
might look like this:
class AddressBook {
vvaarr
$$rreeggiissttrryy;;
ffuunnccttiioonn
AAddddrreessssBBooookk(())
{{
$$tthhiiss->>rreeggiissttrryy
==&&
RReeggiissttrryy::::ggeettIInnssttaannccee(());;
}}
function &findById($id) {
iif
f
((!!$$tthhiiss->>rreeggiissttrryy->>iissVVaalliidd(($$iidd))))
{{
The Registry Pattern98
$$tthhiiss->>rreeggiissttrryy->>sseett(($$iidd,,
nneeww
CCoonnttaacctt(($$iidd))));;
}}
rreettuurrnn
$$tthhiiss->>rreeggiissttrryy->>ggeett(($$iidd));;
}
}
The
AddressBook
constructor binds the registry to an instance variable. When a particular ID is cre-
ated and requested in the
findById()
method, the Registry is checked to see if the object has already
been cached. If not, the new object is created and stored in the Registry. The requested object is then
returned by the function by extracting it from the Registry.
The Registry Pattern 99
6
The MockObject
Pattern
THE RICHNESS OF OBJECT-ORIENTED PROGRAMMING comes in part from the
interconnections and interactions between objects. A single object can encapsulate a complex
subsystem, making otherwise complicated operations as simple as calling a handful of meth-
ods. (The ubiquitous database connection is one such object.)
But often, the interactions between objects are so complex that you become faced with a chick-
en and egg”-like conundrum: how to develop and test a new object that depends on the creation of
many other objects or on some circumstance that is difficult to realize, such as the recreation of an
entire database.
The Problem
How can you easily isolate and test a segment of code that depends on other objects and resources?
How can you recreate one or more objects or application states to validate that your code is operating
properly?
The Solution
When it’s difficult or expensive to test an object in situ (or in a facsimile of its production environ-
ment), use a MockObject to simulate behavior. A MockObject has the same interface as the real object
it’s standing in for, but provides pre-programmed responses, tracks method calls, and validates call
sequences.
MockObjects are the special forces” of the testing world. Trained in stealth, they infiltrate target-
ed code, emulate and monitor communication patterns, and report back results. MockObjects can
halp search for and destroy bugs and can support the more mundane “peacekeeping” operations of
a normal application test suite.
This chapter first presents a very simple example that demonstrates the basic mechanics of
SimpleTest MockObjects. It then shows how you can use MockObjects to help restructure legacy code
and test the new solution.
Sample Code
A MockObject is a substitute object that makes testing code much simpler. For instance, rather than
use a real database connection—which may be impractical for any number of reasons—you can cre-
ate a MockObject to simulate it. Practically, this means a MockObject needs to respond to the exact
same API as the code that it’s standing in for.
Let’s create a MockObject to stand-in for a simple class called
Accumulator
that sums numeric
values. Heres the original
Accumulator
:
Not Really a Design Pattern
This chapter is different from the other chapters in this book because MockObject is a testing pattern
rather than a design pattern. This may seem like an odd diversion, but the use of this testing pattern can
really become foundational and is well worth having in your coding tool set. It differs in another aspect
as well while the basics of how to code this pattern is covered, more emphasis is placed on the usage of
the existing MockObject implementation in SimpleTest.
The ServerStub
The MockObject pattern is an extension of another testing pattern called the ServerStub. The ServerStub
pattern stands-in for a resource and returns known values in response to method calls. A ServerStub
becomes a MockObject when you can anticipate the specific sequence of method calls to be made on
your ServerStub.
The MockObject Pattern102
L
L
L
L
// PHP4
class Accumulator {
var $total=0;
function add($item) {
$this->total += $item;
}
function total() {
return $this->total;
}
}
add()
accumulates values in instance variable
$total,
and
total()
returns whats been accumulat-
ed so far. A simple use of
Accumulator
is shown below (the code is written as functions, but could be
a class just as well).
function calc_total($items, &$sum) {
foreach($items as $item) {
$sum->add($item);
}
}
function calc_tax(&$amount, $rate=0.07) {
return round($amount->total() * $rate,2);
}
The first function,
calc_total()
, uses an
Accumulator
to sum the values in a list and is simple
enough to test:
class MockObjectTestCase extends UnitTestCase {
function testCalcTotal() {
$sum =& new Accumulator;
calc_total(array(1,2,3), $sum);
$this->assertEqual(6, $sum->total());
}
}
Let’s move on to the second case. Assume that instantiating a real
Accumulator
is very expensive. Itd
be ideal if a simple object could stand in for
Accumulator
and return a set of responses to the sur-
rounding code. Using SimpleTest, you can create a mock
Accumulator
with this code:
The MockObject Pattern 103
Mock::generate(‘Accumulator’);
class MockObjectTestCase extends UnitTestCase {
// ...
function testCalcTax() {
$amount =& new MockAccumulator($this);
$amount->setReturnValue(‘total’,200);
$this->assertEqual(
14, calc_tax($amount));
}
}
To use a MockObject, you must typically create a new class for it by hand (more on that momentari-
ly). Luckily, SimpleTest has an easy means of accomplishing this: the
Mock::generate()
method.
In the example above, the method creates a class named
MockAccumulator
that responds to all
the
Accumulator
class methods. Additionally, the
MockAccumulator
has other methods to manipulate
the MockObject instance itself. Once such method is
setReturnValue()
. Given a method name and
a value,
setReturnValue()
changes the MockObject to return the given value when the named
method is called. So, the statement
$amount->setReturnValue(‘total’, 200)
returns 200 whenev-
er the
total()
method is called.
Once initialized, you can pass the
MockAccumulator
class into the
calc_tax()
function to have it
act in the place of a real
Accumulator
object.
If you stopped here—with an object returning canned” responses to method calls—you would
have implemented the ServerStub pattern. But the MockObject goes further to validate which meth-
ods were called, how many times, and in what sequence.
Heres an example of validating the flow” through an object:
class MockObjectTestCase extends UnitTestCase {
// ...
function testCalcTax() {
$amount =& new MockAccumulator($this);
$amount->setReturnValue(‘total’,200);
$$aammoouunntt->>eexxppeeccttOOnnccee((ttoottaall));;
$this->assertEqual(
14, calc_tax($amount));
$$aammoouunntt->>ttaallllyy(());;
}
}
The MockObject Pattern104
The
expectOnce()
method takes a string containing the name of a method that you expect to be
called once. The
tally()
is the actual check to determine if your expectations were met. Here, if
MockAccumulator::total()
isnt called once and only once, the test fails.
You can use this tracking” feature of a MockObject in many ways. For example, if you pass an
array of three values into
calc_total()
, is
Accumulator::add()
called three times as is expected?
class MockObjectTestCase extends UnitTestCase {
// ...
function testCalcTotalAgain() {
$sum =& new MockAccumulator($this);
$sum->expectOnce(‘add’);
calc_total(array(1,2,3), $sum);
$sum->tally();
}
}
Whoops, what happened here? The test failed instead of passing. The SimpleTest error message
states something like:
MockObject PHP4 Unit Test
1) Expected call count for [add] was [1] got [3] at line [51]
in testcalctotalagain
in mockobjecttestcase
FAILURES!!!
Test cases run: 1/1, Passes: 2, Failures: 1, Exceptions: 0
This error message indicates that the
add()
method was called three times, not the single time the
expectOnce()
assertion asked for. Instead of
expectOnce()
, the test should use
expectCallCount()
.
class MockObjectTestCase extends UnitTestCase {
// ...
function testCalcTotalAgain() {
$sum =& new MockAccumulator($this);
$$ssuumm->>eexxppeeccttCCaallllCCoouunntt((aadddd,,
33));;
calc_total(array(1,2,3), $sum);
$sum->tally();
}
}
The MockObject Pattern 105
A MockObject has the role of an actor—as a SeverStub providing reasonable test data in response to
method calls—and the role of a critic, validating assumptions about which methods were called.
A Legacy Application
As the next example let’s use the MockObject to assist in the restructuring of a legacy application.
Consider a simple script that mimics the kind of behaviors you might expect to see in any number
of PHP applications: A PHP page generates a login for the user if the user has not yet logged in; the
very same page acts as a form handler for the form; it shows different content after a successful
login; and it provides logout.
Let’s write such a page. First, display a login form if the user hasnt logged in yet:
<html>
<body>
<form method=”post”>
Name:<input type=”text” name=”name”>
Password:<input type=”password” name=”passwd”>
<input type=”submit” value=”Login”>
</form>
</body>
</html>
Next, provide some content if the user is logged in:
<html>
<body>Welcome <?php echo $_SESSION[‘name’]; ?>
<br>Super secret member only content here.
<a href=”<?php echo SELF; ?>?clear”>Logout</a>
</body>
</html>
Adding in the form handling capabilities, session startup, and logout capabilities, and the whole
script might look like:
sseessssiioonn__ssttaarrtt(());;
ddeeffiinnee((SSEELLFF,,
hhttttpp::////..$$__SSEERRVVEERR[[SSEERRVVEERR__NNAAMMEE]]..$$__SSEERRVVEERR[[PPHHPP__SSEELLFF]]));;
iiff
((aarrrraay
y__kkeeyy__eexxiissttss((nnaammee,,
$$__RREEQQUUEESSTT))
&&&&
aarrrraayy__kkeeyy__eexxiissttss((ppaasssswwdd,,
$$__RREEQQUUEESSTT))
&&&&
aaddmmiinn
====
$$_
_RREEQQUUEESSTT[[nnaammee]]
The MockObject Pattern106
&&&&
sseeccrreett
====
$$__RREEQQUUEESSTT[[ppaasssswwdd]]))
{{
$$__SSEESSSSIIOONN[[nnaammee]]
==
aaddmmiinn;;
hheeaaddeerr((LLooccaattiioonn::
..SSEELLFF));;
}}
iiff
((aarrrraayy__kkeeyy__eexxiissttss((cclleeaarr,,
$$__RREEQQUUEESSTT))))
{{
uunnsseett(($$__SSEESSSSIIOONN[[nnaammee]]));;
}}
iiff
((aarrrraayy__k
keeyy__eexxiissttss((nnaammee,,
$$__SSEESSSSIIOONN))
&&&&
$$__SSEESSSSIIOONN[[nnaammee]]))
{{
??>>
<html>
<body>Welcome <?=$_SESSION[‘name’]?>
<br>Super secret member only content here.
<a href=”<?php echo SELF; ?>?clear”>Logout</a>
</body>
</html> <?php
}}
eellssee
{{
??>>
<html>
<body>
<form method=”post”>
Name:<input type=”text” name=”name”>
Password:<input type=”password” name=”passwd”>
<input type=”submit” value=”Login”>
</form>
</body>
</html> <?php
}}
A goal of restructuring this legacy application should be to create a “testable” application.
Immediately, this goal affects the design: if you choose to use some of the convenient features of
PHP—such as the superglobals—you sacrifice testing for convenience.
For example, if you use
$_SESSION
directly, say, then the only way to test such code is to alter
$_SESSION
. Alas, if you forget to change
$_SESSION
back to a known state, you could experience inter-
ference between tests.
A solution to this problem is to wra
p
$_SESSION
inside of another class and pass an instance of that wrapper class into any object
that needs access to
$_SESSION
. If you then make a MockObject version of the wrapper object for test-
ing, you can have complete control over the object’s responses to method calls (acting as a
ServerStub) and you can verify how it was called (which is the purpose of the MockObject).
With this in mind, lets see what a wrapper for the
$_SESSION
superglobal might look like.
class Session {
function Session() {
$this->init();
}
function init() {
The MockObject Pattern 107
if (!isset($_SESSION)) {
if (headers_sent()) {
trigger_error(
‘Session not started before creating session object’);
} else {
session_start();
}
}
}
function isValid($key) {
return array_key_exists($key, $_SESSION);
}
function get($key) {
return (array_key_exists($key, $_SESSION))
? $_SESSION[$key]
: null;
}
function set($key, $value) {
$_SESSION[$key] = $value;
}
function clear($key) {
unset($_SESSION[$key]);
}
}
Session
is a wrapper for the
$_SESSION
superglobal. The tests for
Session
are similar to the tests
developed for the Registry class earlier (see Chapter 5), but without any intention of getting or set-
ting the values by reference.
You may have noticed the constructor calls a
Session::init()
method. Why is this method not
a part of the constructor itself? Its separate so you can call it statically to make sure the session was
started. Here is an example of how the class might be used:
Session::init();
$page =& new PageDirector(new Session);
Most testing literature devoted to MockObjects suggest that you write MockObjects by hand. If you
want to do that, just flesh out the methods you need far enough to get by testing. For instance, a
hand-coded ServerStub for the
Session
class might look like:
class MyMockSessionUser1 {
function isValid($key) {
return (‘user_id’ == $key) ? true : false;
}
The MockObject Pattern108
function get($key) {
if (‘user_id’ == $key) {
return 1;
}
}
}
Fortunately, you can avoid this error-prone drudgery using SimpleTest. The
Mock::generate()
method allows you to generate a class that you can instantiate and configure dynamically to respond
as you need.
Heres how to recreate
MyMockSessionUser1
(shown above) in a SimpleTest-generated MockObject
test case:
Mock::Generate(‘Session’);
class PageDirectorTestCase extends UnitTestCase {
function testSomethingWhichUsesSession() {
$session =& new MockSession($this);
$session->setReturnValue(‘isValid’, true);
$session->setReturnValue(‘get’, 1);
// ...
}
}
Further, you can set expectations about what methods will be called and how many times. You can
even verify some methods should not be called at all.
Heres an expanded test to create and validate some mroe compliex expectations.
class PageDirectorTestCase extends UnitTestCase {
function testSomethingWhichUsesSession() {
$session =& new MockSession($this);
$session->setReturnValue(‘isValid’, true);
MockObject Techniques
SimpleTests approach is just one of many techniques for using MockObjects. Hand-coding MockObjects
is another (as shown above). With the advent of PHP5, you might see a PHP MockObject implementation
that makes use of the
____ccaallll(())
method on objects.
The MockObject Pattern 109
L
L
$session->setReturnValue(‘get’, 1);
$$sseessssiioonn->>eexxppeeccttOOnnccee((iissVVaalliidd,,
aarrrraayy((uusseerr__iidd))));;
$$sseessssiioonn->>eexxppeeccttOOnnccee((ggeett,,
aarrrraayy((uusseerr__iidd)
)));;
$$sseessssiioonn->>eexxppeeccttNNeevveerr((sseett));;
// the actual code which uses $session
$$sseessssiioonn->>ttaallllyy(());;
}
}
There are many more reasons and ways to use the MockObject. Before continuing, lets put together
some additional classes to have a context to work from.
Here is the next component in the refactoring of the legacy script, a
UserLogin
class to check if
the user credentials are correct.
class UserLogin {
var $_valid=true;
var $_id;
var $_name;
function UserLogin($name) {
switch (strtolower($name)) {
case ‘admin’:
$this->_id = 1;
$this->_name = ‘admin’;
break;
default:
trigger_error(“Bad user name ‘$name’”);
$this->_valid=false;
}
}
function name() {
if ($this->_valid) return $this->_name;
}
function Validate($user_name, $password) {
if (‘admin’ == strtolower($user_name)
&& ‘secret’ == $password) {
return true;
}
return false;
}
}
(In a real application, you’d likely base this kind of logic on querying a database table. This sort of a
small, hard-coded class represents what you might code as a ServerStub—a small class that behaves
the way you want, but only in a limited set of circumstances.)