Aus Linux-Magazin 01/2021

Neuerungen von PHP 8

© Zlikovec, 123RF

Nach rund zwei Jahren Entwicklungsarbeit erschien am 26. November 2020 die Version 8 der populären Skriptsprache PHP. Sie bringt Neuerungen mit und schneidet ein paar alte Zöpfe ab.

Eine der größeren Innovationen von PHP soll der Skriptsprache Beine machen, braucht aber etwas Vorwissen. Seit Version 5.5 nämlich landet der auszuführende Code in einem Cache, den die Opcache-Erweiterung bereitstellt [1]. Dank dieses Zwischenspeichers muss der Interpreter nicht bei jeder Anfrage die Skripte erneut einlesen.

Mit PHP 8 bekommt besagter Opcache nun einen Just-in-Time-Compiler spendiert. Der übersetzt Teile des PHP-Codes in nativen Programmcode, den der Prozessor dann direkt und somit deutlich schneller ausführt. Als weitere Beschleunigungsmaßnahme landet auch der generierte Binärcode im Cache. Startet der zugehörige PHP-Code noch einmal neu, greift PHP 8 dann einfach zum bereits fertig übersetzten Binärcode aus dem Zwischenspeicher.

Wie erste Benchmarks des PHP-Entwicklers Brent Roose zeigen [2], profitieren von diesen Maßnahmen offenbar Skripte mit sich wiederholenden oder umfangreicheren Berechnungen. Beim Behandeln einzelner kurzer Anfragen spielt der JIT-Compiler seine Vorteile deutlich weniger aus.

Abkürzung

Die in PHP 8 an einigen Stellen leicht geänderte Syntax erspart Entwicklern vor allem Tipparbeit. So stellt die Skriptsprache dem bekannten »switch« mit »match« einen mächtigeren und dabei kompakteren Kollegen an die Seite. Der »$blinker« in Listing 1 ist nur dann »An«, wenn der »$hebel« auf »Oben« oder »Unten« steht. Stimmt keiner der Vergleichswerte vor dem »=>«, gilt der Wert hinter »default«.

Listing 1

match

$blinker = match ($hebel) {
  0 => "Warnblinker",
  'Oben', 'Unten' => "An",
  default => "Aus"
};

Ein Aufruf von »$user->adress->getBirthday()->asString()« in Listing 2 funktioniert nur dann, wenn »$adress« existiert und »getBirthday()« ein gültiges Objekt zurückliefert. Um das sicherzustellen, musste der Entwickler bislang mehrere If-Zweige verschachteln. Der neue Nullsafe-Operator »?->« prüft die Existenz automatisch und reduziert den Test auf eine Zeile (letzte Zeile).

Listing 2

Happy Birthday?

// ---- früher --------
if ($user !== null) {
  $a = $user->address;
  if ($a !== null) {
    $b = $user->getBirthday();
    if ($b !== null) {
      $bday = $b->asString();
    }
  }
}
// ---- in PHP 8 --------
$bdate = $user?->adress?->getBirthday()?->asString();

Soll eine Klasse eine Adresse oder andere Daten kapseln, notiert der Entwickler für gewöhnlich zunächst die passenden Variablen, die dann ein Konstruktor mit Werten belegt (Listing 3, oben). In PHP 8 lässt sich das kurz und knackig schreiben (Listing 3, unten).

Der Konstruktor sammelt jetzt alle nötigen Informationen, aus denen PHP 8 automatisch den kompletten Aufbau der Klasse ableitet. Dank dieser Constructor Property Promotion erzeugt der Programmierer deutlich schneller Datenobjekte und refaktoriert diese später auch fixer.

Listing 3

Daten kapseln

// ---- früher --------
class Adresse
{
  public string $name;
  public DateTimeImmutable $geburtstag;
  public function __construct(string $n, DateTimeImmutable $g)
  {
    $this->name = $n;
    $this->$geburtstag = $g;
  }
}
// ---- in PHP 8 --------
class Adresse
{
  public function __construct(
    public string $name,
    public DateTimeImmutable $geburtstag,
  ) {}
}

Ausnahmeerscheinung

Bislang mussten PHP-Entwickler eine Exception immer in einer Variablen auffangen, auch wenn sie mit dem entsprechenden Objekt gar nichts weiter anstellen wollten. Ab PHP 8 lassen sie in solchen Situationen die Variable einfach weg und geben nur den Typ weiterhin an. Wer sämtliche Fehler im Catch-Block behandeln möchte, fängt dazu einfach »Throwable« (Listing 4).

Listing 4

Throwable

try {
  [...]
} catch (Throwable) {
  Log::error("Fehler!");
}

Die Funktion »get_class()« liefert den Klassennamen eines Objekts. An diesen kommen Entwickler ab PHP 8 auch mit einem angehängten »::class«, wie Listing 5 demonstriert.

Listing 5

Klassenname

$a = new Adress();
var_dump($a::class);

Typisch!

Funktionen erwarten ihre Parameter in einer vorgegebenen Reihenfolge. Insbesondere bei sehr vielen Parametern geht dabei gern einmal der Überblick verloren. Die folgende Berechnung des Volumens eines Quaders macht beispielsweise nicht ganz ersichtlich, welcher Wert für seine Breite steht:

$v = volumen(10, 3, 2);

In PHP 8 dürfen Entwickler explizit die Namen der entsprechenden Parameter notieren und so für Klarheit sorgen. Beim Einsatz dieser Named Arguments wählt der Entwickler zudem die Reihenfolge der Parameter nach Gusto. Optionale Parameter dürfen obendrein wegfallen, wie im Beispiel aus Listing 6 die »$tiefe«. Das Beispiel zeigt ganz nebenbei eine weitere Neuerung: Hinter dem letzten Parameter darf noch einmal ein Komma stehen, selbst wenn kein weiterer Parameter folgt.

Listing 6

Benannte Parameter

function volumen(int $breite, int $hoehe, int $tiefe = 1) { return $breite * $hoehe * $tiefe; }
$v = volumen(hoehe: 3, breite: 10,);

Gleitzeit

Beim Rechnen mit Ganzzahlen entstehen schnell Gleitkommazahlen. Daher wäre es ganz nützlich, wenn die entsprechende Funktion je nach Bedarf einen »int«- oder »float«-Wert zurückgeben könnte. Das gelingt in PHP 8 mit den sogenannten Union Types. Sie fassen einfach mehrere mögliche Datentypen mit dem Pipe-Zeichen zusammen. Im Beispiel aus Listing 7 liefert »flaeche()« entweder einen »int«- oder »float«-Wert zurück.

Listing 7

Datentypen mit Pipe

class Rect {
  public int|float $x, $y, $w, $h;
  public function flaeche(): int|float {
    return $this->w * $this->h;
  }
}

Ein weiterer neuer Datentyp hört auf den Namen »mixed«. Er steht stellvertretend für einen der Datentypen »array«, »bool«, »callable«, »int«, »float«, »null«, »object«, »resource« oder »string«. Der Datentyp »mixed« springt immer dann ein, wenn Typinformationen fehlen. Ein Entwickler kann beispielsweise mit »mixed« anzeigen, dass er den Typ einer Variablen an der entsprechenden Stelle nicht näher bestimmen kann oder möchte.

Die neue Datenstruktur »WeakMap« funktioniert ähnlich wie ein Array, nutzt aber Objekte als Schlüssel (Listing 8). Die verwendeten Objekte darf die Garbage Collection zudem einsammeln. Zerstört das Programm also irgendwann im weiteren Verlauf »$a« (etwa mutwillig per »unset($a);«), würde PHP automatisch den entsprechenden Eintrag »$cache[$a]« entfernen.

Listing 8

WeakMap

$cache = new WeakMap;
$a = new Adress;
$cache[$a] = 123;

In Abbildung 1 gibt »var_dump()« einmal die WeakMap aus, in der ein Adressobjekt als Schlüssel (»key«) dient. Anschließend vernichtet »unset()« dieses Objekt, wodurch automatisch der Eintrag aus der WeakMap verschwindet. Dies beweist die zweite Ausgabe von »var_dump()« (in Form der unteren beiden Zeilen). Ein Haupteinsatzbereich der WeakMap ist ein maßgeschneiderter Cache.

Abbildung 1: Mit dem Objekt verschwindet auch der Eintrag in der WeakMap.

Abbildung 1: Mit dem Objekt verschwindet auch der Eintrag in der WeakMap.

Funktionsreicher

Ob der String »Hallo Welt« das Wort »Welt« enthält, ermittelt die Funktion »strpos()«. In PHP 8 steht dafür alternativ »str_contains()« bereit (Listing 9). Hinzu gesellen sich noch die Brüder »str_starts_with()« und »str_ends_with()«, die das Wort am Anfang beziehungsweise am Ende des Strings suchen. Die Funktion »fdiv()« teilt eine Gleitkommazahl ohne zu murren auch durch null und liefert dann passend »INF«, »-INF« oder »NAN« zurück.

Listing 9

str_contains()

if (str_contains('Hallo Welt', 'Welt')) { [...] }

Die Funktion »get_debug_type()« ermittelt den Datentyp einer Variablen. Im Gegensatz zum bereits vorhandenen »gettype()« identifiziert die neue Funktion auch Strings, Arrays sowie geschlossene Ressourcen und verrät die Klasse von Objekten. Wie der Funktionsname andeutet, soll sie vor allem das Schreiben von Debug-Meldungen erleichtern. Jede Ressource, wie etwa eine geöffnete Datenbankverbindung, erhält eine interne Identifikationsnummer. An die kommen Entwickler ab sofort auch typsicher mit »get_resource_id()«.

Die altbekannte Funktion »token_get_all()« liefert zu einem PHP-Quelltext die passenden PHP-Token zurück [3]. Dabei bekommt der Entwickler entweder einen String oder ein Array geliefert, die alles andere als handlich sind. PHP 8 führt deshalb die Klasse »PhpToken« ein. Ihre Methode »getAll()« liefert ein Array von PhpToken-Objekten zurück, die wiederum die einzelnen Token kapseln. Dieser »token_get_all()«-Ersatz lässt sich zwar einfacher nutzen, verbraucht im Gegenzug aber auch mehr Speicher.

Angetackert

Viele andere Sprachen bieten schon länger sogenannte Annotations, um Meta-Informationen an Klassen zu heften. Auf diese Weise notieren sich die Entwickler beispielsweise die Datenbanktabelle, in der die Klasse ihre Daten speichert. Eine solche Möglichkeit erhält PHP 8 mit den Attributes. Die eigentliche Information steht dabei zwischen »#[ … ]« direkt vor der Klasse, wie Listing 10 zeigt.

Listing 10

Attributes

#[DatabaseTable("User")]
class User
{
  #[DatabaseColumn]
  public $name;
  public function setBirthday(#[ExampleAttribute] $bday) { }
}

Wie das Beispiel demonstriert, lassen sich Attributes nicht nur Klassen anheften, sondern auch Variablen, Konstanten, Methoden, Funktionen und Parametern. Den Aufbau und den Inhalt der Informationen bestimmt der Entwickler beziehungsweise ein Framework, das die Attributes auswertet. In Listing 10 erhält die Klasse das Attribut »DatabaseTable«. Bei Bedarf übergibt der Entwickler in den Klammern noch Parameter. Das Beispiel verrät, dass die Datenbanktabelle »User« heißt.

An der Syntax der Attributes haben die PHP-Entwickler recht lange geschraubt. Zwischenzeitlich waren als Kennzeichnung »<<ExampleAttribute>>« und »@@ExampleAttribute« im Gespräch, die sich auch noch in zahlreichen Blog-Beiträgen zu PHP 8 finden.

Attributes lassen sich über die Reflection API auslesen. Das Beispiel in Listing 11 holt über die neue Methode »getAttributes()« alle Attribute der Klasse »User« in Form eines Arrays mit allen Attributes. Jedes einzelne kapselt wiederum ein Objekt vom Typ »ReflectionAttribute«. Diese neue Klasse besitzt unter anderem die Methode »getName()«, welche den Namen des Attributes verrät.

Listing 11

Attributes auslesen

$reflectionClass = new \ReflectionClass(User::class);
$attributes = $reflectionClass->getAttributes();
var_dump($attributes[0]->getName());
var_dump($attributes[0]->getArguments());

Darauf greift die dritte Zeile von Listing 11 zurück, die einfach per »var_dump()« den Namen des Attributes ausgibt – im Beispiel »DatabaseTable«. Analog liefert in der vierten Zeile »getArguments()« die zugehörigen Parameter als Array zurück (Abbildung 2).

Abbildung 2: F&uuml;r das Attribut &raquo;#[DatabaseTable("User")]&laquo; liefert die Reflection-API korrekt &raquo;DatabaseTable&laquo; als Name und &raquo;User&laquo; als Parameter. Alle Parameter sind dabei in ein Array verpackt.

Abbildung 2: Für das Attribut »#[DatabaseTable(“User”)]« liefert die Reflection-API korrekt »DatabaseTable« als Name und »User« als Parameter. Alle Parameter sind dabei in ein Array verpackt.

Kleinkram

Die von PHP angebotenen Sortierfunktionen arbeiteten bislang nicht stabil und überließen die Reihenfolge von identischen Elementen im sortierten Ergebnis dem Zufall. In PHP 8 übernehmen alle Sortierroutinen gleiche Elemente in der Reihenfolge des ursprünglichen Arrays.

Des Weiteren führen die PHP-Entwickler ein neues »Stringable«-Interface ein, das automatisch eine Klasse implementiert, sobald sie die Funktion »__toString()« anbietet. Der Union Type »string|Stringable« akzeptiert dann sowohl Strings als auch Objekte mit »__toString()«-Methode. Dies wiederum soll die Typsicherheit erhöhen.

Da das Datenformat JSON mittlerweile in vielen Webanwendungen dominiert, lässt sich PHP 8 nicht mehr ohne die JSON-Extension übersetzen. Entwickler können »DateTime« und »DateTimeImmutable« mittels »DateTime::createFromInterface()« und »DatetimeImmutable::createFromInterface()« einfacher ineinander umwandeln. »static« lässt sich zudem als Rückgabetyp verwenden (Listing 12).

Listing 12

static als Rückgabewert

class Foo {
  public function create(): static {
    return new static();
  }
}

Strenger Blick

Über sogenannte Traits schmuggeln Entwickler Funktionen in Klassen, ohne dabei auf die Vererbungshierarchie achten zu müssen [4]. Listing 13 zeigt das an einem Beispiel. Ein Trait kann dabei wie im Beispiel auch abstrakte Funktionen definieren, die dann wiederum die einzelnen Klassen implementieren müssen. PHP 8 prüft erstmals die Signatur der Funktionen. Die Implementierung der Klasse »Kreis« führt somit aufgrund des falschen »string« zu einer Fehlermeldung (Abbildung 3).

Listing 13

Traits

trait Koordinaten {
  abstract public function flaeche(): int;
}
class Rechteck {
  use Koordinaten;
  public function flaeche(): int { [...] }
}
class Kreis {
  use Koordinaten;
  public function flaeche(): string { [...] }
}
Abbildung 3: Hier erkennt PHP&nbsp;8, dass die Klasse &raquo;Kreis&laquo; die Funktion &raquo;flaeche()&laquo; falsch implementiert.

Abbildung 3: Hier erkennt PHP 8, dass die Klasse »Kreis« die Funktion »flaeche()« falsch implementiert.

An anderer Stelle geht PHP ignoranter vor: Bislang wendete der Interpreter einige Vererbungsregeln auf private Methoden an, auch wenn diese in der abgeleiteten Klasse gar nicht sichtbar waren. In PHP 8 geschieht das nicht mehr, wodurch der Code aus Listing 14 jetzt ohne Fehlermeldung durchläuft.

Listing 14

Vererbungslehre

class Foo {
  final private function calc() { [...] }
}
class Bar extends Foo {
  private function calc() { [...] }
}

Umbruch

PHP 8 bügelt ein paar Inkonsistenzen aus und bricht dabei mit der Abwärtskompatibilität. Beispielsweise gilt jetzt »0 == “foo”« als falsch. Den Operator ».« zum Aneinanderhängen von Strings wertet PHP 8 erst nach einer Addition oder Subtraktion aus. Den Code »echo “Länge: ” . $y – $x;« interpretiert PHP 8 folglich jetzt als »echo “Länge: ” . ($y – $x);«.

Namespaces dürfen keine Leerzeichen mehr im Namen enthalten, dafür sind ab sofort auch reservierte Schlüsselwörter als Bestandteile eines Namespace-Bezeichners erlaubt.

Innerhalb der Reflection API ändert sich die Signatur einiger Methoden. So nutzt PHP 8 anstelle von »ReflectionClass::newInstance($args);« die Methode »ReflectionClass::newInstance(…$args);«. Sofern der PHP-Code unter PHP 7 und 8 laufen soll, rät das PHP-Team jedoch dazu, die Notation aus Listing 15 zu verwenden.

Listing 15

ReflectionClass-Notation

@nonumber
ReflectionClass::newInstance($arg = null, ...$args);

Verschärfte Meldepflicht

Wer bestehende Skripte unter PHP 8 aufruft, muss mit zahlreichen Fehlermeldungen rechnen, die neben einigen inkompatiblen Änderungen vor allem ein verschärfter Umgang mit Fehlern verursacht. So kommt ab sofort standardmäßig der Error-Report-Level »E_ALL« zum Einsatz. Zudem lassen sich über den Operator »@« keine schwerwiegenden Fehler (Fatal Errors) mehr unterdrücken. Auch bei Datenbankverbindungen über die PDO-Schnittstelle müssen sich Entwickler auf SQL-Fehler einstellen: Dort arbeitet jetzt das Error Handling standardmäßig im Exceptions-Modus (»PDO::ERRMODE_EXCEPTION«).

Arithmetische und bitweise Operatoren werfen einen »TypeError«, sofern es sich bei einem der Operanden um ein Array, eine Ressource oder ein nicht entsprechend überladenes Objekt handelt. Eine Ausnahme bildet das Zusammenführen von zwei Arrays via »array + array«. Die Division durch null erzeugt in PHP 8 einen »DivisionByZeroError«.

Abschließend entfernt die neue Version einige bereits in der Version 7.x als veraltet (»Deprecated«) gekennzeichnete Funktionen und Sprachkonstrukte. Sämtliche inkompatiblen Änderungen listen die PHP-Entwickler penibel in einem langen Dokument auf [5]. Wer bestehenden PHP-Code pflegt, sollte die dortige Liste unbedingt durchgehen.

Fazit

PHP 8 umfasst viele nützliche Neuerungen, die das Entwicklerleben erleichtern und für kompakteren Code sorgen. Insbesondere das neue »match«, die Constructor Property Promotion und die Attributes dürften schnell viele Freunde finden. Ergänzend wirft PHP 8 behutsam einige veraltete Verhaltensweisen und Konstrukte über Bord. Bestehende PHP-Anwendungen bedürfen dadurch jedoch mit hoher Wahrscheinlichkeit einer Anpassung. Ob der JIT-Compiler wirklich den erhofften Leistungsschub bringt oder nur in Spezialfällen PHP-Code Beine macht, muss sich in der Praxis erst noch zeigen. (kki)

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 5 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben