PHP 8 wurde am 26. November 2020 offiziell für die allgemeine Verfügbarkeit freigegeben!

Dieses neue größere Update bringt eine ganze Reihe von Optimierungen und leistungsstarken Funktionen in die Sprache, und wir freuen uns darauf, dir die interessantesten Änderungen vorzustellen, die es uns ermöglichen, besseren Code zu schreiben und leistungsfähigere Anwendungen zu erstellen.

PHP 8.0 Ankündigungs-Addendum
PHP 8.0 Ankündigungs-Addendum

Bist du bereit? Lasst uns eintauchen!

PHP JIT (Just in Time Compiler)

Das am meisten gelobte Feature, das mit PHP 8 kommt, ist der Just-in-time (JIT) Compiler. Was hat es mit JIT auf sich?

Der RFC-Vorschlag beschreibt JIT wie folgt:

„PHP JIT ist als ein fast unabhängiger Teil von OPcache implementiert. Es kann zur PHP-Kompilierzeit und zur Laufzeit aktiviert/deaktiviert werden. Wenn es aktiviert ist, wird der native Code der PHP-Dateien in einer zusätzlichen Region des OPcache-Shared Memory gespeichert und op_array→opcodes[].handler(s) behalten Zeiger auf die Einstiegspunkte des JIT-ed-Codes.“

Also, wie sind wir zu JIT gekommen und was ist der Unterschied zwischen JIT vs. OPcache?

Um besser zu verstehen, was JIT für PHP ist, wollen wir einen kurzen Blick darauf werfen, wie PHP vom Quellcode bis zum Endergebnis ausgeführt wird.

Die PHP-Ausführung ist ein 4-stufiger Prozess:

Das folgende Bild zeigt eine visuelle Darstellung des grundlegenden PHP-Ausführungsprozesses.

Grundlegender PHP-Ausführungsprozess
Grundlegender PHP-Ausführungsprozess

Also, wie macht OPcache PHP schneller? Und was ändert sich im Ausführungsprozess mit JIT?

Die OPcache-Erweiterung

PHP ist eine interpretierte Sprache. Das bedeutet, wenn ein PHP-Skript läuft, parst, kompiliert und führt der Interpreter den Code bei jeder Anfrage wieder und wieder aus. Dies kann zu einer Verschwendung von CPU-Ressourcen und zusätzlicher Zeit führen.

Hier kommt die OPcache-Erweiterung ins Spiel:

„OPcache verbessert die PHP-Leistung, indem vorkompilierter Skript-Bytecode im Shared Memory gespeichert wird, wodurch PHP nicht mehr bei jeder Anfrage Skripte laden und parsen muss.“

Wenn OPcache aktiviert ist, durchläuft der PHP-Interpreter den oben erwähnten 4-Stufen-Prozess nur beim ersten Ausführen des Skripts. Da PHP-Bytekodes im Shared Memory gespeichert werden, sind sie sofort als low-level Zwischendarstellung verfügbar und können sofort auf der Zend VM ausgeführt werden.

PHP-Ausführungsprozess mit aktiviertem OPcache
PHP-Ausführungsprozess mit aktiviertem OPcache

Ab PHP 5.5 ist die Zend OPcache-Erweiterung standardmäßig verfügbar und du kannst überprüfen, ob du sie korrekt konfiguriert hast, indem du einfach phpinfo() von einem Skript auf deinem Server aufrufst oder deine php.ini-Datei auscheckst (siehe OPcache-Konfigurationseinstellungen).

Vorgeschlagene Lektüre: Wie man die PHP-Speicherbegrenzung in WordPress verbessern kann.

Zend OPcache Sektion in einer phpinfo Seite
Zend OPcache Sektion in einer phpinfo Seite

Vorladen

OPcache wurde kürzlich mit der Implementierung des Vorladens verbessert, einer neuen OPcache-Funktion, die mit PHP 7.4 hinzugefügt wurde. Das Vorladen bietet dir eine Möglichkeit, einen bestimmten Satz von Skripten im OPcache-Speicher zu speichern, „bevor irgendein Anwendungscode ausgeführt wird“, aber es bringt keine greifbare Leistungsverbesserung für typische webbasierte Anwendungen.

Du kannst mehr über das Vorladen in unserer Einführung zu PHP 7.4 lesen.

Mit JIT geht PHP einen Schritt vorwärts.

JIT — The Just in Time Compiler

Auch wenn die Opcodes in Form von niedrigstufigen Zwischendarstellungen vorliegen, müssen sie dennoch in Maschinencode übersetzt werden. JIT „führt keine zusätzliche IR (Intermediate Representation)-Form ein“, sondern verwendet DynASM (Dynamic Assembler für Codegenerierungs-Engines), um nativen Code direkt aus dem PHP-Bytecode zu generieren.

Kurz gesagt, JIT übersetzt die heißen Teile des Zwischencodes in Maschinencode. Da es die Kompilierung umgeht, kann es erhebliche Verbesserungen der Leistung und des Speicherverbrauchs bringen.

Zeev Surasky, Co-Autor des PHP-JIT-Vorschlags, zeigt, um wie viel schneller Berechnungen mit JIT wären:

 

Aber, würde JIT die Leistung von WordPress effektiv verbessern?

JIT für Live Web Apps

Laut dem JIT RFC sollte die just in time Compiler Implementierung die PHP Performance verbessern. Aber würden wir solche Verbesserungen wirklich in echten Anwendungen wie WordPress erleben?

Die frühen Tests zeigen, dass JIT CPU-intensive Arbeitslasten deutlich schneller laufen lassen würde, warnt der RFC jedoch:

„… wie bei den vorherigen Versuchen – es scheint derzeit keine signifikante Verbesserung von Real-Life-Anwendungen wie WordPress zu bringen (mit opcache.jit=1235 326 req/sec vs 315 req/sec).

Es ist geplant, dir zusätzlichen Aufwand zu bieten, indem JIT für real-life Apps verbessert wird, mit Hilfe von Profiling und spekulativen Optimierungen“.

Mit aktiviertem JIT würde der Code nicht von der Zend VM, sondern von der CPU selbst ausgeführt werden, und das würde die Geschwindigkeit bei der Berechnung verbessern. Webanwendungen wie WordPress hängen auch von anderen Faktoren wie TTFB, Datenbankoptimierung, HTTP-Anfragen, etc. ab.

Relativer JIT-Beitrag zur Leistung von PHP 8
Relativer JIT-Beitrag zur Leistung von PHP 8 (Bildquelle: PHP 8.0 Ankündigungs-Addendum)

Also, wenn es um WordPress und ähnliche Anwendungen geht, sollten wir keine große Steigerung der PHP-Ausführungsgeschwindigkeit erwarten. Nichtsdestotrotz könnte JIT den Entwicklern einige Vorteile bringen.

Laut Nikita Popov:

„Die Vorteile des JIT-Compilers sind ungefähr (und wie bereits im RFC beschrieben):

  • Deutlich bessere Leistung für numerischen Code.
  • Etwas bessere Leistung für „typischen“ PHP-Webanwendungscode.
  • Das Potential, mehr Code von C nach PHP zu verschieben, da PHP nun ausreichend schnell sein wird.

Also, während JIT kaum riesige Verbesserungen der WordPress-Performance bringen wird, wird es PHP auf die nächste Stufe heben und es zu einer Sprache machen, in der viele Funktionen nun direkt geschrieben werden könnten.

Der Nachteil wäre allerdings die größere Komplexität, die zu steigenden Kosten für Wartung, Stabilität und Debugging führen kann. Laut Dmitry Stogov:

„JIT ist extrem einfach, aber trotzdem erhöht es den Grad der gesamten PHP-Komplexität, das Risiko neuartiger Bugs und die Kosten für Entwicklung und Wartung.

Der Vorschlag, JIT in PHP 8 aufzunehmen, wurde mit 50 zu 2 Stimmen angenommen.

PHP 8 Verbesserungen und neue Funktionen.

Abgesehen von JIT, können wir viele Funktionen und Verbesserungen mit PHP 8 erwarten. Die folgende Liste ist unsere handverlesene Auswahl der kommenden Ergänzungen und Änderungen, die PHP zuverlässiger und effizienter machen sollen.

Constructor Property Promotion

Als Ergebnis einer andauernden Diskussion darüber, wie man die Objekt-Ergonomie in PHP verbessern kann, schlägt der Constructor Property Promotion RFC eine neue und prägnantere Syntax vor, die die Property-Deklaration vereinfacht und sie kürzer und weniger überflüssig macht.

Dieser Vorschlag bezieht sich nur auf Promoted-Parameter, d.h. jene Methodenparameter, denen öffentliche, geschützte und private Sichtbarkeits-Keywords vorangestellt werden.

Derzeit müssen alle Eigenschaften mehrmals (mindestens viermal) wiederholt werden, bevor wir sie mit Objekten verwenden können. Betrachte das folgende Beispiel aus dem RFC:

class Point {
    public int $x;
    public int $y;
    public int $z;

    public function __construct(
        int $x = 0,
        int $y = 0,
        int $z = 0,
    ) {
        $this->x = $x;
        $this->y = $y;
        $this->z = $z;
    }
}

Laut Nikita Popov, dem Autor des RFC, müssen wir den Property-Namen mindestens viermal an drei verschiedenen Stellen schreiben: die Property-Deklaration, die Constructor-Parameter und die Property-Zuweisung. Diese Syntax ist nicht besonders brauchbar, vor allem in Klassen mit einer guten Anzahl von Eigenschaften und aussagekräftigeren Namen.

Dieser RFC schlägt vor, den Konstruktor und die Parameterdefinition zusammenzuführen. Ab PHP 8 haben wir also eine brauchbarere Art und Weise, Parameter zu deklarieren, und der oben programmierte Code kann sich wie unten gezeigt ändern:

class Point {
    public function __construct(
        public int $x = 0,
        public int $y = 0,
        public int $z = 0,
    ) {}
}

Und das war’s. Wir haben also einen neuen Weg, um Eigenschaften zu bewerben, der kürzer, lesbarer und weniger fehleranfällig ist. Laut Nikita:

Es ist eine einfache syntaktische Umwandlung, die wir machen. Aber das reduziert die Menge an Boilerplate Code, den man vor allem für Wertobjekte schreiben muss.

Die Eigenschaftsdeklaration wird transformiert, da wir diese Eigenschaften explizit deklariert haben, und wir können die Reflection-API benutzen, um Eigenschaftsdefinitionen vor der Ausführung zu introspektieren (siehe Desugaring):

Reflection (und andere Introspektionsmechanismen) werden den Zustand nach dem Desugaring beobachten. Das bedeutet, dass beförderte Eigenschaften genauso wie explizit deklarierte Eigenschaften erscheinen werden, und beförderte Konstruktor-Argumente werden wie gewöhnliche Konstruktor-Argumente erscheinen.

// before desugaring
class Point {
    public function __construct(public int $x = 0) {}
}

// after desugaring
class Point {
    public int $x;

    public function __construct(int $x = 0) {
        $this->x = $x;
    }
}

Inheritance

Wir haben keine Einschränkungen bei der Verwendung von Vererbung in Verbindung mit Promotions-Parametern. Wie auch immer, es gibt keine besondere Beziehung zwischen Parent- und Child-Klassenkonstruktoren. Laut Nikita:

Normalerweise sagen wir, dass Methoden immer mit der Parentmethode kompatibel sein müssen. […] aber diese Regel gilt nicht für den Konstruktor. Der Konstruktor gehört also wirklich zu einer einzigen Klasse, und Konstruktoren zwischen Parent- und Child-Klasse müssen in keiner Weise kompatibel sein.

Hier ist ein Beispiel:

class Test {
    public function __construct(
        public int $x = 0
    ) {}
}

class Child extends Test {
    public function __construct(
        $x, 
        public int $y = 0,
        public int $z = 0,
    ) {
        parent::__construct($x);
    }
}

What’s Not Allowed With Promoted Properties

Promoted Eigenschaften sind in nicht-abstrakten Konstrukteuren und Eigenschaften erlaubt, aber es gibt einige Einschränkungen, die hier erwähnt werden sollten.

Abstract Constructors

Promoted Eigenschaften sind in abstrakten Klassen und Interfaces nicht erlaubt:

abstract class Test {
    // Error: Abstract constructor.
    abstract public function __construct(private $x);
}
 
interface Test {
    // Error: Abstract constructor.
    public function __construct(private $x);
}
Nullability

Eine der bemerkenswertesten Einschränkungen bezieht sich auf die Annullierbarkeit. Früher, wenn wir einen Typ benutzten, der nicht explizit löschbar war, aber mit einem Standardwert von null, war der Typ implizit löschbar. Aber bei Property-Typen haben wir dieses implizite Verhalten nicht, weil die Promoted-Parameter eine Property-Deklaration erfordern und der nullbare Typ explizit deklariert werden muss. Siehe das folgende Beispiel aus dem RFC:

class Test {
    // Error: Using null default on non-nullable property
    public function __construct(public Type $prop = null) {}

    // Correct: Make the type explicitly nullable instead
    public function __construct(public ?Type $prop = null) {}
}
Callable Type

Da Callable kein unterstützter Typ für Eigenschaften ist, ist es uns nicht erlaubt, den Callable-Typ in beworbenen Eigenschaften zu verwenden:

class Test {
    // Error: Callable type not supported for properties.
    public function __construct(public callable $callback) {}
}
The var Keyword Is Not Allowed

Nur ein visibility-Schlüsselwort kann mit promoted-Parametern verwendet werden, daher ist es nicht erlaubt, Konstruktoreigenschaften mit dem var-Schlüsselwort zu deklarieren (siehe das folgende Beispiel aus dem RFC):

class Test {
    // Error: "var" keyword is not supported.
    public function __construct(var $prop) {}
}
No Duplications Allowed

Wir können promoted Eigenschaften und explizite Eigenschaften in der gleichen Klasse kombinieren, aber Eigenschaften können nicht zweimal deklariert werden:

class Test {
    public string $prop;
    public int $explicitProp;

    // Correct
    public function __construct(public int $promotedProp, int $arg) {
        $this->explicitProp = $arg;
    }

    // Error: Redeclaration of property.
    public function __construct(public string $prop) {}
}
Variadic Parameters Are Not Allowed

Der Grund dafür ist, dass sich der deklarierte Typ von dem variadischen Parameter unterscheidet, der eigentlich ein Array ist:

class Test {
    // Error: Variadic parameter.
    public function __construct(public string ...$strings) {}
}

Further Readings

Um einen genaueren Blick auf Costructor Property Promotion zu werfen, hört euch dieses Interview mit Nikita Popov an. Für einen tieferen Einblick in die Objekt-Ergonomie in PHP, schau dir diesen Beitrag und das folgende Interview mit Larry Garfield an. 

Validierung für abstrakte Eigenschaftsmethoden

Eigenschaften werden definiert als „ein Mechanismus zur Wiederverwendung von Code in Sprachen mit einer einzigen Vererbung wie PHP“. Typischerweise werden sie verwendet, um Methoden zu deklarieren, die in mehreren Klassen verwendet werden können.

Eine Eigenschaft kann auch abstrakte Methoden enthalten. Diese Methoden deklarieren einfach die Signatur der Methode, aber die Implementierung der Methode muss innerhalb der Klasse unter Verwendung der Eigenschaft erfolgen.

Gemäß dem PHP-Handbuch,

„Eigenschaften unterstützen die Verwendung abstrakter Methoden, um Anforderungen an die ausstellende Klasse zu stellen.”

Das bedeutet auch, dass die Signaturen der Methoden übereinstimmen müssen. Mit anderen Worten, die Art und die Anzahl der benötigten Argumente müssen gleich sein.

Wie auch immer, laut Nikita Popov, Autor des RFC, wird die Signaturvalidierung derzeit nur punktuell durchgesetzt:

  • In den meisten Fällen wird sie nicht erzwungen, da die Methodenimplementierung von der verwendenden Klasse angeboten wird: https://3v4l.org/SeVK3
  • Es wird durchgesetzt, wenn die Implementierung von einer Parent-Klasse kommt: https://3v4l.org/4VCIp
  • Es wird durchgesetzt, wenn die Implementierung von einer Child-Klasse kommt: https://3v4l.org/q7Bq2

Das folgende Beispiel von Nikita bezieht sich auf den ersten Fall (nicht erzwungene Unterschrift):

trait T {
	abstract public function test(int $x);
}
 
class C {
	use T;

	// Allowed, but shouldn't be due to invalid type.
	public function test(string $x) {}
}

Davon abgesehen schlägt dieser RFC vor, immer einen fatalen Fehler zu werfen, wenn die Implementierungsmethode nicht mit der abstrakten Eigenschaftsmethode kompatibel ist, unabhängig von ihrem Ursprung:

Fatal error: Declaration of C::test(string $x) must be compatible with T::test(int $x) in /path/to/your/test.php on line 10

Dieser RFC wurde einstimmig angenommen.

Inkompatible Methodensignaturen

In PHP werfen Vererbungsfehler aufgrund inkompatibler Methodensignaturen entweder einen fatalen Fehler oder eine Warnung aus, je nachdem, was die Ursache des Fehlers ist.

Wenn eine Klasse ein Interface implementiert, führen inkompatible Methodensignaturen zu einem fatalen Fehler. Laut der Dokumentation zu Objekt Interfaces:

„Die Klasse, die das Interface implementiert, muss eine Methodensignatur verwenden, die mit dem LSP (Liskov Substitutionsprinzip) kompatibel ist. Wenn sie dies nicht tut, führt dies zu einem fatalen Fehler.“

Hier ist ein Beispiel für einen Vererbungsfehler mit einem Interface:

interface I {
	public function method(array $a);
}
class C implements I {
	public function method(int $a) {}
}

In PHP 7.4 würde der obige Code den folgenden Fehler auslösen:

Fatal error: Declaration of C::method(int $a) must be compatible with I::method(array $a) in /path/to/your/test.php on line 7

Eine Funktion in einer Child-Klasse mit einer inkompatiblen Signatur würde eine Warnung auslösen. Siehe den folgenden Code aus dem RFC:

class C1 {
	public function method(array $a) {}
}
class C2 extends C1 {
	public function method(int $a) {}
}

In PHP 7.4 würde der obige Code einfach eine Warnung ausgeben:

Warning: Declaration of C2::method(int $a) should be compatible with C1::method(array $a) in /path/to/your/test.php on line 7

Nun schlägt dieser RFC vor, bei inkompatiblen Methodensignaturen immer einen fatalen Fehler zu werfen. Mit PHP 8 würde der Code, den wir weiter oben gesehen haben, folgendes auslösen:

Fatal error: Declaration of C2::method(int $a) must be compatible with C1::method(array $a) in /path/to/your/test.php on line 7

Arrays, die mit einem negativen Index beginnen

In PHP, wenn ein Array mit einem negativen Index (start_index < 0) beginnt, beginnen die folgenden Indizes bei 0 (mehr dazu in der array_fill Dokumentation). Schau dir das folgende Beispiel an:

$a = array_fill(-5, 4, true);
var_dump($a);

In PHP 7.4 würde das Ergebnis wie folgt aussehen:

array(4) {
	[-5]=>
	bool(true)
	[0]=>
	bool(true)
	[1]=>
	bool(true)
	[2]=>
	bool(true)
}

Nun, dieser RFC schlägt vor, die Dinge so zu ändern, dass der zweite Index start_index + 1 wäre, was auch immer der Wert von start_index ist.

In PHP 8 würde der obige Code zu folgendem Array führen:

array(4) {
	[-5]=>
	bool(true)
	[-4]=>
	bool(true)
	[-3]=>
	bool(true)
	[-2]=>
	bool(true)
}

Mit PHP 8 ändern Arrays, die mit einem negativen Index beginnen, ihr Verhalten. Lies mehr über Rückwärtsinkompatibilitäten im RFC.

Union Types 2.0

Union types akzeptieren Werte, die von unterschiedlicher Art sein können. Derzeit bietet PHP keine Unterstützung für Union types, mit Ausnahme der ?Type syntax und dem special iterable type.

Vor PHP 8 konnten Union types nur in phpdoc-Annotationen angegeben werden, wie das folgende Beispiel aus dem RFC zeigt:

class Number {
	/**
	 * @var int|float $number
	 */
	private $number;

	/**
	 * @param int|float $number
	 */
	public function setNumber($number) {
		$this->number = $number;
	}

	/**
	 * @return int|float
	 */
	public function getNumber() {
		return $this->number;
	}
}

Nun schlägt der RFC für Union types 2.0 vor, Unterstützung für Union types in Funktionssignaturen hinzuzufügen, so dass wir uns nicht mehr auf die Inline-Dokumentation verlassen, sondern stattdessen Union types mit einer T1|T2|... Syntax definieren würden:

class Number {
	private int|float $number;

	public function setNumber(int|float $number): void {
		$this->number = $number;
	}

	public function getNumber(): int|float {
		return $this->number;
	}
}

Wie von Nikita Popov im RFC erklärt,

„Die Unterstützung von Union-Types in der Sprache erlaubt es uns, mehr Typinformationen aus phpdoc in Funktionssignaturen zu verschieben, mit den üblichen Vorteilen, die das mit sich bringt:

  • Typen werden tatsächlich durchgesetzt, so dass Fehler frühzeitig erkannt werden können.
  • Weil sie erzwungen werden, ist die Wahrscheinlichkeit geringer, dass Typinformationen veraltet sind oder Randfehler übersehen werden.
  • Die Typen werden während der Vererbung überprüft, wodurch das Liskov-Substitutionsprinzip durchgesetzt wird.
  • Die Typen sind über Reflection verfügbar.
  • Die Syntax ist viel weniger Boilerplate-y als phpdoc“.

Union types unterstützen alle verfügbaren Typen, mit einigen Einschränkungen:

  • Der Typ void kann nicht Teil einer Union sein, da void bedeutet, dass eine Funktion keinen Wert zurückgibt.
  • Der null-Typ wird nur in Union types unterstützt, aber seine Verwendung als eigenständiger Typ ist nicht erlaubt.
  • Die nullbare Typ-Notation (?T) ist auch erlaubt, was T|null bedeutet, aber wir dürfen die ?T-Notation nicht in Union types verwenden (?T1|T2 ist nicht erlaubt und wir sollten stattdessen T1|T2|null verwenden).
  • Da viele Funktionen (z.B. strpos(), strstr(), substr(), etc.) false unter den möglichen Rückgabetypen einschließen, wird auch der false Pseudo-Typ unterstützt.

Mehr über Union Types V2 könnt ihr im RFC nachlesen.

Konsistente Typ-Fehler für interne Funktionen

Wenn ein Parameter vom illegalen Typ übergeben wird, verhalten sich interne und benutzerdefinierte Funktionen unterschiedlich.

Benutzerdefinierte Funktionen werfen einen TypeError, aber interne Funktionen verhalten sich unterschiedlich, abhängig von verschiedenen Bedingungen. Wie auch immer, das typische Verhalten ist, eine Warnung zu werfen und null zurückzugeben. Siehe das folgende Beispiel in PHP 7.4:

var_dump(strlen(new stdClass));

Dies würde zu folgender Warnung führen:

Warning: strlen() expects parameter 1 to be string, object given in /path/to/your/test.php on line 4
NULL

Wenn strict_types aktiviert ist, oder argument information Typen angibt, wäre das Verhalten anders. In solchen Szenarien wird der Typfehler erkannt und führt zu einem TypeError.

Diese Situation würde zu einer Reihe von Problemen führen, die in der Issues-Sektion des RFC gut erklärt sind.

Um diese Inkonsistenzen zu beseitigen, schlägt dieser RFC vor, die internen Parameter-Parsing-APIs so zu gestalten, dass sie immer einen ThrowError erzeugen, wenn ein Parametertyp nicht übereinstimmt.

In PHP 8 wirft der obige Code den folgenden Fehler aus:

Fatal error: Uncaught TypeError: strlen(): Argument #1 ($str) must be of type string, object given in /path/to/your/test.php:4
Stack trace:
#0 {main}
  thrown in /path/to/your/test.php on line 4

throw Expression

In PHP ist throw eine Anweisung, daher ist es nicht möglich, sie an Stellen zu verwenden, an denen nur ein Ausdruck erlaubt ist.

Dieser RFC schlägt vor, die throw-Anweisung in einen Ausdruck umzuwandeln, so dass sie in jedem Kontext verwendet werden kann, wo Ausdrücke erlaubt sind. Zum Beispiel arrow Funktionen, Null-Koaleszenz-Operator, ternäre und Elvis-Operatoren, etc.

Siehe die folgenden Beispiele aus dem RFC:

$callable = fn() => throw new Exception();

// $value is non-nullable.
$value = $nullableValue ?? throw new InvalidArgumentException();
 
// $value is truthy.
$value = $falsableValue ?: throw new InvalidArgumentException();

Weak Maps

Eine Weak Map ist eine Sammlung von Daten (Objekten), in der Schlüssel schwach referenziert werden, was bedeutet, dass sie nicht daran gehindert werden, Müll zu sammeln.

PHP 7.4 fügte Unterstützung für schwache Referenzen hinzu, um einen Verweis auf ein Objekt zu erhalten, was nicht verhindert, dass das Objekt selbst zerstört wird. Wie von Nikita Popov angemerkt,

„Rohe schwache Verweise sind für sich genommen nur von begrenztem Nutzen und Weak Maps werden in der Praxis viel häufiger verwendet. Es ist nicht möglich, eine effiziente Weak Map zusätzlich zu den PHP-Schwachen Referenzen zu implementieren, da die Möglichkeit, einen Zerstörungs-Callback zu registrieren, nicht angeboten wird.“

Aus diesem Grund führt dieser RFC eine WeakMap-Klasse ein, um Objekte zu erstellen, die als Weak Map-Schlüssel verwendet werden, die zerstört und von der Weak Map entfernt werden können, wenn es keine weiteren Referenzen auf das Schlüsselobjekt gibt.

In lang laufenden Prozessen würde dies Speicherlecks verhindern und die Performance verbessern. Siehe das folgende Beispiel aus dem RFC:

$map = new WeakMap;
$obj = new stdClass;
$map[$obj] = 42;
var_dump($map);

Mit PHP 8 würde der obige Code das folgende Ergebnis erzeugen (siehe den Code in Aktion hier):

object(WeakMap)#1 (1) {
	[0]=>
	array(2) {
		["key"]=>
		object(stdClass)#2 (0) {
		}
		["value"]=>
		int(42)
	}
}

Wenn du das Objekt entsperrst, wird der Schlüssel automatisch von der Weak Map entfernt:

unset($obj);
var_dump($map);

Nun wäre das Ergebnis wie folgt:

object(WeakMap)#1 (0) {
}

Für eine genauere Betrachtung der Weak Maps siehe RFC. Der Vorschlag wurde einstimmig angenommen.

Nachkomma in Parameterliste

Nachkommas sind Kommas, die an Listen von Elementen in verschiedenen Kontexten angehängt werden. PHP 7.2 führte die Anhängung von Kommas in der Listensyntax ein, PHP 7.3 führte die Anhängung von Kommas in Funktionsaufrufen ein.

PHP 8 führt in Parameterlisten mit Funktionen, Methoden und Abschlüssen nun auch Nachkommas in Parameterlisten ein, wie das folgende Beispiel zeigt:

class Foo {
	public function __construct(
		string $x,
		int $y,
		float $z, // trailing comma
	) {
		// do something
	}
}

Dieser Antrag wurde mit 58 zu 1 Stimmen angenommen.

Allow ::class syntax on objects

Um den Namen einer Klasse abzurufen, können wir die Syntax Foo\Bar::class verwenden. Dieser RFC schlägt vor, die gleiche Syntax auf Objekte auszudehnen, so dass es nun möglich ist, den Namen der Klasse eines gegebenen Objekts zu holen, wie im folgenden Beispiel gezeigt:

$object = new stdClass;
var_dump($object::class); // "stdClass"
 
$object = null;
var_dump($object::class); // TypeError

Mit PHP 8 bietet $object::class das gleiche Ergebnis wie get_class($object). Wenn $object kein Objekt ist, wirft es eine TypeError-Ausnahme aus.

Dieser Vorschlag wurde einstimmig angenommen.

Attributes v2

Attribute, auch als Annotationen bekannt, sind eine Form strukturierter Metadaten, die zur Angabe von Eigenschaften für Objekte, Elemente oder Dateien verwendet werden können.

Bis PHP 7.4 waren Doc-Kommentare die einzige Möglichkeit, Metadaten zu Deklarationen von Klassen, Funktionen usw. hinzuzufügen. Jetzt führt der RFC Attributes v2 Attribute für PHP ein und definiert sie als eine Form von strukturierten, syntaktischen Metadaten, die zu Deklarationen von Klassen, Eigenschaften, Funktionen, Methoden, Parametern und Konstanten hinzugefügt werden können.

Attribute werden vor den Deklarationen, auf die sie sich beziehen, hinzugefügt. Siehe die folgenden Beispiele aus dem RFC:

<>
class Foo
{
	<>
	public const FOO = 'foo';

	<>
	public $x;

	<>
	public function foo(<> $bar) { }
}

$object = new <> class () { };

<>
function f1() { }

$f2 = <> function () { };

$f3 = <> fn () => 1;

Attribute können vor oder nach einem Dok-Block-Kommentar hinzugefügt werden:

<>
/** docblock */
<>
function foo() {}

Jede Deklaration kann ein oder mehrere Attribute haben, und jedes Attribut kann einen oder mehrere zugehörige Werte haben:

<>
<<SingleArgument(0)>>
<<FewArguments('Hello', 'World')>>
function foo() {}

Einen tieferen Überblick über PHP-Attribute, Anwendungsfälle und alternative Syntax findest du im RFC.

Benannte Argumente

Benannte Argumente bieten eine neue Möglichkeit, Argumente an eine Funktion in PHP zu übergeben:

Benannte Argumente ermöglichen die Übergabe von Argumenten an eine Funktion, die auf dem Parameternamen und nicht auf der Parameterposition basiert.

Wir können benannte Argumente an eine Funktion übergeben, indem wir einfach den Parameternamen vor ihrem Wert hinzufügen:

callFunction(name: $value);

Wir dürfen auch reservierte Schlüsselwörter verwenden, wie das folgende Beispiel zeigt:

callFunction(array: $value);

Aber es ist uns nicht erlaubt, einen Parameternamen dynamisch zu übergeben. Der Parameter muss ein Bezeichner sein und die folgende Syntax ist nicht erlaubt:

callFunction($name: $value);

Laut Nikita Popov, dem Autor dieses RFC, bieten die genannten Argumente mehrere Vorteile.

Zunächst einmal werden benannte Argumente uns helfen, verständlicheren Code zu schreiben, weil ihre Bedeutung selbstdokumentierend ist. Das folgende Beispiel aus dem RFC ist selbsterklärend:

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

Benannte Argumente sind auftragsunabhängig. Das bedeutet, dass wir nicht gezwungen sind, Argumente an eine Funktion in der gleichen Reihenfolge wie die Funktionssignatur zu übergeben:

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

Es ist auch möglich, benannte Argumente mit Positionsargumenten zu kombinieren:

htmlspecialchars($string, double_encode: false);

Ein weiterer großer Vorteil von benannten Argumenten ist, dass sie es erlauben, nur die Argumente anzugeben, die wir tatsächlich ändern wollen, und dass wir keine Standardargumente angeben müssen, wenn wir Standardwerte nicht überschreiben wollen. Das folgende Beispiel aus dem RFC macht dies deutlich:

htmlspecialchars($string, default, default, false);
// vs
htmlspecialchars($string, double_encode: false);

Benannte Argumente können mit PHP-Attributen verwendet werden, wie das folgende Beispiel aus dem RFC zeigt:

<<MyAttribute('A', b: 'B')>>
class Test {}

Die Übergabe von Positionsargumenten nach benannten Argumenten ist jedoch nicht erlaubt und führt zu einem Kompilierzeitfehler. Dasselbe passiert, wenn derselbe Parametername zweimal übergeben wird.

Benannte Argumente sind bei Klassendeklarationen besonders nützlich, da Konstruktoren in der Regel eine große Anzahl von Parametern haben und benannte Argumente eine „ergonomischere“ Art der Deklaration einer Klasse bieten.

Für eine nähere Betrachtung der benannten Argumente, mit Einschränkungen, Rückwärtsinkompatibilitäten und mehreren Beispielen, siehe RFC zu benannten Argumenten.

Nullsafe-Operator

Dieser RFC führt den nullsicheren Operator $-> mit vollständiger Kurzschlussauswertung ein.

Bei der Kurzschlussbewertung wird der zweite Operator nur dann bewertet, wenn der erste Operator nicht auf null bewertet. Wenn ein Operator in einer Kette auf null auswertet, stoppt die Ausführung der gesamten Kette und wertet auf null aus.

Betrachte die folgenden Beispiele aus dem RFC:

$foo = $a?->b();

Wenn $a null ist, wird die Methode b() nicht aufgerufen und $foo wird auf null gesetzt.

Siehe den nullsafe operator RFC für zusätzliche Beispiele, Ausnahmen und zukünftigen Anwendungsbereich.

Saner String to Number-Vergleiche

In früheren PHP-Versionen hat PHP bei einem nicht strikten Vergleich zwischen Zeichenketten und Zahlen zunächst die Zeichenkette in eine Zahl umgewandelt und dann den Vergleich zwischen ganzen Zahlen oder Fließkommazahlen durchgeführt. Auch wenn dieses Verhalten in verschiedenen Szenarien recht nützlich ist, kann es zu falschen Ergebnissen führen, die auch zu Bugs und/oder Sicherheitsproblemen führen können.

Betrachte das folgende Beispiel aus dem RFC:

$validValues = ["foo", "bar", "baz"];
$value = 0;
var_dump(in_array($value, $validValues));
// bool(true)

PHP 8 führt Saner-Zeichenketten in Zahlenvergleiche ein, um Vergleiche von Zeichenketten mit Zahlen vernünftiger zu machen. Mit den Worten von Nikita Popov,

Dieser RFC beabsichtigt, Zahlenvergleichen mit Zeichenketten ein vernünftigeres Verhalten zu geben: Wenn du mit einer numerischen Zeichenfolge vergleichst, verwende einen Zahlenvergleich (wie jetzt). Andernfalls konvertiere die Zahl in eine Zeichenkette und verwenden einen Zeichenkettenvergleich.

Die folgende Tabelle vergleicht das Verhalten von Zeichenketten im Vergleich zu früheren PHP-Versionen und in PHP 8:

Comparison    | Before | After
------------------------------
 0 == "0"     | true   | true
 0 == "0.0"   | true   | true
 0 == "foo"   | true   | false
 0 == ""      | true   | false
42 == "   42" | true   | true
42 == "42foo" | true   | false

Lies mehr über die vielen Auswirkungen dieser Änderung und wie sich die Vergleiche zwischen Strings und Nummern in PHP 8 ändern, im offiziellen RFC von Nikita Popov.

Saner numerische Zeichenketten

In PHP fallen Zeichenketten, die Zahlen enthalten, in drei Kategorien:

  • Numerische Zeichenfolgen: Zeichenfolgen, die eine Zahl enthalten, der optional Leerzeichen vorangestellt sind.
  • Führende numerische Zeichenfolge: Zeichenfolgen, deren Anfangszeichen numerische Zeichenfolgen sind und deren Endzeichen nicht numerisch sind.
  • Nicht-numerische Zeichenfolge: Zeichenfolgen, die in keine der beiden vorherigen Kategorien fallen.

Numerische Zeichenketten und vorangestellte numerische Zeichenketten werden je nach der durchgeführten Operation unterschiedlich behandelt. Zum Beispiel:

  • Explizite Konvertierungen von Zeichenfolgen in Zahlen (d. h. (int) und (float) Casts) konvertieren numerische und führende numerische Zeichenfolgen in Zahlen. Die explizite Umwandlung einer nicht-numerischen Zeichenfolge in eine Zahl ergibt 0.
  • Implizite Konvertierungen von Zeichenketten in Zahlen (d.h. keine strict_type-Deklaration) führen zu unterschiedlichen Ergebnissen für numerische und nicht-numerische Zeichenketten. Nicht-numerische Umwandlungen von Zeichenketten in Zahlen führen zu einem TypeError.
  • is_numeric() gibt nur für numerische Zeichenketten true zurück.

String-Offsets, arithmetische Operationen, Inkrement- und Dekrementoperationen, String-zu-String-Vergleiche und bitweise Operationen führen ebenfalls zu unterschiedlichen Ergebnissen.

Dieser RFC schlägt vor:

Vereinheitliche die verschiedenen numerischen Zeichenfolgenmodi in einem einzigen Konzept: Nur numerische Zeichen, wobei sowohl führende als auch nachlaufende Leerzeichen erlaubt sind. Jeder andere Zeichenkettentyp ist nicht numerisch und führt zu TypeErrors, wenn er in einem numerischen Kontext verwendet wird.

Das bedeutet, dass alle Zeichenfolgen, die derzeit den E_NOTICE „A non well formed numeric value encountered“ ausgeben, in den E_WARNING „A non-numeric value encountered“ umklassifiziert werden, es sei denn, die führende numerische Zeichenfolge enthielt nur am Ende Leerzeichen. Und die verschiedenen Fälle, die derzeit eine E_WARNING ausgeben, werden in TypeErrors umgewandelt.

Einen tieferen Überblick über numerische Zeichenketten in PHP 8 mit Codebeispielen, Ausnahmen und Abwärtskompatibilitätsproblemen findest du im RFC.

Übereinstimmungs-Ausdruck v2

Der neue match-Ausdruck ist dem switch ziemlich ähnlich, aber mit einer sichereren Semantik und der Möglichkeit, Werte zurückzugeben.

Um den Unterschied zwischen den beiden Kontrollstrukturen zu verstehen, betrachte das folgende switch-Beispiel aus dem RFC:

switch (1) {
	case 0:
		$result = 'Foo';
		break;
	case 1:
		$result = 'Bar';
		break;
	case 2:
		$result = 'Baz';
		break;
}
 
echo $result;
//> Bar

Wir können jetzt das gleiche Ergebnis wie im obigen Code mit dem folgenden match ausdruck erhalten:

echo match (1) {
	0 => 'Foo',
	1 => 'Bar',
	2 => 'Baz',
};
//> Bar

Ein großer Vorteil der Verwendung des neuen match ausdrucks besteht darin, dass während switch Werte lose vergleicht (==), was möglicherweise zu unerwarteten Ergebnissen führt, bei match ist der Vergleich eine Identitätsprüfung (===).

Der match ausdruck kann auch mehrere durch Komma getrennte Ausdrücke enthalten, was eine prägnantere Syntax ermöglicht (quelle):

$result = match ($x) {
	// This match arm:
	$a, $b, $c => 5,
	// Is equivalent to these three match arms:
	$a => 5,
	$b => 5,
	$c => 5,
};

Weitere Beispiele und Anwendungsfälle findest du im Match expression v2 RFC und in der PHP-Dokumentation.

Strengere Typprüfungen für arithmetische/bitweise Operatoren

In früheren Versionen von PHP war es erlaubt, arithmetische und bitweise Operatoren auf ein Array, eine Ressource oder ein nicht überladenes Objekt anzuwenden. Wie auch immer, das Verhalten war manchmal inkonsistent.

In diesem RFC zeigt Nikita Popov anhand eines einfachen Beispiels, wie unvernünftig dieses Verhalten sein könnte:

var_dump([] % [42]);
// int(0)

Nikita erklärt, wie die Anwendung eines arithmetischen oder bitweisen Operators auf Arrays, Ressourcen oder nicht überladene Objekte zu unterschiedlichen Ergebnissen führte:

Operatoren +, -, *, /, **:

  • Fehlerausnahme auf Array-Operand werfen. (Ausschließen von +, wenn beide Operanden Array-Operanden sind).
  • Wandelt einen Ressourcenoperanden stillschweigend in die Ressourcen-ID als Ganzzahl um.
  • Konvertiert einen Objektoperanden in den ganzzahligen Wert Eins, während eine Benachrichtigung ausgegeben wird.

Operatoren %, <<, >>, &, |, ^:

  • Wandelt einen Array-Operanden stillschweigend in die Ganzzahl Null um, wenn er leer ist, oder in die Ganzzahl Eins, wenn er nicht leer ist.
  • Wandelt einen Ressourcenoperanden stillschweigend in die Ressourcen-ID als Ganzzahl um.
  • Konvertiert einen Objektoperanden in den ganzzahligen Wert Eins, während eine Benachrichtigung ausgegeben wird.

Operator ~:

  • Wirft eine Fehlerausnahme für Array-, Ressourcen- und Objektoperanden.

Operatoren ++ und –:

  • Schweigend nichts tun, wenn der Operand ein Array, eine Ressource oder ein Objekt ist.

Mit PHP 8 ändern sich die Dinge und das Verhalten ist für alle arithmetischen und bitweisen Operatoren gleich:

Wirf eine TypeErrorAusnahme für Array-, Ressourcen- und Objektoperanden.

Neue PHP-Funktionen

PHP 8 bringt mehrere neue Funktionen in die Sprache:

str_contains

Vor PHP 8 waren strstr und strpos die typischen Optionen für Entwickler, um nach einer Nadel innerhalb einer gegebenen Zeichenkette zu suchen. Das Problem ist, dass beide Funktionen nicht als sehr intuitiv angesehen werden und ihre Verwendung für neue PHP-Entwickler verwirrend sein kann. Siehe das folgende Beispiel:

$mystring = 'Managed WordPress Hosting';
$findme = 'WordPress';
$pos = strpos($mystring, $findme);

if ($pos !== false) {
	echo "The string has been found";
} else {
	echo "String not found";
}

Im obigen Beispiel haben wir den Vergleichsoperator !== verwendet, der auch prüft, ob zwei Werte vom gleichen Typ sind. Dies verhindert, dass wir einen Fehler erhalten, wenn die Position der Nadel 0 ist:

„Diese Funktion kann den booleschen FALSE zurückgeben, kann aber auch einen nicht-booleschen Wert zurückgeben, der als FALSE gewertet wird. […] Verwende den Operator ===, um den Rückgabewert dieser Funktion zu testen.“

Darüber hinaus bieten dir mehrere Frameworks Hilfsfunktionen an, um nach einem Wert innerhalb einer gegebenen Zeichenfolge zu suchen (siehe Laravel-Helfer-Dokumentation als Beispiel).

Nun schlägt dieser RFC die Einführung einer neuen Funktion vor, die es erlaubt, innerhalb einer Zeichenkette zu suchen: str_contains.

str_contains ( string $haystack , string $needle ) : bool

Seine Verwendung ist ziemlich einfach. str_contains prüft, ob $needle in $haystack gefunden wird und gibt entsprechend true oder false zurück.

Dank str_contains können wir also den folgenden Code schreiben:

$mystring = 'Managed WordPress Hosting';
$findme   = 'WordPress';

if (str_contains($mystring, $findme)) {
	echo "The string has been found";
} else {
	echo "String not found";
}

Der besser lesbar und weniger fehleranfällig ist (siehe diesen Code in Aktion hier).

Zum Zeitpunkt des Verfassens dieses Artikels unterscheidet str_contains zwischen Groß- und Kleinschreibung, aber das könnte sich in Zukunft ändern.

Der Vorschlag str_contains wurde mit 43 zu 9 Stimmen angenommen.

str_starts_with() und str_ends_with()

Zusätzlich zur Funktion str_contains ermöglichen zwei neue Funktionen die Suche nach einer Nadel innerhalb einer gegebenen Zeichenfolge: str_starts_with und str_ends_with.

Diese neuen Funktionen prüfen, ob eine gegebene Zeichenkette mit einer anderen Zeichenkette beginnt oder endet:

str_starts_with (string $haystack , string $needle) : bool
str_ends_with (string $haystack , string $needle) : bool

Beide Funktionen geben false zurück, wenn $needle länger als $haystack ist.

Laut Will Hudgins, dem Autor dieses RFC,

„Die Funktionalität str_starts_with und str_ends_with wird so häufig benötigt, dass viele wichtige PHP-Frameworks sie unterstützen, darunter Symfony, Laravel, Yii, FuelPHP und Phalcon.“

Dank ihnen konnten wir nun die Verwendung suboptimaler und weniger intuitiver Funktionen wie substr, strpos vermeiden. Bei beiden Funktionen wird zwischen Groß- und Kleinschreibung unterschieden:

$str = "WordPress";
if (str_starts_with($str, "Word")) echo "Found!";

if (str_starts_with($str, "word")) echo "Not found!";

Du kannst diesen Code hier in Aktion sehen.

Dieser RFC wurde mit 51 zu 4 Stimmen genehmigt.

get_debug_type

get_debug_type ist eine neue PHP-Funktion, die den Typ einer Variablen zurückgibt. Die neue Funktion funktioniert ziemlich ähnlich wie die gettype-Funktion, aber get_debug_type liefert native Typnamen zurück und löst Klassennamen auf.

Das ist eine gute Verbesserung für die Sprache, da gettype() für die Typüberprüfung nicht nützlich ist.

Der RFC bietet dir zwei nützliche Beispiele, um den Unterschied zwischen der neuen get_debug_type()-Funktion und gettype() besser zu verstehen. Das erste Beispiel zeigt gettype bei der Arbeit:

$bar = [1,2,3];

if (!($bar instanceof Foo)) { 
	throw new TypeError('Expected ' . Foo::class . ', got ' . (is_object($bar) ? get_class($bar) : gettype($bar)));
}

Mit PHP 8 könnten wir stattdessen get_debug_type verwenden:

if (!($bar instanceof Foo)) { 
	throw new TypeError('Expected ' . Foo::class . ' got ' . get_debug_type($bar));
}

Die folgende Tabelle zeigt die Rückgabewerte von get_debug_type und gettype:

Value gettype() get_debug_type()
1 integer int
0.1 double float
true boolean bool
false boolean bool
null NULL null
„WordPress“ string string
[1,2,3] array array
A class with name „Foo\Bar“ object Foo\Bar
An anonymous class object class@anonymous

Zusätzliche RFCs

Hier ist eine kurze Liste der zusätzlichen genehmigten Verbesserungen, die mit PHP 8 kommen:

  1. Stringable Interface: Dieser RFC führt ein Stringable Interface ein, das automatisch zu Klassen hinzugefügt wird, die die __to String()-Methode implementieren. Das Hauptziel hier ist die Verwendung des Union-Typs string|Stringable.
  2. Neue DOM-Lebensstandard-APIs in ext/dom: dieser RFC schlägt vor, den aktuellen DOM-Lebensstandard in die PHP-DOM-Erweiterung zu implementieren, indem neue Interfaces und öffentliche Eigenschaften eingeführt werden.
  3. Statischer Rückgabetyp: PHP 8 führt die Verwendung von static als Rückgabetyp neben self und parent Typen ein.
  4. Variable Syntax Tweaks: dieser RFC löst einige verbleibende Inkonsistenzen in der Variablensyntax von PHP auf.

Zusammenfassung

Was für eine Fahrt! In diesem Beitrag haben wir die interessantesten Optimierungen und Funktionen von PHP 8 behandelt. Die am meisten erwartete davon ist sicherlich der Just in Time-Compiler, aber PHP 8 bietet noch viel mehr.

Setze dir für diesen Blog-Beitrag ein Lesezeichen für die zukünftige Verwendung. 🤓

Jetzt bist du an der Reihe: Bist du bereit, die neuen PHP-Funktionen zu testen? Welche ist deine Lieblingsfunktion? Schreibe eine Zeile in den Kommentarbereich unten.

Carlo Daniele Kinsta

Carlo is a passionate lover of webdesign and front-end development. He has been playing with WordPress for more than 20 years, also in collaboration with Italian and European universities and educational institutions. He has written hundreds of articles and guides about WordPress, published both on Italian and international websites, as well as on printed magazines. You can find him on LinkedIn.