Ein Blick auf PHP 8: Teil 10

PHP 8.0-Features im Fokus: Named Arguments
Keine Kommentare

PHP 8 wurde veröffentlicht und mit ihm eine Reihe von Verbesserungen, neuen Funktionen und einer generellen Überarbeitung, um die weit verbreitete serverseitige Websprache noch besser zu machen. In dieser Serie wollen wir darauf eingehen, was Sie über die neue Version wissen sollten.

In der letzten Woche haben wir über das kontrovers diskutierte Attribute-Feature von PHP 8.0 gesprochen. In unserer zehnten und letzten Folge behandeln wir ebenfalls ein ehemals strittiges Thema, das es geschafft hat, eins der wichtigsten neuen Funktionen von PHP 8.0 zu werden: Named Arguments.

In jeder Programmiersprache, die über Funktionen verfügt (also alle), können diese aufgerufen werden, indem man ihnen eine geordnete Liste von Argumenten als Eingabeparameter übergibt. Das funktioniert recht gut, hat aber einige Ausnahmen, bei denen das nicht ideal ist. Wenn es zum Beispiel mehr als einen Parameter gibt, kann schnell die Reihenfolge oder die Bedeutung durcheinander gebracht werden. Und selbst wenn es nur einen gibt, kann es sein, dass es an der Call Site nicht offensichtlich ist, was er bedeutet.

Es gibt verschiedene Workarounds, wie etwa 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 (normalerweise optional) erlauben, dass der Caller die Parameter über den Namen und nicht über die Position angibt. PHP 8.0 darf sich jetzt zu diesen Sprachen zählen.

Named Parameters

In PHP können Named Parameters vollständig kontrolliert werden. Alle Funktionen und Methoden sind automatisch named-parameter-unterstützend. Wenn die Funktion aufgerufen wird, kann gewählt werden, ob ein Positional Argument, ein Named Argument oder sogar eine Kombination aus beiden verwendet werden soll. Ein Beispiel: Die PHP-Funktion array_fill() erzeugt ein neues Array einer bestimmten Größe, bei dem alle Elemente auf den gleichen Startwert gesetzt werden. Dies kann etwa so aussehen:

<?php

$new = array_fill(0, 100, 50);

Was bedeuten diese Zahlen? Wird $new ein Array mit 100 Elementen von 50 oder ein Array mit 50 Elementen von 100 sein? Das ist nicht offensichtlich, es sei denn, Sie wissen, wie array_fill() funktioniert.
Mit Named Parametern kann stattdessen so gearbeitet werden:

<?php

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

Jetzt ist klar, was wir zurückbekommen: 100 Array-Elemente, wobei die Keys bei 0 beginnen und alle Werte auf 50 gesetzt sind. Aber der Nutzer kann die Parameter auch so anordnen, wie er möchte:

<?php

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

Die Namen der Argumente werden unabhängig von der Reihenfolge mit denen der Parameter in der Funktionsdefinition abgeglichen. In diesem Beispiel haben wir die Parameter auch in vertikaler Form dargestellt und ein nachgestelltes Komma am letzten Parameter. (Was neu in PHP 8.0 ist und genau diesen Fall unterstützt.) Die Namen der Parameter müssen Literale sein, sie können keine Variable in der Bezeichnung haben.

Es ist auch möglich, nur bestimmte Parameter über den Namen und andere über die Position anzugeben. Genauer gesagt, Sie können Parameter positionell auflisten, bis Sie zu benannten Argumenten wechseln. Folgendes ist also völlig in Ordnung:

<?php

array_fill(0,
  value: 50,
  count: 100, 
);

Hingegend ist dies nicht möglich:

<?php

array_fill(
  value: 50,
  0,
  count: 100, 
);

Named Variadics

Eine knifflige Frage bei benannten Parametern dreht sich um Variadics. „Variadics“ ist der Name für die Möglichkeit, eine variable Anzahl von Parametern an eine Funktion zu übergeben. Das ist in PHP schon lange möglich:

<?php

include_these($product, 1, 2, 3, 4, 5);

function include_these(Product $product, int ...$args)
{
    // $args is now an array of ints.

    $vals = ['a', 'b'];
    do_other_thing(...$vals);
}

Hier sammelt der ...-Operator, liebevoll splat genannt, je nach Kontext entweder Argumente in einem Array ein oder verteilt sie aus einem Array heraus. Aber wie interagiert das mit Named Arguments? Die Art und Weise, wie sie interagieren, würde man logischer Weise erwarten. Ein indiziertes Array, welches exportiert, wird als Positionsargumente exportiert. Ein assoziatives Array, wird als Named Arguments exportiert. Um das frühere Beispiel fortzusetzen:

<?php

// This is a named array, so the values will map to named parameters.
$params = ['count' => 100, 'start_index' => 0, 'value' => 50];
array_fill(...$params);

Wenn Variadic Arguments gesammelt werden, werden sie entweder als numerisch indizierte Array-Werte gesammelt (wenn sie nach Position übergeben werden) oder als Array-Werte mit String Keyed Array Values, wenn sie mit Namen übergeben werden. Man sollte sich ins Gedächtnis rufen, dass es sich bei Variadic Parameters um ein Array handelt, das aus einer Mischung von numerischen und benannten Keys besteht. Das führt auch zu interessanten Möglichkeiten, einen Funktionsaufruf dynamisch aufzubauen. Etwa 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);

Begrenzungen

Der Hauptvorwurf gegen Named Arguments war und ist, dass es „zu einfach“ sei, Funktionen mit vielen Argumenten zu haben, und dass dies ein schlechtes API-Design fördern würde. Zum Beispiel ist dieser Methodenaufruf ohne Frage schwer zu verstehen:

<?php

read_value($object, true, false, false, false, true, true, 7, false, true);

Ein solche API sollte wirklich neu entworfen werden. Die Befürchtung ist, dass Named Parameters den API-Designern die Möglichkeit geben würden, zu sagen: „Naja, benutze einfach Namen für sie, dann kannst du sogar alle Voreinstellungen, die dich nicht interessieren, auf diese Weise überspringen.“ Das ist wahr, geht aber auch an dem Punkt vorbei, dass die API zu kompliziert ist.

Es gibt hier kein starkes Präventivmittel außer „Benutzen Sie das nicht als Krücke, um schlechte APIs zu machen“, was man über fast jede Sprachfunktion sagen könnte.

Aber warum?

Named Parameter wurden erstmals 2013 vorgeschlagen, haben sich aber bis jetzt nicht durchgesetzt. Was sie wirklich wieder zum Gespräch machte, waren mehrere Diskussionen darüber, wie man die Konstruktion von Objekten einfacher machen kann. Anfang 2020 wurden mehrere Vorschläge für spezielle, einmalige Syntaxen für einfachere „struct“-Objekte gemacht, also für Objekte, die nur eine Sammlung von wahrscheinlich öffentlichen Properties sind.

International PHP Conference

Frameworkless – the new black?

by Carsten Windler (KW-Commerce GmbH)

Getting started with PHP-FFI

by Thomas Bley (Bringmeister GmbH)

JSON-Schema von 0 auf 100

by Ingo Walther (Electronic Minds GmbH)

IT Security Camp 2021

Von der Cloud über DevSecOps bis hin zu Datenschutzproblem von WhatsApp

Christian Schneider spricht im Interview über aktuelle Security-Trends. Welche Themen rücken besonders heute stark in den Security-Fokus, warum alles rund um die Cloud immer mehr an Bedeutung gewinnt, welche Aspekte in der Cyberabwehr effektiv sind u.v.m.

IT-Security Experte, Christian Schneider

Christian ist als freiberuflicher White Hat Hacker, Trainer und Security-Coach tätig. Als Softwareentwickler mit mittlerweile 20 Jahren Erfahrung, fand er 2005 seinen Themenschwerpunkt im Bereich IT-Security.


Sprachen wie Go und Rust machen es sehr einfach, Structs mit Named Parameters zu erstellen, weil sie technisch gesehen keine Objekte haben, wie sie in PHP vorkommen. Sie haben eine Struktur mit Properties, die sogar als strukturelles Literal definiert und an die Methoden gehängt werden können. Das Endergebnis ist ähnlich, aber nicht ganz dasselbe wie der Java/C++/PHP-Stil von Objekten. Trotzdem sind viele Leute zu Recht neidisch darauf, wie einfach diese Sprachen es machen, neue komplexe Strukturen zu erstellen. Keine der vorgeschlagenen Syntaxen, hat das Problem wirklich auf eine Weise angegangen, die zu PHP „passte“. Mehrere Leute, mich eingeschlossen, merkten jedoch an, dass ein robusterer Ansatz darin bestünde, die zugrundeliegenden Probleme so zu lösen, dass eine bessere Syntax für die Objektkonstruktion auf natürliche Weise „herausfällt“.

Ich habe das Argument dafür in einem Blog-Beitrag im März im Detail dargelegt. Kurz gesagt, Constructor Property Promotion plus Named Arguments würden uns zusammen effektiv eine Syntax für die Struct-Initialization-Syntax geben. Das Ganze wäre größer als die Summe seiner Teile. Glücklicherweise stimmte mir der vielbeschäftigte Nikita Popov zu und nahm den Ball auf.

Das bringt uns zu einem der wenigen Anwendungsfälle, an denen benannte Argumente wirklich verwendet werden sollten: die Erstellung von struct-Objekten. Zur Veranschaulichung betrachten wir 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 Constructor Promotion das Schreiben erheblich erleichtert. Named Arguments machen es auch viel einfacher, 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');

Dies ist nun alles andere als perfekt, aber es muss auf diese Weise gelöst werden, um an die späteren Argumente zu gelangen. Mit Named Parameters wird das zu dieser kürzeren und selbstdokumentierenden Alternative:

<?php

$url = new Url(host: 'platform.sh', path: '/blog', fragment: 'latest');

// Or if you prefer vertical:

$url = new Url(
    path: '/blog', 
    host: 'platform.sh', 
    fragment: 'latest',
);

Sie können die Parameter auch in beliebiger Reihenfolge angeben. Die beiden Funktionen ergänzen sich gegenseitig und machen die Arbeit mit leichtgewichtigen Objekten wesentlich sauberer als in früheren Versionen. Value Object Constructors sind, so würde ich argumentieren, einer der drei wichtigsten Anwendungsfälle, um Named Arguments zu verwenden.

Argumente in Attributen

Das zweite wichtige Ziel befindet sich in den Attributen. Im letzten Artikel hatten wir folgenden Fall:

<?php

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

Attribute sind syntaktisch ein Object Constructor Call, der bei Bedarf über das Reflection-API aufgerufen wird. Weil es ein Constructor Call ist, kann er (fast) alles tun, einschließlich der Verwendung von Named Arguments. Tatsächlich sage ich voraus, dass die meisten Attributverwendungen Named Arguments verwenden werden. Das große Ziel ist es, flexible Metadaten innerhalb der Syntax der Sprache selbst verfügbar zu machen. Viele Verwendungen von Doctrine-Annotationen verlassen sich heute sehr stark auf Named Keys, da sie selbstdokumentierender und flexibler sind, wenn es viele optionale Argumente gibt. Es ist zu erwarten (und zu fördern), dass Attribute dem gleichen Muster folgen.

Warum die Reihenfolge unwichtig wird

Der dritte Anwendungsfall, bei dem ich eine häufige Verwendung von Named Arguments erwarte, sind Funktionen, bei denen die Parameter benötigt werden und verwirrend sind und bei denen es keinen selbstverständlichen Weg gibt, diese zu umgehen. Das Beispiel array_fill() von vorhin ist ein gutes Beispiel dafür. Ein anderes gutes Beispiel? $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 das API nach C modelliert wird. Jetzt brauchen Sie sich die Reihenfolge nicht mehr zu merken.

<?php

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

Ist das die Reihenfolge, in der die Parameter in der Funktion stehen? Wen interessiert das schon? 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 das als Grund anzuführen, warum PHP schlecht ist. (Ich bin sicher, dass die Leute das immer noch tun werden; sie werden nur noch falscher liegen, als sie es ohnehin schon taten.)

API-Implikationen

Eine Warnung jedoch. Wie bereits erwähnt, funktionieren Named Arguments für alle Funktionen und Methoden automatisch. Das bedeutet, dass die Parameternamen in Funktionen, Methoden und Interfaces jetzt API-relevant sind. Wenn Sie diese ändern, können Benutzer, die sie namentlich aufrufen, daran scheitern. (Dies wäre ein guter Zeitpunkt, um Ihre Parameternamen zu überdenken und gleichzeitig sicherzustellen, dass 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 zerstört wird. Da die überwiegende Mehrheit der Methoden in 99,9% der Fälle nicht mit benannten Parametern aufgerufen werden, ergibt es keinen Sinn, noch mehr Probleme für bestehenden Code zu erzeugen, der Argumente aus völlig logischen Gründen umbenennen könnte. Python und Ruby verfolgen den gleichen Ansatz und sind nicht in ernsthafte Probleme geraten, was PHP als ein gutes Zeichen dafür sah, das gleiche zu tun.

Es sollte keine Überraschung sein, dass der Named Arguments RFC mit freundlicher Genehmigung von Nikita Popov bereitgestellt wurde.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Abonnieren
Benachrichtige mich bei
guest
0 Comments
Inline Feedbacks
View all comments
X
- Gib Deinen Standort ein -
- or -