PHP 8 is officieel uitgebracht voor “General Availability” op 26 november 2020!

Deze nieuwe grote update bevat een hele reeks optimalisaties en krachtige features en we zijn verheugd om je door de meest interessante veranderingen te loodsen waarmee we beter kunnen coderen en krachtigere applicaties kunnen schrijven.

PHP 8 released
PHP 8.0 Announcement Addendum

Ben je er klaar voor? Aan de slag!

PHP JIT (Just in Time Compiler)

De feature van PHP 8 waar de meeste mensen naar uitkijken is de Just-in-Time (JIT) compiler. Maar wat is JIT precies?

Het RFC voorstel beschrijft JIT als volgt:

“PHP JIT is geïmplementeerd als een zo goed als onafhankelijk onderdeel van OPcache. Het kan voor de compileertijd en voor de looptijd van PHP worden ingeschakeld/uitschakeld. Wanneer het is ingeschakeld, wordt de native code van PHP data in een extra regio van OPcache Shared Memory opgeslagen en zorgen op_array→opcodes[].handler(s) voor verwijzigen naar de toegangspunten van ge-JIT-eerde code.”

Maar hoe zijn we bij JIT aanbeland en wat is het verschil tussen JIT en OPcache?

Laten we, om de rol van JIT binnen PHP beter te begrijpen, eens kijken hoe PHP zaken uitvoert: vanaf de broncode tot het uiteindelijke resultaat.

De PHP uitvoering is een proces in 4 fasen:

De volgende afbeelding toont een visuele weergave van het standaard PHP uitvoerproces.

Standaard PHP uitvoeringsproces
Standaard PHP uitvoeringsproces

Maar hoe maakt OPcache PHP nu sneller? En wat verandert er met JIT precies aan dit uitvoeringsproces?

De OPcache extensie

PHP is een geïnterpreteerde taal. Dit betekent dat, wanneer een PHP script wordt uitgevoerd, de interpreter deze parsed (ontleedt), compileert en uitvoert. Dit gebeurt bij elk nieuw verzoek opnieuw en opnieuw. Het nadeel hiervan laat zich raden: de kans is groot dat je hierdoor tijd en CPU resources verspilt.

Dit is waar de OPcache extensie van pas komt:

“Opcache verbetert de prestaties van PHP door een voorgecompileerde script-bytecode in het Shared Memory op te slaan. Hierdoor vervalt voor PHP de noodzaak om bij elk verzoek scripts te laden en te parsen.”

Met OPcache ingeschakeld doorloopt de PHP interpreter het bovengenoemde proces van 4 stappen alleen wanneer het script voor de eerste keer wordt uitgevoerd. Omdat PHP bytecodes in het Shared Memory worden opgeslagen, zijn ze meteen beschikbaar als een low-level intermediate representation en kunnen ze meteen op de Zend VM worden uitgevoerd.

Het PHP uitvoeringsproces met OPcache ingeschakeld
Het PHP uitvoeringsproces met OPcache ingeschakeld

Vanaf PHP 5.5 is de Zend OPcache extensie standaard beschikbaar. Je kan makkelijk controleren of je deze correct hebt geconfigureerd door phpinfo() aan te roepen vanuit een script op je server of door je php.ini bestand te checken (zie OPcache configuratie-instellingen).

Leessuggestie: Zo verhoog je de PHP geheugenlimiet in WordPress.

Zend Opcache sectie in een phpinfo pagina
Zend Opcache sectie in een phpinfo() pagina

Preloading

OPcache is onlangs verbeterd met de toevoeging van preloading, een nieuwe OPcache function die is toegevoegd in PHP 7.4. Preloading biedt een manier om een opgegeven set scripts op te slaan in het OPcache geheugen “voordat welke applicatiecode dan ook wordt uitgevoerd“, maar brengt geen significante verbeteringen voor typische webgebaseerde applicaties.

Je kan meer lezen over preloading in onze inleiding tot PHP 7.4.

Met JIT zet PHP een stap vooruit..

JIT — The Just in Time Compiler

Zelfs als opcodes de vorm hebben van intermediate representations, moeten ze nog steeds worden gecompileerd in machinecode. JIT “introduceert geen aanvullend IR (Intermediate Representation) formulier”, maar gebruikt DynASM (Dynamic Assembler voor codegeneratie-engines) om uit PHP byte-code rechtstreeks native code te genereren.

Kortom, JIT vertaalt de “hete delen” van de intermediate code in machinecode. Door het compilatieproces over te slaan, zou het aanzienlijke verbeteringen in prestaties en geheugenverbruik kunnen betekenen.

Zeev Surasky, co-auteur van het PHP JIT voorstel, laat hoeveel sneller berekeningen zouden zijn met JIT:

Maar zou JIT ook de WordPress prestaties effectief kunnen verbeteren?

JIT voor Live Web Apps

Volgens de JIT RFC zou de implementatie van de Just-in-Time Compiler de prestaties van PHP moeten verbeteren. Maar zouden we dergelijke verbeteringen ook daadwerkelijk ervaren binnen real-life apps als WordPress?

De eerste tests tonen inderdaad aan dat JIT CPU intensieve workloads aanzienlijk versnelt, maar de RFC waarschuwt:

“… net als de vorige pogingen – het lijkt er momenteel niet op dat het significante verbeteringen brengt aan real-life apps als WordPress (met opcache.jit=1235 326 req/sec vs 315 req/sec).

Het is de bedoeling dat we hiermee nog verder aan de slag gaan, om JIT te verbeteren voor real-life apps met behulp van profiling en speculatieve optimalisaties.”

Wanneer JIT is ingeschakeld, wordt de code niet uitgevoerd door Zend VM, maar door de CPU zelf. Dit zou de berekeningssnelheid moeten verbeteren. Web apps als WordPress zijn ook afhankelijk van andere factoren als TTFB, database-optimalisatie, HTTP verzoeken, etc.

Relatieve bijdrage JIT aan performance PHP 8
Relatieve bijdrage JIT aan performance PHP 8 (bron: PHP 8.0 Announcement Addendum)

We hoeven dus geen significante verbetering te verwachten in de snelheid waarmee PHP wordt uitgevoerd als het gaat om WordPress en vergelijkbare apps. Toch kan JIT, met name voor ontwikkelaars, verschillende voordelen met zich meebrengen.

Volgens Nikita Popov:

“De voordelen van de JIT Compiler zijn grofweg (en zoals al beschreven in de RFC):

  • Significant betere prestaties voor numerieke code.
  • Iets betere prestaties voor “typische” PHP webapplicatiecode.
  • De potentie om meer code van C naar PHP te verplaatsen, omdat PHP nu snel genoeg zal zijn.”

Dus hoewel JIT nauwelijks noemenswaardige verbeteringen zal brengen wat betreft WordPress prestaties, tilt het wel PHP naar het volgende niveau, waarmee het een taal wordt waar veel functions nu rechtstreeks in kunnen worden geschreven.

Het nadeel is wel dat de grotere complexiteit kan leiden tot hogere kosten voor onderhoud, stabiliteit en debugging. Volgens Mitry Stogov:

“JIT is uiterst eenvoudig, maar het verhoogt wel het complexiteitsniveau van PHP als geheel, met een verhoogd risico op nieuwe soorten bugs en een hogere kosten voor ontwikkeling en onderhoud.”

Het voorstel om JIT op te nemen in PHP 8 werd aangenomen met 50 stemmen voor en 2 stemmen tegen.

Verbeteringen en nieuwe functions PHP 8

Naast JIT kunnen we veel andere functions en verbeteringen verwachten met PHP 8. De volgende lijst is een zorgvuldig samengestelde selectie van de aankomende toevoegingen en wijzigingen die PHP betrouwbaarder en efficiënter moeten gaan maken.

Constructor Property Promotion

Als gevolg van een voortdurende discussie over hoe we object ergonomics in PHP kunnen verbeteren, stelt de Constructor Property Promotion RFC een nieuwe en beknoptere syntax voor die de property declaration simplificeert, waardoor deze korter en zinvoller wordt.

Dit voorstel heeft alleen betrekking op promoted parameters, dwz de method parameters met de visibility keywords public, protected en private als prefix.

Momenteel moeten alle property’s meerdere keren herhaald worden (minimaal vier keer) voordat we ze met objects kunnen gebruiken. Kijk eens naar het volgende voorbeeld uit de 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;
    }
}

Volgens Nikita Popov, de bedenker van de RFC, moeten we de property name minstens vier keer op drie verschillende plaatsen neerzetten: de property declaration, de constructor parameters en de property assignment. Deze syntax is niet erg bruikbaar, vooral niet in classes met een groot aantal property’s en meer beschrijvende namen.

Deze RFC stelt voor om de constructor en de parameter definition samen te voegen. Vanaf PHP 8 gaan we dus beschikken we dus over een meer bruikbare manier om parameters te declareren en kan je de bovenstaande code veranderen in wat je hieronder ziet:

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

En dat was het al. We hebben dus de beschikking over een nieuwe manier om property’s te promoten: korter, beter leesbaar en minder foutgevoelig. Volgens Nikita:

Het is een kleine syntactische transformatie die we hier voorstellen. Maar het vermindert wel de hoeveelheid code die je moet schrijven voor met name value objects…

De property declaration wordt getransformeerd aangezien we deze property’s al expliciet hebben gedeclared. We kunnen de Reflection API gebruiken om de property definitions vóór de uitvoering te introspecten (zie Sesugaring):

Reflection (en andere andere introspectionmechanismen) observeren de state na desugaring. Dit betekent dat de promoted property’s er hetzelfde uitzien als expliciet gedeclarede property’s en promoted constructor arguments verschijnen dus als ordinary constructor arguments.

// 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

We hebben geen beperkingen opgesteld bij het gebruik van inheritance in combinatie met promoted parameters. Hoe dan ook, er is specifieke relatie tussen constructors van parent en child class. Volgens Nikita:

Meestal zeggen we dat methoden altijd compatibel moeten zijn met de bovenliggende methode. […] maar deze regel is niet van toepassing op de constructor. De constructor behoort dus echt tot een single class en constructors tussen parent en child class hoeven op geen enkele manier compatibel te zijn.

Dit is een voorbeeld:

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);
    }
}

Wat niet toegestaan is bij promoted property’s

Promoted property’s zijn toegestaand in non-abstract constructors en traits, maar er zijn een aantal beperkingen die het vermelden waard zijn.

Abstract constructors

Promoted property’s zijn niet toegestaan in abstract classes en interfaces:

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

Een van de meest opvallende beperkingen houdt verband met nullability. Eerder, wanneer we een type gebruikten dat niet expliciet nullabel was, maar die wel een null als default value had, dan was het type impliciet nullabel. Maar met property types hebben we dit impliciete gedrag niet, omdat promoted parameters een property declaration vereisen. Het nullable type moet dus expliciet gedeclared worden. Bekijk het onderstaande voorbeeld van de 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

Aangezien “callable” geen property type is dat wordt ondersteund, mogen we geen callable type gebruiken in promoted property’s:

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

Alleen een visibility keyword kan worden gebruikt met promoted parameters, dus het declaren van constructor property’s met het var keyword is niet toegestaan (zie het onderstaande voorbeeld van de RFC):

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

We kunnen promoted property’s en explicit property’s combineren binnen dezelfde class, maar property’s kunnen niet twee keer worden gedeclared:

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

De reden is dat het declared type verschilt van de variadic parameters, wat eigenlijk een array is:

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

Verder lezen

Als je meer wil weten over Constructor Property Promoted, luister dan naar dit interview met Nikita Popov. Voor een uitgebreid overzicht van object ergonomics in PHP, zie dit artikel en het volgende interview met Larry Garfield.

Validatie voor abstract trait methods

Traits worden gedefinieerd als “een mechanisme voor hergebruik van code in single-inheritance talen als PHP”. Normaal gesproken worden ze gebruikt om methods vast te leggen die in meerdere classes kunnen worden gebruikt.

Een trait kan ook abstract methods bevatten. Deze methods leggen alleen de signature van de method vast, de implementatie van de method moet binnen de class worden gedaan met behulp van de trait.

Volgens de PHP handleiding,

“Traits ondersteunen het gebruik van abstract methods om eisen te stellen aan de exhibiting class.”

Dit betekent ook dat de signatures van de methods overeen moeten komen. Met andere woorden, het type en het aantal vereiste arguments moeten hetzelfde zijn.

Hoe dan ook, volgens de auteur van de RFC, Nikita Popov, wordt de signature-validatie momenteel alleen sporadisch afgedwongen:

  • In de meeste gevallen wordt het niet afgedwongen wanneer de method implementatie wordt geleverd door de using class: https://3v4l.org/SeVK3
  • Het wordt afgedwongen als de implementatie afkomstig is van een parent class: https://3v4l.org/4VCIp
  • Het wordt afgedwongen als de implementatie afkomstig is van een child class: https://3v4l.org/q7Bq2

Het volgende voorbeeld van Nikia heeft betrekking op het eerste geval (de niet-afgedwongen signature):

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) {}
}

Dat gezegd hebbende, deze RFC stelt voor om altijd een fatal error te geven wanneer de implementing method niet compatibel is met de abstract trait method, ongeacht de oorsprong:

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

Deze RFC is unaniem goedgekeurd.

Incompatibele method signatures

In PHP veroorzaken inheritance errors als gevolg van incompatibele method signatures een fatal error of een waarschuwing, afhankelijk van wat de fout veroorzaakt.

Als een class een interface implementeert, dan veroorzaken incompatibele method signatures een fatal error. Volgens de Oject Interfaces documentatie:

“De class die de interface implementeert moet een method signature gebruiken die compatibel is met LSP (Liskov Substitution Principle). Als je dit niet doet, krijg je een fatal error.”

Dit is een voorbeeld van een inheritance error met een interface:

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

In PHP 7.4 zou de bovenstaande code de volgende error opleveren:

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

Een function in een child class met een incompatible signature zou een error geven. Bekijk de volgende code van de RFC:

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

In PHP 7.4 zou de bovenstaande code alleen een waarschuwing geven:

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

Deze RFC gaat een stapje verder en stelt voor om een fatal error te genereren voor incompatibele method signatures. Met PHP 8 zou de code die we eerder zagen het volgende oproepen:

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 beginnend met een negatieve index

Als een array in PHP begint met een negatieve index (start_index <0), dan beginnen de indices erna vanaf 0 (meer hierover in array_fill documentatie). Dit wordt toegelicht in het volgende voorbeeld:

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

In PHP 7.4 zou dit resulteren in het volgende:

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

Deze RFC stelt hierin een wijziging voor, zodat de tweede index start_index +1 zou zijn, ongeacht de value van start_index.

In PHP zou bovenstaande code dus resulteren in de volgende array:

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

Met PHP vertonen arrays die beginnen met een negatieve index dus ander gedrag. Lees meer over backward incompatibilities in de RFC zelf.

Union types 2.0

Untion types accepteren values die van verschillende types kunnen zijn. Momenteel biedt PHP geen ondersteuning voor union types, met uitzondering van de ?Type syntax en special type iterable.

Voor PHP 8 konden union types alleen worden gespecificeerd in phpdoc annotations, zoals je kan zien in het onderstaande voorbeeld van de RFC:

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;
	}
}

De RFC Union Types 2.0 stelt voor om ondersteuning voor union types toe te voegen in function signatures, zodat we niet meer afhankelijk zijn van inline documentatie, maar nu union types kunnen definiëren met een T1|T2|… syntax:

class Number {
	private int|float $number;

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

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

Dit wordt uitgelegd door Nikita Popov in de RFC,

“Door in de taal union types te ondersteunen kunnen we meer type information verplaatsen van phpdoc naar function signatures, met de gebruikelijke voordelen die dit met zich meebrengt:

  • Types worden daadwerkelijk afgedwongen, dus fouten kunnen vroegtijdig worden opgemerkt.
  • Omdat ze worden afgedwongen, is het minder waarschijnlijk dat type information verouderd raakt of edge-cases mist.
  • Types worden tijdens inheritance al gecontroleerd, waardoor het Liskov Substitution Principle kan worden gehandhaafd.
  • Types zijn beschikbaar via Reflection.
  • De syntax is veel minder boilerplate-erig dan phpdoc.”

Union types ondersteunen alle beschikbare types, met enkele beperkingen:

  • Het void type kan geen deel uitmaken van een union, omdat void inhoudt dat een function geen enkele value retourneert.
  • Het null type wordt uitsluitend ondersteund in union types, maar gebruik ervan als een standalone type is niet toegestaan.
  • De nullable type notation (?T) is ook toegestaan, wat T|null Het is echter niet toegestaan om de ?T notatie op te nemen in union types (?T1|T2 is niet toegestaan en we moeten dus in plaats daarvan T1|T2|null gebruiken).
  • Aangezien veel functions (dwz. strpos(), strstr(), substr() false bevatten als mogelijke return types, wordt het pseudo-type false ook ondersteund.

Je kan in het RFC meer lezen over Union Types V2..

Consistent type errors voor internal functions

Wanneer een parameter van een illegaal type wordt doorgegeven, gedragen internal en user-define functions zich anders.

User-defined functions veroorzaken een TypeError, maar internal functions gedragen zich op verschillende manieren, afhankelijk van verschillende voorwaarden. Hoe dan ook, het typische antwoord is om een waarschuwing te geven en een null te retourneren. Zie het onderstaande voorbeeld in PHP 7.4:

var_dump(strlen(new stdClass));

Dit zou de volgende waarschuwing opgeven:

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

Als strict_types is ingeschakeld of argument information types specificeert, dan zien we ander gedrag. In dergelijke scenario’s wordt de type error gedetecteerd en resulteert het in een TypeError.

Deze situatie zou leiden tot een aantal problemen. Dit wordt uitvoerig uitgelegd in de Issues sectie van de RFC.

Om deze inconsistenties te verwijderen, stelt deze RFC voor om de internal parameter die API’s parseert altijd een ThrowError te laten genereren in het geval een parameter type niet overeenkomt.

Fatal error: Uncaught TypeError: strlen(): Argument #1 ($str) must be of type string, object given in /path/to/your/test.php:4

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 is throw een statement. Het is dus niet mogelijk om deze te gebruiken op plekken waar alleen een expression is toegestaan.

Deze RFC stelt voor om het throw statement om te zetten in een expression zodat deze ook kan worden gebruikt in context waarin expressions zijn toegestaan. Denk hierbij aan arrow functions, null coalesce operator, ternary en elvis operators, etc.

Bekijk de volgende voorbeelden van de 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

Een weak map is een collectie van data (objects) waarin keys weakly referenced zijn. Dit voorkomt dat ze garbaged collected worden.

PHP 7.4 voegde ondersteuning toe voor weak references als een manier om een reference naar een object te behouden, maar dat niet verhindert dat het object zelf wordt vernietigd. Zoals Nikita Popov opmerkte,

“Raw weak references zijn op zichzelf van beperkt nut en weak maps worden in de praktijk veel vaker gebruikt. Het is niet mogelijk om een efficiënte weak map bovenop PHP weak references te implementeren, omdat de mogelijkheid om een destruction callback te registreren niet wordt geboden.”

Dat is waarom deze RFC een WeakMap class introduceert om objects te maken die kunnen worden gebruikt als weak map keys. Deze kunnen worden vernietigd en verwijderd van de weak map als er geen verdere verwijzingen zijn naar het key object.

In langlopende processen zou dit memory leaks kunnen voorkomen en prestaties kunnen verbeteren. Bekijk het onderstaande voorbeeld van de RFC:

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

Met PHP zou de bovenstaande code het volgende resultaat opleveren (zie de code hier in actie):

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

Als je het object unset, wordt de key automatisch uit de weak map verwijderd:

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

Nu zou het resultaat het volgende zijn:

object(WeakMap)#1 (0) {
}

Voor meer informatie over Weak Maps kan je het best de RFC lezen. Het voorstel was unaniem goedgekeurd.

Trailing comma in parameter list

Trailing commas zijn komma’s die zijn toegevoegd aan lijsten met items in verschillende contexten. PHP 7.2 introduceerde trailing commas in list syntax en PHP 7.3 introduceerde trailing commas in function calls.

PHP introduceert nu trailing commas in parameter lists met functions, methods en closures. In onderstaand voorbeeld kan je meer zien:

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

Het voorstel werd aangenomen met 58 tegen 1 stemmen.

::class toegestaan voor objects

Om de naam van een class te fetchen, kan je de syntax Foo\Bar::class gebruiken. Deze RFC stelt voor om dezelfde syntax uit te breiden naar objects. Hierdoor is het nu mogelijk om de naam van de class van een bepaald object op te halen, zoals je in onderstaand voorbeeld kan zien:

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

Met PHP 8 geeft $object:class hetzelfde resultaat als get_class($object). Als $object geen object is, genereert het een TypeError exception.

Dit voorstel was unaniem goedgekeurd.

Attributes v2

Attributes, ook al bekend als annotations, zijn een vorm van structured metadata die kunnen worden gebruikt om properties te specificeren voor objects, elements of files.

Tot PHP 7.4 waren doc-comments de enige manier om metadata toe te voegen aan declarations van classes, functions, etc. De Attributes v2 RFC introduceert attributes voor PHP die ze definiëren als een vorm van structured, syntactic metadata die kunnen worden toegevoegd aan declarations van classes, properties, functions, methods, parameters en constants.

Attributes worden toegevoegd vóór de declarations waarnaar ze verwijzen. Bekijk de volgende voorbeelden van de RFC:

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

	<>
	public $x;

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

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

<>
function f1() { }

$f2 = <> function () { };

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

Attributes kunnen voor of na een doc-block comment worden toegevoegd:

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

Elke declaration kan een of meerdere attributen hebben en elke attribute kan een of meer bijbehorende values hebben:

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

Bekijk de RFC voor een meer gedetailleerd overzicht van PHP attributes, use cases en alternatieve syntax.

Named Arguments

Named arguments bieden een nieuwe manier om arguments door te geven aan een function in PHP:

Named arguments maken het doorgeven van arguments aan een function mogelijk op basis van de naam van de parameter in plaats van de positie van de parameter.

We kunnen named arguments doorgeven aan een function door simpelweg de naam van de parameter toe te voegen voor de waarde ervan:

callFunction(name: $value);

We mogen ook reserved keywords gebruiken, zoals te zien is in het onderstaande voorbeeld:

callFunction(array: $value);

We mogen de naam van een parameter echter niet dynamisch doorgeven. De parameter moet een identifier zijn en de volgende syntax is niet toegestaan:

callFunction($name: $value);

Volgens Nikita Popov, de auteur van deze RFC, bieden named arguments verschillende voordelen.

Ten eerste zullen named argumenten ons helpen om meer begrijpelijke code te schrijven, omdat hun betekenis zelfdocumenterend is. Het onderstaande voorbeeld van de RFC spreekt voor zich:

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

Bij named arguments maakt de volgorde niet uit. Dit betekent dat we niet worden gedwongen om argumenten door te geven aan een functie in dezelfde volgorde als de signature van de functie:

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

Ook is het mogelijk om named arguments te combineren met positional arguments:

htmlspecialchars($string, double_encode: false);

Een ander groot voordeel van named arguments is dat ze het toestaan om alleen die arguments te specificeren die we daadwerkelijk willen veranderen en dat we geen default arguments hoeven op te geven als we de default values niet willen overschrijven. Het volgende voorbeeld uit de RFC maakt het duidelijk:

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

Named arguments kunnen worden gebruikt met PHP attributes, zoals getoond in het volgende voorbeeld van de RFC:

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

Het doorgeven van positional arguments na named arguments is echter niet toegestaan en zal resulteren in een compileerfout. Hetzelfde gebeurt wanneer dezelfde parameternaam twee keer wordt doorgegeven.

Named arguments zijn vooral handig bij class declarations omdat constructors meestal een groot aantal parameters hebben en named arguments een meer “ergonomische” manier bieden om een class te declaren.

Voor een uitgebreidere beschrijving van Named Arguments, met beperkingen, achterwaartse incompatibiliteiten en verschillende voorbeelden, zie de Named Arguments RFC.

Nullsafe Operator

Deze RFC introduceert de nullsafe operator $-> met volledige short-circuit evaluation.

Bij short-circuit evaluation wordt de tweede operator alleen geëvalueerd als de eerste operator niet als null wordt geëvalueerd. Als een operator binnen een chain naar null evalueert, stop de uitvoering van de hele chain en wordt deze geëvalueerd naar null.

Kijk naar de volgende voorbeelden van de RFC:

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

Als $a null is, wordt method b() niet gecalled en wordt $foo op null gezet.

Zie de nullsafe operator RFC voor meer voorbeelden, uitzonderingen en toekomstige scope.

Saner string to number comparisons

In eerdere PHP versies castte PHP een string eerst aan een nummer en voerde deze vervolgens de comparison uit tussen integers of floats bij het maken van een non-strict comparison tussen strings en nummers. Ook al was dit gedrag behoorlijk nuttig in verschillende scenario’s, het kan verkeerde resultaten opleveren die kunnen leiden tot bugs en/of beveiligingsproblemen.

Kijk naar het volgende voorbeeld van de RFC:

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

PHP 8 introduceert Saner string to number comparisons met als doel om string to number comparisons logischer te maken. In de woorden van Nikita Popov,

Deze RFC is bedoeld om string to number comparisons een logischer gedrag te geven: bij het gebruiken van een numeric string, gebruik een number comparison (hetzelfde als nu). In andere gevallen converteer het nummer naar een string en gebruik een string comparison.

De volgende tabel vergelijkt het gedrag van string to number comparisons tussen eerdere PHP versies en 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

Lees meer over de vele implicaties van deze wijziging en hoe string to number comparisons veranderen in PHP 8 in de officiële RFC van Nikita Popov.

Saner Numeric Strings

In PHP vallen strings met nummers in drie categorieën:

  • Numeric strings: strings die een nummer bevatten, optioneel voorafgegaan door spaties.
  • Leading-numeric string: strings waarvan de eerste tekens numerieke strings zijn en de achterliggende tekens niet-numeriek.
  • Non-numeric string: strings die in geen van de vorige categorieën vallen.

Numeric strings en leading-numeric strings worden verschillend behandeld, afhankelijk van de operation die wordt uitgevoerd. Bijvoorbeeld:

  • Explicit string to number conversions (dwz (int) en (float) tpe casts) converteren numeric en leading-numeric string nummers. Het expliciet converteren van een non-numeric string naar een nummer produceert 0.
  • Implicit string to number conversions (dwz geen strict_type declaration) leiden tot verschillende resultaten voor numeric en non-numeric strings. Non-numeric strings to number conversions genereren een TypeError.
  • is_numeric() retourneert alleen true voor numeric tekenreeksen.

String offsets, arithmetic operations, incremend en decrement operations, string-to-string-comparisons en bitwise operations leiden ook tot verschillende resultaten.

Deze RFC stelt voor om:

De verschillende numeric string modussen binnen één concept samen te brengen: alleen numeric string met zowel leading als trailing witruimte zijn toegestaan. Elk ander type string is non-numeric en genereert een TypeError bij gebruik in een numerieke context.

Dit betekent dat alle strings die momenteel de E_NOTICE “A non well formed numeric value encountered” opleveren, opnieuw worden geclassificeerd in de E_WARNING “A non-numeric value encountered”, behalve als de leading-numeric string alleen trailing witruimte bevat. En de verschillende gevallen die momenteel een E_WARNING opleveren, zullen worden gepromoveerd tot TypeErrors.

Zie de RFC voor een uitgebreider overzicht van numeric strings in PHP 8, met codevoorbeelden, uitzonderingen en achterwaartse compatibiliteitsproblemen.

Match Expression v2

De nieuwe match expressie lijkt veel op switch, maar met een veiligere semantiek en waardoor waarden kunnen worden geretourneerd.

Bekijk het volgende switch voorbeeld van de RFC om het verschil tussen de twee control structures te begrijpen:

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

We kunnen nu hetzelfde resultaat krijgen als de bovenstaande code met de volgende match expression:

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

Een groot voordeel van het gebruik van de nieuwe match expression is dat, terwijl switch losjes values vergelijkt (==), wat mogelijk leidt tot onverwachte resultaten, bij match de vergelijking een identity check is (===).

De match expression kan ook meerdere door komma’s gescheiden expressions bevatten, waardoor een beknoptere syntaxis mogelijk is (bron):

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

Zie de Match expression v2 RFC en de PHP documentatie voor meer voorbeelden en use cases.

Strengere type checks voor arithmetic/bitwise operators

In eerdere versies van PHP was het applyen van arithmetic en bitwise operators aan een array, resource of non-overloaded object toegestaan.  Hoe dan ook, het gedrag was soms inconsistent.

In deze RFC laat Nikita Popov zien hoe onlogisch dat gedrag zou kunnen zijn met een eenvoudig voorbeeld:

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

Nikita legt uit hoe het applyen van een arithmetic of bitwise operator op arrays, resources of niet-overbelaste objecten tot verschillende resultaten leidde:

Operators +, -, *, /, **:

  • Geeft Error exception bij array operand. (Excluding + if beide operands array zijn.)
  • Converteer een resource operand automatisch naar de resource ID als een integer.
  • Converteer een object operand naar integer one, terwijl je een melding genereert.

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

  • Converteer een array operand naar integer zero als deze leeg is of integer one als deze niet leeg is.
  • Converteer een resource operand automatisch naar de resource ID als een integer.
  • Converteer een object operand naar integer one, terwijl je een melding genereert.

Operator ~:

  • Geeft een Error exception voor array, resource en object operands.

Operators ++ and –:

  • Doet niks als de operand een array, resource of object is.

Met PHP 8 veranderen zaken en is het gedrag hetzelfde voor alle arithmetic en bitwise operators:

Geeft een TypeError exception voor array, resource en object operands.

Nieuwe PHP functions

PHP 8 brengt verschillende nieuwe functions naar de taal:

str_contains

Voor PHP 8 waren strstr en strpos de typische opties voor developers om te zoeken naar een needle binnen een opgegeven string. Het probleem is dat beide functions niet erg intuïtief zijn en het gebruik ervan kan verwarrend zijn voor nieuwe PHP ontwikkelaars. Zie het volgende voorbeeld:

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

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

In het bovenstaande voorbeeld gebruikten we de comparison operator !== die ook controleert of twee values van hetzelfde type zijn. Dit voorkomt dat we een foutmelding krijgen als de positie van de needle 0 is:

“Deze function kan Boolean FALSE retourneren, maar het kan ook een non-Boolean value die evaluates naar FALSE. […] Gebruik de === operator om de teruggegeven value van deze function te testen.”

Bovendien bieden verschillende frameworks helper functions om te zoeken naar een value binnen een gegeven string (bekijk Laravel Helpers documentatie als voorbeeld).

Deze RFC stelt de introductie voor van een nieuwe function die het mogelijk maakt om binnen een string te zoeken: str_contains.

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

Het gebruik ervan is vrij eenvoudig. str_contains controleert of $needle wordt gevonden in $haystack en retourneert dienovereenkomstig true of false.

Dankzij str_contains kunnen we dus de volgende code schrijven:

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

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

Dit is veel beter leesbaar en bovendien minder vatbaar voor fouten (zie hier de code in actie).

Op moment van schrijven is str_contains hoofdlettergevoelig, maar dit kan in de toekomst veranderen.

Het voorstel om str_contains toe te voegen werd aangenomen met 43 stemmen voor en 9 stemmen tegen.

str_starts_with() and str_ends_with()

Naast de function str_contains, zijn er nog twee andere functions toegevoegd die het mogelijk maken om binnen een bepaalde string te zoeken naar een needle: str_starts_with en str_ends_with.

Deze nieuwe functions controleren of een bepaalde string begint of eindigt met een andere string:

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

Beide functions retourneren false als $needle langer is dan $haystack.

Volgens Will Hudgins, de auteur van deze RFC,

“De functionaliteit str_starts_with en str_ends_with is zó nodig dat veel grote PHP frameworks het al ondersteunen, waaronder Symfony, Laravel, Yii, FuelPHP en Phalcon.”

Dankzij hen kunnen we nu suboptimale en minder intuïtieve functions vermijden als substr, strpos. Beide functions zijn hoofdlettergevoelig:

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

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

Je kan deze code hier in actie zien.

De RFC heeft deze goedgekeurd met 51 voor en 4 stemmen tegen.

get_debug_type

get_debug_type is een nieuwe PHP function die het type van een variabele retourneert. De nieuwe function werkt op een vergelijkbare manier als de gettype function, maar get_debug_type retourneert native names en zorgt bij class names voor een resolve.

Dit is een verbetering voor de taal, omdat gettype() niet erg nuttig is voor type checking.

De RFC geeft twee handige voorbeelden om het verschil tussen de nieuwe functions get_debug_type() en gettype() beter te begrijpen. Het eerste voorbeeld toont de werking van gettype:

$bar = [1,2,3];

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

Met PHP 8 kan je in plaats daarvan get_debug_type gebruiken:

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

De volgende tabel toont de returning values van get_debug_type en 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

Overige RFC’s

Hier is een korte lijst met aanvullende goedgekeurde verbeteringen die met PHP 8 gepaard gaan:

  1. Stringable interface: deze RFC introduceert een Stringable interface die automatisch wordt toegevoegd aan classes die de __to String() method Het voornaamste doel is om hier de union type string|Stringable te gebruiken.
  2. Nieuwe DOM Living Standard API’s in ext/dom: deze RFC stelt voor om de huidige DOM Living Standard te implementeren in de PHP DOM extensie door nieuwe interfaces en public properties te introduceren.
  3. Static return type: PHP 8 introduceert het gebruik van static als return type, naast self en parent
  4. Variable syntax tweaks: deze RFC lost enkele resterende inconsistenties op in de variable syntax van PHP..

PHP 8 prestatiebenchmarks

Als je je afvraagt hoe snel PHP 8 is, dan hebben wij het antwoord. We hebben 20 PHP platforms/configuraties gebenchmarkt op 7 verschillende PHP versies (5.6, 7.0, 7.1, 7.2, 7.3 en 8.0).

PHP 8.0 kwam als winnaar uit de bus op de meeste platforms die dit ondersteunen, waaronder WordPress en Laravel.

Samengestelde PHP benchmarks van de top platforms
Samengestelde PHP benchmarks van de top platforms

WordPress op PHP 8.0 kan bijvoorbeeld 18,4% meer verzoeken per seconde verwerken dan PHP 7.4. Laravel op PHP 8.0 kan 8,5% meer aanvragen per seconde verwerken dan PHP 7.3.

Als je website of app volledig compatibel is met PHP 8.0, moet je het zo snel mogelijk bijwerken van de omgeving van je server naar PHP 8.0 inplannen. Jij (en je gebruikers) zullen de prestatievoordelen zeker waarderen. Test je site echter grondig voordat je de update uitvoert.

Je kunt ons PHP benchmarkartikel lezen voor meer informatie, zoals gedetailleerde prestatiegegevens, inzichten en mooie grafieken!

Samenvatting

Wat een rit! In dit bericht hebben we de meest interessante optimalisaties en features van PHP 8 besproken. Waar jullie het meest naar uit hebben gekeken is waarschijnlijk de Just in Time compiler, maar er zoveel meer in PHP 8.

Zorg ervoor dat je dit blogbericht als bladwijzer gebruikt voor toekomstig gebruik. 🤓

Nu is het jouw beurt: ben je klaar om de nieuwe PHP features te testen? Welke is jouw favoriet? Je kan je antwoord in het reactiegedeelte hieronder kwijt.

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.