Acceptable to instantiate from an existing object?

2019-05-28 10:56发布

问题:

I discovered this by accident, and was wondering if this is expected behaviour:

Interactive shell

php > class dog {
php {   public $name='doggy';
php {   public function speak() {
php {     echo "bark, bark! (my name is " . $this->name . ")\n";
php {   }
php { }
php >
php > $puppy = new dog;
php > $puppy->speak();
bark, bark! (my name is doggy)
php >
php > $taffy = new $puppy;
php > $taffy->speak();
bark, bark! (my name is doggy)
php >
php > $taffy->name = "Taffy";
php > $taffy->speak();
bark, bark! (my name is Taffy)
php >
php > $puppy->speak();
bark, bark! (my name is doggy)
php >

My question is about the line, $taffy = new $puppy;

Normally, I would have coded $taffy = new dog;

I wouldn't have expected to be able to create a new object by instantiating from an instantiation... if that makes any sense.

Is this expected functionality, or just a happy accident?


I discovered this when building a model that needed to talk to several different db tables, and I use dependency injection to pass the database abstraction:

class ShowConnectedTables {
  public function __constructor($db) {
    $this->modelOne = new ModelOne($db);
    $this->modelTwo = new ModelTwo($db);
  }
  // ... snip ...
}

$table = new ShowConnectedTables(new Database);

Sometimes, though, a database call to ModelTwo in the middle of a loop fetching ModelOne's rows could clobber the fetching of ModelOne's rows. So is it acceptable to

class ShowConnectedTables {
  public function __constructor($db) {
    $this->modelOne = new ModelOne($db);
    $this->modelTwo = new ModelTwo(new $db); // note new abstraction
  }
  // ... snip ...
}

$table = new ShowConnectedTables(new Database);

回答1:

I've never seen this or done this. As a result, I would probably avoid it: if it is going to cause other developers to look at it and scratch their heads (just like you did), it probably isn't worth it. I can't see any reason why you would specifically have to do this, after all.

Don't Panic has pointed out that, while not explicitly documented, there is an example of doing this in the PHP documentation. Example 5 on this page.

That being said, there is no cloning going on:

class dog {
   public $name='doggy';
   public function speak() {
     echo "bark, bark! (my name is " . $this->name . ")\n<br>";
   }
    public function __clone() {
        print 'This is not called!';
        parent::__clone();
    }
}
$class = 'dog';
$puppy = new dog;
$puppy->name = 'Puppy';
$taffy = new $puppy;
if ( spl_object_hash( $taffy ) != spl_object_hash( $puppy ) )
    print 'not the same object';
$taffy->speak();

If you do the above you will find that the objects are not the same and the __clone method is never called. Also, your taffy will be initialized with the name of doggy, not Puppy. It seems this is effectively shorthand for:

$class = get_class( $puppy );
$taffy = new $class;


回答2:

This is not a matter of creating a new reference to existing object. The constructor is executed, so that means, this is a new object. I've tired it:

<?php

class dog {
   public $name;
   public function __construct($name = '') {
       $this->name = $name ?: 'doggy';
   }
   public function speak() {
     echo "bark, bark! (my name is " . $this->name . ")\n";
   }
}

$puppy = new dog;
$puppy->speak(); #bark, bark! (my name is doggy)

$taffy = new $puppy('Snuffels');
$taffy->speak(); #bark, bark! (my name is Snuffels)

$taffy->name = "Taffy";
$taffy->speak(); #bark, bark! (my name is Taffy)

$puppy->speak(); #bark, bark! (my name is doggy)
echo $puppy;

The reason IMO is, that you can already use things like new self or new parent, see http://php.net/manual/en/language.oop5.basic.php

In the class context, it is possible to create a new object by new self and new parent.

I assume, that they implement a class recognition from things after new. But still a good WTF :)



标签: php oop