Ausblick auf PHP 8: Teil 6

PHP 8.0-Features im Fokus: Strengere Regeln für logischeres Verhalten
Keine Kommentare

PHP 8 wird strikter und damit logischer! Das steht bereits fest. Das Release von PHP 8 steht nämlich kurz bevor und mit ihm eine Reihe von Verbesserungen, neuen Funktionen und die generelle Überarbeitung, um die weit verbreitete serverseitige Websprache noch besser zu machen. In dieser Serie wollen wir darauf eingehen, was Sie über PHP 8 wissen sollten.

Der Countdown zum großen Release von PHP 8 ist bereits angebrochen. Die lang erwartete neue Version enthält eine ganze Reihe von neuen Funktionen und Verbesserungen, die den Entwicklern das Leben leichter machen sollen. In dieser Serie werfen wir einen Blick auf diese Neuerungen und was sie für die Nutzer bedeuten.

Im letzten Kapitel sind wir einige kleinere Feature-Verbesserungen in PHP durchgegangen. Heute behandeln wir ein Paar kleinere Sprachänderungen, die PHP sicherer machen, aber älteren Code straucheln lassen könnten.

In den letzten Jahren gab es in PHP einen sehr stetigen Trend zur strengeren Gestaltung der Sprache. Das bedeutet, dass mehr Grenzfälle, die „undefiniertes Verhalten“ darstellen, das die meiste Zeit irgendwie stillschweigend funktioniert, sich in explizite Warnungen oder Fehler verwandeln. Verhalten, das dokumentiert wurde, aber völlig unlogisch ist, wird angepasst und logischer gemacht und so weiter. Gewöhnlich sind die Auswirkungen gering, und Code, der sich gut benimmt, wird normalerweise keinen Unterschied zeigen. Aber wie wir alle wissen, ist nicht jeder Code so gut erzogen.

PHP 8.0 führt diesen Trend zu einer strikteren Sprachgestaltung fort. Also behandeln wir heute einige der kommenden Aufräumarbeiten, die bereits existierenden Code beeinflussen können.

Stabiles Sortierungsverhalten

Was passiert, wenn man ein Array hat, das man sortieren möchte, aber zwei der Elemente gleich sind? Ändert sich ihre Reihenfolge oder nicht?

Wenn die Dinge, die man sortieren möchte, Strings oder Integers sind, macht das keinen Unterschied. Wenn sie aber Objekte sind, dürfte das offensichtlich nicht so sein. Angenommen man sortiert ein Array aus Person-Objekten nach Alter: Viele Leute können natürlich dasselbe Alter haben. In welcher Reihenfolge tauchen diese Personen gleichen Alters also auf?

In PHP 7 war die Antwort darauf ¯\(ツ)/¯. Die daraus resultierende Sortierung war unvorhersehbar. In PHP 8 ist die Reihenfolge jetzt „so, wie sie vorher war“. Das bedeutet, wenn im ursprünglichen Array Jorge, 40 Jahre alt, vor Melissa, 40 Jahre alt, stand und sie nach Alter sortiert werden, dann würde Jorge immer noch vor Melissa stehen. Oder, um es in Code zu sagen:

<?php
$people[] = new Person('Jorge', 40);
$people[] = new Person('Melissa', 40);

usort($people, fn($a, $b) => $a->age <=> $b ->age);
// Jorge steht ganz sicher noch vor Melissa

Als Nebeneffekt war es früher nominell möglich, von der Vergleichsfunktion einen Boolean statt des erwarteten Integers ausgeben zu lassen. In PHP können Booleans in vielen Situationen einen Integer von 1 und 0 „schwach auswerfen“. Das wird für Sortier- und Vergleichsfunktionen nicht weiter unterstützt und wird nun Warnungen auslösen. (Das Verhalten stellte schon immer einen Bug dar, der jetzt nur explizit markiert ist.)

Den RFC zu Stable Sorting verdanken wir Nikita Popov.

Logischere Handhabung numerischer String

Wie die meisten bekannten interpretierten Sprachen macht PHP Gebrauch von erzwungener Typumwandlung. D.h., dass eine Variable ihren Typ abhängig vom Ort der Verwendung verändern kann, insofern es im jeweiligen Kontext sinnvoll ist. Die meisten dieser Umwandlungen beinhalten, dass man Schindluder mit String- und Nummerntypen (int und float) betreibt. Beispielsweise sind der Integer 42 und der String „42“ im Allgemeinen „ausreichend nah“ an der gleichen Sache, dass man sie als gleich (==), aber nicht als identisch (===) bezeichnen kann.

IT Security Camp 2021

Interview mit Christian Schneider zum Thema „DevSecOps“

DevSecOps ist, bezogen auf Security-Checks, die logische Fortführung der Automatisierung im DevOps-Sinne

IT-Security Experte, Christian Schneider

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

Dieser „das ist wahrscheinlich gut genug“-Ansatz hat seine Vorteile, führt aber auch viele bizarre Grenzfälle ein. Aus diesem Grund hat PHPs Unterstützung skalarer Typen für Funktionssignaturen (eingeführt in 7.0) sowohl einen schwachen Modus (der diese Art der stillen Konvertierung erlaubt) als auch einen strengen Modus (was nicht der Fall ist), wobei der strenge Modus im Allgemeinen für die meisten Anwendungsfälle empfohlen wird.

Eine Vielzahl anderer häufiger Fehler verstecken sich jedoch in dieser stillen Umwandlung. In PHP 7.4 sind die folgenden Umwandlungen, unglaublicher Weise, wahr:

<?php
0 == "wait, what?";           // true
0 == "";                      // true
99 == '99 bottles of beer';   // true
42 == '     42';              // true
42 == '42     ';              // false
in_array(0, ['a', 'b', 'c']); // true???

Darüber hinaus wird 42 in manchem Kontext als „nahe genug“ an int(42) bezeichnet und in anderen nicht.

Der technische Ausdruck für diese Situation ist „total verrückt“. Glücklicherweise bereinigen ein Paar RFC diesen Unsinn in PHP 8.0.

Die RFCs enthalten einige Feinheiten, aber zusammengefasst geht es darum:

  • „Numerische Strings“ sind nun alle Strings, die komplett numerische Werte mit voran- oder nachstehendem Leerzeichen beinhalten, die man sicher ignorieren kann, anstatt nur solche mit voranstehendem Leerzeichen. (Man beachte aber, dass „numerische Werte“ Hochzahlen und einen Punkt beinhalten können, beispielsweise "42.5e4" und nicht nur Ziffern.)
  • Numerische Strings werden in allen Fällen konsequent behandelt und definiert.
  • Wenn ein numerischer String mit einem Integer verglichen wird (mit „==“ oder „>“, bzw. „<„), dann wird der String in einen Integer umgewandelt und anschließend werden sie als Integers vergleichen.
  • Wenn ein nicht-numerischer String mit einem Integer verglichen wird, wird der Integer in einen String umgewandelt und sie werden anschließend als Strings vergleichen. Nie wieder 0 == "seriously, what?"
  • Wenn ein String mit einem Float verglichen wird, passiert das gleiche. Aber der Float-Wert könnte zuerst durch eine Floating Point Precision kaputt gemacht werden, also kann man nicht garantieren, dass es sich so verhält, wie man es erwartet. (So ist das Leben mit Floating-Point-Nummern und binären Computern.)
  • Wenn man versucht, einen nicht-numerischen String an eine Funktion weiterzugeben, die eine Nummer erwartet, wird nun ein TypeError ausgeworfen.
    Wenn man ausdrücklich einen zahlenführenden String (wie „99 Flaschen voll Bier“) an einen Integer überträgt, wird dieser immer noch in int(99) resultieren, aber das ist auch die einzige Situation, in der das jetzt noch passieren kann.

All diese Änderungen helfen dabei, die Sprache vorhersehbarer, logischer und konsistenter zu machen, aber sie sind eben Änderungen. Wenn euer Code jetzt schon unsauber mit Typen- und String-to-Number-Umwandlungen umgeht, könntet ihr einige subtile Verhaltensänderungen feststellen. Glücklicherweise treibt der meiste Code heutzutage keinen so großen Blödsinn mit Typen (genau, um diese seltsamen Vorkommnisse zu verhindern).

Diese Aufräumarbeiten haben wir einem RFC von Nikita Popov und einem von George Banyard zu verdanken. Und jetzt: Los, aktiviert strikte Typen in eurer Codebasis!

<?php
declare(strict_types=1);

Nicht-numerische Arithmetik

Es geht weiter mit noch einer Lösung der Sorte „wie bitte?!“. Historisch betrachtet erlaubte PHP, dass alle arithmetischen Operationen nicht-numerischen Typen zugeordnet werden dürfen. Nicht, weil das Sinn ergeben würde, sondern weil in den guten, alten Zeiten die Idee vorherrschte, dass Code nicht abstürzen, sondern versuchen sollte, etwas irgendwie-halbwegs vernünftiges zu tun, selbst wenn es nicht logisches zu tun gab.

Das führt zu so seltsamen Dingen wie [] % [5] == 0. Das ergibt überhaupt keinen logischen Sinn, aber sowas fällt einfach zufällig aus der Engine.

In PHP 8.0 werfen diese unsinnigen Kombinationen einen TypeError aus, anstatt unbemerkt irgendetwas zu tun, das Sinn ergeben könnte oder eben nicht. Ein paar wenige ergeben Sinn, so wie eine Addition in Arrays einen Merge-Typ darstellen. Diese sind davon aber nicht betroffen. Das Verhalten bei Primitives (Strings, Bool, Float, etc.) ist auch unverändert.
Auch dieser Aufräum-RFC stammt vom König der Konsistenz, Nikita Popov.

Strengere Magie

Objekte unterstützen in PHP eine Vielzahl von „magischen Methoden“: Methoden, die einen speziellen Namen und ein spezielles Verhalten in der Engine haben. Wir haben schon __toString() in Teil 1 unserer Serie als Beispiel gesehen. Alle magischen Methoden beginnen mit __, um anzuzeigen, dass irgendetwas Spezielles mit ihnen los ist. (Und wenn ihr selbst Methoden habt, die mit einem doppelten Unterstrich anfangen, aber nicht in ein spezielles Engine-Verhalten fallen, dann macht ihr etwas fasch). Die meisten dieser Methoden wurden vor über einer Dekade hinzugefügt. Das bedeutet, dass sie schon da waren, bevor PHP die weitverbreitete Typenunterstützung in Methodensignaturen hinzugefügt hat.

Das ist kein riesiges Problem, solange der Code sich gut verhält. Aber der ganze Sinn hinter typisierten Funktionssignaturen ist, dass die die Sprache euch auf die Finger haut, wenn der Code sich nicht gut verhält, damit ihr ihn fixt, bevor er ganz still Bugs produziert, die einen Datenverlust hervorrufen. Leider hat die Sprache aber Entwicklern erlaubt, Typenbeschreibungen zu ihren magischen Methoden hinzuzufügen… Selbst dann, wenn diese Beschreibungen gegensätzlich zu dem waren, was die Methode tun sollte. Ups.

PHP 8.0 erlaubt Euch nun wahlweise, die richtigen Typen in euren Methodensignaturen zu beschreiben und haut euch dann auf die Finger, (der technische Ausdruck ist „throw a Fatal error“) wenn ihr die falschen spezifiziert. Für den Großteil der Nutzer ändert sich nichts, aber dadurch wird es für diejenigen, die die Sprache für sich arbeiten lassen wollen, sicherer.

Die folgenden magischen Methoden unterstützen – und erzwingen – die folgenden typisierten Signaturen:

<?php
Foo::__call(string $name, array $arguments): mixed;
 
Foo::__callStatic(string $name, array $arguments): mixed;
 
Foo::__clone(): void;
 
Foo::__debugInfo(): ?array;
 
Foo::__get(string $name): mixed;
 
Foo::__invoke(mixed $arguments): mixed;
 
Foo::__isset(string $name): bool;
 
Foo::__serialize(): array;
 
Foo::__set(string $name, mixed $value): void;
 
Foo::__set_state(array $properties): object;
 
Foo::__sleep(): array;
 
Foo::__unserialize(array $data): void;
 
Foo::__unset(string $name): void;
 
Foo::__wakeup(): void;
?>

Obwohl das optional ist, empfehle ich, die Typen in jedem Fall einzubeziehen, damit der Code sich besser selbst dokumentiert und damit er pedantischer darin ist, Fehler früh zu erkennen.

Diese zusätzliche Typensicherheit gibt es dank Gabriel Caruso.

Striktere Warnungen und Fehler

In PHP gibt es eine Vielzahl von Fehler-Stufen (Notice, Warning, Error), die ausgelöst werden, wenn etwas schief geht. Gleichzeitig hat die Sprache die Fähigkeit, Exceptions oder Engine-Fehler auszuwerfen. Die angemessene Schwere eines Problems zu bestimmen, ist immer kniffelig, insbesondere, wenn einige dieser Optionen noch gar nicht existiert haben, als ein bestehender Fehler das erste Mal definiert wurde.

In PHP 8.0 sind einige Fehler strikter geworden. Die komplette Liste ist im RFC enthalten, aber die besonders beachtenswerten sind diese:

  • Viele Notices, die sich um fehlende Werte oder falsch verwendete Typen an seltsamen Stellen drehen, sind jetzt Warnings. Das beinhaltet auch das Lesen von undefinierten Variablen, Properties und Array Keys.
  • Mehrere Warnings, die sich um fehlerhafte Verwendungen von Arrays und Traversables drehen, sind jetzt TypeError Exceptions. Sie sind ein Typenproblem und alles, was der Code danach tun möchte, ist mit Sicherheit falsch. Das ist also durchaus sinnvoll.
  • Division durch Null (wer macht das?!) wirft nun einen DivisionByZeroError aus anstelle einer Warning.

Dreimal dürft Ihr raten, wem wir diesen RFC zu verdanken haben und die ersten beiden Versuche von Nikita Popov zählen nicht.

Viele Resources werden zu Objekten

Schlussendlich wurde einiges an Arbeit hinter den Kulissen darauf verwendet, Resources in Objekte zu verwandeln, obwohl das kein RFC war.

„Resources“, um es mit PHP zu sagen, sind ein spezieller Datentyp aus der Zeit, bevor PHP Objekte hatte. Wir sprechen hier also von der Zeit vor der Präsidentschaft von Bill Clinton. Resources sind so etwas wie Objekte, nur schlimmer. Sie können nur durch Erweiterungen implementiert werden und unterstützen kaum etwas davon, was Objekte unterstützten. Es ist allgemein anerkannt, dass sie eine schlechte Idee waren und, dass echte Objekte in fast jedem Fall eine bessere Lösung darstellen, aber es gibt noch einige sehr verbreitete und sehr alte Erweiterungen, die noch Resources statt Objekten für den User-Code zugänglich machen. Das trifft insbesondere auf sowas wie Datenbankverbindungen, das Dateisystem und andere „externes Zeug“ zu.

Es gab fortlaufende Bemühungen, diese Resources in Objekte umzuwandeln, ein Prozess, der zu 99% transparent für den Benutzercode ist, mit dem Endziel, die Resources vollständig aus der Sprache zu entfernen. Ein Großteil dieser Konvertierung wurde in PHP 8.0 abgeschlossen, wenn auch hauptsächlich für die weniger benutzten Erweiterungen.

Die Wahrscheinlichkeit, dass das euren Code betrifft, ist sehr gering. Der wahrscheinlich einzige Grund, warum es euch betreffen könnte, ist, wenn ihr für eine Variable is_resource() checken wollt und die CURL-, OpenSSL-, Sockets-, XML-RPC-, ZIP- oder ZLIB-Erweiterungen verwendet. Falls dem so ist, wird diese Funktion in PHP 8.0 ein false anstelle eines true auswerfen. Sonst bemerkt ihr wahrscheinlich nichts.

Wenn Ihr keine Ahnung habt, um was es geht: Herzlichen Glückwunsch – Ihr müsst euch nicht darum kümmern.

Im nächsten Teil dieser Serie widmen wir uns dem neuen JIT-Compiler.

Unsere Redaktion empfiehlt:

Relevante Beiträge

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