...$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.
Inhaltsverzeichnis
- 1. Parameter-Deklaration versus Aufruf: zwei unterschiedliche Bedeutungen
- 2. Variadic-Parameter als typsicherer Ersatz für func_get_args
- 3. Named Arguments in Kombination mit Variadics
- 4. Praxis: Wrapper-Funktionen mit transparenter Argumentweiterleitung
- 5. Fluent-Builder mit variabler Argumentzahl
- 6. Typisierung von Variadic-Parametern und Referenz-Übergabe
- 7. Vergleich zu call_user_func_array: der historische Weg
- 8. Einschränkungen: Position, Standardwerte und Kombination mit regulären Parametern
- 9. Wann sich Variadic-Parameter im Alltag wirklich lohnen
- 10. Zusammenfassung
- 11. FAQ
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.