I'm having a blonde moment with __clone()

PHP programming forum. Ask questions or help people concerning PHP code. Don't understand a function? Need help implementing a class? Don't understand a class? Here is where to ask. Remember to do your homework!

Moderator: General Moderators

Post Reply
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

I'm having a blonde moment with __clone()

Post by Chris Corbyn »

Trying to clone an object which contains other objects. I need these other objects to be cloned too so I'm using __clone(). The problem I'm having is that I have a property which is an array of references to other objects. Some of these reference exactly the same object and I need to ensure that when I clone a reference, anything which also referenced the original object now references the clone instead.

I'll try to give a simplified example:

Code: Select all

class MyClass {
  public $references = array();
  public function addReference($key, $obj) {
    $this->references[$key] = $obj; //This is done by reference in PHP5!
  }
  public function __clone() {
    //I need to clone $this->references too, but keeping the duplicates in tact 
  }
}

$o = new MyClass();
$o->addReference("foo", $obj1);
$o->addReference("bar", $obj2);
$o->addReference("zip", $obj1); //This points to the same object as foo!!

//$o->references["foo"] and $o->references["zip"] reference the same object

$o2 = clone $o;

//How do I make sure $o2->references["foo"] and $o2->references["zip"] reference the same object
// But still make sure they have been cloned out of $o ??
Any clues?

I tried this but it doesn't seem to work:

Code: Select all

public function __clone() {
  $tracker = null;
  $add = null;
  foreach ($this->references as $k => $obj) {
    if ($obj !== $tracker) {
      $tracker = $obj;
      $add = clone $obj;
   }
   $this->references[$k] = $add;
  }
}
EDIT | Typo

Test case for anyone up for a challenge:

Code: Select all

<?php

error_reporting(E_ALL);
ini_set("display_errors", true);

require_once "/home/d11wtq/PHPLibs/simpletest/unit_tester.php";
require_once "/home/d11wtq/PHPLibs/simpletest/reporter.php";

class MyClass {
  public $references = array();
  public function addReference($key, $obj) {
    $this->references[$key] = $obj; //This is done by reference in PHP5!
  }
  public function __clone() {
    $tracker = null;
    $add = null;
    foreach ($this->references as $k => $obj) {
      if ($obj !== $tracker) {
        $tracker = $obj;
        $add = clone $obj;
      }
      $this->references[$k] = $add;
    }
  }
}

class X {}
class Y {}

class TestOfClone extends UnitTestCase
{
  public function testClone()
  {
    $x = new X();
    $y = new Y();
    $o = new MyClass();
    $o->addReference("foo", $x);
    $o->addReference("bar", $y);
    $o->addReference("zip", $x);
    $this->assertReference($o->references["foo"], $o->references["zip"]);
    
    $o2 = clone $o;
    
    $this->assertCopy($o->references["foo"], $o2->references["foo"]);
    $this->assertCopy($o->references["bar"], $o2->references["bar"]);
    $this->assertCopy($o->references["zip"], $o2->references["zip"]);
    
    $this->assertReference($o2->references["foo"], $o2->references["zip"]);
  }
}

$test = new TestOfClone();
$test->run(new HtmlReporter());
TestOfClone
Fail: testClone -> [Object: of X] and [Object: of X] should reference the same object at [/home/d11wtq/public_html/TestClone.php line 48]
1/1 test cases complete: 4 passes, 1 fails and 0 exceptions.
EDIT | Test case updated to avoid cheaters :P
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

*sigh* Of course, I've created a value object once I've used clone so I'll have to use the reference operator when I re-assign it:

Code: Select all

public function __clone() {
    $tracker = null;
    $add = null;
    foreach ($this->references as $k => $obj) {
      if ($obj !== $tracker) {
        $tracker = $obj;
        $add = clone $obj;
      }
      $this->references[$k] =& $add;
    }
  }
That fixes it.
Post Reply