*adjective, easily modified or changed; opposite of hardcoded

web design article

    home »  site map »  e-mail » 

The Magic __set_state Method

In PHP 4 there are only two magic methods, __serialize and __unserialize. PHP 5 .0 defines a number of new magic methods the most well-known being the constructor and destructor. Apart from beginning with a double underscore, magic methods in PHP are (usually) invoked indirectly1. For instance, when a __construct method is defined, it is called whenever an object is created. If we have a class named “A”, then the __construct method is indirectly invoked by the code:

$a = new A;

The reasons for the existence of magic methods vary. The __toString method, for example, could perhaps best be described as merely a convenience. By defining a __toString method, we can control how an object is displayed when it is used with the echo or print functions. However, the same effect could be achieved by defining a display method and calling it whenever we wanted to display an object. At the opposite end of the spectrum is a magic method like __clone. This method is absolutely necessary in order to control what happens when an object is copied. Without it, copying an aggregate object would always result in a shallow copy - often not the desired effect.

The newest magic method, __set_state first appears in PHP 5.1. Like other magic methods it is invoked in the background, in this case by a call to var_export. In this respect it bears a strong resemblance to the __toString method. As we have seen, the __toString method is also invoked by a PHP function call. It's clear that the string representation of a simple data type such as an integer should simply be it's value. If we have assigned the value of “5” to the variable $x, then we expect to see that value when the variable is echoed. But an object is a complex data type. Defining a __toString method makes sense because the string representation of an object is not a straightforward matter. The need for a magic __set_state method also arises from the fact that objects are complex data types.

To better understand the need for a magic __set_state method, let's look at what var_export does. var_export is one of numerous variable handling functions and it is very similar to var_dump and print_r. These functions are typically used for debugging purposes and the values returned provide more or less the same information. The principal difference is that var_export returns valid, parseable PHP code. Here's what the php.net site has to say:

var_export() gets structured information about the given variable. It is similar to var_dump with one exception: the returned representation is valid PHP code. “

When using var_export with primitives or even with arrays, it is pretty evident how variables should be represented. Let's have a quick look at var_export when used with non-objects.

Take a simple array such as:

$array = array('a', 'b', 'c');

When passed to var_export like so:

var_export($array);

The parseable code:

array ( 0 => 'a', 1 => 'b', 2 => 'c', )

is displayed – the exact PHP syntax for creating an array..

The var_export function also accepts an optional Boolean second parameter. When set to true, a copy of the exported variable can be created instead of just being echoed. We can copy the $array variable defined above in the following way:

eval('$newarray = ' . var_export($array, true) . ';');

When executed this code creates the variable $newarray, a copy of $array. Dumping the newly created variable will confirm this.

You might expect because var_export works with an array it might well work with an object but not so. What should the parseable representation of an object be? Arrays are limited to being either associative or numerical but objects can take many varied forms. We noted earlier that because an object is a complex data type the string representation is problematic. The bug report at http://bugs.php.net/bug.php?id=29361 highlights the problem with objects and var_export. When an object is an element of an array, the return value of var_export (PHP 4.3.8) is as follows:

array (
  0 => 
  class myclass {
    var $myProperty = 'myValue';
  },
);

This might well be an adequate representation of the object but it isn't parseable code.

The introduction of a magic __set_state method is a solution to this problem. Let's create a simple class and then export it using var_export.

class Person{

private $name = "Peter";

private $password = "secret";

public function getName(){

return $this->name;

}

}

Add the code lines:

$p = new Person;

var_export($p);

The output is as follows:

Person::__set_state(array( 'name' => 'Peter', 'password' => 'secret', ))

This code makes a call to a static method of the Person class passing an array of the object's properties.

To ensure that this code is parseable let's try and execute the code returned by var_dump by adding the following line of code:

eval(var_export($p, true) . ';');

This code will raise a fatal error because no __set_state method is yet defined in the Person class. Correct this by adding the following static method to the Person class definition.

public static function __set_state(array $arr){

foreach ($arr as $key => $value){

echo "$key: $value <br />";

}

}

Type hinting the parameter to __set_state is optional but since PHP 5.1 supports this feature for arrays as well as objects, it makes sense to use it. What the __set_state method actually does is your decision. In the example above we simply output each element of the array. Actual working code can be found on the eZpublish subversion server:

public static function __set_state( array $array )

{

return new ezcPhpGeneratorParameter( $array['variable'], $array['type'] );

}

In this case, the object's properties are passed to the constructor and a new object is returned. The same effect can be achieved with the Person class by rewriting the __set_state method like so:

public static function __set_state(array $arr){

$p = new Person;

$p->name = $arr['name'];

$p->password = $arr['password'];

return $p;

}

Even though the name and the password properties of the Person class are private, they can still be directly accessed from within a class method and so an exact copy can be created and exported.

The __set_state method solves the problem of creating parseable code when var_export is used with objects. It bears some resemblance to the magic __toString method because it is invoked by a call to a built-in PHP function. However, it is not a mere convenience, syntactic sugar perhaps, but, like the __clone method, it's a necessity for a fully functional object-oriented language.

Resources

http://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.set-state

http://bugs.php.net/bug.php?id=29361

http://ilia.ws/archives/66-Security-Implications-of-var_exportprint_r.html

http://derickrethans.nl/varexport_and_classes.php


1While it's possible to call any magic method directly, the only sensible direct call to a magic method is a call to the parent constructor and then only from within the child constructor i.e. parent::__construct().

About the Author

Peter Lavin runs a Web Design/Development firm in Toronto, Canada. He has been published in a number of magazines and online sites, including UnixReview.com, php|architect and International PHP Magazine. He is a contributor to the recently published O'Reilly book, PHP Hacks and is also the author of Object Oriented PHP, published by No Starch Press.

Please do not reproduce this article in whole or part, in any form, without obtaining written permission.