Object Overloading in PHP 5
by Martin Jansen06/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
|
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.
Showing messages 1 through 16 of 16.
-
_sleep()
2007-02-26 07:20:05 quark [View]
Isn't it better to use the __sleep() method to persist the data to the database instead of __destruct()?
-
can anybody tell url to download php6
2006-07-28 05:42:11 abc@yahoo.com [View]
can anybody tell url to download php6
-
php6 released on 27 july 2006
2006-07-28 05:40:03 abc@yahoo.com [View]
can anybody help installing php6 with apache and mysql5.0
-
installing php6
2006-07-28 04:13:33 abc@yahoo.com [View]
can anybody help installing php6 with apache, mysql5 !
-
function __autoload()
2006-07-26 21:38:20 abc@yahoo.com [View]
can anybody help me!
-
function __autoload()
2006-07-24 23:48:41 abc@yahoo.com [View]
how does this code works
<?php
function __autoload($class_name) {
require_once $class_name . '.php';
}
$obj = new MyClass1();
$obj2 = new MyClass2();
?>
-
set_include_path ,get_include_path
2006-07-24 23:34:18 abc@yahoo.com [View]
Thanks Martin for solving my problem.Now again I ask another question:---> " why and how do we change include_path at runtime using set_include_path.Explain me by an example "
-
__construct overloading
2006-07-24 02:07:36 abc@yahoo.com [View]
why the following code gives error!
<?
class A
{
public function __construct()
{
echo "hye";
}
public function __construct($p)
{
echo $p,"<br />";
}
}
$ob1 = new A(5);
$ob2 = new A();
?>
-
__construct overloading
2006-07-24 08:25:38 MartinJansen [View]
Multiple constructors per class are not supported. You'll have to use something like
<?php
...
public function __construct($p = null) {
if ($p === null) {
echo "hye";
} else {
echo $p . "<br />";
}
}
...
?>
-
__construct overloading
2007-02-19 05:15:53 ambikadevi [View]
The great drawback in PHP is there is no constructor/function overloading.
-
What about __set() and __get() ?
2005-10-12 16:26:22 chiology [View]
Is it just me or am I the only person that knows about the __get() and __set() functions? It seems like this would be a wonderful alternative to this rather sloppy method.
Look it up, it's in the PHP5 Objects documentation under the 'Magic Functions' heading (I think).
M.T. -
What about __set() and __get() ?
2005-10-13 00:42:43 MartinJansen [View]
What exactly do you mean? The first part of the article actually talks about those two magic methods, while the second part covers __call which belongs to the same method family.
-
On dynamic getters and setters
2005-09-01 08:17:25 OliverBrown [View]
"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"
This isn't actually a problem since there is nothing to stop you defining a real getter or setter for a property that does need special attention (since __call is only executed if the method doesn't exist).
-
method overloading ala C++/Java
2005-07-15 03:47:50 simoncpu [View]
Is there a straightforward way to overload methods having the same name but with different number of parameters? Can you please share a technique to do that?
Thanks.
[ simon.cpu] -
method overloading ala C++/Java
2005-07-17 13:56:25 MartinJansen [View]
PHP 5 does not support polymorphism as you know it from Java or C++, but you can sort of emulate it.
function foo($arg1, $arg2 = null) {
if (!isset($arg2)) {
$arg2 = 42;
}
return $arg1 + $arg2;
}
is "equivalent" to the following Java code:
public int foo(int arg1, int arg2) {
return arg1 + arg2;
}
public int foo(int arg1) {
return foo(arg1, 42);
}
In a similar manner you can support homonymous methods with different parameter types:
function foo($arg1) {
if (!is_array($arg1)) {
$arg1 = array($arg1);
}
/* ... */
}
is "equivalent" to the following Java (pseudo-)code:
public void foo(HashTable arg1) {
/* ... */
}
public void foo(int arg1) {
HashTable a = new HashTable;
a.push(arg1);
foo(a);
}
-
method overloading ala C++/Java
2005-10-04 08:08:40 trollll [View]
instead of:
function foo($arg1, $arg2 = null) {
if (!isset($arg2)) {
$arg2 = 42;
}
return $arg1 + $arg2;
}
why not just say:
function foo($arg1, $arg2 = 42) {
return $arg1 + $arg2;
}
other than that, glad to see someone else misses polymorphism. i wish i had the time to add it myself... i haven't checked the game plan on future releases lately - anyone know when and if i can start looking forward to this?






