Array-Unpacking mit String-Keys: Der Spread-Operator seit PHP 8.1
AI generated
8.4
PHP · Arrays · PHP 8.1
Array-Unpacking mit String-Keys im Detail
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.

11 Min. Lesezeit Spread-Operator Array-Unpacking PHP 8.1 array_merge

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.

11. FAQ: Array-Unpacking mit String-Keys: Das Wichtigste auf einen Blick

1Seit welcher PHP-Version funktioniert Array-Unpacking mit String-Keys?
Seit PHP 8.1. Zuvor führte ein String-Schlüssel beim Unpacking innerhalb eines Array-Literals zu einem TypeError.
2Verhält sich String-Key-Spread wie array_merge oder wie der Plus-Operator?
Wie array_merge. Bei doppelten String-Schlüsseln gewinnt der später entpackte Wert, anders als beim Plus-Operator, bei dem der erste Wert erhalten bleibt.
3Werden numerische Schlüssel beim String-Key-Spread ebenfalls neu vergeben?
Ja. Numerische Schlüssel werden unabhängig vom Schlüsseltyp der übrigen Elemente immer neu durchnummeriert, während String-Schlüssel exakt erhalten bleiben.
4Was passiert bei einer Kollision zwischen zwei entpackten Arrays?
Der Wert aus dem später im Literal aufgeführten Array gewinnt, unabhängig von der internen Reihenfolge im jeweiligen Quellarray.
5Kann ich Spread-Ausdrücke mit direkt notierten Schlüssel-Wert-Paaren mischen?
Ja, das ist einer der Hauptvorteile gegenüber array_merge. Direkt notierte Paare werden an ihrer Position im Literal ausgewertet und können von nachfolgenden Spreads überschrieben werden.
6Ist String-Key-Spread schneller als array_merge?
Nur geringfügig, weil der Funktionsaufruf-Overhead entfällt. Der Unterschied ist für die meisten Anwendungsfälle nicht relevant und sollte nicht das Hauptkriterium für die Wahl sein.
7Kann ich mit Spread eine variable Anzahl an Arrays zusammenführen?
Nicht direkt im Array-Literal, dort ist die Anzahl der Ausdrücke fest im Code sichtbar. Für eine variable Anzahl bleibt array_merge in Kombination mit Spread als Funktionsargument die richtige Wahl.
8Werden null-Werte beim Unpacking herausgefiltert?
Nein, null wird wie jeder andere Wert behandelt und unverändert übernommen. Zum Filtern ist ein expliziter array_filter-Aufruf vor dem Merge nötig.
9Ist Spread als Funktionsargument dasselbe wie Spread im Array-Literal?
Nein, beide teilen sich das Syntax-Symbol, folgen aber unterschiedlichen Regeln. Als Funktionsargument werden String-Schlüssel seit PHP 8.1 als Named Arguments interpretiert.
10Wann sollte ich weiterhin array_merge statt Spread verwenden?
Wenn die Anzahl der zu kombinierenden Arrays zur Laufzeit variabel ist und selbst als Array vorliegt, etwa bei dynamischen Plugin-Konfigurationen unbekannter Länge.