PHP V5.3 invigorates object-oriented programming with late static binding

Taking another look at OOP and design patterns

PHP V5.3 resolved a number of object-oriented programming (OOP) issues with its late static binding (LSB) feature. Find out how LSB fixes some of PHP's OOP coding issues and learn how to implement some well-known object-oriented design patterns that require the use of LSB.

Don Denoncourt, Author, Consultant

Don DenoncourtDon 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.



15 February 2011

Also available in Japanese

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.

Revisiting OOP

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();

Active record

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.


Dynamic attributes and __get

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).


Give me static

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.


Download

DescriptionNameSize
Sample PHP scriptsos-php-53static-latestaticbinding_denoncourt.zip34KB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Open source on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=626243
ArticleTitle=PHP V5.3 invigorates object-oriented programming with late static binding
publish-date=02152011