Como crio uma cópia de um objeto no PHP?

168

Parece que no PHP os objetos são passados ​​por referência. Mesmo operadores de atribuição não parecem estar criando uma cópia do objeto.

Aqui está uma prova simples e artificial:

<?php

class A {
    public $b;
}


function set_b($obj) { $obj->b = "after"; }

$a = new A();
$a->b = "before";
$c = $a; //i would especially expect this to create a copy.

set_b($a);

print $a->b; //i would expect this to show 'before'
print $c->b; //i would ESPECIALLY expect this to show 'before'

?>

Nos dois casos de impressão, estou recebendo 'depois'

Então, como passo $ a para set_b () por valor, não por referência?

Nick Stinemates
fonte
2
Existem muito poucos casos em que você realmente deseja esse comportamento. Então, se você se usa com frequência, talvez haja algo mais fundamental errado na maneira como você escreve seu código?
troelskn
1
Não, ainda não foi necessário usá-lo.
Nick Stinemates 9/10/08
(object) ((array) $objectA)pode resultar nos mesmos resultados desejados com melhor desempenho do que em clone $objectAou new stdClass.
Binyamin

Respostas:

284

No PHP 5+, os objetos são passados ​​por referência. No PHP 4, eles são passados ​​por valor (é por isso que o tempo de execução é passado por referência, que ficou obsoleto).

Você pode usar o operador 'clone' no PHP5 para copiar objetos:

$objectB = clone $objectA;

Além disso, são apenas objetos que são passados ​​por referência, não tudo como você disse na sua pergunta ...

Eran Galperin
fonte
Só quero adicionar a quem estiver lendo isso, que a clonagem manterá referência ao objeto original. A execução de consultas MySQL usando o objeto clonado pode ter resultados imprevisíveis por causa disso, pois a execução pode não ocorrer de maneira linear.
Ælex
20
Para corrigir um equívoco comum (acho que até os documentos do PHP entendem errado!) Os objetos do PHP 5 não são "passados ​​por referência". Como em Java, eles têm um nível adicional de indireção - a variável aponta para um "ponteiro de objeto" e isso aponta para um objeto. Assim, duas variáveis ​​podem apontar para o mesmo objeto sem serem referências ao mesmo valor. Isso pode ser visto neste exemplo: $a = new stdClass; $b =& $a; $a = 42; var_export($b);aqui $bestá uma referência à variável $a ; se você substituir =&com um normais =, é não uma referência, e ainda aponta para o objeto original.
IMSOP
A passagem do tempo de execução por referência é uma péssima idéia, pois faz com que o efeito de uma chamada de função dependa da implementação da função, e não da especificação. Não tem nada a ver com a passagem por valor ser o padrão.
Oswald
1
@Alex Você pode elaborar seu comentário? (Aqui ou em outro lugar.) Seu ponto de vista é um pouco obscuro.
Chris Middleton
@ChrisMiddleton Pense nos termos de C ou C ++: se você clonou uma referência a um objeto que é gratuito, fora do escopo ou liberado, sua referência clonada é invalidada. Assim, você pode obter um comportamento indefinido, dependendo do que aconteceu com o objeto original, ao qual você mantém uma referência por meio de clonagem.
Lexlex
103

As respostas são comumente encontradas em livros Java.

  1. clonagem: se você não substituir o método clone, o comportamento padrão será a cópia superficial. Se seus objetos tiverem apenas variáveis ​​de membros primitivas, tudo estará bem. Mas em uma linguagem sem letra com outro objeto como variável membro, é uma dor de cabeça.

  2. serialização / desserialização

$new_object = unserialize(serialize($your_object))

Isso permite uma cópia profunda com um alto custo, dependendo da complexidade do objeto.

yogman
fonte
4
+1 ótima, ótima, ótima maneira de fazer uma cópia PROFUNDA em PHP, muito fácil também. Em vez disso, deixe-me perguntar uma coisa sobre a cópia superficial padrão oferecida pela palavra-chave clone do PHP, você disse que apenas as variáveis ​​membro primitivas são copiadas: as matrizes / strings do PHP são consideradas variáveis ​​membro primitivas, para que sejam copiadas, estou certo?
Marco Demaio 11/07
3
Para qualquer um que escolher: uma cópia "superficial" ( $a = clone $bsem __clone()métodos mágicos em jogo) é equivalente a olhar para cada uma das propriedades do objeto $bno termo e atribuir à mesma propriedade em um novo membro da mesma classe, usando =. Propriedades que são objetos não terão cloned nem objetos dentro de uma matriz; o mesmo vale para variáveis ​​vinculadas por referência; tudo o resto é apenas um valor e é copiado como em qualquer tarefa.
IMSOP
3
Perfeito! json_decode (json_encode ($ obj)); não clone privado / propriedades protegidas e qualquer método ... unserialize (serialize não métodos clone também ...
zloctb
Impressionante! Finalmente me livrei do erro do PhpStorm; Call to method __clone from invalid context:)
numediaweb
Amigo está recebendo erro de análise do PHP ao fazer o seguinte: $new_date = (clone $date_start)->subDays(1);Falha com o (), se eu removê-los, recebo um erro diferente. O problema é que estamos usando exatamente o mesmo php 7.2.3 e o meu funciona bem. Alguma ideia? Procurado em todos os lugares ..
emotality 28/03
21

De acordo com o comentário anterior, se você tiver outro objeto como variável de membro, faça o seguinte:

class MyClass {
  private $someObject;

  public function __construct() {
    $this->someObject = new SomeClass();
  }

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

}

Agora você pode fazer a clonagem:

$bar = new MyClass();
$foo = clone $bar;
Stanislav
fonte
4

Apenas para esclarecer, o PHP usa copy on write, então basicamente tudo é uma referência até você modificá-lo, mas para objetos você precisa usar o clone e o método mágico __clone () como na resposta aceita.

Patricio Rossi
fonte
1

Esse código ajuda a clonar métodos

class Foo{

    private $run=10;
    public $foo=array(2,array(2,8));
    public function hoo(){return 5;}


    public function __clone(){

        $this->boo=function(){$this->hoo();};

    }
}
$obj=new Foo;

$news=  clone $obj;
var_dump($news->hoo());
zloctb
fonte
este código é um pouco inútil, ele iria trabalhar, mesmo se remover o método __clone :)
Amik
1

Eu estava fazendo alguns testes e consegui isso:

class A {
  public $property;
}

function set_property($obj) {
  $obj->property = "after";
  var_dump($obj);
}

$a = new A();
$a->property = "before";

// Creates a new Object from $a. Like "new A();"
$b = new $a;
// Makes a Copy of var $a, not referenced.
$c = clone $a;

set_property($a);
// object(A)#1 (1) { ["property"]=> string(5) "after" }

var_dump($a); // Because function set_property get by reference
// object(A)#1 (1) { ["property"]=> string(5) "after" }
var_dump($b);
// object(A)#2 (1) { ["property"]=> NULL }
var_dump($c);
// object(A)#3 (1) { ["property"]=> string(6) "before" }

// Now creates a new obj A and passes to the function by clone (will copied)
$d = new A();
$d->property = "before";

set_property(clone $d); // A new variable was created from $d, and not made a reference
// object(A)#5 (1) { ["property"]=> string(5) "after" }

var_dump($d);
// object(A)#4 (1) { ["property"]=> string(6) "before" }

?>
Pyetro
fonte
1

Neste exemplo, criaremos a classe iPhone e faremos uma cópia exata dela clonando

class iPhone {

public $name;
public $email;

    public function __construct($n, $e) {

       $this->name = $n;
       $this->email = $e;

    }
}


$main = new iPhone('Dark', '[email protected]');
$copy = clone $main;


// if you want to print both objects, just write this    

echo "<pre>"; print_r($main);  echo "</pre>";
echo "<pre>"; print_r($copy);  echo "</pre>";
Muhammad Ebrahim
fonte
-1

Se você deseja copiar completamente as propriedades de um objeto em uma instância diferente, convém usar esta técnica:

Serialize-o para JSON e des serialize-o novamente para Object.

diy_nunez
fonte
7
Hmm, eu evitaria isso como o inferno.
Jimmy Kane