Contact salesFree trial
Blog

PHP 8.0 benannte Argumente

PHPMerkmaleVerbesserungen
19 November 2020
Larry Garfield
Direktor für Entwicklererfahrung
Teilen Sie

In jeder Programmiersprache, die über Funktionen verfügt (d. h. alle), können Funktionen aufgerufen werden, indem ihnen eine geordnete Liste von Argumenten als Eingabeparameter übergeben wird. Das funktioniert ganz gut, hat aber einige Nachteile, die nicht ideal sind. Insbesondere, wenn es mehr als einen Parameter gibt, kann man leicht vergessen, in welcher Reihenfolge sie stehen oder was ein bestimmter Parameter bedeutet. (Und selbst wenn es nur einen gibt, kann es sein, dass es am Aufrufort nicht offensichtlich ist, was er bedeutet).

Es gibt verschiedene Umgehungsmöglichkeiten, wie z.B. die Übergabe eines assoziativen Arrays anstelle von diskreten Parametern, die aber alle ihre eigenen Probleme mit sich bringen. Die Übergabe eines assoziativen Arrays umgeht zum Beispiel jegliche Typsicherheit.

Einige wenige Sprachen gehen dieses Problem an, indem sie es dem Aufrufer erlauben (normalerweise optional), Parameter nach Namen und nicht nach Position zu spezifizieren. PHP 8.0 ist jetzt eine dieser Sprachen.

Benannte Parameter

In PHP werden benannte Parameter vollständig durch den Aufrufer kontrolliert. Alle Funktionen und Methoden unterstützen automatisch benannte Parameter. Wenn die Funktion aufgerufen wird, kann der Aufrufer wählen, ob er ein Positionsargument, ein benanntes Argument oder sogar eine Kombination aus beidem verwenden möchte.

Ein Beispiel: Die PHP-Funktion array_fill() erzeugt ein neues Array mit einer bestimmten Größe, bei dem alle Elemente auf denselben Startwert gesetzt werden. Etwa so:

<?php $new = array_fill(0, 100, 50);

Wenn ich das so lese ... was bedeuten diese Zahlen? Wird $new ein Array mit 100 Elementen aus 50 oder ein Array mit 50 Elementen aus 100 sein? Das ist nicht offensichtlich, es sei denn, Sie wissen, wie array_fill() funktioniert.

Mit benannten Parametern kann man es stattdessen so aufrufen:

<?php array_fill(start_index: 0, count: 100, value: 50);

Jetzt ist klar, was wir zurückbekommen: 100 Array-Elemente, deren Schlüssel bei 0 beginnen und deren Werte alle auf 50 gesetzt sind. Sie können die Parameter aber auch so anordnen, wie Sie wollen:

<?php array_fill( value: 50, count: 100, start_index: 0, );

Die Namen der Argumente werden unabhängig von der Reihenfolge mit den Parameternamen in der Funktionsdefinition abgeglichen. In diesem Beispiel haben wir die Parameter auch in vertikaler Form dargestellt, was in Ordnung ist, und ein Komma am Ende des letzten Parameters gezeigt. (Das ist, wie erwähnt, neu in PHP 8.0 und unterstützt genau diesen Fall).

Parameternamen müssen Literale sein; Sie können keine Variablen als Parameternamen verwenden.

Es ist auch möglich, nur bestimmte Parameter namentlich und andere positionsbezogen anzugeben. Genauer gesagt, können Sie Parameter an der Position auflisten, bis Sie zu benannten Argumenten wechseln und danach benannte Argumente verwenden. Dies ist also völlig in Ordnung:

<?php array_fill(0, value: 50, count: 100, );

Aber dies ist nicht:

<?php array_fill( value: 50, 0, count: 100, );

Benannte Variadik

Eine knifflige Frage bei benannten Parametern ist die nach den Variadics. "Variadics" ist der schicke Name für die Möglichkeit, einer Funktion eine variable Anzahl von Parametern zu übergeben. In PHP kann man das schon lange tun:

<?php include_these($product, 1, 2, 3, 4, 5); function include_these(Product $product, int ...$args) { // $args ist jetzt ein Array von Ints. $vals = ['a', 'b']; do_other_thing(...$vals); }

Hier sammelt der ...- Operator, der liebevoll "splat" genannt wird, je nach Kontext entweder Argumente in einem Array oder verteilt sie in einem Array. Aber wie interagiert das mit benannten Argumenten?

Die Art und Weise, wie sie interagieren, entspricht meiner Meinung nach dem, was man logischerweise erwarten würde. Ein indiziertes Array, das ausgebreitet wird, wird als Positionsargumente ausgebreitet. Ein assoziatives Array, das sich ausbreitet, wird sich als benannte Argumente ausbreiten. Um das frühere Beispiel fortzusetzen:

<?php // Dies ist ein benanntes Array, also werden die Werte benannten Parametern zugeordnet. $params = ['count' => 100, 'start_index' => 0, 'value' => 50]; array_fill(...$params);

Beim Sammeln von variablen Argumenten werden diese entweder als numerisch indizierte Array-Werte gesammelt, wenn sie positionell übergeben werden, oder als String-Schlüssel-Array-Werte, wenn sie namentlich übergeben werden. Seien Sie sich bewusst, dass, wenn Sie einen variadischen Parameter haben, das bedeutet, dass Sie ein Array haben können, das eine Mischung aus numerischen und benannten Schlüsseln ist.

Das führt auch zu interessanten Möglichkeiten, einen Funktionsaufruf dynamisch aufzubauen, indem man zuerst ein assoziatives Array dynamisch aufbaut und dann die Funktion mit diesem mittels splat aufruft.

<?php $args['value'] = $request->get('val') ?? 'a'; $args['start_index'] = 0; $args['count'] = $config->getSetting('array_size'); $array = array_fill(...$args);

Beschränkungen

Der Hauptvorwurf gegen benannte Argumente war und ist, dass es "zu einfach" sei, Funktionen mit vielen Argumenten zu haben, was zu einem schlechten API-Design führen würde. Zum Beispiel ist dieser Methodenaufruf zweifelsohne schwer zu verstehen:

<?php read_value($object, true, false, false, false, true, true, true, 7, false, true);

Eine solche API sollte wirklich umgestaltet werden. Die Befürchtung ist, dass benannte Parameter den API-Designern die Möglichkeit geben würden, zu sagen: "Nun, benutzt einfach Namen für sie, dann könnt ihr sogar alle Standardwerte, die euch nicht interessieren, auf diese Weise überspringen." Das ist richtig, geht aber auch an der Tatsache vorbei, dass das Problem darin besteht, dass die API zu kompliziert ist.

Es gibt hier keine wirksame Vorbeugung, außer: "Benutzen Sie dies nicht als Krücke, um schlechte APIs zu erstellen", was man über fast jede Sprachfunktion sagen könnte.

Aber ... warum?

Benannte Parameter wurden erstmals 2013 ernsthaft vorgeschlagen, haben sich aber bis jetzt nicht durchgesetzt. Was sie wirklich wieder in den Vordergrund rückte, waren mehrere Diskussionen darüber, wie man die Konstruktion von Objekten vereinfachen kann. Anfang 2020 gab es mehrere Vorschläge für spezielle, einmalige Syntaxen für einfachere "struct"-Objekte, d. h. Objekte, die nur eine Sammlung von wahrscheinlich öffentlichen Eigenschaften sind.

Sprachen wie Go und Rust machen es sehr einfach, Structs mit benannten Parametern zu erstellen, weil sie technisch gesehen keine Objekte haben, so wie es PHP hat. Sie haben eine Struktur mit Eigenschaften, die man sogar als Strukturliteral definieren kann, und dann kann man Methoden an sie hängen. Das Ergebnis ist ähnlich, aber nicht ganz dasselbe wie die Java/C++/PHP-Objektform. Dennoch sind viele Leute zu Recht neidisch darauf, wie einfach diese Sprachen es machen, neue komplexe Strukturen zu erstellen.

Keine der vorgeschlagenen einmaligen Syntaxen hat das Problem wirklich in einer Weise gelöst, die zu PHP "passt". Mehrere Leute, mich eingeschlossen, merkten jedoch an, dass ein robusterer Ansatz darin bestünde, die zugrundeliegenden Probleme so zu lösen, dass eine schönere Syntax für die Konstruktion von Objekten auf natürliche Weise "herausfallen" könnte.

Ich habe das Argument dafür in einem Blogbeitrag im März ausführlich dargelegt. Kurz gesagt, die Förderung von Konstruktoreigenschaften und benannte Argumente würden uns zusammen effektiv eine Syntax für die Strukturinitialisierung geben. Das Ganze wäre mehr als die Summe seiner Teile. Glücklicherweise stimmte mir der vielbeschäftigte Nikita Popov zu und nahm den Ball auf und führte ihn weiter.

Das bringt uns zu einem der wenigen Orte, an denen benannte Argumente wirklich verwendet werden sollten: die Konstruktion von struct-Objekten.

Betrachten wir zur Veranschaulichung ein Wertobjekt für ein PSR-7-URL-Objekt.

<?php class Url implements UrlInterface { public function __construct( private string $scheme = 'https', private ?string $authority = null, private ?string $userInfo = null, private string $host = '', private ?int $port = 443, private string $path = '', private ?string $query = null, private ?string $fragment = null, ) {} }

Wir haben bereits gesehen, wie die Konstruktor-Promotion das Schreiben erheblich erleichtert. Benannte Argumente machen es auch viel einfacher zu benutzen, da viele dieser Parameter legitimerweise in einer gültigen URL fehlen können, einschließlich derer am Anfang der Liste wie $authority. In PHP 7.4 müssten Sie es wie folgt verwenden:

<?php $url = new Url('https', null, null, 'platform.sh', 443, '/blog', null, 'latest');

Das ist ... eklig, aber man muss das tun, um an die späteren Argumente zu kommen. Mit benannten Parametern lässt sich das auf diese kürzere und besser zu dokumentierende Alternative reduzieren:

<?php $url = new Url(host: 'platform.sh', path: '/blog', fragment: 'latest'); // Oder wenn Sie es lieber vertikal haben: $url = new Url( path: '/blog', host: 'platform.sh', fragment: 'latest', );

Sie können die Parameter dann in beliebiger Reihenfolge zum Booten verwenden. Die beiden Funktionen ergänzen sich gegenseitig und machen die Arbeit mit leichtgewichtigen Objekten wesentlich sauberer als in früheren Versionen. Konstruktoren für Wertobjekte sind meiner Meinung nach einer der drei wichtigsten Orte für die Verwendung benannter Argumente.

Argumente in Attributen

Das zweite wichtige Ziel sind Attribute:

<?php class SomeController { #[Route('/path', name: 'action')] public function someAction() { // ... } }

Attribute sind syntaktisch ein Aufruf eines Objektkonstruktors, der bei Bedarf über die Reflection-API aufgerufen wird. Da es ein Konstruktoraufruf ist, kann er (fast) alles tun, was ein Konstruktoraufruf tun kann, einschließlich der Verwendung benannter Argumente.

Ich sage sogar voraus, dass die meisten Attribute benannte Argumente verwenden werden. Es geht darum, flexible Metadaten innerhalb der Syntax der Sprache selbst verfügbar zu machen. Viele Doctrine-Anmerkungen stützen sich heute sehr stark auf benannte Schlüssel, da sie besser selbstdokumentierend und flexibler sind, wenn es viele optionale Argumente gibt. Es ist logisch zu erwarten (und zu fördern), dass Attribute demselben Muster folgen.

Benannte Argumente über Argumente

Der dritte Ort, an dem ich erwarte, dass benannte Argumente häufig verwendet werden, sind Funktionen, bei denen die Parameter legitimerweise benötigt werden und legitimerweise verwirrend sind, und bei denen es keinen offensichtlichen Weg gibt, sie zu umgehen. Das array_fill() -Beispiel von vorhin ist ein gutes Beispiel dafür.

Ein weiteres gutes Beispiel? Jedermanns Lieblingsspruch, $haystack, $needle vs. $needle, $haystack. String-Funktionen verwenden in der Regel die eine Reihenfolge, Array-Funktionen in der Regel die andere, aus Gründen, die sinnvoll sind, wenn Sie Ihre API auf C modellieren, aber sonst nicht. Jetzt müssen Sie sich nicht mehr an die Reihenfolge erinnern.

<?php if (in_array(haystack: $arr, needle: 'A')) { // ... }

Ist das die Reihenfolge, in der die Parameter in der Funktion stehen? Wen kümmert das? Der Funktionsaufruf gibt genau an, was übergeben wird, so dass die Reihenfolge irrelevant ist. (Sie sind nicht in der Reihenfolge, falls Sie sich das gefragt haben.)

Endlich können wir aufhören, uns über die Reihenfolge der Parameter zu beschweren und dies als Grund anzuführen, warum PHP schlecht ist. (Ich bin sicher, dass die Leute das auch weiterhin tun werden; sie werden nur noch falscher liegen, als sie es ohnehin schon taten.)

API-Implikationen

Eine Warnung jedoch. Wie bereits erwähnt, funktionieren benannte Argumente für alle Funktionen und Methoden automatisch. Das bedeutet, dass die Parameternamen in Funktionen, Methoden und Schnittstellen jetzt API-relevant sind. Eine Änderung dieser Parameter kann für Benutzer, die sie namentlich aufrufen, zum Problem werden. (Dies wäre ein guter Zeitpunkt, um Ihre Parameternamen zu überdenken und gleichzeitig zu überprüfen, ob Ihre Bibliotheken für PHP 8.0 bereit sind).

Die Bedeutung von Variablennamen wird bei der Vererbung derzeit nicht erzwungen. Das ist eine pragmatische Entscheidung, um zu vermeiden, dass zu viel bestehender Code beschädigt wird, da die überwiegende Mehrheit der Methoden in 99,9 % der Fälle nicht mit benannten Parametern aufgerufen wird, so dass es keinen Sinn macht, bestehenden Code, der Argumente aus völlig logischen Gründen umbenennt, noch mehr zu beschädigen. Python und Ruby verfolgen den gleichen Ansatz und hatten keine ernsthaften Probleme, was PHP als ein gutes Zeichen dafür sah, dass es sicher war, das Gleiche zu tun.

Es sollte nicht überraschen, dass der RFC für benannte Argumente mit freundlicher Genehmigung von Nikita Popov zu uns kommt.

Ihr größtes Werk
steht vor der Tür

Kostenloser Test
Discord
© 2025 Platform.sh. All rights reserved.