Come faccio a creare una copia di un oggetto in PHP?


168

Sembra che in PHP gli oggetti vengano passati per riferimento. Persino gli operatori di assegnazione non sembrano creare una copia dell'oggetto.

Ecco una semplice prova inventata:

<?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'

?>

In entrambi i casi di stampa sto ottenendo 'dopo'

Quindi, come faccio a passare $ a a set_b () per valore, non per riferimento?


2
Esistono pochissimi casi in cui si vorrebbe effettivamente questo comportamento. Quindi, se ti ritrovi a usarlo spesso, allora forse c'è qualcosa di più fondamentale nel modo in cui scrivi il tuo codice?
troelskn,

1
No, non ho ancora avuto bisogno di usarlo.
Nick Stinemates

(object) ((array) $objectA)potrebbe ottenere gli stessi risultati desiderati con prestazioni migliori rispetto all'utilizzo di clone $objectAo new stdClass.
Binyamin,

Risposte:


284

In PHP 5+ gli oggetti vengono passati per riferimento. In PHP 4 vengono passati per valore (ecco perché aveva un runtime pass per riferimento, che è diventato obsoleto).

Puoi usare l'operatore 'clone' in PHP5 per copiare oggetti:

$objectB = clone $objectA;

Inoltre, sono solo gli oggetti che vengono passati per riferimento, non tutto come hai detto nella tua domanda ...


Voglio solo aggiungere a chiunque stia leggendo questo, che la clonazione manterrà il riferimento all'oggetto originale. L'esecuzione di query MySQL utilizzando l'oggetto clonato può avere risultati imprevedibili a causa di ciò, poiché l'esecuzione potrebbe non avvenire in modo lineare.
Ælex

20
Per correggere un malinteso comune (penso che anche i documenti PHP abbiano sbagliato!) Gli oggetti di PHP 5 non vengono "passati per riferimento". Come in Java, hanno un ulteriore livello di riferimento indiretto: la variabile punta a un "puntatore a oggetto" e quello punta a un oggetto. Pertanto, due variabili possono puntare allo stesso oggetto senza essere riferimenti allo stesso valore. Questo può essere visto da questo esempio: $a = new stdClass; $b =& $a; $a = 42; var_export($b);ecco $bun riferimento alla variabile $a ; se si sostituisce =&con un normale =, questo non è un riferimento e punta comunque all'oggetto originale.
IMSoP,

Il passaggio di runtime per riferimento è una cattiva idea, perché fa sì che l'effetto di una chiamata di funzione dipenda dall'implementazione della funzione, piuttosto che dalle specifiche. Non ha nulla a che fare con il passaggio per valore come predefinito.
Oswald,

1
@Alex Puoi approfondire il tuo commento? (O qui o altrove.) Il tuo punto viene fuori un po 'poco chiaro IMO.
Chris Middleton,

@ChrisMiddleton Pensa ai termini di C o C ++: se hai clonato un riferimento a un oggetto che è libero, fuori campo o rilasciato, il tuo riferimento clonato viene invalidato. Così si può ottenere un comportamento indefinito a seconda di quello che è successo con l'oggetto originale, a cui si tiene un riferimento tramite clonazione.
Ælex,

103

Le risposte si trovano comunemente nei libri di Java.

  1. clonazione: se non si ignora il metodo clone, il comportamento predefinito è copia superficiale. Se i tuoi oggetti hanno solo variabili membro primitive, è del tutto ok. Ma in un linguaggio non tipizzato con un altro oggetto come variabili membro, è un mal di testa.

  2. serializzazione / deserializzazione

$new_object = unserialize(serialize($your_object))

Ciò consente una copia profonda con un costo elevato a seconda della complessità dell'oggetto.


4
+1 ottimo, fantastico, ottimo modo per fare una copia DEEP in PHP, anche molto semplice. Vorrei invece chiederti qualcosa sulla copia superficiale standard offerta dalla parola chiave clone di PHP, hai detto che vengono copiate solo le variabili membro primitive: le matrici / stringhe PHP sono considerate variabili membro primitive, quindi vengono copiate, ho ragione?
Marco Demaio,

3
Per chiunque lo raccolga: una copia "superficiale" ( $a = clone $b, senza __clone()metodi magici in gioco) equivale a guardare ciascuna delle proprietà dell'oggetto $bin termini, e assegnare alla stessa proprietà in un nuovo membro della stessa classe, usando =. Le proprietà che sono oggetti non otterranno cloned, né oggetti all'interno di un array; lo stesso vale per le variabili legate per riferimento; tutto il resto è solo un valore e viene copiato proprio come con qualsiasi compito.
IMSoP,

3
Perfetto! json_decode (json_encode ($ obj)); non clonare proprietà private / protette e qualsiasi metodo ... annullare la serializzazione (serializzare anche i metodi non
clonati

Eccezionale! Finalmente mi sbarazzo dell'errore di PhpStorm; Call to method __clone from invalid context:)
numediaweb,

L'amico riceve un errore di analisi PHP quando lo fa in questo modo: $new_date = (clone $date_start)->subDays(1);non riesce con (), se li rimuovo ottengo un errore diverso. Il fatto è che stiamo usando esattamente lo stesso php 7.2.3 e il mio funziona benissimo. Qualche idea? Cercato ovunque ..
emotalità

21

In base al commento precedente, se si dispone di un altro oggetto come variabile membro, procedere come segue:

class MyClass {
  private $someObject;

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

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

}

Ora puoi fare la clonazione:

$bar = new MyClass();
$foo = clone $bar;


4

Solo per chiarire PHP usa copia su scrittura, quindi praticamente tutto è un riferimento fino a quando non lo modifichi, ma per gli oggetti devi usare clone e il metodo magico __clone () come nella risposta accettata.


1

Questo codice aiuta a clonare i metodi

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());

questo codice è un po 'inutile, funzionerebbe anche se rimuovessi il metodo __clone :)
amik,

1

Stavo facendo dei test e ho ottenuto questo:

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" }

?>

1

In questo esempio creeremo la classe iPhone e ne faremo una copia esatta clonandola

class iPhone {

public $name;
public $email;

    public function __construct($n, $e) {

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

    }
}


$main = new iPhone('Dark', 'm@m.com');
$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>";

-1

Se si desidera copiare completamente le proprietà di un oggetto in un'istanza diversa, è possibile utilizzare questa tecnica:

Serializzalo su JSON e quindi ridenializzalo nuovamente su Oggetto.


7
Hmm lo eviterei come l'inferno.
Jimmy Kane,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.