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

vor 4 Jahren von David Grudl  

PHP Version 8.0 ist veröffentlicht worden. Sie ist voll von neuen Funktionen wie keine andere Version zuvor. Ihre Einführung hat vier separate Artikel verdient. Im letzten Teil werfen wir einen Blick auf neue Funktionen und Klassen und stellen den Just in Time Compiler vor.

Neue Funktionen

Die PHP-Standardbibliothek hat Hunderte von Funktionen und in Version 8.0 sind sechs neue hinzugekommen. Das scheint nicht viel zu sein, aber die meisten von ihnen beheben Schwachstellen der Sprache. Das passt gut zum Gesamtkonzept der Version 8.0, die PHP wie keine Version zuvor strafft und konsolidiert. Eine Übersicht über alle neuen Funktionen und Methoden finden Sie im Migration Guide.

str_contains() str_starts_with() str_ends_with()

Funktionen zur Bestimmung, ob eine Zeichenkette beginnt, endet oder eine Teilzeichenkette enthält.

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

Mit der Einführung dieser Dreifaltigkeit definiert PHP, wie eine leere Zeichenkette bei der Suche zu behandeln ist, woran sich alle anderen verwandten Funktionen halten, und zwar eine leere Zeichenkette wird überall gefunden:

str_contains('Nette', '')     // true
str_starts_with('Nette', '')  // true
strpos('Nette', '')           // 0 (previously false)

Dadurch ist das Verhalten der Trinität völlig identisch mit dem der Nette-Analoga:

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? Standardbibliotheken aller Sprachen sind immer durch die historische Entwicklung belastet; Ungereimtheiten und Fehltritte lassen sich nicht vermeiden. Gleichzeitig sind sie aber auch ein Zeugnis der jeweiligen Sprache. Überraschenderweise fehlen dem 25 Jahre alten PHP Funktionen für so grundlegende Operationen wie die Rückgabe des ersten oder letzten Elements eines Arrays, das Escapen von HTML ohne böse Überraschungen (htmlspecialchars escaped kein Apostroph) oder einfach nur die Suche nach einem String in einem String. Es gilt nicht, dass es irgendwie umgangen werden kann, weil das Ergebnis kein lesbarer und verständlicher Code ist. Dies ist eine Lektion für alle API-Autoren. Wenn Sie sehen, dass ein großer Teil der Funktionsdokumentation von Erklärungen zu Fallstricken (wie den Rückgabewerten von strpos) eingenommen wird, ist das ein klares Zeichen, die Bibliothek zu ändern und str_contains hinzuzufügen.

get_debug_type()

Ersetzt das inzwischen veraltete get_type(). Anstelle von Langtypen wie integer gibt er den heute verwendeten int zurück, bei Objekten direkt den Typ:

Value 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)

Migration von Ressourcen zu Objekten

Die Werte des Ressourcentyps stammen aus einer Zeit, in der PHP noch keine Objekte hatte, diese aber tatsächlich benötigte. So wurden die Ressourcen geboren. Heute haben wir Objekte, und im Vergleich zu Ressourcen arbeiten sie viel besser mit dem Garbage Collector zusammen, daher ist der Plan, sie nach und nach alle durch Objekte zu ersetzen.

Ab PHP 8.0 werden die Ressourcen images, curl joins, openssl, xml, etc. durch Objekte ersetzt. In PHP 8.1 werden FTP-Verbindungen, etc. folgen.

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

Diese Objekte haben noch keine Methoden, und man kann sie auch nicht direkt instanziieren. Bis jetzt geht es wirklich nur darum, veraltete Ressourcen aus PHP zu entfernen, ohne die API zu ändern. Und das ist gut so, denn die Entwicklung einer guten API ist eine eigene und anspruchsvolle Aufgabe. Niemand wünscht sich die Schaffung neuer PHP-Klassen wie SplFileObject mit Methoden namens fgetc() oder fgets().

PhpToken

Der Tokenizer und die Funktionen rund um token_get_all werden ebenfalls in Objekte umgewandelt. Diesmal geht es nicht darum, Ressourcen loszuwerden, sondern wir erhalten ein vollwertiges Objekt, das ein PHP-Token repräsentiert.

<?php
$tokens = PhpToken::tokenize('<?php $a = 10;');
$token = $tokens[0];         // instance 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() liefert true für die Token T_WHITESPACE, T_COMMENT, T_DOC_COMMENT und T_OPEN_TAG.

Schwache Karten

Weak Maps hängen mit dem Garbage Collector zusammen, der alle Objekte und Werte, die nicht mehr verwendet werden, aus dem Speicher freigibt (d.h. es gibt keine Variable oder Eigenschaft, die sie enthält). Da PHP-Threads nur kurzlebig sind und wir auf unseren Servern reichlich Speicher zur Verfügung haben, befassen wir uns in der Regel gar nicht mit Fragen der effektiven Speicherfreigabe. Aber für länger laufende Skripte sind sie unerlässlich.

Das Objekt WeakMap ist ähnlich wie SplObjectStorage. Beide verwenden Objekte als Schlüssel und erlauben es, beliebige Werte unter ihnen zu speichern. Der Unterschied ist, dass WeakMap nicht verhindert, dass das Objekt vom Garbage Collector freigegeben wird. D.h. wenn der einzige Ort, an dem das Objekt derzeit existiert, ein Schlüssel in der weak map ist, wird es aus der map und dem Speicher entfernt.

$map = new WeakMap;
$obj = new stdClass;
$map[$obj]  = 'data for $obj';

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

Wozu ist das gut? Zum Beispiel für das Caching. Nehmen wir an, wir haben eine Methode loadComments(), der wir einen Blog-Artikel übergeben und die alle seine Kommentare zurückgibt. Da die Methode wiederholt für denselben Artikel aufgerufen wird, erstellen wir eine weitere 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]
	}

	...
}

Wenn das Objekt $article freigegeben wird (z. B. wenn die Anwendung mit einem anderen Artikel zu arbeiten beginnt), wird auch sein Eintrag aus dem Cache freigegeben.

PHP JIT (Just in Time Compiler)

Sie wissen vielleicht, dass PHP in so genannten Opcode kompiliert wird, d.h. in Low-Level-Anweisungen, die Sie z. B. hier sehen können und die von einer virtuellen PHP-Maschine ausgeführt werden. Und was ist ein JIT? JIT kann PHP auf transparente Weise direkt in Maschinencode kompilieren, der direkt vom Prozessor ausgeführt wird, so dass die langsamere Ausführung durch die virtuelle Maschine umgangen wird.

JIT ist also dazu gedacht, PHP zu beschleunigen.

Die Bemühungen, JIT in PHP zu implementieren, gehen auf das Jahr 2011 zurück und werden von Dmitry Stogov unterstützt. Seitdem hat er drei verschiedene Implementierungen ausprobiert, aber keine davon hat es in eine endgültige PHP-Version geschafft, und zwar aus drei Gründen: Das Ergebnis war nie eine signifikante Leistungssteigerung für typische Webanwendungen; es erschwert die PHP-Wartung (d. h. niemand außer Dmitry versteht es 😉 ); es gab andere Möglichkeiten, die Leistung zu verbessern, ohne ein JIT verwenden zu müssen.

Der sprunghafte Anstieg der Leistung in PHP Version 7 war ein Nebenprodukt der Arbeit an JIT, obwohl es paradoxerweise nicht eingesetzt wurde. Dies geschieht erst jetzt in PHP 8. Aber ich werde mich mit übertriebenen Erwartungen zurückhalten: Sie werden wahrscheinlich keinen Geschwindigkeitszuwachs sehen.

Warum also hält JIT Einzug in PHP? Erstens gehen andere Möglichkeiten zur Leistungssteigerung langsam aus, und JIT ist einfach der nächste Schritt. Bei gewöhnlichen Webanwendungen bringt es keine Geschwindigkeitsverbesserungen, aber es beschleunigt z. B. mathematische Berechnungen erheblich. Damit eröffnet sich die Möglichkeit, diese Dinge in PHP zu schreiben. In der Tat wäre es möglich, einige Funktionen direkt in PHP zu implementieren, die bisher aus Geschwindigkeitsgründen eine direkte C-Implementierung erforderten.

JIT ist Teil der opcache Erweiterung und wird zusammen mit dieser in der php.ini aktiviert (lesen Sie die Dokumentation über diese vier Ziffern):

zend_extension=php_opcache.dll
opcache.jit=1205              ; configuration using four digits OTRC
opcache.enable_cli=1          ; in order to work in the CLI as well
opcache.jit_buffer_size=128M  ; dedicated memory for compiled code

Sie können überprüfen, ob JIT läuft, z. B. im Informationsfenster der Tracy Bar.

JIT funktioniert sehr gut, wenn alle Variablen klar definierte Typen haben und sich auch beim wiederholten Aufruf desselben Codes nicht ändern können. Ich frage mich daher, ob wir eines Tages auch in PHP Typen für Variablen deklarieren werden: string $s = 'Bye, this is the end of the series';