__clone, Deep Copy und Shallow Copy im Griff
Das clone-Schlüsselwort in PHP erzeugt standardmäßig nur eine Shallow Copy, verschachtelte Objekte bleiben geteilt. Mit der Magic Method __clone lässt sich Object Cloning gezielt zur Deep Copy erweitern, inklusive readonly Properties und dem klassischen Prototype Pattern als Erzeugungsstrategie.
Inhaltsverzeichnis
- 1. Warum Object Cloning ein eigenes Thema ist
- 2. Das clone-Schlüsselwort und die Standard-Shallow-Copy
- 3. Die Magic Method __clone: Deep Copy gezielt steuern
- 4. Verschachtelte Objekte und Arrays korrekt klonen
- 5. Object Cloning und readonly Properties seit PHP 8.1
- 6. Das Prototype Pattern: Cloning als Erzeugungsmuster
- 7. Ressourcen, Referenzen und Grenzen des Cloning
- 8. Häufige Fehler beim Object Cloning
- 9. Shallow Copy versus Deep Copy im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum Object Cloning ein eigenes Thema ist
Object Cloning in PHP klingt zunächst nach einer Nebensächlichkeit, dem einfachen Duplizieren eines Objekts mit dem clone-Schlüsselwort. Tatsächlich ist Object Cloning eine der Stellen im Sprachdesign, an denen sich subtile, schwer auffindbare Bugs verstecken, weil das Standardverhalten nicht das ist, was die meisten Entwickler intuitiv erwarten. Wer ein Objekt mit verschachtelten Objekt-Properties klont und annimmt, beide Kopien seien vollständig unabhängig, erlebt eine unangenehme Überraschung, sobald eine Änderung an der Kopie auch im Original sichtbar wird.
Der Grund liegt darin, wie PHP Objekte intern referenziert. Eine PHP-Variable, die ein Objekt enthält, speichert einen Objekt-Handle, keinen echten Wert im klassischen Sinn. Object Cloning erzeugt eine neue, unabhängige Instanz mit denselben Property-Werten, aber wenn eine Property selbst ein Objekt ist, wird nur der Handle kopiert, nicht das referenzierte Objekt. Genau dieses Verhalten, Shallow Copy genannt, ist der Ausgangspunkt für alles Weitere in diesem Artikel.
In der Praxis betrifft Object Cloning vor allem Value Objects, Konfigurationsobjekte und das Prototype Pattern, bei dem ein vorkonfiguriertes Objekt als Vorlage für viele Varianten dient. Wer Object Cloning in PHP nicht bewusst steuert, riskiert bei jedem dieser Anwendungsfälle geteilten, unerwartet mutierten Zustand zwischen eigentlich unabhängigen Objekten.
2. Das clone-Schlüsselwort und die Standard-Shallow-Copy
Das clone-Schlüsselwort erzeugt eine neue Instanz derselben Klasse und kopiert alle Properties eins zu eins auf die neue Instanz. Für skalare Werte wie Strings, Integer und Booleans funktioniert das genau wie erwartet, weil PHP diese Werte ohnehin nach dem Value-Prinzip kopiert. Problematisch wird es, sobald eine Property selbst ein Objekt ist. Object Cloning kopiert in diesem Fall nur die Referenz auf das verschachtelte Objekt, nicht das Objekt selbst. Original und Klon zeigen danach auf dasselbe innere Objekt.
Dieses Verhalten heißt Shallow Copy, eine flache Kopie, die nur die oberste Ebene der Objektstruktur wirklich dupliziert. Für Klassen ohne Objekt-Properties ist eine Shallow Copy ausreichend und sogar performanter, weil kein zusätzlicher Kopiervorgang für verschachtelte Strukturen nötig ist. Für Klassen mit veränderlichen, verschachtelten Objekten ist eine Shallow Copy jedoch fast immer ein Bug, der erst in Produktion auffällt, wenn zwei vermeintlich unabhängige Objekte sich gegenseitig beeinflussen.
<?php
declare(strict_types=1);
final class Address
{
public function __construct(
public string $city,
) {
}
}
final class Customer
{
public function __construct(
public string $name,
public Address $address,
) {
}
}
$original = new Customer('Jane Doe', new Address('Berlin'));
// Default clone is a shallow copy: only the top-level object is duplicated
$copy = clone $original;
$copy->name = 'John Doe';
$copy->address->city = 'Hamburg';
echo $original->name; // Jane Doe — top-level property is independent
echo $original->address->city; // Hamburg — nested object is SHARED, not copied!
Dieses Beispiel zeigt den Kern des Problems: $copy->name ändert sich unabhängig vom Original, weil name ein Scalar ist. $copy->address->city hingegen ändert auch $original->address->city, weil beide Objekte dieselbe Address-Instanz referenzieren. Genau dieses Verhalten von Object Cloning muss jeder PHP-Entwickler kennen, bevor er clone in einer Klasse mit Objekt-Properties einsetzt.
3. Die Magic Method __clone: Deep Copy gezielt steuern
PHP bietet für genau dieses Problem die Magic Method __clone(). Sie wird automatisch aufgerufen, unmittelbar nachdem PHP die Shallow Copy erstellt hat, aber bevor der Aufrufer Zugriff auf die neue Instanz erhält. Innerhalb von __clone() ist $this bereits die neue, geklonte Instanz, während auf die referenzierten Objekt-Properties zu diesem Zeitpunkt noch die alten, geteilten Referenzen zeigen. Die Aufgabe von __clone() ist, diese geteilten Referenzen durch eigene Kopien zu ersetzen und so aus der automatischen Shallow Copy gezielt eine Deep Copy zu machen.
Der übliche Weg innerhalb von __clone() ist, jede Objekt-Property erneut mit clone zu behandeln: $this->address = clone $this->address;. Das funktioniert rekursiv, das heißt, wenn Address selbst wieder verschachtelte Objekte enthält, muss auch Address eine eigene __clone()-Methode implementieren, damit die Deep Copy vollständig bis in die unterste Ebene reicht. Object Cloning ist also kein einmaliger Vorgang, sondern muss auf jeder betroffenen Ebene der Objektstruktur konsistent implementiert werden.
<?php
declare(strict_types=1);
final class Address
{
public function __construct(
public string $city,
) {
}
}
final class Customer
{
public function __construct(
public string $name,
public Address $address,
) {
}
/**
* Deep-copies nested objects when this instance is cloned.
*/
public function __clone(): void
{
$this->address = clone $this->address;
}
}
$original = new Customer('Jane Doe', new Address('Berlin'));
$copy = clone $original;
$copy->address->city = 'Hamburg';
echo $original->address->city; // Berlin — nested object is now truly independent
echo $copy->address->city; // Hamburg
Mit dieser __clone()-Implementierung wird aus dem automatischen Object Cloning eine echte Deep Copy für die Address-Property. Der Rest der Klasse bleibt unverändert, nur die eine Zeile in __clone() entscheidet über das gesamte Kopierverhalten. Wichtig ist, sich bei jeder neuen Objekt-Property in der Klasse bewusst zu fragen, ob sie in __clone() ergänzt werden muss.
4. Verschachtelte Objekte und Arrays korrekt klonen
Arrays verhalten sich beim Object Cloning grundlegend anders als Objekte, weil PHP-Arrays nach dem Copy-on-Write-Prinzip arbeiten und bei einer Zuweisung wie einem echten Value-Type kopiert werden. Ein Array mit skalaren Werten wird beim Klonen der Elternklasse also automatisch korrekt dupliziert, ganz ohne __clone(). Problematisch wird es erst, wenn das Array selbst Objekte enthält, denn dann kopiert PHP zwar die Array-Struktur, aber die enthaltenen Objekt-Referenzen bleiben geteilt, exakt dasselbe Verhalten wie bei einer einzelnen Objekt-Property.
Für ein Array von Objekten muss __clone() daher über das Array iterieren und jedes enthaltene Objekt einzeln klonen. Dieses Muster kommt häufig bei Aggregatobjekten vor, etwa einer Order-Klasse mit einem Array von OrderLine-Objekten. Object Cloning einer Order ohne entsprechende Behandlung des Arrays würde dazu führen, dass Klon und Original dieselben OrderLine-Instanzen teilen, obwohl die Order selbst als unabhängig gilt.
<?php
declare(strict_types=1);
final class OrderLine
{
public function __construct(
public string $sku,
public int $quantity,
) {
}
}
final class Order
{
/**
* @param array<int, OrderLine> $lines
*/
public function __construct(
public string $orderNumber,
public array $lines,
) {
}
/**
* Deep-copies every object contained in the lines array.
*/
public function __clone(): void
{
$this->lines = array_map(
static fn (OrderLine $line): OrderLine => clone $line,
$this->lines,
);
}
}
$original = new Order('ORD-1001', [new OrderLine('SKU-1', 2)]);
$copy = clone $original;
$copy->lines[0]->quantity = 99;
echo $original->lines[0]->quantity; // 2 — array elements were cloned individually
echo $copy->lines[0]->quantity; // 99
Diese Kombination aus array_map() und clone innerhalb von __clone() ist das Standardmuster für Object Cloning bei Arrays von Objekten. Für sehr große Arrays lohnt sich, die Kosten dieser Deep Copy im Blick zu behalten, weil jedes Element einzeln geklont wird und bei tausenden Einträgen ein spürbarer, wenn auch meist noch akzeptabler Overhead entsteht.
5. Object Cloning und readonly Properties seit PHP 8.1
Readonly Properties, seit PHP 8.1 verfügbar, verändern die Regeln für Object Cloning in einem wichtigen Detail. Eine readonly Property darf nach der Initialisierung im Konstruktor nicht mehr direkt zugewiesen werden, auch nicht innerhalb von __clone(), mit einer Ausnahme: PHP 8.3 hat die Regel gelockert, sodass eine readonly Property innerhalb von __clone() erneut zugewiesen werden darf, solange sie zuvor bereits initialisiert war. Vor PHP 8.3 musste man für eine Deep Copy readonly Objekt-Properties stattdessen komplett neu instanziieren, da eine direkte Neuzuweisung einen Error auslöste.
Für Projekte, die noch PHP 8.1 oder 8.2 unterstützen müssen, bedeutet Object Cloning bei readonly Properties in der Praxis, dass man entweder eine komplett neue Instanz über den Konstruktor baut, oder auf readonly für Objekt-Properties verzichtet, die tief geklont werden müssen, und stattdessen nur die primitiven Werte als readonly deklariert. Ab PHP 8.3 vereinfacht sich das erheblich, weil __clone() readonly Properties wie gewohnt neu zuweisen darf.
<?php
declare(strict_types=1);
final class Money
{
public function __construct(
public readonly int $cents,
public readonly string $currency,
) {
}
}
final class Invoice
{
public function __construct(
public readonly string $invoiceNumber,
public readonly Money $total,
) {
}
/**
* Since PHP 8.3, readonly properties may be reassigned inside __clone().
*/
public function __clone(): void
{
// Money has no mutable nested state, but reassignment illustrates the rule
$this->total = new Money($this->total->cents, $this->total->currency);
}
}
$original = new Invoice('INV-2026-01', new Money(9900, 'EUR'));
$copy = clone $original;
var_dump($original->total === $copy->total); // false — genuinely separate instances
Der wichtigste Merksatz für Object Cloning mit readonly Properties: Vor PHP 8.3 ist die Zuweisung in __clone() für bereits initialisierte readonly Properties verboten, ab PHP 8.3 ist sie erlaubt. Beim Schreiben neuer Bibliotheken lohnt sich, die unterstützte Mindestversion explizit zu prüfen, bevor man sich auf dieses Verhalten verlässt.
6. Das Prototype Pattern: Cloning als Erzeugungsmuster
Das Prototype Pattern nutzt Object Cloning gezielt als Erzeugungsstrategie, statt Objekte immer über einen Konstruktor mit vielen Parametern zu bauen. Die Idee: Ein vorkonfiguriertes Prototyp-Objekt wird einmal erstellt, und jede weitere benötigte Instanz entsteht durch clone des Prototyps, gefolgt von gezielten Anpassungen. Das lohnt sich besonders, wenn die Objekterstellung selbst teuer ist, etwa weil ein Konstruktor aufwendige Berechnungen durchführt, während das reine Kopieren eines bereits fertigen Objekts vergleichsweise günstig ist.
Ein typisches Beispiel ist ein Dokumenten-Template-System, in dem ein Basis-Template mit Standardformatierung einmal erstellt und für jedes neue Dokument geklont wird, statt die komplette Formatierung jedes Mal neu aufzubauen. Object Cloning im Prototype Pattern setzt konsequente __clone()-Implementierung voraus, weil ein Template typischerweise verschachtelte Formatierungsobjekte enthält, die nicht zwischen allen Dokumenten geteilt werden dürfen.
<?php
declare(strict_types=1);
final class DocumentStyle
{
public function __construct(
public string $fontFamily = 'Arial',
public int $fontSize = 12,
) {
}
}
final class DocumentTemplate
{
public function __construct(
public string $title,
public DocumentStyle $style,
) {
}
public function __clone(): void
{
$this->style = clone $this->style;
}
}
// Prototype: built once with the expensive default configuration
$prototype = new DocumentTemplate('Untitled', new DocumentStyle());
// Every new document clones the prototype instead of rebuilding it from scratch
$invoiceDoc = clone $prototype;
$invoiceDoc->title = 'Invoice';
$invoiceDoc->style->fontSize = 10;
$contractDoc = clone $prototype;
$contractDoc->title = 'Contract';
echo $prototype->style->fontSize; // 12 — prototype itself remains untouched
Das Prototype Pattern ist eng mit Object Cloning verwandt, aber nicht identisch damit. Object Cloning ist der technische Mechanismus, das Prototype Pattern ist die bewusste Entwurfsentscheidung, diesen Mechanismus als primären Erzeugungsweg für eine Familie ähnlicher Objekte einzusetzen, anstatt ihn nur gelegentlich für einzelne Kopien zu nutzen.
7. Ressourcen, Referenzen und Grenzen des Cloning
Nicht jeder Zustand lässt sich sinnvoll klonen. Ressourcen-Typen wie offene Datenbankverbindungen, Datei-Handles oder Netzwerk-Sockets sollten in __clone() niemals einfach kopiert werden, weil zwei Objekte, die dieselbe zugrunde liegende Verbindung teilen, sich beim Schließen oder bei parallelem Zugriff gegenseitig stören können. Für solche Fälle ist die richtige Strategie meist, in __clone() eine komplett neue Verbindung aufzubauen oder, häufiger, die Ressourcen-Property explizit aus dem Klon zu entfernen und bei Bedarf neu zu erzeugen, statt sie automatisch zu übernehmen.
Eine weitere Grenze betrifft Objekte, die absichtlich als Singleton konzipiert sind, etwa ein zentrales Logger- oder Konfigurationsobjekt. Für solche Klassen ist Object Cloning meist unerwünscht, weil ein Klon der Singleton-Idee widerspricht, es könnten dann zwei unabhängige Instanzen existieren, wo eigentlich nur eine gemeint war. PHP erlaubt, clone für eine Klasse vollständig zu unterbinden, indem __clone() eine Exception wirft.
<?php
declare(strict_types=1);
final class AppConfig
{
private static ?self $instance = null;
private function __construct(
public readonly array $settings,
) {
}
public static function instance(): self
{
return self::$instance ??= new self(['env' => 'production']);
}
/**
* Explicitly forbids cloning to preserve the singleton guarantee.
*/
public function __clone(): void
{
throw new LogicException('AppConfig must not be cloned.');
}
}
$config = AppConfig::instance();
// clone $config; // throws LogicException: AppConfig must not be cloned.
Diese defensive Nutzung von __clone() ist genauso wichtig wie die Deep-Copy-Nutzung. Object Cloning bewusst zu verbieten ist ein legitimes Design, sobald ein Klon die Invarianten einer Klasse verletzen würde.
8. Häufige Fehler beim Object Cloning
Der häufigste Fehler ist schlicht, __clone() zu vergessen, obwohl die Klasse Objekt-Properties enthält. Der Bug zeigt sich oft nicht sofort, sondern erst Wochen später, wenn zwei vermeintlich unabhängige Objekte plötzlich denselben Zustand teilen und niemand mehr weiß, warum. Ein zweiter Fehler ist eine unvollständige Deep Copy, bei der __clone() zwar existiert, aber nur die erste Ebene behandelt, während tiefer verschachtelte Objekte weiterhin geteilt bleiben, weil die entsprechende innere Klasse selbst kein __clone() implementiert.
Ein dritter, subtilerer Fehler betrifft Arrays mit gemischtem Inhalt, teils Skalare, teils Objekte. Wird pauschal angenommen, ein Array werde beim Object Cloning immer vollständig kopiert, übersieht man leicht die enthaltenen Objekt-Referenzen. PHPStan kann solche Fälle nicht automatisch erkennen, ein manueller Code-Review jeder __clone()-Methode gegen die tatsächliche Property-Liste der Klasse bleibt daher notwendig, besonders nach dem Hinzufügen neuer Properties.
9. Shallow Copy versus Deep Copy im Vergleich
Die Entscheidung zwischen Shallow Copy und Deep Copy beim Object Cloning hängt vom konkreten Zustand der Klasse ab, nicht von einer generellen Präferenz. Die folgende Tabelle fasst die wichtigsten Unterschiede zusammen.
| Szenario | Shallow Copy (Standard) | Deep Copy (mit __clone) | Empfehlung |
|---|---|---|---|
| Nur skalare Properties | Ausreichend | Unnötig | Kein __clone() nötig |
| Verschachtelte, veränderliche Objekte | Riskant, geteilter Zustand | Korrekt | __clone() mit clone je Property |
| Array von Objekten | Elemente bleiben geteilt | Korrekt | array_map mit clone im __clone() |
| Immutable Value Objects | Meist ausreichend | Selten nötig | Nur bei mutierbaren Kindobjekten nötig |
| Ressourcen, Singletons | Gefährlich | Meist unpassend | Cloning per Exception verbieten |
Als Faustregel gilt: Sobald eine Klasse eine veränderliche, verschachtelte Objekt-Property besitzt, ist Object Cloning ohne __clone() ein latenter Bug. Nur bei ausschließlich skalaren Werten oder bewusst geteilten, unveränderlichen Objekten ist die Standard-Shallow-Copy tatsächlich das richtige Verhalten.
Mironsoft
PHP-Architektur, Objektdesign und wartbare Backend-Systeme
Geteilter Zustand durch fehlerhaftes Object Cloning?
Wir prüfen bestehende PHP-Klassen auf fehlende oder unvollständige __clone()-Implementierungen und bauen saubere Deep-Copy-Strategien für verschachtelte Objekte und das Prototype Pattern.
Cloning-Audit
Klassen mit Objekt-Properties auf fehlendes __clone() prüfen
Deep-Copy-Refactoring
Verschachtelte Objekte und Arrays korrekt duplizieren lassen
Prototype Pattern
Teure Objekterstellung durch geklonte Vorlagen ersetzen
10. Zusammenfassung
Object Cloning in PHP ist mit clone technisch simpel, aber semantisch voller Fallstricke. Die Standard-Shallow-Copy dupliziert nur die oberste Ebene eines Objekts, verschachtelte Objekte und die enthaltenen Objekte in Arrays bleiben geteilt, solange __clone() nichts anderes vorgibt. Die Magic Method __clone() ist der zentrale Hebel, um aus dieser Shallow Copy gezielt eine Deep Copy zu machen, muss dafür aber auf jeder betroffenen Ebene der Objektstruktur konsistent implementiert werden.
Readonly Properties verändern die Regeln seit PHP 8.1 in einem wichtigen Detail, ab PHP 8.3 dürfen sie innerhalb von __clone() erneut zugewiesen werden. Das Prototype Pattern nutzt Object Cloning als bewusste Erzeugungsstrategie für Familien ähnlicher Objekte. Für Ressourcen und Singletons ist Object Cloning oft die falsche Wahl und sollte über eine Exception in __clone() explizit verboten werden. Wer diese Regeln kennt, vermeidet die häufigste Quelle stiller Bugs rund um geteilten Objektzustand in PHP.
Object Cloning in PHP — Das Wichtigste auf einen Blick
Standardverhalten
clone erzeugt eine Shallow Copy. Verschachtelte Objekte und Objekte in Arrays bleiben ohne __clone() geteilt.
Deep Copy mit __clone()
Jede Objekt-Property und jedes Objekt in einem Array einzeln mit clone behandeln, rekursiv auf jeder Ebene.
Readonly Properties
Neuzuweisung in __clone() ist erst ab PHP 8.3 erlaubt. Davor komplette Neuinstanziierung nötig.
Grenzen
Ressourcen und Singletons sollten Cloning über eine Exception in __clone() explizit verbieten.