Php 8 komt eraan

Het ontwikkeltraject van PHP

Sinds de release van PHP in 1997 heeft de taal een flinke ontwikkeling doorgemaakt. Het begon als een eenvoudige wrapper om C, zodat Rasmus Lerdorf sneller websites kon bouwen, maar de syntax van versie 7.4 lijkt weinig meer op deze eerste PHP-release.

De reputatie dat PHP geen serieuze ontwikkeltaal zou zijn, ligt al ver achter ons. Bijna 80 procent van alle websites die aan de serverzijde worden opgebouwd, zijn geschreven in PHP. Ook de leukste techsite van Nederland is 'powered by' PHP.

In december verschijnt een nieuwe major versie, PHP 8. De eerste testversie van PHP 8 is al drie maanden beschikbaar en de eerste release candidate staat gepland voor 17 september. Met een feature freeze ruim een maand achter ons, is dit een mooi moment om te kijken welke veranderingen PHP 8 met zich meebrengt.

RFC-proces

Om te begrijpen hoe deze nieuwe versie van PHP tot stand is gekomen, is het belangrijk om te kijken naar het ontwikkeltraject. Het begint met de beschermers van PHP, de core developers. Dit zijn de ontwikkelaars met de meeste ervaring met het werken in de broncode van PHP en zij handelen de bugreports af. Zij hebben de bijkomende taak om de ontwikkeling van PHP in de juiste richting te sturen. Niet alleen de broncode van PHP is open, maar ook het ontwikkeltraject. Het staat iedereen die een beetje karma heeft verzameld, vrij een Request For Comments in te dienen om de taal te verbeteren.

Of een RFC slaagt, hangt ervan af of de auteur de core developers heeft kunnen overtuigen van het nut van de RFC. De core developers kijken niet alleen naar het voorstel zelf, maar ook naar de impact op de rest van de taal, of het strookt met de visie van PHP en in hoeverre er rekening gehouden is met backwards compatibility.

Voor beginners is het verstandig om onderzoek te doen en de wijziging te polsen bij de core developers, zodat er geen onnodige energie wordt gestoken in een voorstel dat het waarschijnlijk toch niet gaat halen. Een voorstel om de functienamen en volgorde van argumenten gelijk te trekken, zal waarschijnlijk niet worden geaccepteerd, omdat de gevolgen hiervan te groot zijn.

Wanneer een voorstel genoeg steun heeft, kan deze door een lid op de wiki worden gepubliceerd, en worden aangekondigd op de PHP-mailinglist. Daarna volgt er een discussiefase, waarbij iedereen vragen kan stellen en voorziene problemen kan aankaarten over de RFC. Deze vragen en problemen worden allemaal in de RFC vastgelegd. Wanneer de discussie afgerond is, maar niet eerder dan twee weken na publicatie van de RFC, volgt er een stemming. De stemming is altijd een ja/nee-stem waarbij een tweederdemeerderheid nodig is om de RFC te laten slagen. Soms zijn er naast een ja/nee-stem ook nog een of meerdere vervolgstemmingen, die kunnen gaan over de implementatieaanpak. Het stemmen is voorbehouden aan ontwikkelaars die hebben bijgedragen aan de broncode van PHP, afgevaardigden vanuit de PHP-community en vaste deelnemers aan de interne discussies.

Snelheidswinst

De upgrade van PHP 5 naar 7 heeft er voor gezorgd dat veel websites veel sneller konden worden gerenderd, zonder dat de PHP-code aangepast hoefde te worden. Dit kwam door de update van de virtuele machine die de php-code uiteindelijk uitvoert, de zend engine, van versie 2 naar 3.

Dmitry Stogov, de ontwikkelaar die de taak op zich had genomen om PHP sneller te maken, wilde in het begin snelheidswinst behalen met JIT. Hij hoopte PHP-code sneller uit te kunnen voeren door deze vlak voor de uitvoering om te zetten naar machinecode, net als in bijvoorbeeld Java.

Hij merkte echter dat het geheugenbeheer en de interne functies knelpunten waren voor de uitvoeringstijd. Hij besloot daarom om dit eerst te verbeteren, in de hoop dat PHP-code sneller uitgevoerd kon worden. Na een paar maanden werk bleek inderdaad dat er een flink snelheidswinst was behaald, en stond deze proof of concept met de naam phpng aan de wieg van zend engine versie 3.

In PHP 8 wordt nu wel JIT geïntroduceerd. JIT, of just in time, is een manier om een script te compileren voordat het wordt uitgevoerd. Dit is de tegenhanger van AOT, of ahead of time, waarbij je vooraf een programma compileert, zoals het geval is in bijvoorbeeld C en C++.

Het voorstel om JIT op te nemen in PHP 8 werd vorig jaar maart met ruime meerderheid aangenomen

Om te begrijpen wat dit als voordeel heeft, is het belangrijk om te begrijpen hoe PHP uitgevoerd wordt. Een PHP-script wordt eerst door een lexer omgezet naar tokens, die daarna door een parser worden omgezet naar een abstract syntax tree, of AST. De AST wordt vervolgens gecompileerd naar opcodes die door de virtuele machine worden uitgevoerd. In PHP 7 kan de opcache-extensie de gecompileerde versie in het geheugen opslaan, zodat het lexen, parsen en compileren niet steeds opnieuw gedaan hoeven te worden en de opcodes direct in de virtuele machine worden uitgevoerd. Hierdoor kunnen veelgebruikte scripts sneller worden uitgevoerd.

JIT gaat een stap verder en slaat niet de opcacheversie van een script op, maar compileert de opcode naar machinecode, zoals x86-instructies. Hiermee wordt de code dan niet in de virtuele machine, maar direct op de processor uitgevoerd. Hiermee kunnen scripts in theorie nog meer snelheidswinst behalen. De kanttekening hierbij is dat dit vooral voordeel heeft voor code die gebonden is aan de cpu. Veel PHP-applicaties zullen echter vooral io bound zijn, omdat ze bijvoorbeeld wachten op een database. Het is dus de vraag in hoeverre JIT voor websites die geschreven zijn in PHP voor snelheidswinst kan zorgen. Het lijkt daarom dat JIT vooral interessant kan zijn om andere typen applicaties te maken in PHP. Tevens zou het een alternatief kunnen zijn voor sommige extensies die nu nog in C geschreven zijn, om deze om te zetten naar PHP-code.

Veranderingen onder de motorkap

Negatieve sleutelwaarden uitbreiden

Het is in PHP mogelijk om een array uit te breiden met een nieuw element, waarbij de sleutelwaarde ‘doortelt’ aan de hand van de hoogste sleutelwaarde. Dit doortellen werkt alleen als de sleutelwaarde 0 of groter is. Is er geen sleutelwaarde van 0 of groter, dan begint het doortellen bij 0.

<?php
$d[-2] = true;
$d[] = true;
$d[] = true;

In PHP 7.4 en lager geeft dit een array met de sleutelwaarden -2, 0 en 1. In PHP 8 is de restrictie vervallen dat een sleutelwaarde 0 of groter moet zijn. Dit houdt in dat in PHP 8 de sleutelwaardes -2, -1 en 0 zijn.

Nog meer veranderingen

PHP heeft de afgelopen releases vaker wijzigingen doorgevoerd die de ontwikkelaar in staat stellen de taal strikter te gebruiken. De declare(strict_types) van versie 7 is daar een recent voorbeeld van. Ook de standard PHP library, of SPL, lijkt strikter te worden, of in ieder geval consistenter, door vanaf PHP 8 vaker een error te geven. Nikita Popov, een van de coredevelopers, heeft hiervoor een RFC ingediend en deze ook zelf gebouwd. Hierdoor zullen meer functies die een argument van het verkeerde type ontvangen, geen waarschuwing geven, maar een TypeError geven, zoals nu ook gebeurt in userlandfuncties met type hinting.

Een andere technische wijziging in PHP 8 is de zogenaamde stable sorting. Voor PHP 8 was de positie van elementen met dezelfde waarde in een array na sortering niet gegarandeerd. Het kan dus betekenen dat die elementen na sortering in een andere volgorde staan. PHP 8 biedt de garantie dat elementen in dezelfde volgorde staan als voor de sortering, wanneer elementen gelijk aan elkaar zijn. Dit lijkt een klein verschil, maar kan bij bepaalde code subtiele bugs veroorzaken. Deze zullen dan waarschijnlijk aan het licht komen bij de upgrade naar PHP 8.

Syntax-wijzigingen

PHP 8 heeft enkele RFC’s geaccepteerd die vooral het lezen en typen van code makkelijker maken, zonder dat dit effect heeft op de uitvoering van een script.

Verbeterde argumentdefinitie in functies en closures

Het zal in PHP 8 mogelijk zijn om een lijst van argumenten te laten eindigen met een komma in een functie-definitie en in closures, zonder dat hier een nieuw argument op volgt. Dit was al mogelijk binnen een arraydefinitie, en bij het aanroepen van functies, maar nu dit ook bij functiedefinities mogelijk is, wordt de taal consistenter. Het voordeel van het toestaan van zo’n komma is dat de diff patches simpeler zijn, en het maakt het toevoegen van nieuwe waarden eenvoudiger.

Constructor property promotion

Een andere wijziging in PHP 8 is de mogelijkheid om eenvoudiger een value object te maken dankzij constructor property promotion. Hiermee kan een ontwikkelaar op één plaats in een klasse het type met de zichtbaarheid, toekenning en een eventuele standaardwaarde definiëren van een property, in plaats van dit op drie plaatsen te moeten doen. De RFC voor deze wijziging heeft het volgende voorbeeld van code in PHP 7.4:

<?php
class Point {
	public float $x;
	public float $y;
	public float $z;
 
	public function __construct(
		float $x = 0.0,
		float $y = 0.0,
	float $z = 0.0
	) {
		$this->x = $x;
		$this->y = $y;
		$this->z = $z;
	}
}

Met constructor property promotion kan deze code in PHP 8 als volgt worden geschreven:

<?php
class Point {
	public function __construct(
		public float $x = 0.0,
		public float $y = 0.0,
		public float $z = 0.0,
	) {}
}

De code hoeft minder vaak te worden getypt, waardoor er minder kansen zijn op fouten bij het maken en refactoren van code.

Uitgelichte nieuwe functionaliteiten

Naast optimalisaties en aanpassingen van de syntax, heeft PHP 8 weer een aantal nieuwe functionaliteiten die het werk van ontwikkelaars makkelijker kunnen maken. Hier volgt een overzicht van een aantal geaccepteerde RFC’s.

Attributes

Voor veel volgers van de ontwikkelingen van PHP 8 zijn attributes de feature waar het meest naar uitgekeken wordt. Dit is ook te zien aan het aantal RFC’s, want een groot deel van de RFC’s verwijzen direct of indirect naar attributes.

De eerste poging om attributes aan PHP toe te voegen stamt uit 2016, deze RFC haalde toen onvoldoende stemmen. Met PHP 8 worden attributes een onderdeel van de taal. De nieuwe RFC is inhoudelijk anders dan het aanvankelijke voorstel voor attributes, maar de belangrijkste reden dat deze nu wel is geaccepteerd, is waarschijnlijk dat de taal nu wel 'rijp' is voor deze toevoeging.

Het concept van attributes is in Java beter bekend als annotations en in Python als decorators. Het is een manier om metadata toe te kennen aan een klasse, interface, eigenschap, (anonieme) functie, methode, klasseconstante of hoedanigheid. Deze metadata kan vervolgens worden uitgelezen met de reflectionextensie.

Ondanks dat PHP geen officiële ondersteuning had voor attributes, werden door verschillende applicaties en frameworks alternatieven bedacht. Een veelgebruikt alternatief is het gebruik van annotations in doc-comments, waar een regel in de doc-comment begint met een apenstaartje gevolgd door een trefwoord dat een speciale betekenis heeft. Het nadeel van het gebruik van doc-comments is dat de applicatie of framework zelf de inhoud van een doc-comment moet parsen. Door attributes onderdeel te maken van de syntax van PHP kunnen deze attributes simpelweg worden uitgelezen met Tokenizer, een standaardonderdeel van PHP.

Een attribute wordt als een klasse geresolved en geïnitieerd, die zelf voorzien moet zijn van een attribute om aan te geven dat deze geschikt is als attribute. Een attribute kan optioneel argumenten bevatten, die in de constructor van deze klasse worden meegegeven. De RFC laat vrij wat een attribute precies doet. Een attribute kan bijvoorbeeld gebruikt worden door een framework om methodes in een klasse te markeren als een route, of door een ORM om variabelen te voorzien van de benodigde metadata voor het opslagtype.

<?php
// PHP 7
class Product
{
	/**
	* @ORM\Id
	* @ORM\Column(type="integer")
	* @ORM\GeneratedValue
	*/
	protected $id;
}
 
// PHP 8
class Product
{
	#[ORM\Id]
	#[ORM\Column(type="integer")]
	#[ORM\GeneratedValue]
	protected $id;
}

De RFC stipt aan dat het gebruik van attributes daarnaast de weg vrij maakt voor PHP om deze attributes zelf te gaan gebruiken. Zo zouden attributes in de toekomst bijvoorbeeld gebruikt kunnen worden om PHP te helpen met het markeren van functies die wel of niet geschikt zijn voor JIT.

De RFC heeft een makkelijke overwinning behaald, maar er volgden nog enkele RFC’s over de implementatie. In de eerste geaccepteerde versie was er sprake van om << en >> te gebruiken als scheidingstekens voor attributes, maar als snel volgde er een verhitte discussie of dit leesbaar, makkelijk te schrijven is en niet verwarrend is voor de al bestaande bitwise operations. Daarna volgde een RFC om @@ te gebruiken en op de valreep een RFC om toch "#[" als start- en "]" als eindmarkering te gebruiken. Ook andere 'details', zoals het attribute dat een attribute zelf markeert en of een attribute als een enkele token of reeks tokens wordt gezien, zijn besproken in daaropvolgende RFC’s.

Union types

PHP 8 komt met union types ontwikkelaars tegemoet die type hinting willen gebruiken voor meerdere types. Met de union type kan een ontwikkelaar meerdere type hints voor dezelfde parameter specificeren, zonder hiervoor speciaal een Interface aan te maken. Naast meerdere klassen te specificeren, kunnen ook scalars in union types gebruikt worden.

Er is ook een speciaal type false dat in een union type kan worden aangegeven. In PHP zijn er een aantal functies die een waarde teruggeven, of in het geval dat er iets misgegaan is: false. Userlandfuncties kunnen dat met deze pseudo type ook aangeven. Dit is semantisch gezien correcter dan bool als return type, omdat dit impliceert dat er ook true teruggeven zou kunnen worden.

<?php
class SimpleCache {
	private array $storage;
 
	public function setData(string $key, string|int|float $data): void {
		$this->storage[$key] = $data;
	}
	public function getData(string $key): string|int|float|false {
		if ( ! array_key_exists($key, $this->storage)) {
			return false;
		}
 
		return $this->storage[$key];
	}
}

Non-capturing catches

In 2013 was er een RFC ingediend voor een anonieme try-catchconstructie. Die RFC is toen gesneuveld voor de stemronde, maar een nieuwe RFC met een bijna identiek voorstel is met een overgrote meerderheid geaccepteerd voor PHP 8. Dankzij deze RFC is het in PHP 8 mogelijk om een try-catchconstructie te schrijven, zonder de exception aan een variabele toe te kennen. Het voorbeeld vanuit de RFC:

<?php
try {
	changeImportantData();
} catch (PermissionException $ex) {
	echo "You don't have permission to do this";
}

De exception wordt aan de $ex-variabele toegekend, maar niet gebruikt. Dit kan intentioneel zijn, maar wellicht heeft de ontwikkelaar vergeten iets met de exception te doen. In PHP 8 is het mogelijk om dit als volgt te schrijven:

<?php
try {
	changeImportantData();
} catch (PermissionException) {
	echo "You don't have permission to do this";
}

Hiermee is het voor de lezer duidelijk dat de ontwikkelaar de exception alleen wil afvangen, maar verder niet wil gebruiken.

Named arguments

Met named arguments is het in PHP 8 mogelijk om per argument op te geven welke parameter daar bij hoort. Zo kunnen optionele argumenten worden overgeslagen, en kan het de leesbaarheid vergroten als er meerdere argumenten zijn.

In het volgende voorbeeld wil een developer enkel de parameters string en double_encode doorgeven aan htmlspecialchars, maar omdat de double_encode de vierde parameter is, moet de developer ook parameters 2 en 3 opgeven met de juiste standaardwaarden. Ook is het niet duidelijk bij welke parameter false hoort. In PHP 8 kunnen de andere parameters overgeslagen worden, en is ook direct duidelijk dat false het argument voor double_encode is:

<?php
// PHP 7
htmlspecialchars($string, ENT_COMPAT|ENT_HTML401, ini_get('default_charset'), false));
 
// PHP 8
htmlspecialchars($string, double_encode:false);

Match expression

PHP 8 introduceert de match expression: een variant op de switch statement die een strikte vergelijking doet, geen fallthrough heeft en direct aan een variabele kan worden toegekend.

<?php
$winner = match (1337) {
	1336 => 'Adriaan',
	1337 => 'Henk',
	1338 => 'Bassie',
};

Nullsafe operator

De nullsafe operator maakt het eenvoudiger om methodes te chainen, zonder expliciet op null te hoeven testen. De nullsafe operator onderbreekt de chain zodra er een null-waarde in de chain optreedt.

<?php
// PHP 7
$theAnswer = null;
 
if ($earth !== null) {
	$human = $earth->extractHuman('Arthur Dent');
 
	if ($human !== null) {
		$brain = $human->getBrain();
 
		if ($brain !== null) {
			$theAnswer = $brain->getAnswer();
		}
	}
}
 
// PHP 8
$theAnswer = $earth?->extractHuman('Arthur Dent')?->getBrain()?->getAnswer();

Standard PHP Library-aanpassingen

Buiten de engine beschikt PHP ook over een uitgebreide verzameling interne functies: Standard PHP Library, of SPL.

Philipp Tanlak, een PHP-gebruiker, heeft zijn eerste RFC ingediend voor str_contains, een functie die kijkt of een substring voorkomt in een string. PHP heeft al sinds versie 4 de strpos-functie, die de positie van een substring in een string aangeeft en begint bij 0. Een probleem doet zich volgens de RFC voor, wanneer de uitkomst van deze functie wordt vergeleken met een boolean. Het probleem ontstaat doordat een integer 0 naar boolean false wordt gecast als de substring in de string voorkomt op de eerste positie.

De nieuw voorgestelde functie str_contains zal altijd een boolean geven, waardoor dit veiliger is te gebruiken als deze met een boolean true of false wordt vergeleken.

Will Hudgins, eveneens een onbekende in de wereld van RFC’s, heeft voorgesteld om ook str_starts_with en str_ends_with-functies te introduceren, die net als str_contains een boolean teruggeven. Deze RFC is ook geaccepteerd en daarom zullen deze drie functies in PHP 8 beschikbaar zijn.

Deprecations

Ondanks dat RFC’s onder andere beoordeeld worden op backwards compatibility, is PHP 8 een nieuwe major versie en zullen er dus zaken worden verwijderd die niet meer ondersteund zijn.

PHP heeft de conventie om zaken die worden verwijderd, eerst als deprecated te markeren, voordat deze in een major versie worden verwijderd. Wanneer ontwikkelaars dus goed hun logbestanden in de gaten hebben gehouden, en geen minor versie overslaan bij het upgraden, zullen deze afschrijvingen niet onverwachts komen.

Tot slot

Om op de hoogte te blijven van alle veranderingen van PHP kun je de RFC's gaan lezen, je op de nieuwsgroep van PHP abonneren of een PHP-usergroep in de buurt opzoeken.

Voor een volledig overzicht kun je bij de release notes van PHP 8 kijken, en kom je beslagen ten ijs wanneer je gaat upgraden. Wat PHP 8.1 ons zal brengen, is nog niet duidelijk, maar de eerste RFC's zijn alvast geschreven.

(bron : https://tweakers.net/reviews/8168/all/php-8-is-op-komst-jit-stable-sorting-en-meer-vernieuwingen.html )