Object Cloning in PHP: __clone, Deep Copy und Shallow Copy im Griff
AI generated
<?php
8.4
PHP · OOP-Patterns · Object Cloning · Prototype
Object Cloning in PHP
__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.

17 Min. Lesezeit clone · __clone · Deep Copy · Prototype Pattern PHP 8.1 · 8.3 · 8.4

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.

11. FAQ: Object Cloning in PHP

1Shallow Copy vs. Deep Copy?
Shallow Copy dupliziert nur die oberste Ebene, verschachtelte Objekte bleiben geteilt. Deep Copy dupliziert auch diese, via __clone().
2Wann wird __clone() aufgerufen?
Direkt nachdem PHP die Shallow Copy erstellt hat, bevor der Aufrufer Zugriff auf die neue Instanz erhält.
3Arrays automatisch korrekt geklont?
Die Struktur ja. Enthaltene Objekte bleiben geteilt, bis __clone() jedes Element einzeln klont.
4readonly Properties in __clone() neu zuweisen?
Erst ab PHP 8.3 erlaubt. Davor führt eine Neuzuweisung zu einem Error.
5Prototype Pattern und Cloning?
Ein vorkonfiguriertes Objekt dient als Vorlage, neue Instanzen entstehen durch Klonen statt Neukonstruktion.
6Ressourcen klonen?
Nein. Neue Ressource in __clone() aufbauen oder Cloning für die Klasse ganz verbieten.
7Cloning für eine Klasse verbieten?
__clone() wirft eine Exception, etwa LogicException. Sinnvoll bei Singletons oder Klassen mit strengen Invarianten.
8Jede Ebene braucht __clone()?
Ja, für vollständige Deep Copy. Sonst bleibt eine tiefer verschachtelte Ebene weiterhin geteilt.
9Fehlendes __clone() erkennen?
Manuelle Prüfung jeder Klasse mit Objekt-Properties. PHPStan erkennt fehlende Deep Copies nicht automatisch.
10Nötig bei reinen Value Objects?
Bei vollständig unveränderlichen Value Objects meist nicht. Relevant wird es erst bei mutierbaren enthaltenen Objekten.