PHP 8 släpptes officiellt för allmän tillgänglighet den 26 november 2020!

Den här nya stora uppdateringen lägger till många optimeringar och ett flertal kraftfulla funktioner i språket. Vi är glada över att kunna guida dig igenom de mest intressanta ändringarna som gör att vi kan skriva bättre kod och bygga mer robusta applikationer.

PHP 8.0 Tillägg till tillkännagivande
PHP 8.0 Tillägg till tillkännagivande

Är du redo? Då kör vi igång!

PHP-JIT (Just in Time-kompilator)

Den mest hyllade funktionen som kommer med PHP 8 är Just-In-time (JIT)-kompilatorn. Vad handlar JIT om?

RFC-förslaget beskriver JIT enligt följande:

”PHP JIT implementeras som en nästan oberoende del av OPcache. Det kan vara aktiverat/inaktiverat vid PHPs kompileringstid och vid driftstid. När det är aktiverat lagras inbyggd kod för PHP-filer i en ytterligare region i OPcachens delade minne och op_array→opcodes[].handler sparar visare till ingångspunkterna för JIT-koden.”

Så, hur kom vi till JIT och vad är skillnaden mellan JIT vs OPcache?

För att bättre förstå vad JIT betyder för PHP, låt oss ta en snabb titt på hur PHP exekverar från källkoden till slutresultatet.

PHP-exekveringen är en 4-stegsprocess:

  • Lexing/Tokenisering: först läser tolken PHP-koden och bygger en uppsättning tokens.
  • Parsning: tolken kontrollerar om skriptet matchar syntaxreglerna och använder tokens för att bygga ett abstrakt syntaxträd (AST), vilket är en hierarkisk representation av källkodens struktur.
  • Kompilering: tolken går igenom trädet och översätter AST-noder till Zend-opkoder på låg nivå, vilka är numeriska identifierare som avgör vilken typ av instruktion som utförs av Zend VM.
  • Tolkning: Opkoder tolkas och körs på Zend VM.

Följande bild visar en visuell representation av den grundläggande PHP-exekveringsprocessen.

Grundläggande PHP-exekveringsprocess
Grundläggande PHP-exekveringsprocess

Så, hur gör OPcache PHP snabbare? Och vad förändras i exekveringsprocessen med JIT?

OPcache-tillägget

PHP är ett tolkningsspråk. Detta innebär att när ett PHP-skript körs kommer tolken parsa, kompilera, och exekvera koden om och om igen för varje begäran. Detta kan leda till att det slösas CPU-resurser och tar extra lång tid.

Det är här OPcache-tillägget kommer in i bilden.

”OPcache förbättrar PHP-prestanda genom att lagra förkompilerade skript-bytekod i ett delat minne, vilket tar bort behovet av att PHP laddar och parsar skript för varje begäran.”

Med OPcache aktiverat går PHP-tolken igenom 4-stegsprocessen som nämns ovan endast första gången skriptet körs. Eftersom PHP-bytekoder lagras i ett delat minne är de omedelbart tillgängliga som mellanliggande representation på låg nivå, och kan exekveras på Zend VM direkt.

PHP-exekveringsprocess med OPcache aktiverat
PHP-exekveringsprocess med OPcache aktiverat

Från och med PHP 5.5 är Zend OPcache-tillägget tillgängligt som standard och du kan kontrollera om du har det korrekt konfigurerat genom att helt enkelt anropa phpinfo() från ett skript på din server eller kolla din php.ini-fil (se inställningarna för OPcache-konfiguration).

Föreslagen läsning: Hur man förbättrar PHP-minnesgränsen i WordPress.

Zend OPcache-sektionen i en phpinfo-sida
Zend OPcache-sektionen i en phpinfo-sida

Förladdning

OPcache har nyligen förbättrats med implementeringen av förladdning, en ny OPcache-funktion som lades till med PHP 7.4. Förladdning ger ett sätt att lagra en angiven uppsättning skript i OPcacheminnet ”innan någon applikationskod körs”, men det ger inte någon påtaglig prestandaförbättring för typiska webbaserade applikationer.

Du kan läsa mer om förladdning i vår introduktion till PHP 7.4.

Med JIT tar PHP ett kliv framåt.

JIT – Just in Time-kompilator

Även om Opkoder är mellanliggande representation på låg nivå måste de fortfarande kompileras till maskinkod. JIT ”introducerar inte någon ytterligare IR-form (Mellanliggande Representation)”, men använder DynASM (Dynamic Assembler för kodgenereringsmotorer) för att generera maskinkod direkt från PHP-bytekod.

Kort sagt översätter JIT de heta delarna av den mellanliggande koden till maskinkod. Genom att kringgå kompilering skulle det kunna ge betydande förbättringar i prestanda och minnesanvändning.

Zeev Surasky, medförfattare till PHP JIT-förslaget, visar hur mycket snabbare beräkningarna skulle vara med JIT:

Men, skulle JIT effektivt förbättra WordPress-prestanda?

JIT för webbapplikationer

Enligt JIT-förslaget bör implementeringen av just in time-kompilatorn förbättra PHP-prestanda. Men skulle vi verkligen uppleva sådana förbättringar i verkliga applikationer som WordPress?

De tidiga testerna visar att JIT skulle göra CPU-intensiva arbetsbelastningar betydligt snabbare, men RFC varnar:

”…precis som de tidigare försöken – det verkar för närvarande inte avsevärt förbättra verkliga applikationer som WordPress (med opcache.jit=1235 326 förfrågningar/s vs 315 förfrågningar/s).

Det är planerat att tillhandahålla ytterligare ansträngningar och förbättra JIT för verkliga applikationer, med hjälp av profilering och spekulativa optimeringar.”

Med JIT aktiverat skulle koden inte köras av Zend VM, utan av själva CPU:n och detta skulle förbättra beräkningshastigheten. Webbapplikationer som WordPress är också beroende av andra faktorer som TTFB, databasoptimering, HTTP-förfrågningar etc.

PHP 8 performance diagram
Relativt JIT-tillskott till PHP 8-prestandan (Bildkälla: PHP 8.0 Announcement Addendum)

Så vi bör inte förvänta oss en betydande ökning av PHP-körningshastigheten när det gäller WordPress och liknande appar. Ändå kan JIT ge flera fördelar för utvecklare.

Enligt Nikita Popov:

”Fördelarna med JIT-kompilatorn är ungefär (och som redan beskrivits i dess RFC):

  • Betydligt bättre prestanda för numerisk kod.
  • Något bättre prestanda för ”typisk” PHP-webbapplikationskod.
  • Möjligheten att flytta mer kod från C till PHP, eftersom PHP nu kommer att vara tillräckligt snabbt.”

Så medan JIT knappast kommer att medföra stora förbättringar av WordPress-prestanda kommer det att uppgradera PHP till nästa nivå, vilket gör det till ett språk som många funktioner nu kan skrivas direkt i.

Nackdelen skulle dock vara den större komplexiteten som kan leda till ökade kostnader för underhåll, stabilitet och felsökning. Enligt Dmitrij Stogov:

”JIT är extremt enkelt, men ändå ökar det nivån på hela PHP-komplexiteten, risken för nya typer av buggar och kostnader för utveckling och underhåll.”

Förslaget att inkludera JIT i PHP 8 godkändes med 50 röster mot 2.

PHP 8 förbättringar och nya funktioner

Förutom JIT kan vi förvänta oss många funktioner och förbättringar med PHP 8. Följande lista är vårt handplockade urval av kommande tillägg och förändringar som bör göra PHP mer tillförlitligt och effektivt.

Konstruktörsegenskaps-kampanj

Som ett resultat av en pågående diskussion om att förbättra objekt-ergonomi i PHP föreslår Constructor Property Promotion RFC en ny och mer kortfattad syntax som förenklar egenskaps-deklarationen, vilket gör den kortare och mindre rörig.

Detta förslag gäller endast främjade parametrar, dvs. de metodparametrar som föregås av offentliga, skyddade och privata synlighetsnyckelord.

För närvarande måste alla egenskaper upprepas flera gånger (minst fyra gånger) innan vi kan använda dem med objekt. Begrunda följande exempel från 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;
    }
}

Enligt Nikita Popov, RFC-författaren, måste vi skriva egenskaps-namnet minst fyra gånger på tre olika platser: egenskaps-deklarationen, konstruktörs-parametrarna och egenskaps–tilldelningen. Denna syntax är inte särskilt användbar, särskilt i klasser med många egenskaper och mer beskrivande namn.

Denna RFC föreslår att konstruktörs och parameterdefinitionen slås samman. Så från och med PHP 8 har vi ett mer användbart sätt att deklarera parametrar. Koden ovan kan ändras enligt nedanstående beskrivning:

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

Och det är allt. Så vi har ett nytt sätt att främja egenskaper som är kortare, mer läsbara och mindre benägna att bli fel. Enligt Nikita:

Vi gör en enkel syntaktisk förvandling. Men det minskar mängden pannplåtskod som du måste skriva för värdeobjekt i synnerhet…

Egenskapsdeklarationen omvandlas eftersom vi uttryckligen har deklarerat dessa egenskaper. Vi kan använda reflektions-API:et för att använda oss av introspekt på egenskapsdefinitioner före körningen (se Desugaring):

Reflektion (och andra introspektionsmekanismer) kommer att observera tillståndet efter desugaring. Detta innebär att marknadsförda egenskaper kommer att framträda på samma sätt som uttryckligen deklarerade egenskaper, och marknadsförda konstruktörs-argument visas som vanliga konstruktörs-argument.

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

arv

Vi har inga begränsningar när det gäller att använda arv i samband med främjade parametrar. Det finns hursomhelst inget särskilt samband mellan överordnade och underordnade klasskonstruktörer. Enligt Nikita:

Vanligtvis säger vi att metoder alltid måste vara kompatibla med föräldra-metoden. […] men den här regeln gäller inte för konstruktören. Så konstruktören tillhör verkligen en enda klass, och konstruktörer mellan överordnad och underordnad klass behöver inte vara kompatibla på något sätt.

Här är ett exempel:

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

Vad är inte tillåtet när det gäller främjade egenskaper

Främjade egenskaper är tillåtna i icke-abstrakta konstruktörer och egenskaper, men det finns flera begränsningar som är värda att nämna.

Abstrakta konstruktörer

Främjade egenskaper är inte tillåtna i abstrakta klasser och gränssnitt:

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

En av de mest noterbara begränsningarna är relaterad till ogiltighet. Vi använde tidigare en typ som inte uttryckligen var ogiltig. Men med ett ogiltigt standardvärde var typen underförstått ogiltig. När det gäller egenskapstyper har vi inte detta implicita beteende eftersom främjade parametrar kräver en egenskapsdeklaration och den ogiltiga typen måste uttryckligen deklareras. Se följande exempel från 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) {}
}
Anropande typ

Eftersom anropningsbar inte är en typ som stöds för egenskaper får vi inte använda den anropande typen i främjade egenskaper:

class Test {
    // Error: Callable type not supported for properties.
    public function __construct(public callable $callback) {}
}
Var-nyckelordet är inte tillåtet

Endast ett synlighetsnyckelord kan användas med främjade parametrar, så det är inte tillåtet att deklarera konstruktörs-egenskaper med nyckelordet var (se följande exempel från RFC):

class Test {
    // Error: "var" keyword is not supported.
    public function __construct(var $prop) {}
}
Inga dupliceringar är tillåtna

Vi kan kombinera främjade egenskaper och explicita egenskaper i samma klass, men egenskaper kan inte deklareras två gånger:

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) {}
}
Variadiska parametrar är inte tillåtna

Anledningen här är att den deklarerade typen skiljer sig från den variadiska parametern, som faktiskt är en matris:

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

Ytterligare läsning

För en närmare titt på främjande av konstruktörs-egenskaper, lyssna på den här intervjun med Nikita Popov. För en djupgående översikt över objekt-ergonomi i PHP, se det här inlägget och följande intervju med Larry Garfield.

Validering för Abstrakta Trait-metoder

Traits definieras som ”en mekanism för kodåteranvändning i arvsbaserade språk som PHP”. Vanligtvis används de för att deklarera metoder som kan användas i flera klasser.

De kan också innehålla abstrakta metoder. Dessa metoder deklarerar helt enkelt metodens signatur, men metodens implementering måste ske inom klassen med hjälp av detta trait.

Enligt PHP-manualen;

”Traits stöder användningen av abstrakta metoder för att införa krav på den uppvisande klassen.”

Detta innebär också att metodernas signaturer måste matcha varandra. Med andra ord måste typen och antalet nödvändiga argument vara lika.

Hur som helst, enligt Nikita Popov, författare till denna RFC, är signaturvalidering för närvarande endast verkställt ibland.

Följande exempel från Nikita avser det första fallet (ej verkställd signatur:

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

Med detta sagt föreslår denna RFC att alltid visa ett allvarligt fel om implementeringsmetoden inte är kompatibel med abstract trait-metoden, oavsett 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

Denna RFC har enhälligt godkänts.

Inkompatibla Metodsignaturer

I PHP ger ärvda fel på grund av inkompatibla metodsignaturer antingen ett allvarligt fel eller en varning beroende på vad som orsakar felet.

Om en klass implementerar ett gränssnitt ger inkompatibla metodsignaturer ett allvarligt fel. Enligt dokumentationen för objektgränssnitt:

”Den klass som implementerar gränssnittet måste använda en metodsignatur som är kompatibel med LSP (Liskov Substitutionsprincip). Om du inte gör det kommer det att leda till ett allvarligt fel.”

Här är ett exempel på ett arvsfel med ett gränssnitt:

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

I PHP 7.4 skulle koden ovan ge följande fel:

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

En funktion i en barnklass med en inkompatibel signatur skulle ge en varning. Se följande kod från RFC:

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

I PHP 7.4 skulle koden ovan helt enkelt ge en varning:

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

Nu föreslår denna RFC att alltid ge ett allvarligt fel för inkompatibla metodsignaturer. Med PHP 8 skulle koden vi såg tidigare ovan leda till följande:

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

Arrayer som börjar med ett negativt Index

Om en array börjar i PHPH med ett negativt index (start_index < 0), startar följande index från 0 (mer om detta i array_fill-dokumentationen). Titta på följande exempel:

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

I PHP 7.4 skulle resultatet vara följande:

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

Nu föreslår denna RFC att ändra på detta så att det andra indexet skulle vara start_index + 1, beroende på värdet av start_index.

I PHP 8 skulle koden ovan resultera i följande array:

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

Med PHP 8 ändrar arrayer som börjar med ett negativt index sitt beteende. Läs mer om bakåtkompatibiliteter på RFC-sidan.

Unionstyper 2.0

Unionstyper accepterar värden som kan vara av olika slag. För närvarande tillhandahåller PHP inte stöd för unionstyper, med undantag för ?Type-syntaxen och den speciella iterable-typen.

Innan PHP 8 kunde unionstyper endast anges i phpdoc-anteckningar, vilket visas i följande exempel från 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;
	}
}

Unionstyperna 2.0 RFC föreslår att lägga till stöd för unionstyper i funktionssignaturer, så att vi inte längre kommer att förlita oss på inline-dokumentation, utan skulle definiera unionstyper med en T1|T2|...-syntax istället:

class Number {
	private int|float $number;

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

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

Nikita Popov förklarar i RFC,

”Att stödja unionstyper i språket gör det möjligt för oss att flytta mer typinformation från phpdoc till funktionssignaturer, med de vanliga fördelarna som detta medför:

  • Typer är faktiskt verkställda, så misstag kan fångas tidigt.
  • Eftersom de verkställs är typinformation mindre sannolikt att bli föråldrad eller missa edge-cases.
  • Typer kontrolleras genom arv, vilket verkställer Liskovs Substitutionsprincip.
  • Typer är tillgängliga genom Reflection.
  • Syntaxen är mycket mindre standardiserad än phpdoc.”

Unionstyper stöder alla tillgängliga typer, med vissa begränsningar:

  • Void-typ kan inte vara en del av en union, eftersom void innebär att en funktion inte returnerar något värde.
  • Null-typen stöds endast i unionstyper, men dess användning som en fristående typ är inte tillåtet.
  • Den ogiltiga typ-notationen (?T) är också tillåten, det vill säga T|null, men vi får inte inkludera ?T-notation i unionstyper (?T1|T2 är inte tillåtet och vi bör använda T1|T2|null istället).
  • Eftersom många funktioner (dvs. strpos(), strstr(), substr(), etc.) inkluderat false bland de möjliga returtyperna, stöds också pseudo-typen false också.

Du kan läsa mer om Unionstyper V2 i dess RFC.

Konsekventa typfel för interna funktioner

När en parameter av olaglig typ används beter sig interna och användardefinierade funktioner annorlunda.

Användardefinierade funktioner skapar ett TypeError, men interna funktioner beter sig på olika sätt, beroende på flera omständigheter. Hur som helst, det typiska beteendet är att visa en varning och returnera null. Se följande exempel i PHP 7.4:

var_dump(strlen(new stdClass));

Detta skulle resultera i följande varning:

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

Om strict_types är aktiverat, eller argumentinformationen specificerar typer skulle beteendet vara annorlunda. I sådana scenarier upptäcks typfelet och resulterar ett TypeError.

Denna situation skulle leda till ett antal problem som förklaras väl i problemavsnittet på förslagets RFC-sida.

För att ta bort dessa inkonsekvenser föreslår denna RFC att få den interna parametern som parsar API:er att alltid generera ett ThrowError i händelse av en felmatchad parametertyp.

I PHP 8 ger koden ovan följande fel:

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

Uttrycket throw

I PHP är throw en sats, så det är inte möjligt att använda det på platser där endast ett uttryck är tillåtet.

Denna RFC föreslår att konvertera satsen throw till ett uttryck så att den kan användas i alla sammanhang där uttryck är tillåtna. Till exempel arrow-funktioner, null coalesce-operatör, ternary och elvis-operatörer etc.

Se följande exempel från RFC:

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

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

Svaga kartor

En svag karta är en samling data (objekt) där nycklarna är svagt refererade, vilket innebär att de inte hindras från att samlas in som skräp.

PHP 7.4 lade till stöd för svaga referenser som ett sätt att behålla en referens till ett objekt som inte hindrar själva objektet från att förstöras. Nikita Popov påpekar:

”Råa svaga referenser är bara av begränsad användbarhet för sig själva och svaga kartor används mycket oftare i praktiken. Det är inte möjligt att implementera en effektiv svag karta ovanpå PHP:s svaga referenser eftersom möjligheten att registrera en förstörelse-callback inte tillhandahålls.”

Därför introducerar denna RFC en WeakMap-klass för att skapa objekt som ska användas som svaga kartnycklar som kan förstöras och tas bort från den svaga kartan om det inte finns några ytterligare referenser till nyckelobjektet.

I långvariga processer skulle detta förhindra minnesläckor och förbättra prestanda. Se följande exempel från RFC:

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

Med PHP 8 skulle koden ovan producera följande resultat (se koden här):

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

Om du avmarkerar objektet tas nyckeln automatiskt bort från den svaga kartan:

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

Nu skulle resultatet bli följande:

object(WeakMap)#1 (0) {
}

För en närmare titt på svaga kartor, se förslagets RFC. Förslaget godkändes enhälligt.

Avslutande kommatecken i parameterlistan

Avslutande kommatecken är kommatecken som bifogas listor över objekt i olika sammanhang. PHP 7.2 introducerade avslutande kommatecken i listsyntax, PHP 7.3 introducerade avslutande kommatecken i funktionsanrop.

PHP 8 introducerar nu avslutande kommatecken i parameterlistor med funktioner, metoder och stängningar, som visas i följande exempel:

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

Detta förslag antogs med 58 röster mot 1.

Tillåt ::class-syntax på objekt

För att hämta namnet på en klass kan vi använda Foo\Bar::class-syntaxen. Denna RFC föreslår att utöka samma syntax till objekt så att det nu är möjligt att hämta namnet på klassen för ett visst objekt som visas i exemplet nedan:

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

Med PHP 8 ger  $object::class samma resultat som get_class($object). Om $object inte är ett objekt, ger det ett TypeError-undantag.

Detta förslag godkändes enhälligt.

Attribut v2

Attribut, även känd som annoteringar, är strukturerade metadata som kan användas för att ange egenskaper för objekt, element eller filer.

Fram till PHP 7.4 var doc-kommentarer det enda sättet att lägga till metadata i deklarationer av klasser, funktioner etc. Nu introducerar RFC-förslaget Attribut v2 attribut för PHP som definierar dem som en form av strukturerade, syntaktiska metadata som kan läggas till deklarationer av klasser, egenskaper, funktioner, metoder, parametrar och konstanter.

Attribut läggs till före de deklarationer de hänvisar till. Se följande exempel från RFC:

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

	<<ExampleAttribute>>
	public $x;

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

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

<<ExampleAttribute>>
function f1() { }

$f2 = <<ExampleAttribute>> function () { };

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

Attribut kan läggas till före eller efter en kommentar till ett dokumentblock:

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

Varje deklaration kan ha ett eller flera attribut och varje attribut kan ha ett eller flera associerade värden:

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

Se RFC-sidan för en djupare översikt över PHP-attribut, användning, och alternativ syntax. Observera att Attribut v2 för närvarande väntar på implementering.

Namngivna argument

Namngivna argument erbjuder ett nytt sätt att skicka argument till en funktion i PHP:

Namngivna argument gör det möjligt att skicka argument till en funktion baserat på parameternamnet i stället för parameterpositionen.

Vi kan skicka namngivna argument till en funktion genom att helt enkelt lägga till parameternamnet före dess värde:

callFunction(name: $value);

Vi tillåts även att använda reserverade nyckelord, som visas i exemplet nedan:

callFunction(array: $value);

Men vi får inte passera ett parameternamn dynamiskt. Parametern måste vara en identifierare och följande syntax är inte tillåten:

callFunction($name: $value);

Enligt Nikita Popov, författaren till denna RFC, erbjuder namngivna argument flera fördelar.

Först och främst gör namngivna argument att vi kan skriva en mer förståelig kod eftersom deras mening är självdokumenterande. Exemplet nedan från RFC är självförklarande:

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

Namngivna argument är oberoende av ordningen. Detta innebär att vi inte tvingas skicka argument till en funktion i samma ordning som funktionssignaturen:

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

Det är även möjligt att kombinera namngivna argument med positionsargument:

htmlspecialchars($string, double_encode: false);

En annan stor fördel med namngivna argument är att de endast tillåter att vi specificerar de argument som vi vill ändra. Vi behöver inte specificera standardargument om vi inte vill skriva över standardvärden. Följande exempel från RFC gör detta tydligt:

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

Namngivna argument kan användas med PHP-attribut, som visas i följande exempel från RFC:

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

Det är dock inte tillåtet att skicka positionsargument efter namngivna argument. Detta kommer att resultera i ett kompileringsfel. Samma sak händer när du passerar samma parameternamn två gånger.

Namngivna argument är praktiska med klassdeklarationer eftersom konstruktörer vanligtvis har många parametrar, och namngivna argument ger ett mer ”ergonomiskt” sätt att deklarera en klass.

En närmare titt på Namngivna Argument, med begränsningar, bakåtkompatibiliteter och flera exempel, finns i RFC för Namngivna Argument.

Nullsafe-operatör

Denna RFC introducerar  nullsafe-operatören  $-> med fullständig kortslutningsutvärdering.

Vid kortslutningsutvärdering utvärderas den andra operatören endast om den första operatören inte utvärderas som null. Om en operatör i en kedja utvärderas som null stoppas körningen och hela kedjan utvärderas som null.

Begrunda följande exempel från RFC:

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

Om $a är ogiltig kallas inte metod b() och $foo blir inställd på null.

Se RFC för nullsafe-operatör för ytterligare exempel, undantag och framtida omfattning.

Förnuftigare sträng till taljämförelser

I tidigare PHP-versioner, när man gjorde en icke-strikt jämförelse mellan strängar och tal, kastade PHP först strängen till ett tal och bildade sedan jämförelsen mellan heltal eller floats. Även om det här beteendet är ganska användbart i flera scenarier kan det ge felaktiga resultat som även kan leda till buggar och / eller säkerhetsproblem.

Begrunda följande exempel från RFC:

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

PHP 8 introducerar en förnuftigare sträng till tal-jämförelse, som syftar till att göra sträng till tal-jämförelser mer rimliga. Med Nikita Popovs ord-

Denna RFC avser att ge sträng till tal-jämförelser ett mer rimligt beteende: När du jämför med en numerisk sträng, använd en tal-jämförelse (samma som nu). Annars konverterar du talet till sträng och använder en strängjämförelse.

I följande tabell jämförs sträng till tal-jämförelse med tidigare PHP-versioner och PHP 8:

Jämförelse    | Före | efter
------------------------------
 0 == "0"     | true   | true
 0 == "0.0"   | true   | true
 0 == "foo"   | true   | false
 0 == ""      | true   | false
42 == "   42" | true   | true
42 == "42foo" | true   | false

Läs mer om de många konsekvenserna av denna förändring och hur sträng till tal-jämförelser förändras i PHP 8 i den officiella RFC av Nikita Popov.

Förnuftigare numeriska strängar

I PHP är strängar som innehåller tal indelade i tre kategorier:

  • Numeriska strängar: strängar som innehåller ett tal som eventuellt föregås av blanktecken.
  • Inledande numerisk sträng: strängar vars ursprungliga tecken är numeriska strängar och vars avslutande tecken är icke-numeriska.
  • Icke-numerisk sträng: strängar som inte ingår i någon av de föregående kategorierna.

Numeriska strängar och inledande numeriska strängar behandlas olika beroende på vilken åtgärd som utförs, exempelvis:

  • Explicita sträng till tal-konverteringar (dvs. (int) och (float) typer) konverterar numeriska och inledande numeriska strängnummer. Att uttryckligen konvertera en icke-numerisk sträng till ett tal ger värdet 0.
  • Implicita sträng till tal-konverteringar (dvs. ingen strict_type-deklaration) leder till olika resultat för numeriska och icke-numeriska strängar. Icke-numerisk sträng till tal-konverteringar skapar en TypeError.
  • is_numeric() returnerar endast sant för numeriska strängar.

Strängförskjutningar, aritmetiska operationer, öknings- och minsknings-operationer, jämförelser mellan strängar och bitvis-operationer leder också till olika resultat.

Denna RFC föreslår att du:

Förenar de olika numeriska stränglägena till ett enda koncept: Endast numeriska tecken med både inledande och avslutande blanksteg är tillåtna. Alla andra typer av strängar är icke-numeriska och skapar TypeErrors när de används i en numerisk kontext.

Detta innebär att alla strängar som för närvarande avger E_NOTICE  ”Ett icke välformat numeriskt värde påträffas” kommer att omklassificeras till E_WARNING  ”Ett icke-numeriskt värde påträffas” förutom om den inledande numeriska strängen endast innehöll avslutande blanksteg. Och de olika fall som för närvarande avger  E_WARNING kommer att främjas till TypeErrors.

En mer djupgående översikt över numeriska strängar i PHP 8, med kodexempel, undantag och bakåtkompatibilitetsproblem, finns i RFC.

Match Expression v2

Det nya match expression är ganska likt när det gäller switch men med säkrare semantik och tillåter returnerande av värden.

För att förstå skillnaden mellan de två kontrollstrukturerna bör du överväga följande switch-exempel från RFC:

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

Vi kan nu få samma resultat som koden ovan med följande match expression:

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

En betydande fördel med att använda det nya match expression är att även om switch jämför värden löst (==) vilket kan leda till oväntade resultat, blir jämförelsen en identitetskontroll (===).

Match expression kan även innehålla flera kommaavgränsade uttryck som möjliggör mer kortfattad syntax (källa):

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

Ytterligare exempel och användningsområden finns iMatch expression v2 RFC och PHP-dokumentationen.

Strängare typkontroller för aritmetiska/bitvis-operatörer

I tidigare PHP-versioner tilläts det att aritmetiska och bitvisa operatörer tillämpades på ett matris-, resurs- eller icke-överbelastat objekt. Beteendet var ibland inkonsekvent.

I denna RFC visar Nikita Popov hur orimligt det beteendet kan vara med ett enkelt exempel:

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

Nikita förklarar hur tillämpningen av aritmetisk eller bitvis-operatörer på matriser, resurser eller icke överbelastade objekt ledde till olika resultat:

Operatörer +, -, *, /, **:

  • Kasta felundantag på matris-operatör. (Exklusive + om båda operatörerna är matriser.)
  • Konvertera tyst en resurs-operatör till resurs-ID:t som heltal.
  • Konvertera en objekt-operatör till heltal ett, samtidigt som du kastar ett meddelande.

Operatörerna %, <<, >>, &,  |^

  • Konvertera tyst en matris-operatör till heltal noll om den är tom eller heltal ett om den inte är tom.
  • Konvertera tyst en resurs-operatör till resurs-ID:t som heltal.
  • Konvertera en objekt-operatör till heltal ett, samtidigt som du kastar ett meddelande.

Operatör ~:

  • Kasta ett felundantag för en matris, resurs och objekt-operatör.

Operatörer ++ och –:

  • Gör tyst ingenting om operatören är en matris, resurs eller ett objekt.

Med PHP 8 förändras saker och beteendet är detsamma för alla aritmetiska och bitvisa operatörer:

Generera ett TypeError-undantag för matris-, resurs- och objekt-operatörer.

Nya PHP-functioner

PHP 8 för med sig flera nya funktioner till programmeringsspråket:

str_contains

Innan PHP 8 var strstr och strpos de typiska alternativen för utvecklare som behövde söka efter en nål i en given sträng. Problemet är att ingen av funktionerna anses vara särskilt intuitiva och deras användning kan vara förvirrande för nya PHP-utvecklare. Se följande exempel:

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

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

I exemplet ovan använde vi jämförelseoperatören !==, som också kontrollerar om två värden är av samma typ. Detta hindrar oss att få ett fel om nålens position är 0:

”Den här funktionen kan returnera Boolesk FALSE men kan också returnera ett icke-booleskt värde som evalueras till FALSE […] Använd ===-operatören för att testa returvärdet för denna funktion.”

Dessutom tillhandahåller flera ramverk hjälpfunktioner för att söka efter ett värde i en given sträng (se Laravel Hjälpdokumentation som ett exempel).

Nu föreslår denna RFC införandet av en ny funktion som gör det möjligt att söka i en sträng: str_contains.

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

Dess användning är ganska enkel. str_contains kollar om $needle finns i $haystack och returnerar true eller false i enlighet därmed.

Så tack vare str_contains kan vi skriva följande kod:

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

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

Vilket är mer läsbart och mindre felbenäget (se denna kod här).
Vid skrivande stund är str_contains skiftlägeskänsligt men det kan förändras i framtiden.

Förslaget str_contains antogs med 43 röster mot 9.

str_starts_with() och str_ends_with()

Förutom str_contains-funktionen tillåter två nya funktioner dig att söka efter en nål i en given sträng: str_starts_with och str_ends_with.

Dessa nya funktioner kontrollerar om en viss sträng börjar eller slutar med en annan sträng:

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

Båda funktionerna returnerar false om $needle är längre än $haystack.

Enligt Will Hudgins, författaren till denna RFC:

str_starts_with och str_ends_with-funktionaliteten är så vanliga att många stora PHP-ramverk har stöd för det, inklusive Symfony, Laravel, Yii, FuelPHP, och Phalcon.”

Tack vare dem kunde vi nu undvika att använda suboptimala och mindre intuitiva funktioner som substr, strpos. Båda funktionerna är skiftlägeskänsliga:

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

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

Du kan se denna kod här.

Denna RFC har godkänts med 51 röster mot 4.

get_debug_type

get_debug_type är en ny PHP-funktion som returnerar en variabels typ. Den nya funktionen fungerar på ett ganska likartat sätt som gettype-funktionen, men get_debug_type returnerar inbyggda typnamn och löser klassnamn.

Det är en bra förbättring för språket, eftersom gettype() inte är användbart för typkontroll.

Förslaget ger två användbara exempel för att bättre förstå skillnaden mellan den nya get_debug_type()-funktionen och gettype(). Det första exemplet visar hur gettype fungerar:

$bar = [1,2,3];

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

Med PHP 8 kan vi använda get_debug_type istället:

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

Följande tabell visar returvärden för get_debug_type och 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
En klass med namn ”Foo\Bar” object Foo\Bar
En anonym klass object class@anonymous

Ytterligare RFC

Här är en snabb lista över ytterligare godkända förbättringar som kommer med PHP 8:

  1. Strängbart gränssnitt: denna RFC introducerar ett Strängbart gränssnitt som automatiskt läggs till i klasser som implementerar __to String()-metoden. Huvudmålet här är att använda unionstypen string|Stringable.
  2. Nya DOM Living Standard-API:er i ext/dom: denna RFC föreslår att implementera den nuvarande DOM Living Standarden till PHP DOM-tillägget genom att introducera nya gränssnitt och offentliga egendomar.
  3. Returtypen Static: PHP 8 introducerar användningen av static som returtyp bredvid self och parent-typerna.
  4. Variabel Syntax-justeringar: denna RFC löser vissa kvarvarande inkonsekvenser i PHP:s variabla syntax.

PHP 8 Prestanda Riktmärken

Om du undrar hur snabbt PHP 8 är, har vi svaret. Vi jämförde 20 PHP-plattformar/konfigurationer på 7 olika PHP-versioner (5.6, 7.0, 7.1, 7.2, 7.3 och 8.0).

PHP 8.0 framträdde som vinnaren på de flesta plattformarna som stöder det, inklusive WordPress och Laravel.

Sammanställda PHP-riktmärken för de bästa plattformarna
Sammanställda PHP-riktmärken för de bästa plattformarna

WordPress på PHP 8.0 kan exempelvis hantera 18.4% fler begäranden per sekund än PHP 7.4. Laravel på PHP 8.0 kan köra 8.5% fler begäranden per sekund än om det körs på PHP 7.3.

Om din webbplats eller app är helt kompatibel med PHP 8.0 bör du planera att uppdatera serverns miljö till PHP 8.0  så snart som möjligt. Du (och dina användare) kommer definitivt att uppskatta dess prestandafördelar. Testa dock din webbplats noggrant innan du uppdaterar.

Du kan läsa vår PHP riktmärken-artikel för mer information, som exempelvis detaljerad prestandadata, insikter och vackra grafer!

Sammanfatting

Snacka om imponerande! I det här inlägget täckte vi alla viktiga förändringar och förbättringar som kan förväntas med utgåvan av PHP 8. Den mest efterlängtade är garanterat Just in Time-kompilatorn, men det finns så mycket mer på gång med PHP 8.

Se till att bokmärka detta blogginlägg eftersom vi lägger till våra favoriter i listan så snart de är godkända. 🤓

Nu är det din tur: är du redo att testa de kommande PHP-funktionerna? Vilken är din favorit? Skriv en rad i kommentarfältet nedan.

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.