Variadic-Funktionen und Spread-Operator: ...$args im Detail
AI generated
8.4
PHP · Funktionen · Spread-Operator
Variadic-Funktionen und der Spread-Operator im Detail
...$args als Parameter versus ...$array beim Aufruf

Dieselben drei Punkte bedeuten in einer Funktionssignatur etwas anderes als bei einem Funktionsaufruf, und wer das nicht klar trennt, verwechselt schnell Definition und Verwendung. Wir zeigen, wie Variadic-Parameter und der Spread-Operator beim Aufruf zusammenspielen, wie Named Arguments mit Variadics kombiniert werden und wo sich Wrapper-Funktionen sowie Fluent-Builder mit variabler Argumentzahl in der Praxis lohnen.

12 Min. Lesezeit Variadic Functions Spread-Operator Named Arguments func_get_args

1. Parameter-Deklaration versus Aufruf: zwei unterschiedliche Bedeutungen

Die drei Punkte ... tauchen in PHP an zwei syntaktisch ähnlichen, semantisch aber grundverschiedenen Stellen auf. In einer Funktionssignatur vor einem Parameter, etwa function log(string ...$messages), markieren sie einen Variadic-Parameter, der beliebig viele Argumente einsammelt und als Array innerhalb der Funktion bereitstellt. Beim Funktionsaufruf dagegen, etwa log(...$messages), entpacken dieselben drei Punkte ein bestehendes Array in einzelne Argumente.

Diese Doppelbedeutung ist kein Zufall, sondern spiegelbildlich gedacht: Der eine Fall sammelt mehrere Werte in ein Array ein, der andere entpackt ein Array wieder in mehrere Werte. Wer beide Fälle im selben Ausdruck kombiniert, etwa eine Wrapper-Funktion, die ihre eigenen Variadic-Argumente unverändert an eine andere variadic Funktion weitergibt, nutzt effektiv beide Bedeutungen nacheinander.


function sum(int ...$numbers): int
{
    return array_sum($numbers); // $numbers ist hier ein normales Array
}

echo sum(1, 2, 3); // 6, Variadic-Parameter sammelt ein

$values = [4, 5, 6];
echo sum(...$values); // 15, Spread beim Aufruf entpackt

2. Variadic-Parameter als typsicherer Ersatz für func_get_args

Vor der Einführung variadic Parameter in PHP 5.6 war func_get_args() das einzige Werkzeug, um innerhalb einer Funktion auf eine variable Anzahl übergebener Argumente zuzugreifen, unabhängig von der deklarierten Signatur. Das Problem dabei: Die Funktionssignatur selbst gab keine Auskunft über die erwartete Argumentanzahl oder deren Typ, IDEs und statische Analysewerkzeuge konnten hier folglich auch keine sinnvolle Hilfe leisten.

Ein Variadic-Parameter wie string ...$messages löst dieses Problem vollständig: Die Signatur dokumentiert selbst, dass beliebig viele Argumente eines bestimmten Typs erwartet werden, PHPStan und ähnliche Werkzeuge können jedes einzelne Argument gegen den deklarierten Typ prüfen, und die IDE bietet korrekte Autovervollständigung. func_get_args() sollte in modernem PHP-8.4-Code praktisch nicht mehr vorkommen, außer in seltenen Fällen, in denen tatsächlich auf per-Referenz übergebene Argumente ohne deklarierten Parameter zugegriffen werden muss.

3. Named Arguments in Kombination mit Variadics

Seit PHP 8.0 lassen sich Named Arguments auch mit Variadic-Parametern kombinieren, allerdings mit einer wichtigen Einschränkung: Named Arguments, die keinem regulären, vor dem Variadic-Parameter deklarierten Parameter entsprechen, landen im Variadic-Array mit ihrem Namen als String-Schlüssel statt als numerischem Index. Das verwandelt die Funktion faktisch in eine, die sowohl positionale als auch benannte variable Argumente gleichzeitig verarbeiten kann.

Diese Eigenschaft ist besonders nützlich für Funktionen, die beliebige Zusatzoptionen als Schlüssel-Wert-Paare entgegennehmen sollen, ohne dafür ein separates Array-Parameter mit eigener Struktur zu definieren. Wichtig ist dabei, dass innerhalb der Funktion sowohl positionale als auch benannte Einträge im selben Array landen, eine Unterscheidung ist nur über array_is_list() oder eine explizite Prüfung der Schlüssel möglich.


function buildUrl(string $base, string ...$queryParams): string
{
    $query = http_build_query($queryParams);
    return $query === '' ? $base : "{$base}?{$query}";
}

echo buildUrl('/search', q: 'php', page: '2', sort: 'relevance');
// /search?q=php&page=2&sort=relevance
// die Named Arguments landen als assoziative Einträge im Variadic-Array

4. Praxis: Wrapper-Funktionen mit transparenter Argumentweiterleitung

Ein klassischer Anwendungsfall für die Kombination aus Variadic-Parameter und Spread-Operator ist eine Wrapper-Funktion, die zusätzliches Verhalten um eine bestehende Funktion herum hinzufügt, ohne deren Signatur exakt kopieren zu müssen. Typische Beispiele sind Logging-Wrapper, die vor und nach dem eigentlichen Aufruf Timing-Informationen erfassen, oder Caching-Wrapper, die das Ergebnis eines teuren Aufrufs zwischenspeichern, bevor sie es zurückgeben.

Der entscheidende Vorteil gegenüber einer manuell aufgezählten Parameterliste ist, dass sich die Wrapper-Funktion nicht mehr ändern muss, wenn sich die Signatur der umschlossenen Funktion ändert, solange nur zusätzliche Parameter hinzukommen. Das reduziert Wartungsaufwand deutlich, besonders bei Bibliotheks-Code, der mehrere unterschiedliche Zielfunktionen mit variierenden Signaturen umschließen soll.


function withTiming(callable $fn, mixed ...$args): mixed
{
    $start = microtime(true);
    $result = $fn(...$args); // Weiterreichen aller gesammelten Argumente
    $elapsed = microtime(true) - $start;

    error_log(sprintf('Aufruf dauerte %.4f Sekunden', $elapsed));

    return $result;
}

$result = withTiming('array_sum', [1, 2, 3, 4, 5]);
$result = withTiming(fn (int $a, int $b) => $a * $b, 6, 7);

5. Fluent-Builder mit variabler Argumentzahl

Ein zweiter praxisnaher Anwendungsfall sind Fluent-Builder, deren Methoden eine variable Anzahl an Werten entgegennehmen sollen, etwa ein Query-Builder, der beliebig viele Spalten für eine SELECT-Klausel oder beliebig viele Werte für eine WHERE-IN-Bedingung akzeptiert. Ohne Variadic-Parameter müsste der Aufrufer entweder immer explizit ein Array übergeben, oder die Methode müsste mit mehreren überladenen Signaturen für unterschiedliche Argumentanzahlen nachgebildet werden, was PHP grundsätzlich nicht unterstützt.

Mit einem Variadic-Parameter kann derselbe Builder sowohl mit einzelnen Werten als auch, mittels Spread-Operator beim Aufruf, mit einem bereits vorhandenen Array flexibel genutzt werden. Das macht die API gleichzeitig ergonomisch für den häufigen Fall weniger Werte und kompatibel mit dynamisch zur Laufzeit ermittelten Wertlisten.


final class QueryBuilder
{
    private array $wheres = [];

    public function whereIn(string $column, mixed ...$values): self
    {
        $this->wheres[] = [$column, 'IN', $values];
        return $this;
    }
}

$builder = new QueryBuilder();
$builder->whereIn('status', 'active', 'pending'); // einzelne Werte

$allowedIds = [3, 7, 12, 19];
$builder->whereIn('id', ...$allowedIds); // Spread eines bestehenden Arrays

6. Typisierung von Variadic-Parametern und Referenz-Übergabe

Ein Variadic-Parameter kann wie jeder andere Parameter typisiert werden, inklusive Union-Types und Nullable-Types. PHP prüft dabei jedes einzelne übergebene Argument gegen den deklarierten Typ, ein Typfehler bei nur einem von zehn Argumenten führt bereits zu einem TypeError für den gesamten Aufruf. Das macht Variadic-Parameter deutlich sicherer als ein ungetypter array $args-Parameter, bei dem einzelne Elementtypen erst zur Laufzeit innerhalb der Funktion geprüft werden könnten.

Weniger bekannt ist, dass ein Variadic-Parameter auch als Referenz deklariert werden kann, etwa function double(int &...$numbers). In diesem Fall werden alle übergebenen Variablen per Referenz eingesammelt, Änderungen innerhalb der Funktion wirken sich direkt auf die ursprünglichen Variablen des Aufrufers aus. Diese Kombination ist selten, aber etwa für In-Place-Transformationen mehrerer Variablen gleichzeitig nützlich, sofern der Aufrufer tatsächlich Variablen und keine literalen Werte übergibt.


function double(int &...$numbers): void
{
    foreach ($numbers as &$number) {
        $number *= 2;
    }
}

$a = 5;
$b = 10;
double($a, $b);
echo "{$a}, {$b}"; // 10, 20

7. Vergleich zu call_user_func_array: der historische Weg

Vor der Einführung des Spread-Operators beim Funktionsaufruf in PHP 5.6 war call_user_func_array() das einzige Werkzeug, um ein Array dynamisch als Argumentliste an eine Funktion zu übergeben. Die Funktion nimmt ein Callable und ein Array entgegen und ruft das Callable mit den Array-Elementen als einzelnen Argumenten auf, funktional vergleichbar mit dem heutigen Spread-Operator, allerdings mit spürbar höherem Aufruf-Overhead durch die zusätzliche Funktionsauflösung.

In modernem PHP-8.4-Code sollte call_user_func_array() nur noch dort verwendet werden, wo das Callable selbst zur Laufzeit dynamisch als Variable oder String vorliegt und nicht direkt aufrufbar geschrieben werden kann, etwa bei generischen Dispatch-Mechanismen. Für den Normalfall, ein bekanntes Callable mit einem Array aus Argumenten aufzurufen, ist die Spread-Syntax $fn(...$args) sowohl lesbarer als auch geringfügig performanter, da sie ohne den Umweg über die interne Callable-Auflösungslogik der Funktion auskommt.


// Historisch, vor PHP 5.6:
$result = call_user_func_array('array_sum', [[1, 2, 3]]);

// Moderne Variante mit Spread:
$result = array_sum(...[[1, 2, 3]]); // unüblich, meist reicht array_sum([1, 2, 3])

$fn = 'sprintf';
$args = ['%s ist %d Jahre alt', 'Anna', 32];
echo $fn(...$args); // Anna ist 32 Jahre alt

8. Einschränkungen: Position, Standardwerte und Kombination mit regulären Parametern

Ein Variadic-Parameter muss immer der letzte Parameter in einer Funktionssignatur sein, weitere Parameter danach sind syntaktisch nicht zulässig, da PHP sonst nicht eindeutig bestimmen könnte, wo die variable Argumentliste endet und ein weiterer regulärer Parameter beginnt. Reguläre Parameter vor dem Variadic-Parameter sind dagegen problemlos möglich und werden bei einem Aufruf zuerst positional beziehungsweise per Name aufgelöst, bevor die restlichen Argumente in den Variadic-Parameter fließen.

Ein Variadic-Parameter kann außerdem keinen eigenen Standardwert erhalten, da er implizit bereits mit einem leeren Array vorbelegt ist, wenn keine zusätzlichen Argumente übergeben werden. Wird versucht, dennoch einen expliziten Default zu deklarieren, meldet PHP einen Parse-Fehler. Diese Regel ist konsistent damit, dass ein Variadic-Parameter konzeptionell ohnehin schon optional ist, in dem Sinne, dass null oder beliebig viele Argumente akzeptiert werden.

9. Wann sich Variadic-Parameter im Alltag wirklich lohnen

Variadic-Parameter lohnen sich immer dann, wenn eine Funktion konzeptionell eine unbestimmte, aber homogen typisierte Anzahl an Argumenten gleicher Rolle entgegennehmen soll, etwa mehrere zu summierende Zahlen, mehrere zu loggende Nachrichten oder mehrere Spaltennamen für eine Datenbankabfrage. Sie sind die klar bessere Wahl gegenüber einem einzelnen Array-Parameter, wenn der typische Aufruf mit wenigen, direkt im Code sichtbaren Werten erfolgt, weil sie ohne zusätzliche eckige Klammern auskommen und die IDE-Unterstützung durch Typisierung erhalten bleibt.

Weniger geeignet sind Variadic-Parameter, wenn die übergebenen Werte unterschiedliche Rollen erfüllen, etwa ein Name und ein Alter in derselben Argumentliste, denn hier fehlt jede semantische Struktur, die ein assoziatives Array oder ein dediziertes Datenobjekt bieten würde. In solchen Fällen sind Named Arguments mit regulären, klar benannten Parametern oder ein eigenes Value-Object die robustere und selbstdokumentierende Alternative.

Kontext Bedeutung von ... Beispiel Ergebnis
In Funktionssignatur Variadic-Parameter, sammelt ein function f(int ...$n) $n ist ein Array aller Argumente
Beim Funktionsaufruf Spread-Operator, entpackt f(...$array) Array-Elemente werden einzelne Argumente
Mit Named Arguments Benannte Extra-Argumente landen im Variadic-Array f(a: 1, b: 2) mit variadic $args $args = ['a' => 1, 'b' => 2]
Als Referenz Variablen werden per Referenz eingesammelt function f(int &...$n) Änderungen wirken auf Aufrufer-Variablen
func_get_args() Alter Zugriff ohne Typsicherheit innerhalb jeder Funktion nutzbar Ungetyptes Array, keine IDE-Hilfe

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

Variadic-Funktionen: Das Wichtigste auf einen Blick

Zwei Bedeutungen

...$args in der Signatur sammelt ein, ...$array beim Aufruf entpackt.

Typsicherheit

Variadic-Parameter ersetzen func_get_args mit vollständiger Typprüfung pro Argument.

Named Arguments

Unbekannte benannte Argumente landen mit Namen als Schlüssel im Variadic-Array.

Praxis

Wrapper-Funktionen und Fluent-Builder profitieren am meisten von variabler Argumentzahl.

11. FAQ: Variadic-Funktionen: Das Wichtigste auf einen Blick

1Was ist der Unterschied zwischen ...$args in der Signatur und beim Aufruf?
In der Signatur sammelt der Variadic-Parameter beliebig viele Argumente in ein Array ein. Beim Aufruf entpackt derselbe Operator ein bestehendes Array in einzelne Argumente.
2Ersetzt ein Variadic-Parameter func_get_args vollständig?
Für die überwiegende Mehrheit der Fälle ja, da er zusätzlich Typsicherheit und IDE-Unterstützung bietet. func_get_args bleibt nur für seltene Sonderfälle relevant.
3Wie verhalten sich Named Arguments bei Funktionen mit Variadic-Parameter?
Named Arguments, die keinem regulären Parameter entsprechen, landen mit ihrem Namen als String-Schlüssel im Variadic-Array statt als numerisch indiziertes Element.
4Kann ein Variadic-Parameter typisiert werden?
Ja, wie jeder andere Parameter auch, inklusive Union-Types. PHP prüft dabei jedes einzelne übergebene Argument individuell gegen den deklarierten Typ.
5Kann ein Variadic-Parameter per Referenz deklariert werden?
Ja, mit der Syntax type &...$name. Alle übergebenen Variablen werden dann per Referenz eingesammelt, Änderungen wirken sich direkt auf die Variablen des Aufrufers aus.
6Muss ein Variadic-Parameter der letzte Parameter sein?
Ja, weitere Parameter danach sind syntaktisch nicht zulässig, da PHP sonst nicht eindeutig bestimmen könnte, wo die variable Argumentliste endet.
7Kann ein Variadic-Parameter einen eigenen Standardwert erhalten?
Nein, er ist implizit bereits mit einem leeren Array vorbelegt. Ein expliziter Default führt zu einem Parse-Fehler.
8Wofür eignen sich Wrapper-Funktionen mit Variadic-Parameter besonders gut?
Für Logging- oder Caching-Wrapper, die zusätzliches Verhalten um beliebige Zielfunktionen legen, ohne deren Signatur exakt nachbilden zu müssen und ohne bei Signaturänderungen angepasst werden zu müssen.
9Wie profitieren Fluent-Builder von Variadic-Parametern?
Methoden wie whereIn können sowohl mit einzelnen, direkt geschriebenen Werten als auch per Spread-Operator mit einem bereits vorhandenen Array aufgerufen werden, ohne zwei separate Methoden zu benötigen.
10Wann sind Variadic-Parameter die falsche Wahl?
Wenn die übergebenen Werte unterschiedliche semantische Rollen erfüllen. Dann sind benannte reguläre Parameter oder ein dediziertes Value-Object die robustere und selbstdokumentierende Alternative.