Der Spread-Operator seit PHP 8.1 für assoziative Arrays
Bis PHP 8.0 funktionierte der Spread-Operator in Array-Literalen nur mit numerischen Schlüsseln, assoziative Arrays mussten weiterhin über array_merge zusammengeführt werden. Seit PHP 8.1 unterstützt die Spread-Syntax auch String-Keys direkt. Wir zeigen den Unterschied zum klassischen Spread, das Kollisionsverhalten und wann sich der Umstieg von array_merge lohnt.
Inhaltsverzeichnis
- 1. Der Unterschied zum klassischen numerischen Spread-Operator
- 2. Reindizierungsverhalten: numerische Keys werden neu vergeben
- 3. Praktische Anwendung: Konfigurationsarrays zusammenführen
- 4. Kollisionsverhalten bei doppelten String-Keys im Detail
- 5. Direkter Vergleich zu array_merge: Syntax und Semantik
- 6. Nur eine Ebene: Spread ist kein Deep-Merge
- 7. Performance-Vergleich: Spread gegen array_merge
- 8. Spread als Funktionsargument versus Spread im Array-Literal
- 9. Entscheidungshilfe: Wann Spread, wann array_merge
- 10. Zusammenfassung
- 11. FAQ
1. Der Unterschied zum klassischen numerischen Spread-Operator
Der Spread-Operator ... in Array-Literalen wurde bereits mit PHP 7.4 eingeführt, funktionierte zunächst aber ausschließlich mit numerisch indizierten Arrays. Enthielt eines der zu entpackenden Arrays einen String-Schlüssel, warf PHP bis Version 8.0 einen TypeError zur Laufzeit. Für assoziative Arrays blieb bis dahin nur array_merge() als Werkzeug übrig, was in Code, der sowohl numerische als auch assoziative Strukturen kombiniert, zu einer unschönen Vermischung zweier verschiedener Syntaxformen führte.
Mit PHP 8.1 wurde diese Einschränkung aufgehoben. String-Schlüssel dürfen seitdem beim Unpacking innerhalb von Array-Literalen auftreten, und ihr Verhalten orientiert sich bewusst an array_merge(), nicht am numerischen Spread-Verhalten. Diese bewusste Anlehnung ist entscheidend für das Verständnis der Kollisionsregeln, die im weiteren Verlauf dieses Artikels genauer betrachtet werden.
// Numerischer Spread, bereits seit PHP 7.4 möglich
$a = [1, 2, 3];
$b = [0, ...$a, 4]; // [0, 1, 2, 3, 4], Reihenfolge bleibt erhalten
// String-Key-Spread, erst seit PHP 8.1 möglich
$defaults = ['timeout' => 30, 'retries' => 3];
$overrides = ['retries' => 5];
$config = [...$defaults, ...$overrides]; // ['timeout' => 30, 'retries' => 5]
2. Reindizierungsverhalten: numerische Keys werden neu vergeben
Ein zentraler Unterschied zwischen numerischem und String-Key-Unpacking betrifft die Behandlung der Schlüssel selbst. Numerische Schlüssel werden beim Unpacking immer neu vergeben, unabhängig von ihrer ursprünglichen Position im Quellarray, genau wie es auch beim einfachen Zusammenfügen von Arrays mit dem +-Operator nicht der Fall wäre, aber wie es bei einer manuellen foreach-Schleife mit [] = passieren würde.
String-Schlüssel dagegen bleiben beim Unpacking exakt erhalten, so wie sie im Quellarray definiert waren. Das ist konsistent mit dem Verhalten von array_merge(), wo assoziative Schlüssel ebenfalls unverändert übernommen werden, während rein numerische Schlüssel dort ebenfalls neu durchnummeriert werden. Wer versehentlich ein Array mit gemischten Schlüsseln entpackt, sollte sich dieses unterschiedliche Verhalten für beide Schlüsseltypen bewusst machen, um Überraschungen zu vermeiden.
$source = [5 => 'a', 'name' => 'Anna', 10 => 'b'];
$result = [...$source];
var_dump($result);
// [0 => 'a', 'name' => 'Anna', 1 => 'b']
// numerische Keys (5, 10) werden neu vergeben (0, 1)
// der String-Key "name" bleibt exakt erhalten
3. Praktische Anwendung: Konfigurationsarrays zusammenführen
Der naheliegendste Anwendungsfall für String-Key-Unpacking ist das Zusammenführen von Konfigurationsarrays, etwa Standardwerte eines Moduls mit projektspezifischen Overrides oder umgebungsabhängigen Einstellungen. Die Spread-Syntax macht dabei auf einen Blick sichtbar, welches Array Priorität hat, ohne dass man wie bei array_merge() die Argumentreihenfolge in einer separaten Funktionssignatur nachschlagen muss.
Besonders wertvoll wird das bei mehrstufigen Konfigurationshierarchien, etwa Basis-Konfiguration, Umgebungs-Konfiguration und Laufzeit-Overrides in einem einzigen Ausdruck. Die Lesereihenfolge von links nach rechts entspricht dabei exakt der Priorität von niedrig nach hoch, was die Lesbarkeit gegenüber verschachtelten array_merge()-Aufrufen mit mehreren Argumenten spürbar verbessert.
function buildConfig(array $baseConfig, array $envConfig, array $runtimeOverrides): array
{
return [
...$baseConfig,
...$envConfig,
...$runtimeOverrides, // höchste Priorität, steht zuletzt
];
}
$config = buildConfig(
baseConfig: ['cache' => true, 'debug' => false],
envConfig: ['debug' => true],
runtimeOverrides: ['cache' => false],
);
// ['cache' => false, 'debug' => true]
4. Kollisionsverhalten bei doppelten String-Keys im Detail
Taucht derselbe String-Schlüssel in mehreren entpackten Arrays innerhalb eines Literals auf, gewinnt konsequent der Wert aus dem später aufgeführten Array, genau wie bei array_merge(), wo spätere Argumente frühere überschreiben. Diese Regel gilt unabhängig von der Reihenfolge innerhalb des jeweiligen Quellarrays, entscheidend ist ausschließlich die Position des Spread-Ausdrucks im Ziel-Literal.
Ein Detail, das in der Praxis oft übersehen wird: Diese Überschreibungsregel gilt strikt sequenziell von links nach rechts, auch wenn zwischen zwei Spread-Ausdrücken zusätzliche, direkt notierte Schlüssel-Wert-Paare stehen. Ein direkt im Literal notiertes Paar wird an genau der Position ausgewertet, an der es im Code steht, und kann daher sowohl von einem vorherigen als auch von einem nachfolgenden Spread überschrieben werden.
$a = ['level' => 'info', 'channel' => 'app'];
$b = ['level' => 'debug'];
$merged = [...$a, 'level' => 'warning', ...$b];
// ['level' => 'debug', 'channel' => 'app']
// 'warning' wird sofort von $b['level'] = 'debug' überschrieben,
// weil $b zeitlich zuletzt entpackt wird
5. Direkter Vergleich zu array_merge: Syntax und Semantik
Semantisch verhalten sich String-Key-Spread und array_merge() für den reinen Zusammenführungsfall identisch: Assoziative Schlüssel werden überschrieben, numerische Schlüssel neu durchnummeriert. Der Unterschied liegt vor allem in der Syntax und in der Flexibilität bei der Platzierung. Die Spread-Syntax erlaubt es, entpackte Arrays mit einzelnen, direkt notierten Schlüssel-Wert-Paaren im selben Literal zu mischen, was mit array_merge() einen zusätzlichen, unhandlichen Einzelelement-Array-Literal als Argument erfordern würde.
Ein relevanter Unterschied betrifft null-Werte innerhalb der zu entpackenden Arrays: Spread behandelt sie wie jeden anderen Wert und übernimmt sie unverändert, ebenso wie array_merge(). Wer stattdessen null-Werte beim Merge herausfiltern möchte, etwa um echte Wert-Overrides von reinen Platzhaltern zu unterscheiden, muss das in beiden Fällen explizit über array_filter() vor dem eigentlichen Merge erledigen.
6. Nur eine Ebene: Spread ist kein Deep-Merge
Ein häufiger Fallstrick beim Zusammenführen von Konfigurationsarrays ist die Annahme, Spread würde verschachtelte Arrays rekursiv zusammenführen. Tatsächlich arbeitet sowohl der Spread-Operator als auch array_merge() nur auf der obersten Ebene. Enthält ein Schlüssel in beiden Quellarrays selbst wieder ein Array als Wert, überschreibt das später entpackte Array-Element das frühere vollständig, statt seine inneren Schlüssel einzeln zu vereinigen.
Für tatsächlich rekursives Zusammenführen bleibt array_merge_recursive() die passende Funktion, die allerdings bei numerischen Schlüsseln ein eigenes, oft überraschendes Verhalten zeigt, da sie Werte statt sie zu überschreiben in ein neues Array zusammenfasst. Wer verschachtelte Konfigurationsstrukturen mit Spread zusammenführen will, sollte jede Verschachtelungsebene explizit selbst entpacken oder eine dedizierte, klar dokumentierte Merge-Funktion für den jeweiligen Anwendungsfall schreiben.
$defaults = ['db' => ['host' => 'localhost', 'port' => 3306]];
$overrides = ['db' => ['port' => 3307]];
$result = [...$defaults, ...$overrides];
// ['db' => ['port' => 3307]]
// der gesamte "db"-Unterarray wurde ersetzt, "host" ist verloren!
// Explizites Zusammenführen der verschachtelten Ebene:
$result = [
...$defaults,
'db' => [...$defaults['db'], ...$overrides['db']],
];
// ['db' => ['host' => 'localhost', 'port' => 3307]]
7. Performance-Vergleich: Spread gegen array_merge
In Benchmarks mit realistischen Konfigurationsarrays liegt der Performance-Unterschied zwischen String-Key-Spread und array_merge() im niedrigen einstelligen Prozentbereich und ist für die überwiegende Mehrheit der Anwendungsfälle irrelevant. Beide Mechanismen führen intern eine vergleichbare Kopieroperation über Copy-on-Write-Semantik aus, der Spread-Operator spart lediglich den Funktionsaufruf-Overhead von array_merge() selbst ein.
Relevant wird der Unterschied erst bei sehr häufig wiederholten Merges innerhalb heißer Codepfade, etwa beim Aufbau von Request-Kontexten in einer Schleife mit tausenden Iterationen. Hier zeigt der Spread-Operator einen leichten, aber messbaren Vorteil, weil der Funktionsaufruf-Overhead entfällt. Für die Wahl zwischen beiden Ansätzen sollte in der Praxis dennoch primär die Lesbarkeit entscheiden, nicht ein marginaler Performance-Unterschied, der nur unter Extrembedingungen sichtbar wird.
// Beide Varianten sind semantisch identisch:
$mergedA = array_merge($defaults, $overrides);
$mergedB = [...$defaults, ...$overrides];
// array_merge gewinnt bei variabler Argumentanzahl aus einem Array:
$allConfigs = [$base, $env, $runtime];
$merged = array_merge(...$allConfigs); // Spread ALS Funktionsargument
8. Spread als Funktionsargument versus Spread im Array-Literal
Ein wichtiger Unterschied, der leicht mit dem hier behandelten Thema verwechselt wird, betrifft den Spread-Operator als Funktionsargument, etwa array_merge(...$configs), wobei $configs ein Array aus mehreren Config-Arrays ist. Das entpackt die äußere Liste in einzelne Funktionsargumente, unabhängig davon, ob die inneren Arrays selbst numerische oder String-Schlüssel enthalten, denn hier greift die Semantik der aufgerufenen Funktion, nicht die des Array-Literal-Unpackings.
Diese beiden Anwendungsfälle, Spread im Array-Literal und Spread als Funktionsargument, teilen sich zwar dasselbe Syntax-Symbol, folgen aber unterschiedlichen Regeln. Bei Funktionsaufrufen mit variadic Parametern ist zusätzlich zu beachten, dass String-Schlüssel im entpackten Array seit PHP 8.1 als Named Arguments interpretiert werden, was ein eigenständiges Thema mit eigenen Fallstricken ist und über das reine Array-Merging hinausgeht.
9. Entscheidungshilfe: Wann Spread, wann array_merge
Für Array-Literale mit einer festen, im Code sichtbaren Anzahl an zu kombinierenden Arrays ist die Spread-Syntax fast immer die lesbarere Wahl, weil sie sich nahtlos mit direkt notierten Einzelwerten mischen lässt und die Priorität der Quellen visuell von links nach rechts abbildet. Das gilt besonders für Konfigurationszusammenführungen mit zwei bis vier Quellen, wie sie in Dependency-Injection-Kontexten oder beim Aufbau von HTTP-Client-Optionen häufig vorkommen.
array_merge() bleibt die richtige Wahl, wenn die Anzahl der zu kombinierenden Arrays zur Laufzeit variabel ist und selbst als Array vorliegt, etwa bei einer dynamischen Liste von Plugin-Konfigurationen unbekannter Länge. In diesem Fall lässt sich der Spread-Operator zwar innerhalb des Funktionsaufrufs von array_merge() selbst nutzen, ein reines Array-Literal-Unpacking mit fester Anzahl an ...-Ausdrücken ist dafür syntaktisch nicht geeignet.
| Merkmal | Spread (String-Keys, PHP 8.1+) | array_merge() | Spread (numerisch, PHP 7.4+) |
|---|---|---|---|
| Numerische Keys | Neu durchnummeriert | Neu durchnummeriert | Neu durchnummeriert |
| String-Keys | Bleiben erhalten, letzter gewinnt | Bleiben erhalten, letzter gewinnt | Nicht unterstützt (vor 8.1: TypeError) |
| Mischbar mit Literal-Werten | Ja, direkt im selben Array | Nein, erfordert Wrapper-Array | Ja, direkt im selben Array |
| Variable Argumentanzahl | Nicht direkt, feste Ausdrücke | Ja, über Spread als Argument | Nicht direkt, feste Ausdrücke |
| Performance | Minimal schneller, kein Funktionsaufruf | Minimal langsamer, Funktionsaufruf-Overhead | Minimal schneller, kein Funktionsaufruf |
Mironsoft
PHP-Modernisierung, Code-Qualität und Legacy-Refactoring
Gewachsener PHP-Code, der niemand mehr gern anfasst?
Wir modernisieren PHP-Codebasen auf aktuelle Sprachstandards, führen statische Analyse und Coding Standards ein und refactorn Legacy-Code Schritt für Schritt, ohne den laufenden Betrieb zu gefährden.
Legacy-Refactoring
Gewachsenen PHP-Code strukturiert und risikoarm modernisieren.
Code-Qualität etablieren
PHPStan, Coding Standards und CI-Checks nachhaltig im Team verankern.
Versions-Upgrade
PHP-Major-Version-Upgrades sicher planen und ohne Ausfallzeit umsetzen.
10. Zusammenfassung
Array-Unpacking mit String-Keys: Das Wichtigste auf einen Blick
Neuerung
Seit PHP 8.1 unterstützt der Spread-Operator String-Keys in Array-Literalen direkt.
Semantik
String-Key-Spread verhält sich wie array_merge, spätere Werte überschreiben frühere.
Praxis
Ideal für lesbare Konfigurationshierarchien mit fester, kleiner Anzahl an Quellen.
Grenzen
Bei variabler Argumentanzahl aus einem Array bleibt array_merge mit Spread-Argument nötig.