Late static binding

PHP is one step closer to being an OOM language

One of the best features of php 5.3, better even than namespaces, is Late Static Binding.

Late whatnow? Depending on how you look at it, late static binding is either a wonderful new addition to php, or an ugly fix to one of its older faults.
Depending on how you look at it, it might also be one those buzzwords you heard floating around but never bothered to investigate: that was me anyway.

So what does it do? Well here's what the manual says, but let's do an example instead.

Writing extensible code

Say you want to abstract away a common task: writing a user class. The class has a constructor, some get and set methods for username, password & id and two factory methods: A fetch($id) method that retrieves a user from the database and a create(...) method that creates a whole new user and inserts it into the database*.

You might not agree with this approach, perhaps you'll want to use the constructor to fetch and/or create - and that's fine - but bear with me a little, that's not what this example's all about.

Any other methods, say getFaxNumber() or maybe getPlayerId() will be implemented only in projects that require such methods and so they can be left out of the generic class. Here's what the first half of the User class could look like

<?
/**
 * The once-in-a-lifetime generic but simple User class
 */
class GenericUser
{
    
/**
     * Creates a new GenericUser
     */
    
protected function __construct(array $data)
    {
        
// Set all the little settings
    
}
    
    
// ... get and set methods go here
    
    /**
     * Fetches a user from the database
     * @param $id int A unique identifier for this user
     */
    
public static function fetch($id)
    {
        
$data fetch_from_database($id);
        return new 
GenericUser($data);            // wrong!
    
}
}
?>

Now let's extend it with a project specific user class.

<?
/**
 * A User class for a specific project
 */
class ProjectSpecificUser extends GenericUser
{
    public function 
__construct(array $data)
    {
        
parent::__construct($data);
        
$this->faxNumber '12345678';
    }

    public function 
getFaxNumber()
    {
        return 
$this->faxNumber;
    }
}
?>

Still looks good right? But now let's see what happens:

<?
// Fetch user number 1
$user ProjectSpecificUser::fetch(1);
// And show us what you got
echo get_class($user);
?>

Unfortunately the output of this code will reveal the returned object to be of the type GenericUser.

Looking a the code, this might be forgiven, after all, the fetch() method specifically calls for the creation of a GenericUser object. So fair enough, but what happens if we use the self keyword instead?

<?
    
public static function fetch($id)
    {
        
$data fetch_from_database($id);
        return new 
self($data);            // still wrong!
    
}
?>

Unfortunately this has exactly the same result: the self keyword appears inside the code for GenericUser and so php decides it refers to the GenericUser class, ignoring any rules of inheritance.

Enter the static keyword

Up until php 5.3 this was all you could do, but luckily the situation has been remedied in a backwards compatible - and slightly ugly - fashion: by adding another function to the existing static keyword. So our method becomes:

<?
    
// ...

    
public static function fetch($id)
    {
        
$data fetch_from_database($id);
        return new static(
$data);            // Hurray!
    
}
}

// Let's try again!
$user ProjectSpecificUser::fetch(1);
echo 
get_class($user);
?>

Returns
  ProjectSpecificUser

And all is well in the world.

Even better

A second use is to call static methods - but allow overwriting. For example, imagine we want to add a static login() method to our user class that takes a username and password as input parameters and returns a User object if the login is succesful. The way the password is checked can change from project to project and so it would be a good idea to make this easy to overwrite. Using the static keyword this can be accomplished as follows:

<?
/**
 * The once-in-a-lifetime generic but simple User class
 */
class GenericUser
{
    
/**
     * Creates a new GenericUser
     */
    
protected function __construct(array $data)
    {
        
// Set all the little settings
    
}
    
    
// ... get and set methods go here
    
    /**
     * Fetches a user from the database
     * @param $id int A unique identifier for this user
     */
    
public static function fetch($id)
    {
        
$data fetch_from_database($id);
        return new static(
$data);
    }
    
    
/**
     * Logs a user in
     * @param $username
     * @param $password
     * @return a new User object or null if the login failed
     */
    
public static function login($username$password)
    {
        
// Check password
        
if (static::checkPassword($username$password))
        {
            
// Fetch id from database
            
$id get_id_by_username($username);
            
// Return new user
            
return static::fetch($id);
        }
        return 
null;
    }
    
    
/**
     * Checks a user / password combination
     * @return True if valid
     */
    
protected static function checkPassword($username$password)
    {
        return (
$username == 'michael' && $password == 'superSecretPassword');
    }
}
?>

Now any extending class with a more advanced checkPassword() routine can overwrite the checkPassword method but still use the old login() method.

<?
class ProjectSpecificUser extends GenericUser
{
    protected static function 
checkPassword($username$password)
    {
        return (
$username == 'michael' && $password == 'veryVerySecretPassword');
    }
}

// This user should no longer be allowed in
$user ProjectSpecificUser::login('michael''superSecretPassword');
echo 
is_object($user) ? 'True' 'False';
echo 
'<br />';

// This one should
$user ProjectSpecificUser::login('michael''veryVerySecretPassword');
echo 
is_object($user) ? 'True' 'False';

?>
Returns:
  False
  True

And so - as long as you remember to use static in all the right places - it has suddenly become possible to write much more extensible classes in php.

Jul 18th, 2010

Comments

No comments yet! Feel free to post some using the form below.

Post your comments here

If you wish to add code to your comment you can use code tags, like this: <code class="php">yourCodeHere</code>.
Quite a large number of languages are supported, although I can't guarantee it'll be pretty. Inside the code tags you can use any characters except for the string "</code>".