PHP 8.0: Vollständiger Überblick über die Neuigkeiten (1/4)

vor 4 Jahren von David Grudl  

PHP Version 8.0 wird in diesem Moment veröffentlicht. Sie ist voll von neuen Dingen, wie keine andere Version zuvor. Ihre Einführung hat vier separate Artikel verdient. Im ersten werfen wir einen Blick darauf, was sie auf der Sprachebene bringt.

Bevor wir uns mit PHP befassen, sollten Sie wissen, dass die aktuelle Version von Nette vollständig auf die achte Version vorbereitet ist. Außerdem wurde als Geschenk ein vollständig kompatibles Nette 2.4 veröffentlicht, so dass aus Sicht des Frameworks nichts dagegen spricht, es zu verwenden.

Benannte Argumente

Fangen wir gleich mit einer Bombe an, die man getrost als Game Changer bezeichnen könnte. Argumente können an Funktionen und Methoden nun nicht mehr nur an der Position, sondern anhand ihres Namens übergeben werden. Was absolut cool ist, falls eine Methode wirklich zu viele Parameter hat:

class Response implements IResponse
{
	public function setCookie(
		string $name,
		string $value,
		string|DateInterface|null $time,
		string $path = null,
		string $domain = null,
		bool $secure = null,
		bool $httpOnly = null,
		string $sameSite = null
	) {
		...
	}
}

Die ersten beiden Argumente werden positionell übergeben, die anderen mit ihrem Namen: (named muss nach den positionellen folgen)

$response->setCookie('lang', $lang, sameSite: 'None');

// instead of the horrible
$response->setCookie('lang', $lang, null, null, null, null, null, 'None');

Vor der Einführung dieser Funktion gab es Pläne, eine neue API für das Senden von Cookies in Nette zu erstellen, da die Anzahl der Argumente für setCookie() stark anstieg und die positionale Notation verwirrend war. Das ist jetzt nicht mehr nötig, weil benannte Argumente in diesem Fall die bequemste API sind. Die IDE zeigt sie an und es gibt Typsicherheit.

Sie sind sogar ideal geeignet, um logische Argumente zu erklären, wo ihre Verwendung nicht notwendig ist, aber ein einfaches true oder false reicht nicht aus:

// before
$db = $container->getService(Database::class, true);

// now
$db = $container->getService(Database::class, need: true);

Die Namen der Argumente sind jetzt Teil der öffentlichen API. Es ist nicht mehr möglich, sie nach Belieben zu ändern. Aus diesem Grund wird auch bei Nette geprüft, ob alle Argumente einen passenden Namen haben.

Benannte Argumente können auch in Kombination mit Variadics verwendet werden:

function variadics($a, ...$args) {
	dump($args);
}

variadics(a: 1, b: 2, c: 3);
// $args will contain ['b' => 2, 'c' => 3]

Das Array $args kann nun auch nichtnumerische Schlüssel enthalten, was eine Art BC-Break darstellt. Das Gleiche gilt für das Verhalten von call_user_func_array($func, $args), wo die Schlüssel im Array $args jetzt eine viel wichtigere Rolle spielen. Im Gegensatz dazu sind die Funktionen der func_*() Familie von benannten Argumenten abgeschirmt.

Benannte Argumente stehen in engem Zusammenhang mit der Tatsache, dass der Splat-Operator ... nun assoziative Arrays expandieren kann:

variadics(...['b' => 2, 'c' => 3]);

Überraschenderweise funktioniert er derzeit nicht innerhalb von Arrays:

$arr = [ ...['a' => 1, 'b' => 2] ];
// Fatal error: Cannot unpack array with string keys

Die Kombination von benannten Argumenten und Variadics gibt uns die Möglichkeit, endlich eine feste Syntax zu haben, z.B. für die Methode link(), an die wir sowohl benannte als auch positionale Argumente übergeben können:

// before
$presenter->link('Product:detail', $id, 1, 2);
$presenter->link('Product:detail', [$id, 'page' => 1]); // had to be an array

// now
$presenter->link('Product:detail', $id, page: 1);

Die Syntax für benannte Argumente ist viel attraktiver als das Schreiben von Arrays, so dass Latte sie sofort übernommen hat, wo sie zum Beispiel in den Tags {include} und {link} verwendet werden kann:

{include 'file.latte' arg1: 1, arg2: 2}
{link default page: 1}

Wir werden auf benannte Argumente im dritten Teil der Serie im Zusammenhang mit Attributen zurückkommen.

Ein Ausdruck kann eine Ausnahme auslösen

Das Auslösen einer Ausnahme ist jetzt ein Ausdruck. Sie können ihn in Klammern einschließen und zu einer if Bedingung hinzufügen. Hmmm, das klingt nicht sehr praktisch. Aber das hier ist viel interessanter:

// before
if (!isset($arr['value'])) {
	throw new \InvalidArgumentException('value not set');
}
$value = $arr['value'];


// now, when throw is an expression
$value = $arr['value'] ?? throw new \InvalidArgumentException('value not set');

Da Pfeilfunktionen bisher nur einen Ausdruck enthalten konnten, können sie nun dank dieser Funktion Ausnahmen auslösen:

// only single expression
$fn = fn() => throw new \Exception('oops');

Match-Ausdruck

Die Anweisung switch-case hat zwei große Nachteile:

  • sie verwendet einen nicht-strikten Vergleich == anstelle von ===
  • man muss aufpassen, dass man nicht versehentlich break vergisst

Aus diesem Grund bietet PHP eine Alternative in Form des neuen Ausdrucks match an, der einen strengen Vergleich verwendet und umgekehrt break nicht benutzt.

switch Code-Beispiel:

switch ($statusCode) {
    case 200:
    case 300:
        $message = $this->formatMessage('ok');
        break;
    case 400:
        $message = $this->formatMessage('not found');
        break;
    case 500:
        $message = $this->formatMessage('server error');
        break;
    default:
        $message = 'unknown status code';
        break;
}

Und dasselbe (nur mit strengem Vergleich), geschrieben mit match:

$message = match ($statusCode) {
    200, 300 => $this->formatMessage('ok'),
    400 => $this->formatMessage('not found'),
    500 => $this->formatMessage('server error'),
    default => 'unknown status code',
};

Beachten Sie, dass match keine Kontrollstruktur wie switch ist, sondern ein Ausdruck. In diesem Beispiel weisen wir den resultierenden Wert einer Variablen zu. Gleichzeitig sind die einzelnen “Optionen” auch Ausdrücke, so dass es nicht möglich ist, mehrere Schritte zu schreiben, wie im Fall von switch.

Wenn es keine Übereinstimmung gibt (und es gibt keine Standardklausel), wird die Ausnahme UnhandledMatchError ausgelöst.

Übrigens gibt es in Latte auch die Tags {switch}, {case} und {default}. Ihre Funktion entspricht genau der des neuen match. Sie verwenden einen strikten Vergleich, benötigen keine break und es ist möglich, mehrere durch Kommas getrennte Werte in case anzugeben.

Nullsafe-Operator

Die optionale Verkettung ermöglicht es Ihnen, einen Ausdruck zu schreiben, dessen Auswertung stoppt, wenn er auf null trifft. Das ist dank des neuen ?-> Operators möglich. Er ersetzt eine Menge Code, der sonst immer wieder auf null geprüft werden müsste:

$user?->getAddress()?->street

// approximately translates to
$user !== null && $user->getAddress() !== null
	? $user->getAddress()->street
	: null

Warum “ungefähr”? Weil der Ausdruck in Wirklichkeit raffinierter ausgewertet wird, so dass kein Schritt wiederholt wird. Zum Beispiel wird $user->getAddress() nur einmal aufgerufen, so dass das Problem, dass die Methode beim ersten und zweiten Mal etwas anderes zurückgibt, nicht auftritt.

Diese Funktion wurde von Latte vor einem Jahr eingeführt. Jetzt wird sie von PHP selbst übernommen. Großartig.

Förderung von Konstruktoreigenschaften

Syntaktischer Zucker, der es erspart, den Typ zweimal und die Variable viermal zu schreiben. Schade, dass es nicht zu einer Zeit kam, als wir noch keine so cleveren IDEs hatten, die das heute für uns schreiben 🙂

class Facade
{
	private Nette\Database\Connection $db;
	private Nette\Mail\Mailer $mailer;

	public function __construct(Nette\Database\Connection $db, Nette\Mail\Mailer $mailer)
	{
		$this->db = $db;
		$this->mailer = $mailer;
	}
}
class Facade
{
	public function __construct(
		private Nette\Database\Connection $db,
		private Nette\Mail\Mailer $mailer,
	) {}
}

Es funktioniert mit Nette DI, Sie können es benutzen.

Strengere Typprüfungen für arithmetische und bitweise Operatoren

Was einst den dynamischen Skriptsprachen zum Durchbruch verhalf, ist heute ihr schwächster Punkt. Einst wurde PHP die “magischen Anführungszeichen” und die Registrierung globaler Variablen los, und jetzt wird das lockere Verhalten durch Strenge ersetzt. Die Zeiten, in denen man in PHP fast jeden Datentyp addieren, multiplizieren etc. konnte, für den es keinen Sinn machte, sind längst vorbei. Seit Version 7.0 wird PHP immer strenger und seit Version 8.0 endet der Versuch, irgendwelche arithmetischen/bitweisen Operatoren auf Arrays, Objekte oder Ressourcen anzuwenden, mit einem TypeError. Die Ausnahme ist das Hinzufügen von Arrays.

// arithmetic and bitwise operators
+, -, *, /, **, %, <<, >>, &, |, ^, ~, ++, --:

Sanftere String-Zahlen-Vergleiche

Oder machen Sie lose Operatoren wieder großartig.

Es scheint, dass es keinen Platz mehr für den losen Operator == gibt, dass er nur ein Tippfehler beim Schreiben von === ist, aber diese Änderung bringt ihn wieder zurück auf die Karte. Wenn wir ihn schon haben, dann soll er sich auch vernünftig verhalten. Infolge des früheren “unvernünftigen” Vergleichs könnte zum Beispiel in_array() Sie unangenehm trollen:

$validValues = ['foo', 'bar', 'baz'];
$value = 0;

dump(in_array($value, $validValues));
// surprisingly returned true
// since PHP 8.0 returns false

Die Änderung des Verhaltens von == betrifft den Vergleich von Zahlen und “numerischen” Zeichenketten und ist in der folgenden Tabelle dargestellt:

Vergleich Vor PHP 8.0
0 == "0" wahr wahr
0 == "0.0" wahr wahr
0 == "foo" wahr falsch
0 == "" wahr falsch
42 == " 42" wahr wahr
42 == "42 " wahr wahr
42 == "42foo" wahr falsch
42 == "abc42" falsch falsch
"42" == " 42" wahr wahr
"42" == "42 " falsch wahr

Überraschenderweise handelt es sich um einen BC-Bruch im Kern der Sprache, der ohne jeden Widerstand angenommen wurde. Und das ist gut so. JavaScript könnte in dieser Hinsicht sehr neidisch sein.

Fehlerberichterstattung

Viele interne Funktionen lösen jetzt TypeError und ValueError anstelle von Warnungen aus, die leicht zu ignorieren waren. Eine Reihe von Kernel-Warnungen wurden neu klassifiziert. Der Shutup-Operator @ unterdrückt nun keine fatalen Fehler mehr. Und PDO wirft standardmäßig Ausnahmen.

Nette hat immer versucht, diese Dinge auf irgendeine Weise zu lösen. Tracy änderte das Verhalten des Shutup-Operators, Database änderte das PDO-Verhalten, Utils enthalten Ersatz für Standardfunktionen, die Ausnahmen anstelle von leicht zu übersehenden Warnungen werfen, usw. Es ist schön zu sehen, dass die strenge Richtung, die Nette in seiner DNA hat, die native Richtung der Sprache wird.

Negative Array-Schlüsselinkremente

$arr[-5] = 'first';
$arr[] = 'second';

Was wird der Schlüssel des zweiten Elements sein? Früher war es 0, since PHP 8 it’s -4.

Nachgestelltes Komma

Die letzte Stelle, an der das nachgestellte Komma nicht stehen durfte, war die Definition von Funktionsargumenten. Dies ist eine Sache der Vergangenheit:

	public function __construct(
		Nette\Database\Connection $db,
		Nette\Mail\Mailer $mailer, // trailing comma
	) {
		....
	}

$object::class

Die magische Konstante ::class funktioniert auch mit Objekten $object::class und ersetzt die Funktion get_class() vollständig.

Fangen Sie Ausnahmen nur nach Typ

Und schließlich: Es ist nicht notwendig, in der catch-Klausel eine Variable für die Ausnahme anzugeben:

try {
	$container->getService(Database::class);

} catch (MissingServiceException) {  // no $e
	$logger->log('....');
}

*In den nächsten Teilen werden wir wichtige Neuerungen bei den Datentypen sehen, wir werden zeigen, was Attribute sind, welche neuen Funktionen und Klassen in PHP aufgetaucht sind und wir werden den Just in Time Compiler vorstellen.