PHP DevCenter
oreilly.comSafari Books Online.Conferences.

advertisement


Object Overloading in PHP 5

by Martin Jansen
06/16/2005

A fine implementation of the object-overloading paradigm has found its way into PHP version 5. This article explores the possibilities of the overload methods __call(), __set(), and __get(). After explaining the basic theory of overloading, it dives straight into the topic by using two practical examples: first, implementing persistable classes, and second, figuring out a way to realize dynamic getter and setter methods. If you do not yet know what these terms mean, don't be afraid--it will become clear to you when you see the example code.

Requirements

Besides being comfortable with PHP 5, you should understand the most basic object-oriented programming terms, such as class, property, method, and constructor. You should also know the semantics of the access modifiers private, protected, and public. If you feel like you don't meet these requirements, you can take a look at numerous introductions to object-oriented programming. For general knowledge, Sun's Object-Oriented Programming Concepts is worth a read. If you're looking for information about PHP's object-oriented syntax, the PHP Manual contains valuable information in Chapter 19, Classes and Objects (PHP 5).

What Is Object Overloading?

What is this all about? When talking about object overloading in PHP, people distinguish between two types:

  • Method overloading
  • Property overloading

In the case of method overloading, the code defines a magic class method, __call(), that will act as a wildcard for calls to undefined methods of the corresponding class. This wildcard method will be called only when the class does not contain the method you are trying to access. Without method overloading, the following example will cause PHP to display the error message Fatal error: Call to undefined method ThisWillFail::bar() in/some/directory/example.php on line 9 and abort the program execution:

<?php
class ThisWillFail {
    public function foo() {
        return "Hello World!";
    }
}

$class = new ThisWillFail;
$class->bar();
?>

With the help of method overloading, code can catch such calls and handle them gracefully.

Property overloading works similarly to method overloading. In this case, the class redirects (or even delegates, as some OO buffs might call it) calls to read/write accesses to class properties that do not have explicit definitions in the class body. The special methods here are __set() and __get(). Depending on the error-reporting level (for more information, see David Sklar's excellent ONLamp article PHP Debugging Basics), the PHP interpreter will usually either issue a notice when accessing such an undefined property, or belatedly and silently define the variable. If using property overloading, the interpreter will instead call __set() when setting the value of an undefined property or __get() when accessing the value of such a property.

All things considered, overloading allows for drastically reduced development time in dynamic languages such as PHP.

So much for the theory and the buzzwords. It's time to study some code!

Use Case 1: Persistable Classes

The following code snippet implements the above-mentioned persistable class in less than 50 lines of PHP code by using property overloading. The term persistable means that the class can represent an element from a data structure while keeping it synchronized with an underlying storage system. This rather scientific explanation means in practice that other code can use the class to select a row from a database table. Directly accessing the class properties during runtime manipulates the elements of this row (by both reading and writing). When the script ends, PHP will take care of writing the updated row data back to the database.

Look through the code now to get a first impression of what is happening. After the code is a walk-through of the relevant parts of the code. At the end, you'll have a solid understanding of property overloading.

<?php
// Load the PEAR <a href="http://pear.php.net/package/DB/">DB package</a>
require_once "DB.php";

class Persistable {

    private $data = array();
    private $table = "users";

    public function __construct($user) {
        $this->dbh = DB::Connect("mysql://user:password@localhost/database");

        $query = "SELECT id, name, email, country FROM " .
		    $this->table . " WHERE name = ?";
        $this->data = $this->dbh->getRow($query, array($user),
		  DB_FETCHMODE_ASSOC);
    }

    public function __get($member) {
        if (isset($this->data[$member])) {
            return $this->data[$member];
        }
    }

    public function __set($member, $value) {
        // The ID of the dataset is read-only
        if ($member == "id") {
            return;
        }

        if (isset($this->data[$member])) {
            $this->data[$member] = $value;
        }
    }

    public function __destruct() {
        $query = "UPDATE " . $this->table . " SET name = ?, 
		  email = ?, country = ? WHERE id = ?";
        $this->dbh->query($query, $this->name, $this->email, 
		  $this->country, $this->id);
    }
}

$class = new Persistable("Martin Jansen");

$class->name = "John Doe";
$class->country = "United States";
$class->email = "john@example.com";
?>

Related Reading

Learning PHP 5
By David Sklar

The first item that you may have stumbled across is the method __construct(). This is the new constructor method in PHP 5. In the good old days of PHP 4, constructor names matched their classes. That changed with PHP 5. You don't need to know much about the constructor method except that it is called to create an instance of the class; that it takes one argument here; and that it executes a database query based on this argument. The constructor assigns the return value of this query to the class property $data.

Next, the code defines two special methods called __get() and __set(). They are already familiar to you from the introduction: __get() is called when reading the value of an undefined property, and __set() is called when changing the same property's value.

This means that whenever someone reads or writes an undefined property from the persistable class, the special methods manipulate the information in the $data property array instead of changing the class properties directly. (Remember: $data contains the row from the database!)

The last class method is the counterpart of __construct(). It is the destructor method __destruct(). PHP calls destructors during the "script shutdown phase," which is typically right before the execution of the PHP script finishes. The destructor writes the information from the $data property back into the database. This is what the term synchronization (see above) stands for.

You have surely noticed that the above code uses PEAR's database abstraction layer package. This is solely syntactic sugar--the script of course works the same when using other ways to speak to the database.

If you look closely, you'll notice that the persistable class is limited in its current form. It works only with exactly one database table and thus does not allow the use of more complex data models that employ LEFT JOINs or other fancy database features. The sky's the limit, though; using property overloading in no way limits the flexibility of the database model. With just a bit more code, you can easily use sophisticated features of the database inside the persistable class.

Another little issue is the fact that there is virtually no error handling when the query in the destructor fails. It is in the nature of destructors that it often turns out to be impossible to display an error message in this case, because constructing the HTML markup has usually finished before PHP calls the destructor.

In order to solve this problem, you might rename __destruct() to something such as saveData() and execute the method manually somewhere in the calling script. This doesn't change anything in the concept of persistable classes; it just requires a bit more typing when writing code that uses the class. Alternatively, you can use the error_log() function in the destructor to log the message that belongs to the error in the systemwide error log file.

That's all the code necessary for the use case of property overloading. Next up is method overloading.

Use Case 2: Dynamic Getter/Setter Methods

The following code implements "dynamic" getter and setter methods for controlling class properties with the help of method overloading. Again, read the source code. Don't be afraid if you are unable to understand all of it--an explanation follows the code.

<?php

class DynamicGetterSetter {

    private $name = "Martin Jansen";
    private $starbucksdrink = "Caramel Cappuccino Swirl";

    function __call($method, $arguments) {
        $prefix = strtolower(substr($method, 0, 3));
        $property = strtolower(substr($method, 3));

        if (empty($prefix) || empty($property)) {
            return;
        }

        if ($prefix == "get" && isset($this->$property)) {
            return $this->$property;
        }

        if ($prefix == "set") {
            $this->$property = $arguments[0];
        }
    }
}

$class = new DynamicGetterSetter;

echo "Name: " . $class->getName() . "\n";
echo "Favourite Starbucks flavour: " . $class->getStarbucksDrink() . "\n\n";

$class->setName("John Doe");
$class->setStarbucksDrink("Classic Coffee");

echo "Name: " . $class->getName() . "\n";
echo "Favourite Starbucks flavour: " . $class->getStarbucksDrink() . "\n\n";
?>

As you can see, the two class properties $name and $starbucksdrink are both private, which means that nothing can access those properties directly from outside the class. In object-oriented programming, it's common to implement public getter and setter methods for accessing and modifying the values of nonpublic properties. Implementing those methods is a rather monotonous task and, while easy to achieve using copy and paste, still consumes time and energy.

Method overloading is an easy way to circumvent this task. Instead of implementing getters and setters for every single property, the above code implements only the __call() wildcard method. This means that when calling an undefined getter or setter like setName() or getStarbucksdrink(), PHP does not abort with a fatal error, but instead executes (or delegates to) the magic __call() method.

Those are the basics, but __call() is more complex.

A look inside __call()

The first argument of __call() is the name of the original, unfound method (setName, for example). The second one is a one-dimensional, numerically indexed array containing all of the arguments for the original method. Calling an undefined method with two arguments, "Martin" and 42, will result in the following array:

$class->thisMethodDoesNotExist("Martin", 42);

// leads to the second argument of __call():

Array
(
    [0] => Martin
    [1] => 42
)

Inside __call(), an evaluation takes place if the name of the original method starts with get or set in order to figure out whether the code called a getter or a setter. Additionally, the method looks at the rest of the method name (minus the first three characters), because this string marks the name of the property to which the "virtual" getter or setter refers.

If the method name indicates a getter or setter, the method must either return the value of the corresponding property or set its value to the first argument of the original method. Otherwise it does nothing, continuing the programming execution as if nothing had happened.

The Achieved Goal

In essence, this is a way that allows code to dynamically call arbitrary getter and setter methods for arbitrary properties. This becomes especially handy in situations such as developing an application prototype in a short period of time: instead of wasting man-hours on implementing getters and setters, developers can focus on modeling the API and getting the application's fundamentals right. Outsourcing __call() into an abstract class even allows you to reuse the code during the development phase of all your future PHP projects!

Disadvantages

Where there is light, there is also shadow. Some disadvantages are here too: bigger projects likely use a tool such as phpDocumentor to keep track of the API structure. Using the above trick for dynamic methods, all getter and setter methods will of course not appear in any automatically generated documentation, because the "stupid" generators cannot figure out that __call() is a wildcard for all get* and set* methods.

Another disadvantage is the fact that code outside of the class has access to every private property. When using real getter and setter methods, it's possible to distinguish between private properties that external code may access and really private properties that are totally invisible from the outside. With method overloading enabled, this distinction is no longer possible, because there are virtual getter and setter methods for everything.

Conclusion

Congratulations if you've made it here! Now that you are aware of the possibilities that overloading offers, you have something at your fingertips that allows you to save quite a bit of time and work in the early (and probably also later) stages of your future projects. Really, what's better than telling your boss that you've finished the prototype of that next project long before it's due? :-)

Martin Jansen is a student of computer science at RWTH Aachen University, Germany, and works as a software engineer for Bauer + Kirch GmbH.


Return to the PHP DevCenter.


Valuable Online Certification Training

Online Certification for Your Career
Earn a Certificate for Professional Development from the University of Illinois Office of Continuing Education upon completion of each online certificate program.

PHP/SQL Programming Certificate — The PHP/SQL Programming Certificate series is comprised of four courses covering beginning to advanced PHP programming, beginning to advanced database programming using the SQL language, database theory, and integrated Web 2.0 programming using PHP and SQL on the Unix/Linux mySQL platform.

Enroll today!


Sponsored by: