Object-oriented programming (OOP) allows developers to reduce and simplify code — given a solid understanding of OOP — by using data abstraction, encapsulation, modularity, polymorphism, and inheritance. Access to OOP features also allows PHP coders to leverage design patterns — well-known algorithms used to solve commonly occurring problems. PHP has provided OOP capabilities since V3.0, but until V5.3 came along, some oddities in PHP's OOP implementation prevented the use of a number of popular design patterns. Those oddities have been eradicated with the advent of PHP V5.3's late static binding (LSB) feature.
This article introduces you to the design patterns that had issues prior to PHP V5.3, explaining why those patterns did not work. Then it shows how PHP V5.3's LSB feature resolves those issues, while showing implementations of the singleton and active record design patterns.
If you played with PHP OOP a few times in the past, you may have let it go for one of two reasons:
- You read one of the many blog posts that suggested PHP OOP had issues.
- You tried to implement a simple design pattern, and it didn't work.
As of PHP V5.3, the blog posts on OOP are positive, and the PHP OOP issues have largely been fixed. It's time to revisit PHP OOP. For the scope of this article, you'll look at a few of the design patterns that had issues prior to V5.3: singleton, builder, factory method, and active record.
The singleton, builder, and factory method design patterns are considered creational patterns, as they assist in the construction of objects. The singleton pattern is probably one of the most often used OOP design patterns; it restricts the number of object instances of a class to one. An example use case for a singleton would be a database connection pool: You don't want your application to have multiple resource-intensive instances of a connection pool class.
You use the builder design pattern when you need to separate the construction of complex objects from their representation, so you can use the same construction process to create a variety of objects. The implementation of the builder pattern can be complex, but once the builder is available, it simplifies the construction and use of the objects the builder creates. An example of the requirement for a builder would be a converter that has the ability to output HTML, XML, or a PDF.
The factory method pattern, as its name implies, defines the implementation of a method used to churn out objects. You use the factory method pattern when an application needs to create objects whose type depend on the implementation of subclasses.
You use the active record pattern to wrap relational database persistence methods in domain classes. Each instance of an active record class is tied to a specific row in a database. The class contains methods to insert, delete, and update a row or rows from the database. The active record design pattern was named by Martin Fowler in Patterns of Enterprise Application Architecture, but it became popular with its integrated use in Ruby on Rails.
Pre-LSB creational design pattern implementation issues
All four of the design patterns mentioned use static attributes and methods. For example, take a look at the connection pool singleton in Listing 1.
Listing 1. A simple singleton
<?php
class ConnPool {
private static $onlyOne;
private static $count = 0;
private function __construct() {
// real-world db conn stuff here...
}
public static function getInstance() {
if (!is_object(self::$onlyOne)) {
$klass = __CLASS__;
self::$onlyOne = new $klass();
self::$count++;
}
return self::$onlyOne;
}
public static function getInstanceCount() {return self::$count;}
}
$db = ConnPool::getInstance();
assert (1 == $db->getInstanceCount());
$db2 = ConnPool::getInstance();
assert (1 == $db2->getInstanceCount());
?>
|
Notice the static $onlyOne variable. That variable
is designed to hold an instance of a connection pool object. The static modifier
that precedes $onlyOne ties the variable to the class
itself. The $onlyOne variable is said to be a class
attribute, as it is scoped to the class. There will only be one instance
of the $onlyOne attribute ever. When an attribute does not
have the static modifier, it is said to be an object attribute, as that
property is unique for each instance of the class.
Note that ConnPool's constructor method
(called __construct) is empty. In a production
implementation, you would use this method to create the internals of a database
connection pool.
The static getInstance method contains the template code
for a singleton. It creates an $onlyOne instance only if
the static $onlyOne variable is null. Notice how it uses
the magic __CLASS__ variable to get the class type, and
then create an instance of that class.
The getInstanceCount method is coded merely to prove that
only one instance of the connection pool will ever be created. The four lines of
code at the bottom of Listing 1 prove that no matter how many
times you ask for an instance of the ConnPool pool class,
it will always return the same object.
So, everything is good with your singleton — that is, until you decide you would like to subclass the connection pool in an object-oriented inheritance tree to support multiple databases. Listing 2 shows the inheritance tree (with the instance counter and constructor code removed for clarity).
Listing 2. A failed attempt at a singleton without LSB
<?php
class ConnPool {
private static $onlyOne;
protected static $klass = __CLASS__;
public static function getInstance() {
if (!is_object(self::$onlyOne)) {
self::$onlyOne = new self::$klass();
}
return self::$onlyOne;
}
}
class ConnPoolAS400 extends ConnPool {
protected static $klass = __CLASS__;
}
$db = ConnPoolAS400::getInstance();
assert ('ConnPoolAS400' == get_class($db)); // fails
?>
|
To support multiple singleton class types, the ConnPool
class adds a $klass static variable with the assumption
that it will be overridden in subclasses. The ConnPoolAS400
subclass extends the ConnPool class and provides its own
version of the $klass property. The expectation is that
when an instance of the ConnPoolAS400 class is created,
the $klass property will hold ConnPoolAS400.
But when you execute the code, it doesn't run as expected. The assertion at the
bottom of the code fails when the PHP utility function get_class
returns ConnPool and not ConnPoolAS400.
The issue is that the ConnPool class's getInstance
method used its version of the $klass property rather than
the ConnPoolAS400's overridden version.
The problem you experienced with the code in Listing 2 is that the
self keyword is bound to the referenced property or
method at compile time. The self keyword points to the
containing class and is unaware of subclasses. Essentially, the compiler replaces the
self keyword with the enclosing class's name. It is almost
like the following line:
self::$onlyOne = new self::$klass(); |
Replaced by the compiler with:
ConnPool::$onlyOne = new ConnPool::$klass(); |
Which, to coin a phrase, is early binding. What you need is late binding.
Singleton inheritance with LSB
You can fix the singleton issue using PHP V5.3's LSB functionality. Simply replace the
self specifier self::$onlyOne = new
self::$klass(); with static:
self::$onlyOne = new static::$klass(); |
And a rerun of the code results in a successful assertion.
The static keyword forces PHP to bind to the code
implementation at the latest possible moment. Without LSB, the
self::$klass references the first piece of code found:
the parent class's version.
A new function in PHP V5.3 called get_called_class
simplifies the singleton's code a bit. Listing 3 replaces the use of the
static $klass attribute with the get_called_class function.
Listing 3. A singleton simplified with
get_called_class
<?php
class ConnPool {
private static $instance;
public function get_instance() {
if (!is_object(self::$instance)) {
$klass = get_called_class();
self::$instance = new $klass();
}
return self::$instance;
}
}
class ConnPoolAS400 extends ConnPool {}
$db = ConnPoolAS400::get_instance();
assert ('ConnPoolAS400' == get_class($db));
?>
|
The singleton in Listing 3 is certainly more concise, but what's important is the use of PHP's LSB to reference the appropriate overridden static attribute. Whereas the singleton implementation uses the subclass's class name, other patterns (such as active record, covered later) need to reference other static properties. In addition, LSB works with static functions as well as static attributes. Static functions, like static properties, are scoped to the class rather than to object instances of that class. Listing 4 shows a version of the singleton that uses a method instead of an attribute to specify the appropriate class.
Listing 4. A singleton that uses LSB on a method
<?php
class ConnPool {
private static $onlyOne;
protected static function getClass() {
return __CLASS__;
}
public function get_instance() {
if (!is_object(self::$onlyOne)) {
$klass = static::getClass();
self::$onlyOne = new $klass();
}
return self::$onlyOne;
}
}
class ConnPoolAS400 extends ConnPool {
protected static function getClass() {
return __CLASS__;
}
}
$db = ConnPoolAS400::get_instance();
assert ('ConnPoolAS400' == get_class($db));
?>
|
In this code, the static getClass implementation is defined
in ConnPool and overridden in ConnPool400.
The following line in ConnPool's get_instance
method invokes the appropriate method at runtime:
$klass = static::getClass(); |
Let's take at look at a simple (and partial) implementation of the active record
design pattern. Listing 5 shows an abstract class called ActiveRecord and two subclasses: Customer and Sales.
The subclasses are said to be domain classes because they
provide wrappers to entities that exist in the application domain.
Listing 5. A simple implementation of the active record design pattern
<?php
abstract class ActiveRecord {
protected static $table;
protected $fieldvalues;
public $select; // used for illustration only
static function findById($id) {
$query = "select * from "
.static::$table
." where id=$id";
return self::createDomain($query);
}
function __get($fieldname) {
return $this->fieldvalues[$fieldname];
}
static function __callStatic($method, $args) {
$field = preg_replace('/^findBy(\w*)$/', '${1}', $method);
$query = "select * from "
.static::$table
." where $field='$args[0]'";
return self::createDomain($query);
}
// TODO: code a __set method
private static function createDomain($query) {
$klass = get_called_class();
$domain = new $klass();
$domain->fieldvalues = array();
$domain->select = $query;
foreach($klass::$fields as $field => $type) {
$domain->fieldvalues[$field] = 'TODO: set from sql result';
}
return $domain;
}
// TODO: code static create, update, delete methods
}
class Customer extends ActiveRecord {
protected static $table = 'custdb';
protected static $fields = array(
'id' => 'int',
'email' => 'varchar',
'lastname' => 'varchar'
);
}
class Sales extends ActiveRecord {
protected static $table = 'salesdb';
protected static $fields = array(
'id' => 'int',
'item' => 'varchar',
'qty' => 'int'
);
}
assert ("select * from custdb where id=123" ==
Customer::findById(123)->select);
assert ("TODO: set from sql result" ==
Customer::findById(123)->email);
assert ("select * from salesdb where id=321" ==
Sales::findById(321)->select);
assert ("select * from custdb where Lastname='Denoncourt'" ==
Customer::findByLastname('Denoncourt')->select);
?>
|
The ActiveRecord class uses the
abstract modifier to ensure that coders can't
instance an ActiveRecord object. If you try to create
an ActiveRecord with new
ActiveRecord();, you would receive an error stating "PHP Fatal error: Cannot instantiate
abstract class ActiveRecord." This is a good thing because without subclassing,
the ActiveRecord class doesn't do anything worthwhile.
The ActiveRecord class defines a static $table
variable, which is subsequently overridden by the Customer
and Sales subclasses to specify the SQL table names
custdb and salesdb.
ActiveRecord's static findById
function is an example of a method typically found in an implementation of the
active record design pattern. It is findById's job to
retrieve the appropriate row in the database based on the passed unique identifier,
then build and return the domain object that represents the business entity.
The findById method uses the static
keyword to enable a late-time binding reference to the subclass's table name. The
method builds a SQL select, and then defers the
creation of the domain to the createDomain method.
The createDomain method uses the name of the subclass
(via PHP V5.3's get_called_class function) to instance
the appropriate class. The createDomain method then
creates an array to hold a map of database column names and values. To keep the
example simple, ActiveRecord does not actually run the
SQL code. Also, so that this article's code could illustrate and test the construction
of the SQL select, ActiveRecord
has a $select property that is set in
createDomain. The foreach
statement, instead of setting domain attribute values from a SQL result set, stuffs
the string TODO: set from sql result into each element
of the array of field values. The method returns the newly constructed domain, which
in turn is returned by the findById method. The first of
the four assertions at the bottom of the code verifies that the appropriate SQL
statement was created.
One odd thing you may have noticed about the Customer and
Sales domain classes is that they don't appear to have
any domain attributes. You may have been expecting to see the following lines,
for example, as properties of the Customer class:
$id; $email; $name; |
That way, you could access those domain properties with:
$custObj->id; $custObj->email; $custObj->lastname; |
But those attributes are available; they are kept in the $fieldvalues
array. And the code above works fine. To provide seamless access to domain
properties so the above-referenced syntax works, ActiveRecord
implements the magic __get method. The second assertion
at the bottom of the code shows how a Customer object
is retrieved from the findById method, and the
email property is accessed. Customer
has no email property, but because the
__get method is defined, PHP invokes the
__get method, passing the requested property name as a
parameter. The __get method then simply pulls the
attribute's value from the $fieldvalues array.
Note: Production code should handle requests for attributes that do not exist in the array.
Dynamic finder methods and __callStatic
A cool feature often provided in active record design pattern implementations is
dynamic finder methods. Without dynamic methods, the coder of the domain class
would have to think of every database query users of the class might require to
retrieve one or more instances of a domain (and then code a method similar to
findById).
Take a look at the last assertion at the bottom of Listing 5: It runs
a method called findByLastname. But that method has no
implementation. The method may not exist, but because the Customer
class's parent class has an implementation of PHP V5.3's new
__callStatic magic method, not only is no error thrown but
the findByLastname invocation actually executes and does
something worthwhile.
The __callStatic method takes two parameters: the name of
the method being invoked and an array of arguments. When
findByLastname is invoked, PHP sees that the name doesn't
exist and runs ActiveRecord's __callStatic
method, passing findByLastname in parameter one and
Denoncourt as an array in parameter two.
ActiveRecord's implementation of __callStatic
strips off the string following the findBy prefix and uses
it as the field name in the SQL where clause. The
__callStatic method then uses the Denoncourt
argument as the comparison value. With the dynamic calls available, you could also
have used the following line:
Customer::findByEmail('dondenoncourt@gmail.com');
|
Note that you could also enhance the __callStatic method
to support operators, as follows:
Sales::findAllByQtyGreaterThan(100); |
Clearly, a production implementation of active record would be far more sophisticated
and handle a number of method prefixes other than findBy.
Those method prefixes might include findAllBy to return
an array of rows and countBy to return the rows that
match the criterion.
Note: A couple of active record frameworks — Dirivante and are php.activerecord — are already available that take advantage of PHP V5.3's new features (Resources).
Growing up in the 1970s, we often used a slang phrase "Don't give me no static." But
with PHP V5.3, I'm happy to have lots of static — such as the
static properties and methods in an inheritance tree. PHP V5.3's LSB
functionality allows you to use design patterns that require the use of
static properties and methods. PHP also gives you the get_called_class, which you will use heavily because many of
the design patterns you'll implement require the class name of derived
classes. And even more creativity awaits you with PHP V5.3's magic __callStatic function.
| Description | Name | Size | Download method |
|---|---|---|---|
| Sample PHP scripts | os-php-53static-latestaticbinding_denoncourt.zip | 34KB | HTTP |
Information about download methods
Learn
-
Wikipedia provides a good description and background for creational design patterns.
-
PHP.net is the central resource for PHP developers.
-
Check out the "Recommended PHP reading list."
-
Browse all the PHP content on developerWorks.
-
Follow developerWorks on Twitter.
-
Expand your PHP skills by checking out IBM developerWorks' PHP project resources.
-
To listen to interesting interviews and discussions for software developers, check out developerWorks podcasts.
-
Using a database with PHP? Check out the Zend Core for
IBM, a seamless, out-of-the-box, easy-to-install PHP development and production environment that supports IBM DB2 V9.
-
Stay current with developerWorks' Technical events and webcasts.
-
Check out upcoming conferences, trade shows, webcasts, and other Events around the world that are of interest to IBM open source developers.
-
Visit the developerWorks Open source zone for extensive how-to information, tools, and project updates to help you develop with open source technologies and use them with IBM's products, as well as our most popular articles and tutorials.
-
Watch and learn about IBM and open source technologies and product functions with the no-cost developerWorks On demand demos.
Get products and technologies
-
Learn more about
Dirivante and php.activerecord, which take advantage of
PHP V5.3's new features.
-
Innovate your next open source development project with IBM trial software, available for download or on DVD.
- Download
IBM product evaluation versions
or explore
the online trials in the IBM SOA Sandbox and get your hands on application development tools and middleware products from
DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.
Discuss
-
Get involved in the developerWorks community.
Connect with other developerWorks users while exploring the developer-driven blogs, forums, groups, and wikis.
-
Participate in developerWorks blogs and get involved in the developerWorks community.
-
Participate in the developerWorks PHP Forum: Developing PHP applications with IBM Information Management products (DB2, IDS).

Don Denoncourt is a free-lance consultant, trainer, mentor, and author specializing in Java technology, Groovy, Grails, and PHP. You can reach him at dondenoncourt@gmail.com.




