PHP 8.0: Neue Funktionen, Klassen und JIT (4/4)

vor 4 Jahren von David Grudl  

PHP Version 8.0 ist erschienen. Sie ist so vollgepackt mit Neuerungen wie keine Version zuvor. Ihre Vorstellung erforderte gleich vier separate Artikel. In diesem letzten Teil werfen wir einen Blick auf neue Funktionen und Klassen und stellen den Just-in-Time-Compiler vor.

Neue Funktionen

Die Standardbibliothek von PHP verfügt über Hunderte von Funktionen, und in Version 8.0 sind sechs neue hinzugekommen. Das mag nach wenig klingen, aber die meisten von ihnen schließen Schwachstellen der Sprache. Das passt gut zum Tenor der gesamten Version 8.0, die PHP wie keine Version zuvor vervollständigt und konsolidiert. Eine Übersicht über alle neuen Funktionen und Methoden finden Sie im Migrationsleitfaden.

str_contains() str_starts_with() str_ends_with()

Funktionen zur Feststellung, ob eine Zeichenkette mit einer Teilzeichenkette beginnt, endet oder diese enthält.

if (str_contains('Nette', 'te')) {
	...
}

Mit der Einführung dieses Trios definiert PHP, wie mit einer leeren Zeichenkette bei der Suche umzugehen ist, woran sich auch alle anderen verwandten Funktionen orientieren, und zwar so, dass die leere Zeichenkette überall vorkommt:

str_contains('Nette', '')     // true
str_starts_with('Nette', '')  // true
strpos('Nette', '')           // 0 (früher false)

Dadurch ist das Verhalten des Trios völlig identisch mit den Pendants in Nette:

str_contains()      # Nette\Utils\String::contains()
str_starts_with()   # Nette\Utils\String::startsWith()
str_ends_with()     # Nette\Utils\String::endsWith()

Warum sind diese Funktionen so wichtig? Die Standardbibliotheken aller Sprachen sind immer durch die historische Entwicklung belastet, und Inkonsistenzen und Fehltritte lassen sich nicht vermeiden. Aber gleichzeitig sind sie die Visitenkarte der jeweiligen Sprache. Es ist verwunderlich, wenn bei einem 25 Jahre alten PHP Funktionen für so grundlegende Operationen fehlen wie das Zurückgeben des ersten oder letzten Elements eines Arrays, das Escapen von HTML ohne Fallstricke (htmlspecialchars escapet kein Apostroph) oder eben das Suchen einer Zeichenkette in einer Zeichenkette. Dass es irgendwie umgangen werden kann, hält nicht stand, denn das Ergebnis ist dann kein lesbarer und verständlicher Code. Das ist eine Lehre für alle API-Autoren. Wenn Sie sehen, dass ein erheblicher Teil der Dokumentation einer Funktion die Erklärung von Tücken einnimmt (wie z.B. die Rückgabewerte von strpos), ist das ein klares Signal, die Bibliothek anzupassen und eben str_contains zu ergänzen.

get_debug_type()

Ersetzt das bereits veraltete get_type(). Anstelle langer Typen wie integer gibt es die heute gebräuchlichen int zurück, im Falle von Objekten gibt es direkt den Typ zurück:

Wert gettype() get_debug_type()
'abc' string string
[1, 2] array array
231 integer int
3.14 double float
true boolean bool
null NULL null
new stdClass object stdClass
new Foo\Bar object Foo\Bar
function() {} object Closure
new class {} object class@anonymous
new class extends Foo {} object Foo@anonymous
curl_init() resource resource (curl)
curl_close($ch) resource (closed) resource (closed)

Objektivierung von Ressourcen

Werte vom Typ Ressource stammen aus Zeiten, als PHP noch keine Objekte hatte, sie aber eigentlich brauchte. So kamen die Ressourcen zur Welt. Heute haben wir Objekte, und im Vergleich zu Ressourcen funktionieren sie viel besser mit dem Garbage Collector, daher ist geplant, sie nach und nach alle durch Objekte zu ersetzen.

Ab PHP 8.0 werden Ressourcen für Bilder, Curl-Verbindungen, OpenSSL, XML usw. in Objekte umgewandelt. In PHP 8.1 werden FTP-Verbindungen usw. folgen.

$res = imagecreatefromjpeg('image.jpg');
$res instanceof GdImage  // true
is_resource($res)        // false - BC break

Diese Objekte haben bisher keine Methoden, und Sie können auch nicht direkt ihre Instanzen erstellen. Es geht vorerst wirklich nur darum, die veralteten Ressourcen aus PHP ohne API-Änderung zu entfernen. Und das ist gut so, denn die Erstellung einer guten API ist eine separate und anspruchsvolle Aufgabe. Niemand wünscht sich, dass in PHP weitere Klassen wie SplFileObject mit Methoden namens fgetc() oder fgets() entstehen.

PhpToken

Auch der Tokenizer und damit die Funktionen um token_get_all werden in Objekte verschoben. Diesmal geht es nicht darum, Ressourcen loszuwerden, sondern wir erhalten ein vollwertiges Objekt, das ein einzelnes PHP-Token darstellt.

<?php

$tokens = PhpToken::tokenize('<?php $a = 10;');
$token = $tokens[0];         // Instanz von PhpToken

echo $token->id;             // T_OPEN_TAG
echo $token->text;           // '<?php'
echo $token->line;           // 1
echo $token->getTokenName(); // 'T_OPEN_TAG'
echo $token->is(T_STRING);   // false
echo $token->isIgnorable();  // true

Die Methode isIgnorable() gibt true für die Tokens T_WHITESPACE, T_COMMENT, T_DOC_COMMENT und T_OPEN_TAG zurück.

Weak Maps

Weak Maps hängen mit dem Garbage Collector zusammen, der alle Objekte und Werte aus dem Speicher freigibt, die nicht mehr verwendet werden (d.h. es gibt keine verwendete Variable oder Property mehr, die sie enthält). Da die Lebensdauer eines PHP-Threads kurzlebig ist und wir heute auf Servern ausreichend Speicher zur Verfügung haben, beschäftigen wir uns in der Regel überhaupt nicht mit Fragen der effizienten Speicherfreigabe. Aber bei länger laufenden Skripten sind sie entscheidend.

Das Objekt WeakMap ähnelt SplObjectStorage. In beiden werden Objekte als Schlüssel verwendet und ermöglichen die Speicherung beliebiger Werte unter ihnen. Der Unterschied besteht darin, dass WeakMap nicht verhindert, dass das Objekt vom Garbage Collector freigegeben wird. D.h., wenn der einzige Ort, an dem das Objekt noch vorkommt, der Schlüssel in der Weak Map ist, wird es aus der Map und dem Speicher entfernt.

$map = new WeakMap;
$obj = new stdClass;
$map[$obj]  = 'Daten für $obj';

dump(count($map));  // 1
unset($obj);
dump(count($map));  // 0

Wozu ist das gut? Zum Beispiel zum Caching. Nehmen wir an, wir haben eine Methode loadComments(), der wir einen Blogartikel übergeben und die alle seine Kommentare zurückgibt. Da die Methode wiederholt mit demselben Artikel aufgerufen wird, erstellen wir uns noch getComments(), die das Ergebnis der ersten Methode zwischenspeichert:

class Comments
{
	private WeakMap $cache;

	public function __construct()
	{
		$this->cache = new WeakMap;
	}

	public function getComments(Article $article): ?array
	{
		$this->cache[$article] ??= $this->loadComments($article);
		return $this->cache[$article]
	}

	...
}

Der Clou ist, dass in dem Moment, in dem das Objekt $article freigegeben wird (z.B. weil die Anwendung beginnt, mit einem anderen Artikel zu arbeiten), auch sein Eintrag aus dem Cache freigegeben wird.

PHP JIT (Just-in-Time-Compiler)

Vielleicht wissen Sie, dass PHP in sogenannten Opcode kompiliert wird, das sind Low-Level-Instruktionen, die Sie zum Beispiel hier ansehen können und die von der virtuellen Maschine von PHP ausgeführt werden. Und was ist JIT? JIT kann PHP transparent direkt in Maschinencode kompilieren, der direkt vom Prozessor ausgeführt wird, sodass die langsamere Ausführung durch die virtuelle Maschine umgangen wird.

JIT soll also PHP beschleunigen.

Die Bemühungen, JIT in PHP zu implementieren, reichen bis ins Jahr 2011 zurück und gehen auf Dmitry Stogov zurück. Seitdem hat er 3 verschiedene Implementierungen ausprobiert, aber keine davon schaffte es in das produktive PHP, und zwar aus folgenden Gründen: Das Ergebnis war nie eine wesentliche Leistungssteigerung für typische Webanwendungen; es erschwert die Wartung von PHP (d.h. niemand außer Dmitry versteht es 😉); es gab andere Wege, die Leistung zu verbessern, ohne JIT verwenden zu müssen.

Die sprunghafte Leistungssteigerung von PHP in Version 7 war ein Nebenprodukt der Arbeit an JIT, obwohl es paradoxerweise nicht eingesetzt wurde. Dies geschieht erst in PHP 8. Aber ich werde gleich überzogene Erwartungen bremsen: Wahrscheinlich werden Sie keine Beschleunigung feststellen.

Warum kommt JIT also in PHP? Einerseits gehen andere Wege zur Leistungsverbesserung langsam zur Neige, und JIT ist einfach an der Reihe. In gängigen Webanwendungen bringt es zwar keine Beschleunigung, aber es beschleunigt beispielsweise mathematische Berechnungen erheblich. Damit eröffnet sich die Möglichkeit, diese Dinge in PHP zu schreiben. Und tatsächlich wäre es so möglich, Funktionen direkt in PHP zu implementieren, die bisher aus Geschwindigkeitsgründen eine Implementierung direkt in C erforderten.

JIT ist Teil der Erweiterung opcache und wird zusammen mit ihr in der php.ini aktiviert (lesen Sie die Dokumentation zu diesem vierstelligen Code):

zend_extension=php_opcache.dll
opcache.jit=1205              ; Konfiguration mittels OTRC-Vierer
opcache.enable_cli=1          ; damit es auch in der CLI funktioniert
opcache.jit_buffer_size=128M  ; reservierter Speicher für kompilierten Code

Dass JIT läuft, erfahren Sie beispielsweise im Informationspanel in der Tracy Bar.

JIT funktioniert sehr gut, wenn alle Variablen klar definierte Typen haben und sich bei wiederholtem Aufruf desselben Codes nicht ändern können. Ich bin daher gespannt, ob wir eines Tages in PHP auch Typen für Variablen deklarieren werden: string $s = 'Hallo, das ist der Abschluss der Serie';