Cloning in PHP, or why your singleton might kill WordPress

When you need a copy of an object in PHP you have to use the keyword clone:

$copy = clone $original;

All clones are equal, but some clones are more equal than others

Resident Evil: Clones

$copy is now a full copy of the object at the current state. Well, mostly. Let’s look at a simple case:

$a1 = new stdClass;
$a1->foo = 'hello';
print $a1->foo . PHP_EOL; // "hello"

$b1 = clone $a1;
print $b1->foo . PHP_EOL; // "hello", value is a copy
// change the value
$b1->foo = 'world';

print $a1->foo . PHP_EOL; // still "hello"
print $b1->foo . PHP_EOL; // now "world"

Everything works as expected: $b1 can use the values of $a1, but changes to $b1 do not affect $a1.

In your real code, your objects are probably a little bit more complex than stdClass. Some of their properties might be objects too, and then your clone’s behavior will affect the original.

Another example:

class Original
{
    protected $var;

    public function __construct()
    {
        $this->var = new stdClass;
    }

    public function change_var( $name, $value )
    {
        $this->var->name = $value;
    }

    public function get_var( $name )
    {
        return $this->var->name;
    }
}

$var is now an object. Let’s see what happens when we clone such an object:

$a2 = new Original;
$a2->change_var( 'foo', 'hello' );
print $a2->get_var( 'foo' ) . PHP_EOL; // "hello"

$b2 = clone $a2;
$b2->change_var( 'foo', 'world' );

print $a2->get_var( 'foo' ) . PHP_EOL; // surprise: "world"

This is not what we might have expected. $var is not affected by clone, it is still the same reference, and both objects, $a2 and $b2 change the same object $var.

How can we change that?

We use the magic method __clone() in our class. This method will be called after the object has been cloned. We can use it to clone $var too:

public function __clone()
{
    $this->var = clone $this->var;
}

When we add this method to our class Original and run the test code, we get hello on the second call to print $a2->get_var( 'foo' ), because $b2 works with a clone now. The important point here is: __clone() must be public. If it isn’t, any attempt to clone an object of this class will result in a fatal error.

Singletons

A singleton is a design pattern to prevent a second instance of an object. To do that it needs two components:

  1. __construct() and __clone() must be inaccessible.
  2. A static method accessible for everyone manages the object creation and guarantees there is just one object at any given time.

Here is a very simple example:

class Singleton
{
    protected static $instance;

    public static function get_instance()
    {
        if ( NULL === self::$instance )
            self::$instance = new self;

        return self::$instance;
    }

    final private function __construct() {}

    final private function __clone() {}
}

The obvious problem with singletons is: you give up data integrity with your public method get_instance(). Everyone can change your object now at any time.

$a3 = Singleton::get_instance();
$a3->foo = 'hello';

// someone else:
Singleton::get_instance()->foo = 'world';

print $a3->foo . PHP_EOL; // "world"

The global accessibility of your object makes it unpredictable.

But that’s not what I want to talk about today. The first condition is a problem too: by making clones impossible you are giving up all fine control and leave the user with a fatal error in case someone tries to make a copy of your object. A logger for example might collect objects for later inspection. It should use clone to avoid side effects on your objects.

Sure, third-party code could run a test on your object first with something like this:

/**
 * Check if cloning is possible for a given class or object.
 *
 * @param  string|object $class Class name or object
 * @return boolean
 */
function is_cloneable( $class )
{
    $rc = new ReflectionClass( $class );

    if ( ! $rc->hasMethod( '__clone' ) )
        return TRUE;

    return $rc->getMethod( '__clone' )->isPublic();
}

WordPress doesn’t use such a check. When you call …

add_option( $option_name, $object, '', FALSE );

… or …

update_option(  $option_name, $object );

… WordPress will clone the object without such a check. The classes WP_Admin_Bar and WP_Object_Cache do the same. This is something I appreciate: discourage the singleton pattern as hard as possible by making sure it breaks really bad.

Conclusion

Use object-oriented code when it is appropriate. Avoid the singleton pattern and any other global state. There are better ways to control access to your code.
Use __clone() when your objects hold other objects as variables to control the effects of clones.
See also: Singletons: Solving problems you didn’t know you never had since 1995

Author:

Web developer since 1998, moderator on WordPress Stack Exchange, author, manic reader.

Code is my drug.

Find Thomas Scholz on toscho.de⁠, ⁠, ⁠, ⁠, ⁠, ⁠, and .

1 Comment

  1. Kai – 24.08.2013 20:13

    Just throw an exception on __set() if you don’t want others to be able to add properties.

    Reply

Leave a Reply

Your email address will not be published.