Nové PHP funkce a vlastnosti v jednotlivých verzích

Celkové a přehledné shrnutí nový (nebo zrušených) funkcí a vlastností (angl. features) PHP. Užitečné pro případy, kdy upgradujete server nebo projekt na novější verzi a chcete vědět, co můžete používat, nebo naopak potřebujete spravovat starší server nebo projekt a chcete vědět, které vlastnosti používat v dané verze nemůžete.

Tento článek není určen pro lidi, kteří se chtějí PHP naučit, ale naopak pro programátory, kteří již některou z verzí PHP ovládají a jen se potřebují naučit (nebo si připomenou), jaké jsou odlišnosti jednotlivých verzí.

Doporučuji používat editor PhpStorm, ve které si, mimo jiné, můžete přímo nastavit verzi PHP projektu a on vás pak upozorní, pokud použijete něco, co v dané verzi není dostupné a naopak vám navrhne úpravy vhodné pro danou verzi. Pokud si koupíte osobní licenci, můžete ji používat na neomezeném počtu počítačů (vč. firemních).

Rychlá navigace

PHP 5

PHP 5 je sice stará verze, ale pořád jde o základ všech novějších verzí (5.x, 7.x, 8.x), protože definuje základní principy současného jazyka (třídy, funkce, datové typy, apod.). Zároveň se výrazně liší od předchozích verzí (PHP 3 a 4), které většinu moderních principů nepodporovali; i když naopak i novější verze PHP stále obsahují některé artefakty z těchto verzí (jako jeden příklad uvěďme metodu time(), která byla nahrazena třídou \DateTime, ale stále je používána pro celou řadu implementací pro svou jednoduchost).

Třídy

Hlavním přínosem PHP 5 byl přechod na OOP, zavedení OOP principů a představení definice třídy, tak jak ji známe ze současných verzí PHP (poslední verze PHP 4 již obsahovali definici třídy, ale pouze v základních principech – např. konstruktor třídy byl ve skutečnosti jen funkce se stejným jménem).

Další principy dostupné hned od verze 5 byly:

  • Konstruktor, Destruktor a další magické metody (tzn. metody začínající dvěma podtržítky),
  • viditelnost metod a vlastností (public, private, protected, static, final),
  • dědičnost tříd včetně abstraktních tříd,
  • Interface a jejich použití na třídy,
  • typování proměnných podle třídy a definice provázání tříd mezi sebou (dependency injection, wrappery, fasády, apod.)

MySQL

PHP 5 představilo nový způsob přístupu k MySQL pomocí třídy MySQLi (která je vytvořena jako součást principu PDO, tedy PHP Data Objects), která se lišila od starého funkcionálního přístupu MySQL (tedy funkce začínající mysql_*).

Výjimky

Společně s OOP zavedlo PHP 5 i princip výjimek a jejich vyhazování (THROW) a chytání (TRY-CATCH). Základem byla třída \Exception (resp. Exception, proto PHP 5.0 ještě nemělo namespace) a její potomci (např. RuntimeException).

XML

Další velmi užitečnou vlastností PHP 5 by tzv. SimpleXML, který umožňuje automaticky parsovat XML soubory a převádět je na objektovou strukturu dostupnou z PHP. Samozřemě, pro PHP coby webový jazyk je pak důležité zmínit, že tento parser umí načítat a editovat i HTML soubory (tedy převést textové HTML do objektového DOM-like formátu).

Unicode a INTL (I18L)

I když PHP 5 ještě nemělo plnou podporu Unicode, již ve své první verzi představilo knihovnu INTL, které se pak stala základem pro podporu Unicode a vývoj mezinárodních aplikací (tzn. překlady, časové zóny, národní formáty číslel a datumů, apod.)

PHP 5.1

PDO

Po představení MySQLi v předchozí verzi se v PHP5 stalo PDO rozšířemí standardní součástí PHP.

PHP 5.2

Date/Time

Třídy DateTime a DateTimeZone byly představeny až v PHP 5.2, po té, co ve verzi 5.1 bylo přepsáno jádro pro zpracování času a časových zón.

Podpora JSON a ZIP

Verze 5.2 přidala nativní podporu pro práci s JSON a ZIP soubory.

PHP 5.3

Namespace

Hlavním přínosem verze 5.3 bylo přestavení namespaců, tedy rozdělení tříd do skupin tak, aby bylo možno používat různé knihovny obsahující třídy se stejnýmy jmény.

Namespace se určuje klíčovým slovem namespace a určuje umístění pro všechny třídy a funkce, které jsou následně nadefinovány:

<?php

namespace App\Database\Users

class UserPhoto { ... }
class UserAvatar extends UserPhoto { ... }

Technicky je možné uvést více namespace v jednom souboru, ale v dnešní době to již není doporučeníhodné a je lepší se držet zásady: jedna třída v souboru, soubor ve stejné složce jako jeho namespace (přesně jak to definuje PSR-0).

Při použití třídy, která je umístěna v namespace, je pak potřeba uvést celý namespace – jinak PHP třídu nenajde. Výjimku tvoří kód, který již v namespace je a pak PHP automaticky prohledává daný namespace:

new Config(); //hledá se třída Config bez namespace
new \App\Config(); //hledá se třída Config v namespae App

namespace App;
new Config();  //hledá se třída Config v namespae App

Stejně jako předchozí verze PHP obsahují magické konstanty __FILE__ a __DIR__, verze 5.3 přidává novou konstantu __NAMESPACE__, která vždy obsahuje jméno namespace, ve kterém se váš aktuální kód nalézá (nebo obsahuje prázdný řetězec, pokud žádný namespace není definován).

Alias třídy

Funkce class_alias() umožňuje definovat alternativní jméno pro třídu, která je pak přístupná pod oběma jména. Před tím bylo potřeba pro jiné jméno vytvořit potomka:

//PHP 5.0 - 5.2
class MyClassWithVeryLongName { ... }
class MCWVLN extends MyClassWithVeryLongName {}

$c = new MyClassWithVeryLongName;
$d = new MCWVLN;
$d instanceof MyClassWithVeryLongName  //==true
$c instanceof MCWVLN //==false

//PHP 5.3
class MyClassWithVeryLongName { ... }
class_alias('MyClassWithVeryLongName', 'MCWVLN');

$c = new MyClassWithVeryLongName;
$d = new MCWVLN;
$d instanceof MyClassWithVeryLongName  //==true
$c instanceof MCWVLN //==true

Od verze 8.3 je možno používat class_alias i pro nativní třídy jako je \StdClass, \DateTime, apod.

Late Static Binding

Společně s namespace byla představena i možnost Zpožděné statické vazby.

Vlastnost spočívá v tom, že stejně jako může potomek třídy použít klíčové slovo parent k zavolání metody svého rodiče, měl by mít i rodič možnost zjistit, který jeho potomek jeho metodu volá. Toho lze dosáhnout použitím klíčového slova static (místo self, které odkazuje na vlastní třídu), jelikož dědičnost je vázána na statické definice třídy (konkrétně klíčové slovo extends) a nikoliv na (dynamicky) vytvářené instance (které obsahují pouze data proměnných a odkaz na statickou třídu, ze které byly vytvořeny).

Jméno vychází ze 3 faktů:

  • late (zpožděné) odkazuje na to, že vyhodnocení, která třída provede danou akci, se vyhodnocuje až v době spuštění kódu (tzn. zavolání metody), nikoliv v době, kdy kód píšete nebo kdy se kompiluje před spuštěním.
  • static vyjadřuje fakt, že je nutné ho použít v případě, že chcete použít statickou metodu, vlastnost nebo konstantu. Pro ne-statické metody a vlastnosti totiž můžete použít $this, které vždy odkazuje na potomka.
  • binding (vazba) je základní princip práce s objekty a třídami – objekt, který obsahuje data proměnných, se naváže na třídu, která definuje funkce a jejich svázáním vznikne metoda spuštěná s konkrétním kontextem ($this).
class Rodic {
    public function printName() {
        //Poznámka: konstanta ::class je dostupná až od PHP 5.5!
        echo self::class; //vždy "Rodic"
        echo static::class; //může být "Rodic" nebo "Potomek"
    }
}

class Potomek extends Rodic {
    //medota printName() se dědí od rodiče
}

(new Rodic())->printName(); //Vytiskne "RodicRodic"
(new Potomek())->printName(); //Vytiskne "RodicPotomek"

Pomocí klíčového slova static může rodič také volat statické metody svých dětí, přistupovat k jejich vlastnostem a konstantám a také může označit, že metoda vrátí instanci potomka:

class Rodic {
    protected const VALUE = 0;
    public function getChild() : static { //vrátí instanci potomka
        echo self::VALUE, static::VALUE;
        return $this; //tohle je vždy potomek
    }
}
class Potomek {
    protected const VALUE = 1;
}

(new Potomek)->getChild(); //Vytiskne "01"

Kromě klíčového slova static obsahují zpožděné vazby i funkce get_called_class() (která je obdobou metody get_class(), kde get_class() vrací rodiče, zatímco get_called_class() vrací potomka), forward_static_call() a forward_static_call_array() (které jsou obdobou call_user_func() a call_user_func_array() s tím rozdílem, že místo metody instance mohou volat statickou metodu třídy – ať už rodiče, potomka, nebo úplně jiné třídy). Příklady viz odkazy do PHP dokumentace.

Poslední součást late static binding je nová magická metoda __callStatic(), která se obdobně jako __call() vyvolá v okamžiku, kdy se pokusíte zavolat (statickou) metodu třídy, která není v dané třídě definována.

Anomymní (Lambda) funkce a Uzávěry (Closures)

Anomymní funkce, Lambda funkce a Closures (uzávěry) jsou v PHP víceméně různá jména pro to samé.

Běžná funkce musí mít jméno, pomocí kterého na ním pak odkazujete v kódu. Naproti tomu anonimní funkce žádné jméno nemá a odkázat se na ni dá jedině tak, že referenci na ní (tedy Uzávěru) uložíte do proměnné (nebo ji pošlete do parametru).

function add($a, $b) { return $a + $b; } //pojmenovaná funkce
$remove = function($a, $b) { return $a - $b; } //anonymní funkce

function math($fn, $a, $b) {
    return $fn($a, $b);
}

echo math('add', 2, 1); //Vytiskne "3"
echo math($remove, 2, 1); //Vytiskne "1"

V příkladu si kromě vytvoření anonymní funkce všimněte i toho, že funkce math() očekává jako první parametr uzávěru, kterou následně volá. Pokud chcete jako uzávěru použít pojmenovanou funkci, můžeme na ní odkázat jednoduše jejím jménem. Pokud ale chcete poslat anonymní funkci, musíme předat její referenci z proměnné (protože žádné jméno nemá).

Výhoda anonymních funkcí je v tom, že zatímco pojmenované funkce zůstávají v paměti po celou dobu běhu skriptu, anonymní funkce může Garbage collector z paměti odstranit hned, jak se vyprázdní proměnná, ve které byla funkce uložena. To na druhou stranu představuje nevýhodu, že při každém zavolání kódu, který funkci ukládá do proměnné, musí PHP metodu znovu zkompilovat z kódu a uložit ji do paměti.

Další výhoda a důvod, proč se anonymní funkce také nazývají Uzávěry, je ten, že kromě vstupních parametrů může do sebe metoda i takzvaně uzavřít proměnné svého rodičovského kódu. Např. v Javascriptu tohle probíhá automaticky (což v minulosti vedlo k velkým memory leakům), ale v PHP musíte přímo určit, které proměnné se uzavřou klíčovým slovem use:

$a = 1;
$b = 2;
$c = 3;
$d = 4;
$fn = function($c, $d) use ($a, $b) {
    echo $a, $b, $c, $d;
};
unset($a); //smaže proměnnou, ale $fn() ji má už uzavřenu v sobě!

$fn($c, $d); //Vytiskne "1234"
$fn($d, $c); //Vytiskne "1243"

NOWDOC

Ternární operátor ?:

goto

Trochu paradoxně PHP představilo funkci goto (tedy skok do jiné části programu) až po zavedení OOP (u většiny jazyků jako C nebo Pascal se goto používá často právě jako náhražka za nemožnost používat strukturování kódu do tříd).

Díky tomu je ale goto v PHP omezeno jen na to, že skákat můžete jen v rámci stejné funkce a stejné úrovně cyklů (WHILE, FOR, FOREACH, apod.) a mělo by tak být lépe předvídatelné. Výjimkou (a asi hlavní předností) je možnost předčasného vyskočení ven z cyklu (tedy náhražka za víceúrovňový BREAK).

for (...) {
    for (...) {
        while (...) {
           if (...) { break 2; } //není úplně jasné, jak skočí
        }
    }
}

for (...) {
    for (...) {
        while (...) {
           if (...) { goto skip; } //lepší?!
        }
    }
    skip:
}

Odstranění cyklických odkazů

Zend engine dokáže po zapnutí (INI) možnosti zend.gc_enable zpracovávat i cykliklé (tedy kruhové) odkazy, které mohou jinak vést k memory leakům.

Lepší zaokrouhlování

O verze 5.3 má PHP přepracované zaokrouhlování desetinných míst. Uvádím jen proto, aby bylo jasné, že po upgradu projektu můžete dostat trochu jiné výsledky.

PHP 5.4

Traity

Zkrácená pole

Od verze 5.4 můžete pole vytvářet pomocí hranatých závorek [] (stejně jako v JS a JSON) místo konstrukce array() a také můžete [] použít pro destrukturalizaci pole (dříve konstrukce list() = array()).

Plná podpora Unicode

Zrušení magických uvozovek

V předchozích verzích PHP se automaticky escapovaly všechny řetězce jako ochrana proti SQL Injection. To bylo v PHP 5.4 odstraněno a o správné ošetření vstupu se musí starat programátor a nebo framework (např. Nette).

Zrušení globálních parametrů (register_globals)

V předchozích verzích PHP byly všechny vstupní parametry dostupné (po zapnutí příslušné možnosti v INI souboru) v samostatných proměnných (např. URL parametr „username“ jste mohli přečíst z proměnné $username). To vedlo k celé řadě možných útoků a chyb a proto byla tato funkce z PHP 5.4 zcela odstraněna a vstupní parametry jsou již dostupné jen přes $_GET, $_POST, $_FILE a $_SESSION (kromě ostatních metod jako session_*() metody a fopen('php://input')).

Zrušení Safe modu

Safe mode byl používán na veřejných hostingách k oddělení jednotlivých uživatelů (zákazníků). Od PHP 5.4 toto není možné a hostingy musí tento problém řešit jinak (např. přístupovými právy k disku).

Odstranění předání reference (Call time reference)

V předchozích PHP bylo možno předat funkci referenci místo hodnoty proměnné. Od PHP 5.4 je možno předat referenci pouze pokud o to funkce sama požádá:

$x = 1;
function a($a) {... };
function b(&$a) {... };

a($x); //předá hodnotu 1;
a(&$x); //předá referenci na proměnnou $x - nemožno od PHP 5.4
b($x); //předá referenci na proměnnou $x (vždy)

Rozdíl je v tom, že předáním hodnoty nemůže funkce změnit hodnotu původní proměnné. Předáním reference naopak funkce přímo mění hodnotu původní proměnné. Nečekané předání reference pak mohlo vytvářet problém se zapouzdřením, kdy vnější kód mohl číst interní hodnoty funkce (např. funkce si mohla do svého parametru $password uložit hash hesla a vnější k´od pak byl schopen hash přečíst a zjistit, jak funkce hesla zakódovává).

PHP 5.5

Generátory

Try-Finally

Jméno třídy přes ::class

Přístup do statického pole (a řetězce)

V PHP 5.4 a starších nebylo možno rovnou přistupovat k poli a znakům řetězce, které vrátila funkce. PHP 5.5 toto opravuje a odstraňuje nutnost vytvářet pomocné proměnné:

//PHP 5.4
$a = get_array();
$b = $a[0];

//PHP 5.5
$b = get_array()[0];

Statické empty()

Stejně jako v předchozím případě nebylo možnost použít funkci empty() na výsledek funkce. Od PHP 5.5 to možné je.

FOREACH klíče (z generátorů)

V PHP 5.4 a starších mohl příkaz foreach zpracovávat pouze numerické a textové klíče (z polí). Díky zavedení generátorů je foreach schopno zpracovat i klíče, které obsahují objekty nebo jiné struktury.

foreach ($arr as $key => $value) { ... } //$key musí být číslo nebo řetězec

foreach ($generator as $key => $value { ... } //$key může být cokoliv

FOREACH destrukturalizace

V předchozích verzích mohl příkaz foreach číst pouze klíč a hodnotu pole. Od PHP 5.5 můžete hodnotu pole destrukturaliovat do jednolivých proměnných – tzn. tato možnost integruje list() do foreach a odstraňuje nutnost vytvářet proměnnou pro celou hodnotu pole.

foreach ($arr as $value) { //PHP 5.4
    list($a, $b) = $value;
    ...
}

foreach ($arr as list($a, $b)) { ... } //PHP 5.5
foreach ($arr as [$a, $b]) { ... } //PHP 7.1

Díky této změně ani nemusíte ukládat do proměnné celé sub-pole, ale stačí vám z něj vytáhnout jen hodnoty, které uvnitř cyklu potřebujete.

OPcache

PHP 5.5 obsahuje nativně cache pro zkompilované kódu. Více viz samostatný článek o cachování v PHP.

PHP 5.6

Vypočítané konstanty

V předchozích verzích PHP očekávalo, že hodnota konstanty bude konstantní i v okamžiku jejího vytváření. Od PHP 5.6 to již není potřeba a konstakty tak mohou odkazovat na jiné konstanty (např. const DEBUG = self::INFO;) nebo obsahovat jednoduché výpočty (např. const SECONDS_IN_DAY = 24 * 60 * 60;).

Rozbalení parametrů

function add(...$params) { ... };

$a = [1,2,3];
add(...$a);

Mocnina

Od PHP 5.6 můžete vypočítat mocninu operátorem ** místo nutnosti používat funkci pow() (např. 2**10 == 1024).

PHP input místo RAW data

Pokud jste v předchozích verzích chtěli přečíst tělo dotazu (před tím, než z něj PHP vytvořilo $_POST), museli jste přečíst $_HTTP_RAW_POST_DATA. (Tato proměnná je od PHP 5.6 nedostupná).

Od PHP 5.6 můžete použít funkce pro přístup k souborům a číst virtuální adresu php://input. Důležité také je to, že tento vstupní soubor funguje stejně jako normální soubor, takže při každém otevření ho můžete číst od začátku (v předchozích verzích fungoval jen jako čistý vstup z konzole a dokázal tak číst pouze nově zadaný text).

$json = json_decode(file_get_contents('php://input'));

Upload 2+GB

Předchozí verze PHP byly omezeny na upload souborů do 2GB. Od verze PHP 5.6 můžete tuto hodnotu (upload_max_filesize a post_max_size) změnit na větší.

Podpora certifikátů při čtení souborů

Souborové funkce PHP (fopen, file_get_contents, apod.) dokáží číst i jiné protokoly jako HTTP nebo FTP. Od verze 5.6 také podporují HTTPS, FTPS, apod. pro ověření autenticity zdroje pomocí certifikátu (tzn. pokud daná adresa nemá platný certifikát, PHP odmítne přečíst danou adresu jako soubor).

PHP 5.7 a 6.x (co se s nimi stalo?)

PHP verze 6 začala svůj vývoj po verzi PHP 5.3. Jelikož ale obsahovala spoustu nových a zajímavých funkcí ještě dříve, než se nedostala do fáze, kdy by mělo smysl vydat ji jako hlavní verze, bylo PHP 6.x postupně vydáno jako verze 5.4, 5.5 a 5.6 (viz výše). Následně bylo v hlasování rozhodnotu, že verze PHP 5.7 již nebude vydána a všechny vylepšení PHP 6, které se zatím nedostaly do PHP 5.x budou vydány jako PHP 7.0.

PHP 7.0

Typování parametrů a návratových hodnot funkcí

Hlavní změnou v PHP (6.x) 7.0 je možnost dopředu určit typ parametru a návratové hodnoty funkce. To přináší celou řadu výhod od toho, že již nemusíte psát ke každé funkci komentář s tím, co očekává za parametry a co vrací, přes automatickou kontrolu, kdy PHP vyhodí výjimku, pokud pošlete špatný typ parametru, až po psaní kódu pomocí AI, které dokáže odhadnout, jaké proměnné dosadit do volání funkce (např. v PhpStorm – i když ne vždy to tak funguje; občas je to spíše tipování (s měkkým I), které se ne vždy úplně povede).

public function (int $i, string ...$values) : array { ... }

Null coalescing operator

Často jen Null operator, protože slovo coalescing je fakt špatně píše, nahrazuje dříve používané isset() a ternární operátor.

Porovnávání přes Spaceship

Pro porovnání dvou hodnot můžete použít operátor <=> (zvaný spaceship, tedy vesmírná loď). Operátor vrací -1 pokud je levá hodnota menší, 1 pokud je levá hodnota větší a nebo 0 pokud jsou obě hodnoty stejné. To odpovídá tomu, jako hodnoty očekávají funkce sort*() pro řazení polí. Pozor na to, že na rozdíl od sort*() funkcí, tento operátor neví, jak vrátit hodnoty 2 a větší nebo -2 a menší.

Anonymní třídy

Obdobně jako od PHP 5.3 je možnost vytvářet anonymní funkce, v PHP 7.0 je možnost vytvářet anonymní třídy a buď je předávat do funkcí a nebo je naopak vracet.

To se hodí hlavně pro Interfacy typu logger, translator, apod., kdy nemusíte pro danou funkci vytvářet novou třídu v samostatném souboru, ale můžete pomocí interface vytvořit dočasnou třídu, která udělá to, co potřebujete (a pak se zruší):

$class->setLogger(new class implements ILogger) { ... });

public function getData($id, $name) : IIdentity {
    return new class ($id, $name) implements IIdentity {
        public $id;
        public $name;

		public function __construct($id, name) {
			$this->id = $id;
			$this->name = $name;
		}
    };
}

V příkladu si všimněte, jak syntaxe kombinute volání new class(<parametry>) a class <name> implements <interface>,

Volání uzávěry na objektu

PHP 7.0 kombinuje možnosti anonymních funkcí a volání uzávěr tak, že můžete jednoduše zavolat uzávěru jako metodu objektu:

$f = function() { return $this->id; };

$user = new User(12345);
$f->call($user); //vrátí "12345"

Tato možnost se dá například použít k tomu, abyste rychle přečetli privátní vlastnost třídy, ke které nemáte přístup přes její vlastní metody. Není to tedy něco, co byste měli používat při běžném vývoji, ale může se hodit pro různé testery, debuggery nebo frameworky, které dynamicky mění instance tříd.

Volba tříd při unserialize

Unserialize načte kód z řetězce a vytvoří podle něj instance tříd. Pokud je zdroj řetězce nedůvěryhodný (např. z databáze nebo disku – o čtení serializovaných dat z requestu či uploadu asi nemůže být ani řeč), v PHP 5.x to mohlo umožnit útočníkovy získat přístup k datům vytvořením vlastní třídy. Od PHP 7.0 můžete ale určit, které třídy na vstupu očekáváte a tím zamezit spuštění nechtěného kódu.

Skupinový import namespaců

Od PHP 7 můžete použít klíčová slovo use pro import více tříd nebo funkcí najednou (pokud jsou ze stejného rodičovského namespace). Tohle je sice užitečná funkce, pokud píšete celý PHP kód ručně, ale odporuje některým PSR a coding standardům, které říkají, že každá třída (a funkce) by měla mít vlastní use na samostném řádku (to je důležité hlavně pokud používáte version control, např. GIT nebo SVN, které pak dokáží snadněji dohledat přidání nebo odebrání třídy).

Pokud používáte nějaké chytřejší IDE (např. PhpStorm), o doplňování use se bude starat za vás, takže vám nevadí, že bude dlouhé (navíc většina IDE umí use blok sbalit, takže vás při editaci neotravuje svou délkou).

Vrácení z generátoru

Gerátory byly přidány v PHP 5.5, ale v té době jste mohli vracet hodnoty jen přes yield.

Od PHP 7.0 může generátor použít i return jak k předčasnému ukončení tak i k vrácení výsledné hodnoty (která je pak dostupná v metodě $generator->getReturn() po té, co generátor skončil).

Delegování generáru

Generátor může nově vracet výsledky jiného generátoru – užitečné hlavně pokud jeden generátor vrací výsledky z různých zdrojů (kombinace několika různých generátorů či iterátorů).

Parametry session

Při vytváření session v PHP 7.0 můžete do session_start() předat parametry, jak se má session vytvořit. Ve starších verzích to nebylo možné a bylo nutno upravit INI soubor.

preg_replace_callback_array()

Tato funkce umožňuje nadefinovat callback funkci pro každý hledaný výraz samostatně, nebo spustit více hledání najednou. Funkce jako první parametr přijímá pole, kde klíč je regulární výraz a hodnota je funkce, který se spustí, pokud daný řetězec (druhý parametr) odpovídá výrazu.

Destrukturalizace objektů

PHP 7.0 plně podporuje použití konstrukce list() (resp. kratší []=) na objekty – tedy pokud implementují ArrayAccess interface. (V PHP 5.x to bylo technicky možné, ale nefungovalo to správně).

[$id, $name] = new User(12345);

Volání metod klonu

PHP 7.0 opravuje nedostatek, kdy v PHP 5.x nebylo možno zavolat metodu hned po vytvoření kopie instance třídy; např. (clone $user)->sendEmail();.

PHP 7.1

Nulové typy

Po zavedení typování parametrů a vlastností bylo zjištěno, že NULL je často alternativou k vrácení konkrétní hodnoty resp. pro označení volitelného parametru.

public ?array $values; //Pole nebo NULL

public function (?int $i, ?string ...$values) : ?array { ... }
//kterýkoliv parametr může být NULL
//místo pole může vrátit NULL

Funkce bez hodnoty

Po zavedení typování funkcí v PHP 7.0 nebylo možno označit, že funkce nic nevrací (a odlišit ji od funkce, která vrací mixed typ). Nově k tomu slouží klíčové slovo void; např. function set($key, $value) : void { ... }. Jde pouze o alternativní zápis toho, že funkce vrací NULL.

Viditelnost konstant

Od PHP 7.1 je možno určit viditelnost jednotlivých konstant třídy, stejně jako bylo možnost určit viditelnost metod a vlastností.

Iterable typ

Pokud funkce přijímá jako parametr jak pole (array) tak Traversible objekt, můžete nově použít typ iterable pro správnou validaci parametru.

TRY-Multi-CATCH

Destrukturalizace podle klíče

Hledání v řetězci od konce

Převod pole na Closure (fromCallable)

PHP 7.2

Parametry typu object

Změna syntaxe abstraktní metody

Pokud jedna abstrakní třída dědí od jiné abstraktní třídy, může změnit syntaxy jejích abstraktních metod jednoduše tak, že nadefinuje novou abstraktní metodu se stejným jménem.

Vynechání zděděných typů

Při definici metody, jejíž syntaxe byla zděděna od rodiče nebo interface, již nemusíte uvádět typy parametrů a PHP je automaticky dohledá podle dědičnosti.

Windows priority

PHP na Windows může použít funkci proc_nice() pro změnu své priority. (Dříve byla podporována jen na unix/linux jádrech).

Zaheslované ZIP

PHP 7.2 dokáže přečíst a zapsat ZIP soubory s heslem (samozřejmě jen pokud dané heslo znáte).

PHP 7.3

Odsazení pro Heredoc a Nowdoc

V předchozích verzích byl vždy požadavek, aby ukončení Heredoc a Nowdoc bylo na začátku řádky. To vytvářelo problém s tím, že dobře strukturovaný kód musel mít porušenou struktutu, pokud jste chtěli zadat víceřádkový řetězec, protože jste ho museli zapsat od začátku řádky.

    function getText() { //PHP 5.x, 7.x
        return = <<<TEXT
text řetězce
na několika řádcích.
TEXT;
        //End of return value (nemůže být na předchozí řádce)
    }

Od PHP 7.3 může být ukončovací tag nejen libovolně odsazený, ale navíc PHP automaticky stejné odsazení ořízne i ze všech předcházejících řádek. Také již není potřeba, aby ukočovací tag končil středníkem a novou řádkou, takže můžete středníkem ukončit odsazení kódu a připsat komentář.

    function getText() { //PHP 7.3+
        return = <<<TEXT
            text řetězce
            na několika řádcích.
            TEXT
        ; //End of return value
    }

Destrukturalizace pole s referencí

Od PHP 7.3 můžete nově při destrukturalizaci pole použít operátor reference, což zajistí, že se daná hodnota nebude kopírovat, ale pomocí proměnné budete měnit hodnotu přímo uvnitř pole:

[&$first] = $arr;
$first = 0; //změní $arr[0];

Vylepšená práce s klíči pole

Od PHP 7.3 můžete nově použít funkce array_key_first() a array_key_last(), které doplňují existující funkce array_shift() a array_pop() v tom, že můžete získat i jméno klíče před tím, než z pole odstraníte první nebo poslední hodnotu. V předchozích verzích je toto potřeba řešit pomocí foreach() nebo jiných funkcí:

//PHP 5.x, 7.x
foreach ($arr as $key => $value) {
    $firstKey = $key;
    break; //potřebujeme jen první klíč, ostatní nás nezajímají
}
$lastKey = array_pop(array_keys($add)); //pro získání posledního klíče musíme vytvořit nové pole (pomalé, pokud je pole velké)

//PHP 7.3
$firstKey = array_key_first($arr);
$lastKey = array_key_last($arr)

PHP 7.4

Typování vlastností tříd

Od PHP 7.4 můžete kromě typování parametrů funkcí uvádět i typy pro vlastnosti tříd. Pro vlastnosti platí stejná pravidla jako pro parametry funkcí s jednou výjimkou – pokud uvedete typ vlastnosti bez nullového označení, ale neuvedete hodnotu, bude mít vlastnosti sice hodnotu NULL, ale při pokusu o její přečtení dojde k výjimce „Nelze přečíst hodnotu před tí, než se nastaví“.

class MyClass {
    public int $id; //nemá hodnotu -> je "unset"
    public int $idAlt;
    public string $name = 'MyClass'; //má hodnotu
    protected ?int $value; //nemá hodnotu -> je "null",
                           //protože to je platná hodnota

    public function __construct(int $id) {
        $this->id = $id; //nastavení výchozí hodnoty vlastnosti
        $this->name = $id; //vyhodí "type mismatch" výjimku
    }

    public function getAltId() {
        return $this->idAlt; //vyhodí výjimku, protože není nastaveno
    }
}

Šipkové funkce

Po představení anonymních funkcí v PHP 5.3 přináší PHP 7.4 kratší alternativu v podobě zkráceného zápisu.

//PHP 5.3+, 7.x
$f = function($a, $b) use ($c, $d) { return $a + $b + $c + $d; }

//PHP 7.4+
$f = fn($a, $b) => $a + $b + $c + $d;

Pokud znáte šipkové funkce z JavaScriptu, PHP je jim hodně blíží (JS šipkové funkce byly inspirací pro PHP), ale má i svá specifika:

  • PHP šipková funkce se definuje klíčovým slovem fn() s povinnými závorkami (v JS stačí je závorky nebo jméno parametru).
  • PHP šipokový funkce vždy obsahuje pouze jeden příkaz, jehož hodnotu vrátí. (v JS je možno vytvořit šipkovou funkci s blokem kódu).
  • PHP šipková funkce nemůže obsahovat podmínky (IF) ani cykly (FOR, FOREACH, WHILE, apod.). Výjimku tvoří ternární operátor (vč. jeho zkrácených verzí ?: a ??).
  • PHP šipková funkce může mít definovaný typ návratové hodnoty pro automatickou validaci výrazu (fn(string $a) : int => parse_int($a);)
  • PHP šipková funkce nemůže měnit hodnoty proměnných vnější funkce (viz k´od níže).
//Anonymní funkce může měnit hodnotu přes referenci
$f = function($b) use (&$a) { $a = $b; }

//Šipková funkce nemůže měnit hodnoty proměnných
$f => fn($b) => $a = $b; //jen vrátí hodnotu $b, ale nezmění $a

//Šipková funkce může získat referenci v parametru
$f => fn(&$a) => $a = $b;
//Tohle ale mění význam i použití dané funkce
// -> např. nemůže být vrácena ven z rodičovské funkce

//Pouze objekty jsou předány referencí,
//takže šipková funkce může měnit jejich hodnotu
$a = 1;
$helper = (object)['a' => &$a]; //vytvoří referenci na referenci $a
$f = fn($b) => $helper->a = $b;
$f(5);
echo $a; //Vytiskne 5, protože $a je svázané referencí do $helper

Šipkové funkce také podporují jednu kontroverzní vlastnost z JS, kterou jsou vícenásobné uzávěry. Pokud toto vlastnosti chápete z JS, můžete ji klidně použít i v PHP; pokud vám váš mozek tento zápis nebere, klidně ho ignorujte.

$f = fn($a) => fn($b) => fn($c) => $a . $b . $c;

//ukázky předpokládají $x = 'x', apod.
$f($x)($y)($z); //=='xyz'

$g = $f($i)($j); //vrátí funkci s přednastavenými hodnotami
$g($k); //=='ijk'
$g($l); //=='ijl'

Vrácení potomka potomkem

Od zavedení OOP v PHP 5 platilo, že funkce potomka musí mít stejnou syntaxy jako funkce rodiče. Od zavedení typování parametrů v PHP 7.0 pak také platí, že funkce potomka musí mít stejné typy parametrů jako rodič (proto se od PHP 7.2 ani typy u funkcí potomka uvádět nemusí).

PHP 7.4 ale zavedlo jednu výjimku v tom, že pokud je návratová hodnota metody (jiná) třída, může potomek této třídy vracet konkrétního potomka třídy, kterou vrací metoda rodiče.

Snadněji to snad pochopíte na příkladu (inspirovaný nejmenovaným eshopem): mějme třídu User, která má metodu getDefaultDeliveryOption(), která vrací instanci třídy Delivery. Pokud následně vytvoříme potomka PremiumUser, může tato třída v metodě getDefaultDeliveryOption() specifikovat, že nevrací obecný Delivery, ale že vždy vrátí instanci třídy PremiumDelivery (což musí být potomek třídy Delivery).

class User {
    public function getDefaultDeliveryOption() : Delivery {...}
}
class PremiumUser extends User {
    public function getDefaultDeliveryOption() : PremiumDelivery {...}
}

class Delivery { ... }
class PremiumDelivery extends Delivery { ... }

Nevýhoda této vlastnosti je v tom, že musíte používat autoloading pro třídy (např. Composer), protože jinak nebude PHP schopno ověřit, že jednotlivé třídy existují a jsou ve správné dedičné linii.

Praktické použití může být u abstraktních tříd definujících určité design patterns, kde abstraktní rodič bude vracet abstraktní třídu a každý potomek si pak vybere, kterého potomka bude vracet např. abstraktní třída Ship bude v metodě getPower() vracet instance abstr. třídy Engine. Třída SailingBoat (plachetnice) může vracet instanci třídy Sail (plachta), zatímco Yacht bude vracet DieselEngine, Submarine instanci NuclearReactor a VikingShip může vracet třeba instanci Vikings (implementující interface Iterator nad seznamem vikingů, kteří na lodi veslují 😂 ).

Rozbalení pole do pole

PHP 5.6 přineslo možnost rozbalení pole do parametrů (func(...$arr)) a sbalení parametrů do pole (function func(...$params)).

PHP 7.4 přidává obdobnou možnost pro rozbalení jednoho pole do jiného, což se dá efektivně použít pro spojování polí bez použití funkce array_merge().

$c = [1, 2, ...$a, 9, 10];
$d = [...$a, ...$b];

Pro kompatibility s touto vlastností již array_merge() nevyhodí výjimku, pokud ho zavoláte bez parametrů:

$a = [];
$b = [];
$c = [...$a, ...$b]; //platný zápis, vrátí prázdné pole
$d = array_merge(...$a, ...$b); //v předchozích PHP vyhodí výjimku

Lepší zápis čísel

Pokud do kódu potřebujete zapsat nějaké hodně velká kostantní čísla, můžete použít oddělovače pro jejich lepší čtení:

$secondsInYear = 31536000; //jsou to zhruba ~3 miliony?
$secondsInYear = 31_536_000; //ne, je to 31 milionů
$pie = 3.141_592_653_6; //10 míst (aby byla jasná přesnost výpočtu)

$secureKey = 0x1234_5678_ABCD; //8 Bajtový klíč (jen příklad!)
$wordMask = 0b0000_1111_0000_1111; //16-bitová maska

Slabá reference

Garbage collector v PHP zajišťuje, že se z paměti odstraní všechny objekty, na které již není žádná reference. Aby to bylo snažší, PHP 7.4 umožňuje získat tzv. slabou referenci, která uchová odkaz na konkrítní objekt, ale umožní GC objekt vymazat, pokud již neexistuje žádná (silná) reference.

Slavbá reference se získává pomocí předdefinované třídy WeakReference, která má jedinou metodu get(), která vrací buď objekt, nebo NULL, pokud byl již objekt smazán. Také má jednu statickou metodu create() pro získání reference:

protected function getValue() {
    $o = (object)['a' => 1];
    $this->cache = WeakReference::create($o);
    return $o;
}

public function getValueWithCache() {
    if ($this->cache->get()) {
        return $this->cache->get(); //hodnota z cache
    }
    else { //$o již neexistuje - načti novou hodnotu
        return $this->getValue();
    }
}

Tato třída má smysl u PHP skriptů, které běží delší dobu a zpracovávají větší množství dat. U běžných webových serverů nemá moc smysl, protože každý request se zpracuje v řádu (mili)sekund a pak zanikne vč. všech svých dat.

Chytřejší ošetření HTML

PHP nabízí funkci strip_tags pro odstranění HTML tagů z textu. Od verze PHP 7.4 můžete vyjmenovat tagy pomocí pole. V předchozích verzích ale bylo potřeba uvádět celé tagy do řetězce:

//PHP 5.x, 7.x
strip_tags($input, '<script><style><a>');

//PHP 7.4+
strip_tags($input, ['script', 'style', 'a']);

Magická serializace

V PHP 7.4 můžete používat magické metody __serialize() a __unserialize(), které jsou dostupné u každého objektu. V předchozích verzích bylo potřeba použít interface Serializable, který obdobné metody vynutil.

//PHP 5.x, 7.x
class MyData implements Serializable {
    private $myData = [];
    public function serialize() {
        return $this->myData;
    }
    public function unserialize(array $data) {
        $this->myData = $data;
    }
}

//PHP 7.4+ (místo Interface jen magické metody)
class MyData {
    private $myData = [];
    public function __serialize() {
        return $this->myData;
    }
    public function __unserialize(array $data) {
        $this->myData = $data;
    }
}

Funkce proc_open() s parametry v poli

Ve starších PHP fungovala funkce proc_open tak, že jste jí museli zadat (textový) příkaz, který pak metoda spustila.

Díky zavedení možnosti rozbalení pole do pole nyní můžete před funkci seznam parametrů, které funkce sama spojí a ošetří (escapuje):

//PHP 5.x, 7.x
proc_open('php -r '.implode(' ', $params), ...); //neošetřené parametry!

//PHP 7.4+
proc_open(['php', '-r', ...$params], ...); //vše bezpečné

PHP 8.0

Pokročilé využití parametrů

Po zavedení typování parametrů a vlastností v PHP 7.x, přináší PHP 8.0 celou řadu možností, jak toho využít ke zkrácení kódu.

Automatické vlastnosti objektu

Místo opakované definice vlastností a jejich nastavení v konstruktoru si v PHP 8.0 můžete určit, které parametry z konstruktoru si PHP automaticky zapamatuje:

//PHP 5.x, 7.x
class MyKeyValue {
    protected string $key;
    protected int $value;
    public function __construct(string $key, int $value) {
        $this->key = $key;
        $this->value = $value;
    }
    public function __toString() {
        return $this->key . '=' . $this->value;
    }
}
//PHP 8.0+
class MyKeyValue {
    public function __construct(
        protected string $key, 
        protected int $value, //zde si všimněte čárky - viz níže
    ) {
        //$key a $value se automaticky uloží do objektu,
        //protože jsme uvedli jejich viditelnost
    }
    public function __toString() {
        return $this->key . '=' . $this->value;
    }
}

Pojmenované parametry

Jelikož každý parametr funkce může mít nyní svůj typ a výchozí hodnotu, je to již jen krůček k tomu, abysme mohli předávat funkci jen potřebné parametry bez nutnosti uvádět řadu výchozích hodnot parametrů, které nepotřebujeme měnit.

//PHP 5.x, 7.x
function setup(
    int $a = 1, 
    int $b = 2, 
    bool $c = false, 
    bool $d = false
) { ...  }
//protože chceme změnit $d, musíme uvést všechny parametry
// ve správném pořadí
setup(1, 2, false, true);

//PHP 8.0+
function setup(
    a: int $a = 1, 
    b: int $b = 2, 
    c: bool $c = false, 
    d: bool $d = false
) {...}
//Můžeme uvést jen změněný parametr
setup(d: true);
//Nebo můžeme uvést parametry tak, jak nám vyhovuje:
setup(c: true, a: 5);
//Nebo můžeme kombinovat pořadí a jména:
setup (5, c: true); //hodnota 5 je pro parametr a:, protože je první

Pojmenované parametry mají několik omezení:

  • každý parametr může být uveden jen jednou (buď podle umístění nebo podle jména),
  • parametry podle jména mohou být uvedeny až po uvedení parametrů podle pořadí
  • funkce (callable) nemůže být použita jako pojmenovaný parametr, ale může být použita podle pořadí (funkce s callbackem by tedy měli definovat $callback jako první parametr)
  • Jméno parametru musí být unikátní řetězec . Nelze použít obsah proměnné, konstanty, hodnotu funkce, apod.

Vícenásobné typy (vč. mixed)

Pro parametry můžete nyní uvést více různých typů. Nejste tak již omezeni na jeden konkrétní typ a nebo jeden typ plus null. Také již není potřeba v takovém případě ručně validovat podporované typy parametru:

//PHP 5.x, 7.x
function add(int $a, $b) {
    if (is_string($b)) {
        $b = parse_int($b);
    }
    if (is_numeric($b) {
        return $a + $b;
    }
    throw new \InvalidArgumentException('Expected INT or STRING');
}

//PHP 7.4+
function add(int $a, int|string $b) {
    return $a + (int)$b;
}

Kromě typů definovaných v PHP 7.x (string, int, bool, jméno třídy, apod. vč. iterable, traversible a object) můžete pro vícenásobné typy použít ještě null (nahrazuje otazník z PHP 7), false (které na rozdíl od bool určuje, že parametr může být jen false, např, „string|false„) a mixed (které má stejný význam jako parametr bez typu). Typ mixed lze použít pouze jako samostatný typ (nelze kombinovat s ostatními) a naopak null a false nelze použít jako samostatný typ (nedávalo by to smysl).

Pro vícenásobný typ parametru je potřeba použít null (např. string|int|null) – pouze pokud je null kombinované pouze s jedním typem, je možno využít dříve používaného „?“ (např. „int|null“ je stejné jako „?int„). Pozor, neplést s návratovou hodnotou funkce, pro kterou je potřeba použít (pouze) void.

U vícenásobných typů lze každý typ uvést jen jednou a to včetně těch, které sami definují více typů (např. „iterable|array“ nedává smysl nebo „object|User“ také ne a „int|INT“ je vyloženě nesmysl).

Při dědění funkcí může potomek rozšířit typ parametru a/nebo návratové hodnoty. Např. pokud metoda má parametr string, může potomek rozšířit typ na string|array, nebo pokud rodič vrací INT, může potomek vrátit INT|FLOAT, ale nemůže změnit typ jen na array nebo FLOAT.

Sloučení parametrů potomka do pole

Z předchozího odstavce vyplývá, že potomek může rozšířit typ parametru funkce (např. z int na int|float nebo int|string). Díky této vlastnosti může potomek také požádat o to, aby parametry odpovídajících typů získal v poli.

abstract class User {
    public function __construct(int $id, string $name, int $age);
}

class MyUser extends User {
    public function __construct(int|string ...$params) {
        $this->store($db->load(...$params));
   }
}

Čárka na konci parametrů

Od PHP 8.0 podporuje PHP čárku na konci parametrů obdobně, jako podporuje čárku na konci pole. To se hodí hlavně v případě, že potřebujete parametry funkce zapsat na samostatné řádky (např. v konstruktoru, kde nově můžete uvádět viditelnost a různé typy – příklad viz výše).

Atributy

Atributy jsou novou vlastností PHP 8.0. Jelikož ale nahrazují to, co šlo již dříve udělat pomocí Interfaců, Traitů a případně komentářů v PhpDoc, tak nepřináší úplně až tak nic nového.

Hlavní využití Atributů najdete, pokud používáte reflekce tříd, protože pak vám atributy umožňí lépe detekovat, o jakou třídu se jedná, co má vlastnost obsahovat nebo k čemu slouží daná metoda.

Pokud reflekce příliš nepoužíváte a nebo nemáte problém vyřešit vaše problémy pomocí Interfaců a Traitů, tak vlastně Atributy ani nepotřebujete.

Význam atributů bude lépe pochopitelný u dalších verzí, které je využívají pro nové vlastnosti (např. #Deprecated).

Atributy mohou být také nově využívány frameworky k rozšířené definici tříd, vlastností a metod. Výhoda je v rychlosti, protože dříve museli frameworky použít reflexe k načtení komentářů a ručně v nich hledat klíčová slova a parsovat parametry. Pomocí Atributů si jen řeknou o jejich seznam a parsování zajistí PHP engine.

//Symfony routing v PHP 7
/** @Route("/api/get/{id}", methods={"GET"})
public function get($id) { ... }

//Symfony routing v PHP 8
#[Route("/api/get/{id}", methods={"GET"})]
public function get($id) { ... }

----------------------
//Nette DI v PHP 7
protected Cache $cache;
public function injectCache(Case $cache) {
    $this->cache = $cache;
}

//Nette DI v PHP 8
#[Inject]
public Cache $cache;

Match místo Switch

Podobně jako předchozí PHP přidalo ?: nebo ?? operátory pro zjednodušení IFů a ISSETů, PHP 8.0 přidává operátor match pro zjednodušení switchů (a odstranění breaků):

$result = match($type) {
    0 => "nula", //jako ternární operátor,
    1 => "jedna, //ale s více možnostmi
    2 => "dva"
};

Nezapomeňte, že na rozdíl od switch je match operátor, takže je potřeba ukončit příkaz středníkem!

Podobně jako switch můžete match zneužít k vyhodnocení podmínky

$result = match(true) { //vyhodnotí jestli podmínka platí
    $value >= 1 => "Kladné",
    $value === 0 => "Nula",
    $value &lt;= -1 => "Záporné, //tady pozor, ty šipky jsou matoucí!!!
};

Osobně bych doporučil v předchozím případě uvádět podmínky do závorek, aby bylo zřejmé, co je podmínka a co výsledná hodnota (např. ($value <= 1) => "Záporné").

Podmínky se vždy vyhodnocují v daném pořadí a splnění podmínky vždy ukončí vyhodnocení. Lze tedy s klidem uvést jen posloupnost podmínek.

$result = match(true) { //vyhodnotí jestli podmínka platí
    $age &lt; 2 => "Batole",
    $age &lt; 7 => "Dítě",
    $age &lt; 15 => "Žák"
    $age &lt; 18 => "Student"
    true => "Dospělý"
};

Operátor match není potřeba používat v přiřazení a můžete ho tak např. použít pro volání různých funkcí. Také můžete uvést více hodnot, které platí pro danou větev.

match(gettype($value)) {
    'integer', 'float' => $this->processNumber($value),
    'string' => $this->processString($value),
    'array', 'object' => $this->processArray((array)$value)
    default => throw new \InvalidArgumentException('Špatný typ');
};

Null-safe operator

Jelikož funkce a metody mohou vracet třídy a zároveň null (návratový typ např. ?User), může v některých případech docházet k chybě, že nad návratovou hodnotou zavoláte metodu, protože očekáváte objekt, ale přitom se vám vrátí NULL a kód spadne.

Null-safe operátor proto kombinuje Null-coalescing operátor a operátor volání metody (šipku): $this->getObject()?->method(). Při použití operátoru dojde k tomu, že PHP zavolá funkci jen v případě, že předchozí metoda vrátila objekt a nikoliv NULL. Pokud je hodnota před operátorem NULL, bude i výsledek operátoru NULL – je na vás, abyste pak správně ošetřili to, že celkový výsledek může být NULL, i když koncová metoda např. vrací INT nebo String.

Poznámka: tento přístup se obdobný principu Promisů, kde také platí, že když zavoláte řadu then() a jeden z nich selže, tak se další neprovenou a naopak se provede poslední catch(): např. $event->then(...)->then(...)->then(...)->catch(...) – pokud kterýkoliv then() selže, vyvolá se catch().

Slabé pole

PHP 7.4 přidalo WeakReference, které umožňuje uložit referenci na objekt coby hodnotu jiného objektu.

WeakMap pak přidává obdobně kombinaci WearReference a Array (resp. SplObjectStorage), které umožňuje použít objekty jako klíče pro přístup k hodnotám pole. WeakMap pak samozřejmě funguje tak, že sice uloží hodnotu pro daný objekt, ale nezabrání jeho smazání pomocí Garbage collectoru.

$o = new MyClass();
$cache = new WeakMap();

$cache[$o] = $o->getData(); //pamatuje si data objektu, ale jen dokud existuje

echo count($cache); //počítá jen objekty, které stále existují

foreach ($cache as $cls => $data) {
    //... zpracuje pouze data objektů, které stále existují
}

Jelikož klíčem WeakMap je reference na objekt, tak logicky nemůžete pomocí klíče přistoupit k datům objektu, který byl již smazán (pokud máte referenci v proměnné, nemůže být smazán; pokud máte referenci přes WeakReference, bude hodnota NULL a neodkáže tudíž na platná data).

Smysl pole je to, že funkce jako count() a foreach() automaticky přeskočí a odstraní uložená data, pokud zjistí, že patří k objektu (klíči), který již neexistuje. Na druhou stranu to můžete jednoduše použít třeba k tomu, abyste rychle spočítali objekty, které ještě zůstávají v paměti a nebo naopak získali možnost takové objekty ručně vymazat na konci delšího procesu.

Stringable interface

Třída, která implementuje funkci __toString() získává automaticky interface Stringable (ale doporučeno je ho implicitně uvést v definici třídy). Následně pak můžete udělat třeba toto:

function printMe($a) {
    if ($a instanceof Stringable) {
        echo (string)$a;
    }
    else {
        //... ostatní výpisy
    }

Throw jako výraz

V PHP 5.x a 7.x bylo potřeba zadat throw jako samostatný příkaz. Od PHP 8.0 je možno ho použít jako výraz, který je součástí delšího příkazu nebo šipkové funkce:

$this->value = $value ?? throw new InvalidArgumentException();

return fail($err) => throw new RuntimeException($err);

Odstranění omezení privátních metod

V PHP 5.x a 7.x, pokud jedna třída definovala metodu jako privátní, nemohl potomek metodu volat, ale zároveň byl omezen tím, jak byla metoda definována. Od PHP 8.0 může potomek libovolně znovu použít jméno metody, kterou její rodič používá jako privátní (např. jí může udělat abstraktní, statickou (nebo naopak), apod.).

Zároveň tím zaniká možnost označit privátní metodu jako final, protože rodič si již nemůže činit výhradní práva na použití jména metody.

TRY-CATCH bez proměnné

Pokud potřebujete odchytit výjimku, ale nepotřebujete ji zpracovávat, můžete od PHP 8.0 zapsat jen try { ... } catch (MyException) { ... } a PHP automaticky výjimku zahodí a nebude ji ukládat.

Změna porovnávání čísel s řetězci

PHP 5 a 7 při vyhodnocení podmínky s různými typy postupovali PHP zleva doprava, což v některých případech nedávalo smysl. Např. 0 == ‚A‘ platí, protože první je číslo a proto se druhý operand také převede na číslo; jelikož ale ‚A‘ neobsahuje číslo, tak se převede na nulu a vyjde shoda. Stejně tak platí, že 1 == ‚1cm‘, protože PHP automaticky pozná, že „1cm“ má číselnou hodnotu 1.

PHP 8 tohle opravuje a při porovnávání dvou typů vybere ten, který má širší využití. Podmínka 0 == ‚A‘ se tedy vyhodnotí jako ‚0‘ == ‚A‘ a neprojde, protože PHP vyhodnotí, že je důležitější zachovat řetězec, než numerickou hodnotu nuly.

Tato změna je důležitá v tom, že pokud upgradujete z PHP 7 na PHP 8 a využívali jste této chyby k odchycení chybných hodnot nebo automatickému převodu čísel s jednotkami na číslo, může vám to v PHP 8 způsobit problémy.

public function get(string $id) {
    if (0 == $id) { //odchytí nulu a nečíselné hodnoty
        throw new BadMethodCall('ID is required and must be a number');
    } //V PHP 7 ne-číselné hodnoty neprojdou, v PHP 8 ano!!!
    $id = (int)$id; //převeď číslo na INT
    ...
}

public function isTooSmall(string $width) : bool {
    return 1 == $width; //1px is too small
} //v PHP 7 vrátí True pro "1px", v PHP 8 řekne, že "1" se nerovná "1px"

Naopak pokud potřebujete použít kód z PHP 8 na serveru s PHP 7, může dojít k opačnému problému, že čekáte vyhodnocení různých řetězců a PHP řekne, že se shodují, protože je převedl na číslo.

Pro opravu je nejlepší využít typové porovnání s explicitním přetypováním (0 === (int)'A' nebo 1 === (int)"1px").

Změna warningů na Errory

Nativní funkce PHP (obvykle ty zděděné ještě z PHP 3 a 4) vracely při chybě pouze varování.

Od PHP 8 všechny funkce vyhazují výjimky typu \Error (resp. nějakého relevantvního podtypu).

To by na jednu stranu mělo zlepšit možnosti odchytávání chyb (místo ruční kontroly hodnot stačí použít TRY-CATCH), ale pokud kód psal nepořádný programátor, který se o warningy nezajímal nebo je řešil pomocí @ (který warningy potlačí), budete mít problém při upgradu na PHP 8 (operátor @ již na Errory nefunguje).

Lepší řetězení s řetězci a konstantami

V PHP 5 a 7 nebylo možno používat některé kontrukce, protože PHP je kontrolovalo při kompilaci, kdy ještě nebyla známa hodnota. Od PHP 8 je to již možné, protože PHP engine neuvažuje o hodnotě při kontrole syntaxe:

"ABCDEF"[1] //možné v PHP 7
"$a$b$c"[1] //není možné v PHP 7, protože není známa délka řetětce

Lepší vyhodnocení namespaců

Od představení namespace v PHP 5.3 nebylo možno používat klíčová slova pro pojmenování namespaců (a složek podle PSR-0), takže např. \App\Class\Trait\MyTrait nebyla platná struktura, protože class a trait jsou klíčová slova a bylo potřeba použít např. \App\Classes\Traits\MyTrait, což pak vyvolávalo nekonzistenci v použití jednotných a množných čísel pro názvy složek nebo chybné tvary (Classs, Classis, apod.).

PHP 8 mění přístup k namespacům a každý namespace bere jako celý výraz, takže namespace \App\Class\Trait je vyhodnocen jako "App\Class\Trait" a nikoliv jako ["App", "Class", "Trait"] a proto již nemůže dojít ke kolizi s klíčovými slovy.

Implicitní konstanta ::class pro instance

PHP 5.5 představili implicitní konstantu ::class, která vždy obsahuje jméno třídy. Použít šlo ale pouze na třídy (MyClass::class === "MyClass") a pro objekty bylo potřeba pořád používat get_class($obj).

PHP 8.0 rozšiřuje možnosti této konstanty a činí ji viditelnou i pro objekty (např. $obj::class nebo $this::class) a řeší tak odvěkou otázku, jestli je to get_class() nebo getclass().

Jen pozor na to, že pokud proměnná bude obsahovat NULL, jméno třídy (řetězec), nebo jinou hodnotu, vyvolá konstanta chybu \TypeError!

PHP 8.1

ENUM

Enumy jsou známou mechanikou v celé řadě jazyků a databázových enginů. PHP je ale až do verze 8.0 nepodporovalo a podobné situace bylo potřeba řešit pomocí konstant nebo speciálních konstrukcí. Popravdě, až do verze 8, kdy byly představeny multi-typové parametry a PHP se stalo plně typovým jazykem, neměly enumy až takový význam jako mají v silně typových jazycích.

Smysl Enumu je v tom, že místo předávání parametru jako číslo nebo řetězec pomocí konstanty nebo magické hodnoty, uvedete typ parametru Enum a PHP pak bude vědět, že nic jiného než hodnoty daného Enumu nemůže do parametru přijít.

//PHP 5.x - 8.0
public function debug(string $level, string $message) {
    switch ($level) {
        case self::ERROR: return $this->log('Error: '.$message);
        ...
        default: throw new \InvalidArgumentException('Unknonwn level');
    }
}
...
$this->debug('Error', 'Something happened');

//PHP 8.1+
Enum DebugLevel { case Error; case Warning; case Debug; }
public function debug(DebugLevel $level, string $message) {
    ... //již není potřeba kontrolovat, jestli je $level platná hodnota
}
...
$this->debug(DebugLevel::Error, 'Something happened');

Pokud potřebujete převést položku Enumu na řetězec, můžete použít skrytou vlastnost name: DebugLevel::Error->name === 'Error'.

Enum může mít i typ a pak můžete každé položce přiřadit i hodnotu, kterou můžete následně přečíst z vlastnosti value:

Enum DebugLevel : int { 
    case ERROR = 2;
    case WARNING = 1;
    case DEBUG = -1;
}
public function logError(DebugLevel $level, string $message) {
    if ($level->value >= 2) {
        return $this->log($level->name . ': ' . $message);
    }
}
public function debug(DebugLevel $level, string $message) {
    if ($level->value &lt; 0) {
        return $this->log($level->name . ': ' . $message);
    }
}

Vlastnosti pouze pro čtení

Různé verze PHP mají různý způsob, jak lze ukládat a číst vlastnosti objektů. PHP 8.x tyto možnosti sjednocuje a rozšiřuje a jednou z nich jsou vlastnosti pouze pro čtení.

//PHP 5.x - 8.0
class MyClass {
    private $value;

    public function getValue() {
        return $this->value;
    }
    public function setValue($value) {
        if (isset($this->value)) {
            throw new \BadMethodCallException('Value is already set');
        }
        $this->value = $value;
    }
}

Princip je takový, že readonly vlastnost můžete nastavit jen jednou a pak již nelze měnit a lze pouze číst. Ale na rozdíl od konstant u readonly vlastnosti není přímo určeno, kdy musíte hodnotu nastavit (nebo jestli vůbec ji nastavíte).

Nastavit hodnotu můžete v definici, v konstruktoru nebo kdykoliv později. Jediná podmínka je, že ji můžete nastavit jen jednou a pokud o pozdější změnu vyvolá výjimku:

class MyClass {
    public readonly int $value1 = 1;
    public readonly int $value2;
    public readonly int $value3;

    public function __construct(int $value) {
        $this->value2 = $value;
    }
    public function initValue(int $value) {
        $this-value3 = $value;
    }
}

Podmínkou použití vlastnosti pouze pro čtení je, že musí mýt uvedený typ a hodnota do něj přiřazená musí tomuto typu odpovídat.

Reference na metodu

Neboli First-class callable je speciální syntaxe, která zjednodušuje získání reference na metodu třídy. V předchozích PHP bylo potřeba použít pole, do kterého jste uložili referenci na objekt a jméno jeho metody (jako řetězec) nebo použít metodu .

Od PHP 8.1 stačí místo toho metodu objektu zavolat a místo parametrů uvést tři tečky, čímž PHP dáte najevo, že vlastně ještě neznáte parametry a nechcete tedy metodu volat.

//PHP 5.x, 7.x, 8.0
$fn = [$myObj, 'myMethod'];

//PHP 8.1
$fn = $myObj->myMethod(...); //zde jsou 3 tečky součástí syntaxe

Tato syntaxe má stejný význam jako Closure::fromCallbable() a lze tedy použít i na ostatní případy, jako jsou funkce objektu nebo naopak objekty s metodou __call(). Jediné, na co nemůžete tuto syntaxy použít je konstruktor.

Tato syntaxe (stejně jako fromCallable) ignoruje viditelnost původní metody, protože vytváří novou metodu. Můžete ji proto použít i na zavolání privátní metody, pokud to samostný objekt povolí (tím, že sám vytvoří a vrátí referenci):

class MyLoader extends MyAuthenticator {
    //Nelze zavolat přímo...
    private function getData(...$params) {
        //...
    }

    //...ale jen po té, co ověříte právo na získání dat
    public function authenticate(string $token) {
        if ($this->verify($token)) {
            return $this->getData(...);
        }
    }
}

$load = new MyLoader();
$data = $load->authenticate($token)(...$params);

Objekty jako výchozí hodnoty

Od PHP 8.1 můžete určit, že výchozí hodnota parametru funkce (metody), vlastnosti objektu nebo konstanty je objekt (s konkrétními parametry).

class MyData {
    //konstanta může být objekt
    public const TYPE = new Type('default');
    //vlastnosti již není potřeba iniciovat v konstruktoru
    private Loader $loader = new Loader(self::TYPE);

    public function __construct(
        //lze plně využít automatické vlastnosti vč. hodnoty
        private string $db = new Database(Database::MASTER)
    ) {} //tělo konstruktoru již není potřeba, vše máme výše
}

Klíčové slovo new lze nyní použít kdekoliv, takže je možné například vytvářet vnořené objekty potřebné pro vytvoření hlavního objektu:

class MySlaveLoader {
    private const DB = Database::SLAVE;
    public function __construct(
        private MyData $data = new MyData(new Database(self::DB))
    ){}
}

Spojené typy parametrů

PHP 8.0 přidalo možnost určit parametru více typů, takže parametr funkce může být např. buď číslo nebo řetězec.

PHP 8.1 tuto možnost otáčí a umožňuje určit, že daný parametr musí splňovat několik podmínek najednou. U typů jako číslo nebo řetězec to asi nedává smysl, ale plné využítí tato možnost najde u Interfaců – místo vyjmenovávání tříd, které splňují požadované podmínky stačí jen určit, které Interfacy musí třída implementovat, aby šla použít ve vaší funkci. PHP vám pak dá mnohem lepší informaci o tom, proč nejde daný objekt použít (např. „musí mít CreditCardInterface“ místo nejasné „musí být User nebo Company“ v případě, že implementujete nový typ zákazníka).

function getFirstIfHasThem(
    Iterator&Countable $arr, //potřebujeme spočítat prvky a pak je projít
    int $cnt
) : Generator {
    if (count($arr) < $cnt) { //Používáme Countable vlastnost parametru
        return [];
    }
    $i = 0;
    foreach ($arr as $item) { //Používáme Iterator parametru
        yield $item;
        ++$i;
        if ($i > cnt) { return; }
    }
}

function buyPremiumMembership(
    //netřeba uvádět, že potřebujeme třídy User nebo Company
    PremiumInterface&PurchaseInterface&CreditCardInterface $user
) {
    if (
        //Metoda z CreditCardInterface ověří platnost uložené karty
         $user->hasValidCard()
     ) {
         //metoda z PurchaseInterface
         $user->addPurchase( //uloží platbu do objednávek
             //metoda z CreditCardInterface
             $user->pay( //zaplatí danou cenu a vrátí výsledek
                //metoda z PremiumInterface
                 $user->getMembershipPrice()
             )
         );
     }
}
        

V PHP 8.1 zatím není možnost kombinovat spojené a rozdílné typy (např. string|Iterator&Countable).

Funkce bez návratu

PHP 8.1 přidává novou možnost určit, že metoda nikdy (never) nesmí vrátit hodnotu nebo se dostat na konec. Tuto možnost použijete u metod, které buď vždy vyhazují výjimku nebo ukončují běh skriptu. PHP díky tomu bude vědět, že pokud se pousíte provést nějaký kód po zavolání takové metody, tak se kód neprovede a je schopno na to upozornit (stejně jako dříve upozorňovalo na kód po return; nebo die()).

function handleCriticalError(throwable $e) : never {
     $this->log($e);
     die('Cannot continue');
}

try {
    doSomething();
}
catch (throwable $e) {
    handleCriticalError($e);
    $page->showError($e->getMessage()); //PHP ví, že tohle se nespustí
}

Finální konstakty

Od PHP 8.1 je možno označit konstanty jako finální, takže potomek již nemůže změnit jejich hodnotu.

Zastavitelná vlákna (Fibers)

PHP 5.5 představilo Generátory, což jsou metody, které mohou vrátit hodnotu a následně pokračovat v běhu a vrátit jinou hodnotu. Problém generátorů je v tom, že jsou to generátory – funkce, který používá klíčové slovo yield musí vždy vrátit Generator, který je pak potřeba použít specifickým způsobem (obvykle FOREACH).

Poznámka: zde vzniká trochu problém s překladem, protože většina jazyků (vč. PHP) používá české slovo Vlákno pro třídu Threads (správně by ale mělo jít o nítě), která zajišťuje asynchronní běh funkcí. Problém je v tom, že překlad Fibers je (také) vlákno (nebo vláknina).

Naproti tomu Fibers nemusí vracet Generator a místo toho stačí, když bude volat statickou metodu třídy Fiber::suspend(). Znovu-spuštění probíhá pomocí metod resume() nebo throw();

Po pozastavení vždy vlákno vrátí hodnotu předanou do Fiber::suspend() a naopak metoda Fiber::suspend() pak vrátí hodnotu předanou do resume() nebo vyvolá výjimku.

//Funkce načítá uživatele a předává je callbacku, který určí platnost dat
//na závěr vrátí počet platných uživatelů
function loadUsers($callback) : int {
    $cnt = 0;
    foreach ($db->getUsers() as $user) {
        try {
            //funkce nemusí vědět, že běží v zastavitelném vlákně
            if ($callback($user)) {
                ++$cnt;
            }
        }
        catch (Exception $e) {
            break; //ukončí počítání userů
        }
        return $cnt;
    }
}

//určení, kterou funkci bude vlákno volat
$f = new Fiber(loadUsers(...));

//spustí vlákno a předá funkci, která vlákno pozastaví
//pak počká na prvního usera
$user = $f->start(function($user) {
    return Thread::suspend($user);
});

while ($user) {
     if ($user->isDeleted) {
         $f->throw(new InvalidArgument()); //ukončí vlákno
     }
     $isValid = processUser($user);

     //předá vláknu informaci o validitě usera
     //a opět počká na dalšíhu usera
     $user = $f->resume($isValid);
} //start a resume vrátí NULL, pokud vlákno skončilo, což ukončí while

//Přečte návratovou hodnotu funkce loadUsers()
echo 'Nalezeno ', $f->getReturn(), ' validních uživatelů';

Jak je vidět z příkladu, použití Vlákna je trochu složitější oproti FOREACH nad Generátorem, ale umožňuje použití libovolné metody (která přijímá callback), takže lze použít i na existující nebo dokonce nativní metody (např. array_map, usort, iterator_to_array, apod.)

Rozbalení polí s klíči

PHP 7.4 umožňuje rozbalení pole do pole, ale pouze v případě, že má číselné indexy, které se pak přidají na konec pole. Naproti tomu operátor + umožňuje spojení polí s pojmenovanými klíči, ale existující klíče se nepřepisují. Pro přepsání klíčů tak bylo potřeba stále psát zdlouhavé array_merge().

PHP 8.1 přidává možnost rozbalit pole s klíči, čímž se přepíší hodnoty daného klíče:

$a = ['a' => 1, 'b' => 2];
$b = ['a' => 3, 'c' => 4];

var_dump($a + $b); //a => 1, b => 2, c => 4
var_dump([...$a, ...b]); //a => 3, b => 2, c => 4
var_dump(array_merge($a, b)); //a => 3, b => 2, c => 4

Kontrola sekvenčních polí

V PHP 8.1 můžete ověřit, jestli má pole definované všechny indexy od nuly do maxima pomocí funkce array_is_list():

array_is_list(0,1,2,3); //==TRUE
array_is_list(0 => 'A', 1 => 'B', 2 => 'C'); //==TRUE
array_is_list(0 => 'A', 2 => 'C'); //==FALSE (chybí index 1)
array_is_list('A' => 1); //== FALSE (nemá číselné indexy)
array_is_list(1 => 'B', 0 => 'A'); //==FALSE (indexy ve špatném pořadí)

Funkci lze použít pouze na skutečná pole (typ array), protože u iterátorů a generátorů se očekává, že budou vracet hodnoty v pořadí. Navíc kontrolou indexů by mohlo dojít k jejich znehodnocení (např. generátor vyplýtvá hodnoty na kontrolu, takže následně již nebude mít co vracet).

Při downgradu z PHP 8.1 na starší si můžete funkci sami doplnit:

function array_is_list(array $array): bool {
    if (empty($array)) {
        return true;
    }

    $current_key = 0;
    foreach ($array as $key => $noop) {
        if ($key !== $current_key) {
            return false;
        }
        ++$current_key;
    }

    return true;
}

Podpora WEBP a AVIF obrázků

PHP 8.1 přidává podporu pro načítání a ukládání AVIF obrázků pomocí integrované GD knihovny (Na Linux si ověřte, že PHP kompilujete s podporou správné verze knihovny; na Windows a Mac ověřte, že stažená verze knihovnu obsahuje).

if (!function_exists('imageavif')) {
    throw new RuntimeException('AVIF support not available!');
}

Pokud máte verzi bez AVIF podpory, můžete použít externí Magick knihovnu.

Pomocí funkce imagecreatefromavif() můžete načíst libovolný AVIF obrázek a pracovat s ním stejně, jako s jinými formáty. Pomocí funkce imageavif() pak můžete zpracovaný obrázek uložit do AVIF formátu.

Funkce imageavif má parametry $quality, která udává procentuelní kvalitu obrázku (0 až 100, obdoba kvality u JPEG, ale není srovnatelná), a $speed, která udává, jak dobře nebo rychle chcete obrázek zkomprimovat (0 = maximální komprese, 10 = maximální rychlost)

Formát WebP byl do PHP přidán již dříve, ale teprve od PHP 8.1 má plnou podporu všech vlastností včetně bezztrátové komprese.

imagewebp($image, 'small.webp', 20); //20% kvalita a komprese
imagewebp($image, 'big.webp', IMG_WEBP_LOSSLESS); //max.kvalita bez komprese

Nový formát pro oktalová čísla

Oktalová, neboli osmičková, čísla se v PHP dají zadat tak, že na začátku uvedete nulu (010 odpovídá hodnotě 8). Od PHP 8.1 můžete použít alternativní zápis podobný zadávání šestnáctkových a binárních hodnot: 0o10 nebo 0O10 (obě znamenají hodnotu 8).

Nový zápis můžete, ale nemusíte používat. V případě, že downgradujete na PHP 8.0 nebo 7.x, stačí z čísel odstranit „o“ (nahradit /0[oO]([0-7]+)/ za 0$1).

Poznámka: význam osmičkových čísel nemusí být zřejmý a klidně se bez nich můžete obejít, ale pokud často používáte násobky 8 nebo 64 (či 16 nebo 32), mohou osmičková čísla zlepšit čitelnost:

010 === 8  (020 === 16, 040 === 32, 060 === 48)
0100 === 64 (0200 === 128, 0400 === 256)
01000 === 512 (02000 === 1024)
0200_000 === 65536
atd.
//Resp. při zápisu maximálních hodnot:
077 === 63 (0177 === 127, 0377 === 255)
0777 === 511
177_777 === 65535

Jediný rozdíl v kódu může nastat v tom, že funkce is_numeric() bere v úvahu pouze čísla 0 – 9, takže číslo zapsané jako 0777 funkcí projde, zatímco 0o777 nikoliv! Toho lze využít v případě, že chcete oktalová čísla zahrnout nebo vyloučit (a podle toho můžete čísla uvádět v kódu).

PHP 8.2

Další vylepšení parametrů a vlastností

Spojené vícenásobné typy (DNF)

PHP 8.0 představilo vícenásobné (union) typy (např. string|int) a PHP 8.1 přeneslo spojené (intersection) typy (např. Countable&Iterable). PHP 8.2 obě tyto vlastnosti jazyka spojuje a umožňuje definovat více typů, z nichž některé mohou být spojené (anglicky se tato vlastnost nazývá disjunctive normal form neboli DNF).

function processList(List|(Countable&amp;Iterator) $list) {
    if ($list instanceof List) {
        $count = $list->getCount();
        $items = $list->getItems();
    }
    else {
        $count = count($list);
        $items = iterator_to_array($list);
    }
    ... //společné zpracování výsledku
}

Zápis je možný pouze ve formátu (A&B)|(C&D)|E, kde spojené typy musí být uvedeny v závorkách tak, aby ve vícenásobném výpisu působily jako jeden typ. Není možné použít operátory obráceně (např. (A|B)&C), ale je možné jeden typ uvést vícekrát (např. (A&C)|(B&C)).

Nejčastější kombinace samozřejmě bude s NULL, např. (Countable&Iterator)|null.

Třída pouze pro čtení a dynamické vlastnosti

Předchozí PHP 8.1 přineslo možnost označit vlastnost třídy jako pouze pro čtení. PHP 8.2 tuto možnost rozšiřuje tak, že když uvedete klíčové slovo readonly před klíčovým slovem class, budou všechny vlastnosti této třídy automaticky pouze pro čtení.

Základní myšlenkou je to, že uvádět readonly u každé vlastnosti (a parametru konstruktory) je únavné a snižuje to čitelnost. Další výhoda je v tom, že u takové třídy nebudete moci později (omylem) přidat vlastnost, která půjde měnit.

Druhá změna, kterou PHP 8.2 přináší je nemožnost vytvářet dynamicky vlastnosti nad již vytvořenými instancemi třídy (např. $user->newName = ‚AAA‘). Poznámka: PHP 8.2 pouze vyhazuje Deprecated výjimku, která se dá ignorovat, ale PHP 9.0 již bude házet Error, který už nepřeskočíte.

readonly class User {
    public string $name;
    public int $id;
}

function loadUser() : User {
    $user = new User();
    $user->id = 1; //Tohle jde, protože je to první nastavení hodnoty
    $user->name = 'John Doe';
    return $user;
}

$user = getUser();
$user->id = 2; //Není možné -> vlastnost už je nastavena
$user->email = 'john@doe.com'; //Není možné -> dynamická vlastnost

Pokud potřebujete objekt, který může ukládat dynamická data, můžete k tomu použít setter, a ukládat data do připravené vlastnosti, nebo použít nový atribut, který dynamické vlastnosti (dočasně do PHP 9.0) povolí:

//PHP 5.0 - 8.1
class MyData {};
$data = new MyData();
$data->value = 'value';

//Funkční od PHP 5.0, možno i v PHP 8.2
class MyData {
    private array $data = [];
    public function __set($name, $value) {
        $this->data[$name] = $value;
    }
    public function __get($name) {
        return $this->data[$name];
    }
}
$data = new MyData();
$data->value = 'value';

//PHP 8.2 - 8.x
#[AllowDynamicProperties]
class MyData {}
$data = new MyData();
$data->value = 'value';

Poznámka: třída StdClass automaticky podporuje dynamické vlastnosti, takže není potřeba vytvářet prázdnou třídu jen proto, abyste aktivovali tento atribut:

$data = new StdClass();
$data->value = 'value';

Nové (tentative) typy parametrů a návratové hodnoty

PHP 8.2 rozšiřuje možnosti typování parametrů a návratových hodnot funkcí o typy null, false a true. Tyto typy byly v PHP 8.0 představeny jako součást sloučených typů (např. string|false), ale nyní je lze použít samostatně.

function getUser(int $id, null $connection, false $useCache) { ... }

function logError(Throwable $error, bool $abort) : false { ... }

Jejich použití se může zdát být nesmyslné (proto se v angličtině nazývají tentative, tedy něco jako nejasné), protože jaký smysl má parametr, který může mít jen jedinou hodnotu a nebo jaký smysl má funkce, která vždy vrátí to samé?

Smysluplné použití můžete najít při údržbě starších projektů, které určitě obsahují celou řadu funkcí, které již neplní své původní účely, ale jsou volány na desítkách nebo stovkách míst a proto nemá smysl přepisovat všechny soubory, kde jsou volány. Nastavením typu takové parametru na NULL, TRUE nebo FALSE (podle výchozí hodnoty) zajistíte, že se nebude funkce volat s jinou hodnotou, kterou funkce nedokáže zpracovat nebo zohlednit ve výsledku.

V příkladu výše máme funkci, která dříve přijímala parametry $connection a $useCache. Nyní již ale připojení měnit nelze a cache se nepoužívá, takže nemá smysl parametry posílat. Jelikož ale nechceme přepisovat desítky souborů kvůli tomu, že odstraníme nepotřebné parametry, můžeme je jen „umrtvit“.

Obdobně toho můžete využít v případě, že máte funkci, která dříve něco vracela, ale není ji měníte tak, že již návratovou hodnotu nepotřebuje. Např. funkci ,která dříve vracela TRUE nebo FALSE a podle toho se prováděli další ošetření, můžete změnit tak, že bude vracet POUZE TRUE nebo FALSE, čímž pak zabráníte spouštění nechtěného kódu. Navíc vás v takovém případě chytré IDE upozorní, že daná podmínka nemá smysl a vy (během refaktoringu), můžete daný kód odstranit.

Další použití může být u zděděných metod, kde tím můžete označit, že daná metoda v daném kontentu nemůže vrátit jinou hodnotu (a tudíž nemá smysl ji volat). To je možné díky tomu, že zděděná metoda může zpřesnit typ návratové hodnoty (např. ze static na self) a typy true a false jsou pod-typy bool (a null je podtyp jakéhokoliv typu kombinovaného s null).

class Person {
    public function isChild() : bool;
    public function isSenior(): bool;
    public function getMother() : Person|null;
}
class Adult extends Person {
    public function isChild(): false;
}
class Senior extends Adult {
    public function isSenior(): true;
}
class Orphan extends Person {
    public function getMother() : null;
}

Konstanty v Traitech

PHP 5.4 přineslo možnost oddělovat a sdílet části kódu do samostatných souborů definovaných jako traity. Nicméně v té době mohli traity obsahovat pouze metody a vlastnosti, ale nikoliv konstanty. To nebyl až takový problém, protože konstantu bylo možnost nadefinovat buď jako statickou vlastnost a nebo v samostatném Interface.

Jelikož ale PHP 8 kompletně překopává to, jak PHP pracuje s vlastnostmi a konstantami (typování, readonly, apod.), nemá smysl bránit vytváření konstant v traitech. (Navíc se tím PHP snaží předejít dalšímu zhoršení situace tím, že by se místo konstant v traitech vytvářeli readonly vlastnosti.)

Rozdíl oproti definování konstant v Interfacech je ten, že konstanta nadefinovaná v traitu je dostupná pouze pomocí třídy, která trait používá, zatímco když nadefinujete konstantu v Interface, je dostupná přes tento interface.

trait MyConstants {
    public const VALUE1 = 1;
}
interface publicConstants {
    public const VALUE2 = 2;
}
class MyClass implements publicConstants {
    use MyConstants;
}

$one = MyClass::VALUE1;
$two = MyClass::VALUE2; //některá IDE tohle označují za chybu
$otherTwo = publicConstants::VALUE2;
//$otherOne = MyConstants::VALUE1; //Trait nelze použít pro získání konstanty!

Parametry s citlivými údaji

Atribut SensitityParametr se hodí v případě, že pracujete s osobními údaji (hesla, GDPR, HIPPA, apod.). Uvedením tohoto atributu před parametrem funkce zabráníte výpisu hodnoty v případě výjimky nebo pádu aplikace.

public function createUser(
    string $name,
    #[SensitiveParametr]
    string $password,
    #[SensitiveParametr]
    string $rodneCislo,
) {
    if (empty($name) || empty($password)) {
        throw new InvalidArgumentException();
    }
    ...
}

Ve uvedeném příklad bude popis chyby v případě chybějícího jména obsahovat pouze:

Error: Invalid Argument
#0 createUser(
    '', 
    Object(SensitiveParameterValue), 
    Object(SensitiveParameterValue)
) on line ...;

Upozornění: atribut SensitiveParameter je potřeba uvést před každým parametrem každé funkce (kde je potřeba ochránit osobní údaje). Nestačí uvést atribut před funkcí, před prvním parametrem, nebo před parametrem v Interface, který funkci definuje!

Generování náhodných hodnot

PHP 8.2 představuje nový systém generování náhodných hodnot pomocí Random\Randomizer:

//PHP 5.0+
$chance = rand(0,100); //nelze použít pro generování klíčů!
$newArr = shuffle($arr); //nelze použít pro generování klíčů!

//PHP 7.0+
$chance = random_int(0, 100); //kryptograficky bezpečné
$bytes = random_bytes(1024); //kryptografický 1kB dlouhý klíč

//PHP 8.2+
$r = new Random\Randomizer(); //vytvoří náhodnou číselnou řadu
$chance = $r->getInt(0, 100); //první náhodné číslo v řadě
$chance = $r->nextInt(); //další číslo v řadě

$keys = $r->pickArrayKeys($arr, 10); //10 náhodných klíčů z pole
$newArr = $r->shuffleArray($arr); //prohází prvky pole

$bytes = $r->getBytes(1024); //Vytvoří 1kB dlouhý náhodný klíč
$bytes = $r->shuffleBytes($bytes); //prohází hodnoty bytů

//PHP 8.3+
$float = $r->getFloat(0.1, 0.9);
$float = $r->nextFloat();

//128B dlouhý Hexadecimální klíč
$bytes = $r->getBytesFromString('1234567890ABCDEF', 128);

Třída nahrazuje stávající metody jako random_int() a shuffle(). Smysl třídy je v tom, že v jejím konstruktoru můžete použít vlastní generátor náhodných hodnot. Pokud ho neuvedete, použie PHP výchozí \Random\Engine\Secure, který je určený pro generování kryptograficky bezpečných klíčů. Alternativně můžete v použít generátory PCG (PcgOneseq128XslRr64), Mersenne Twister (Mt19937), Xoshiro256** (Xoshiro256StarStar) nebo si napsat vlastní:

class ForgedDice implements \Random\Engine {
    public function generate(): string {
        return hex2bin('FFFFFFFFFFFFFFFF'); //maximální hodnota náhodnosti
    }
}

$dice = new Random\Randomizer(new ForgedDice);
$dice->getInt(1, 6); //hodí 6
$dice->getInt(1, 6); //a opět 6

Poznámka: uvedený příklad je pouze teoretický – PHP obsahuje kontrolní mechanizmus, který nejprve zkusí vygenerovat 50 náhodných čísel a pokud zjistí, že nejsou dostatečně náhodná, odmítne generátor použít (Výjimka BrokenRandomEngineError). Na to je potřeba dát pozor: pokud by váš generátor vracel čísla podle připravené řady, je nutno počítat s tím, že prvních 50 hodnot se přeskočí.

Upozornění: metody nextInt() a nextFloat() se zdají, že by mohli vracet další číslo ve stejném rozsahu jako předchozí getInt() a getFloat(), ale ve skutečnosti nextInt() vrací číslo v rozsahu [1, INT_MAX] a nextFloat() v rozsahu [0.000001, 0,999999].

Převod jednotek na čísla

Ve světě IT je běžné používat hodnoty jako 100K, 10M nebo 1G. PHP 8.2 přináší funkci, která tyto hodnoty dokáže přečíst a převést na skutečné číslo (INT). Jelikož se takové hodnoty nejčastěji používají v konfiguračních souborech, má funkce jméno ini_parse_quantity(). Pozor na to, že tato funkce pracuje s násobky 1024 a nikoliv 1000 (opět protože nejčastěji slouží pro nastavení velikosti paměti či cache, limitu velikosti souboru, apod.):

ini_parse_quantity('1k'); //== 1024
ini_parse_quantity('1m'); //== 1048576
ini_parse_quantity('1g'); //== 1073741824

Změna strtolower a strtoupper

Od PHP 8.2 fungují funkce strtolower a strtoupper pouze na písmena AZ. Pokud chcete převést národní znaky, je potřeba použít mb_strtolower a mb_strtoupper.

Zrušení funkcí utf8_*()

Nemusíte se bát, PHP 8.2 neruší podporu UNICODE. Pouze odstraňuje staré funkce z PHP 4, které již neplnily svoji funkci v době, kdy mám PHP plnou podporu UNICODE pomocí mb_*() funkcí nebo iconv a intl rozšíření.

Funkce utf8_encode() převáděla ANSI (přesněji LATIN 1) na UTF-8. Jelikož ale většina znaků používaných z LATIN 1 (tedy prvních 127 znaků) je stejných i v UTF-8, tak většinou vlastně nic nedělala (převáděla znaky jako jsou rámečky a čtverečky apod., které se používali v textových konzolích a v moderních systémech – hlavně v HTML5 prohlížečích – nejsou potřeba).

Funkce utf8_decode() převáděla UTF-8 na LATIN 1 tím, že nepoužitelné znaky převedla na ?, což již dnes není potřeba, protože většina zařízení již podporuje UNICODE (a nebo má vlastní jiné metody jak UNICODE uložit).

PHP 8.3

Typované konstanty

Ke konstantám je nyní možné uvést typ. Může se zdát, že do nedává moc smysl, jelikož není možné programově měnit hodnotu, ale důvodů je několik:

Zaprvé, PHP může vyhodnotit, jestli je možné konstantu použít jako parametr funkce nebo jak nejlépe přetypovat ostatní hodnoty ve výrazu.

Zadruhé umožňuje kontrolovat konstanty, které se mohou měnit programátorsky za účelem změn fungování:

class MyClass {
    protected bool WRITE_DEBUG_OUTPUT = 1;
    protected int CACHE_TIMEOUT_SECONDS = "10 minutes";

V obou uvedených případech se vyhodí fatální výjimka, protože programátor zadal 1 místo TRUE pro zapnutí debugování a hodnotu "10 minutes" místo 600, což by mohlo vyvolat neočekávané chyby nebo přetypování v kódu.

V případě, že uvedete declare(strict_types=0); provede naopak PHP automaticky přetypování na uvedený typ konstanty, takže z 1 udělá TRUE a z 10 minut zachová jen 10 (což má v uvedeném případě význam 10 sekund).

Jako typ můžete uvést jakýkoliv typ, který PHP podporuje, včetně spojených typů. Výjimku tvoří typy void a never, které jsou určeny pouze pro návratové hodnoty a typ callable, který nedává smysl (konstanta typu callable je vlastně metoda).

Získání constanty (a ENUM) podle jména

PHP umožňuje přistupovat k vlastnostem objektů pomocí jména uloženého v proměnné: $data[$name] = $obj->$name; . Od PHP 8.3 můžete tento přístup použít i pro konstanty třídy a hodnoty ENUM.

Problém je ale v tom, že na rozdíl od objektů, kde $obj->$name samostatně nedává smysl, tak třídy již používají Class::$name pro přístup ke statickým vlastnostem. Pokud to tedy chcete použít pro přístup ke konstantě, musíte proměnnou obalit do složených závorek.

Pro downgrade je potřeba přepsat tento zápis s použitím funkce constant():

//PHP 8.2 a starší
constant("MyClass::$constantName");
constant("MyEnum::$memberName");

//PHP 8.3+
MyClass::{$constantName}
MyEnum::{$memberName}

Validace JSON

PHP nyní umožňuje validovat JSON funkcí json_validate() před tím, než ho zkusíte rozparsovat. Před touto funkcí bylo potřeba řešit to kontrolou návratové hodnoty nebo výjimky.

//Původní ošetření nevalidního JSON
//(nepoznáte, zda NULL není hodnota JSONu a je potřeba to hlídat)
$data = json_decode($json);
if (null === $data &amp;&amp; 'null' !== strotolower($json)) {
    $this->error('Invalid JSON');
}

//PHP 7.3+ (používá výjimky, ale musíte uvést všechny parametry)
try {
    $data = json_decode($json, false, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
    $this->error('Invalid JSON: '.$e->getMessage());
}

//PHP 8.3+
if (!json_validate($json)) {
    $this->error('Invalid JSON');
}
$data = json_decode($json);

Změna práce se streamy

PHP 8.3 představuje novou funkci, která má v PHP 9.0 nahradit dosavadní práci se streamy:

//PHP 8.2 a starší
stream_context_set_option($stream, $wrapper, $options);
stream_context_set_option($stream, $more_options);

//PHP 8.3+
stream_context_set_option($stream, $wrapper, $options);
stream_context_set_options($stream, $more_options);
//Pozor na "s" na konci -^-

//Pro downgrade použijte pollyfill:
if (\PHP_VERSION_ID &lt; 80300) {
    function stream_context_set_options($context, array $options): bool {
        return \stream_context_set_option($context, $options);
    }
}

Od PHP 8.4 bude použití způsobu z 8.2 vyvolávat Deprecated výjimku!

Kontrola dědičnosti metod (atribut Override)

Třída, která dědí metodu od rodiče nebo interfacu, musí zachovat jméno, parametry a viditelnost metody, což PHP striktně kontroluje. Opačná kontrola ale neexistuje, a pokud rodič nebo interface přejmenuje metodu, potomek se o tom nedozví a může dojít k tomu potomek implementuje metodu, která ale nebude použita. A nebo může prostě dojít k tomu, že se programátor upsal a pojmenoval metodu jinak. A nebo může dojít k tomu, že rodič nebo interface metodu ostraní a sloučí s jinou, ale potomek se o tom nedozví a příslušně neupraví implementaci (např. když updatujete externí knihovnu, od níž dědíte své třídy).

PHP 8.3 umožňuje použít atribut Override, který vynutí kontrolu, zda rodič (nebo Interface) má stejnojmennou metodu:

class MyUserInterface {
    public function getUserList();
}

class MyUser implements MyUserInterface {

    #[\Override]
    public function getUsersList() {
        return ...;
    }
}

class MyChildUser extends MyUser {
    #[\Override]
    public function getUsersList() {
        return ...;
    }
}

Výše uvedený kód vyhodí výjimku „MyUser::GetUsersList ha attribute Override, but no matching parent method exists„, protože třída MyUser použila špatné jméno (User*s*List) metody, která v Interface neexistuje. Po opravě v MyUser se pak vyhodí stejná výjimka i pro MyChildUser, protože ta použila stejné jméno jako rodič a po přejmenová nebude pasovat.

Alias třídy

Od PHP 5.3 je možno vytvářet aliasy tříd, ale pouze těch vytvořených. Od PHP 8.3 je možno vytvořit i aliasy nativních tříd:

//PHP 8.3+
class_alias(\DateTime::class, 'Date');
class_alias(\DateTime::class, 'Time');

Pro downgrade PHP viz kapitola pod PHP 5.3 s potomkem.

Lepší výjimky pro DateTime

Před PHP 8.3 vracela třída DateTime obyčejné výjimky (tedy přímo třídu \Exception, v některých případech i \Error). Od PHP 8.3 vrací specifické výjimky, např. \DateTimeInvalidTimeZoneException. Při upgradu ze starší verze se nic nemění, protože výjimky jsou správně odděděné od \Exception a \Error, takže všechny TRY-CATCH budou fungovat.

Problém může nastat, pokud budete downgradovat kód do starší verze – pak budete muset všechny třídy správně přepsat: např. \DateTimeInvalidTimeZoneException na \Exception nebo \DateRangeError na \Error. Neměl by to být takový problém, protože výjimek je (jen) 7 a každá má ve svém názvu správné *Exception nebo *Error.

Více metod pro náhodná čísla

Viz kapitola pod PHP 8.2.

PHP 8.4

Asymetrická viditelnost a správa vlastností

PHP 8.4 konečně zavádí dlouho očekávanou možnost (nazvanou Property hooks) mít vlastnosti objektu, které jsou veřejně viditelné, ale nejdou měnit a nebo jdou měnit jen za určitých podmínek (Setter). Stejný systém pak umožňuje definovat virtuální vlastnosti, které nemají vlastní hodnotu, ale hodnotu vypočítávají z jiných vlastností (Getter).

class MyClass {
    public string $value1; //veřejně viditelná i měnitelná
    protected string $value2; //není veřejně viditelná ani měnitelná

    //PHP 8.4+
    public protected(set) string $value3; //lze veřejně číst, ale ne měnit
    protected private(set) string $value4; //potomci mohou číst, ale ne měnit

    //Setter (uloží jako malá písmena)
    public string $value5 {
        set => strtolower($value); //nepoužívá se fn()
    }

    //Setter (uloží jako malá písmena)
    public string $value6 {
        set { //nepoužívá se function() - jediný parametr je $value
            $this->value6 = strtoupper($value);
        }
    }

    //Virtuální vlastnost
    public array $allValues {
        get => [$this->value1, $this->value2, $this->value3, 
                $this->value4, $this->value5, $this->value6];        
        set => { [$this->value1, $this->value2, $this->value3, 
                $this->value4, $this->value5, $this->value6] = $value; }
    }
}

$storage = new MyClass;
$storage->allValues = [1,2,3,4,5,6];
echo $storage->value5; //vytiskne 5;
$storage->value5 = 'UNDEFINED';
var_dump($storage->allValues); //vytiskne [1,2,3,4,'undefined',6]

Pro asymetrickou viditelnost platí několik pravidel. Zaprvé zápis „public private(set)“ je pevně daný a není možné používat např. „private public(get)“. První viditelnost je vždy pro getter a druhá pro setter (přípona (set) je pouze pro ujasnění, že nejde o překlep zdvojené viditelnosti). Také platí to, že setter nemůže mít vyšší viditelnost než getter, takže není možnost „protected public(set)“ nebo „private protected(set)“ – v takovém případě je potřeba použít set metodu nebo to obejít před getter, který vyhodí výjimku nebo něco podobného.

Getter a setter je možné zapsat dvě způsoby: buď ve stylu šipkové funkce (bez použítí fn($value)), kde platí, že vstupní parametr je $value a vrácená hodnota se automaticky uloží do vlastnosti, a nebo delší ve stylu plné funkce (opět bez function($value), kde je (v případě setteru) ale potřeba přímo uložit hodnotu do vlastnosti, protože návratová hodnota se ignoruje. U getteru není rozdíl, zda použijete krátký nebo dlouhý zápis a vrácená hodnota se vždy vrátí z vlastnosti. U virtuální vlastnosti je potřeba vždy použít setter v dlohém zápisu, protože použití krátkého zápisu by danou vlastnost vytvořilo.

Pozor na to, že getter/setter není možno kombinovat s automatickými vlastnostmi z konstruktoru.

Zpožděná inicializace objektů

Tato možnost je hlavně určena pro frameworky, které mohou své objekty vytvářet zpožděně (lazy-loading) až když jsou potřeba. Je k tomu potřeba vytvořit objekt pomocí nové metody ReflectionClass::newLazyGhost().

Přetězení konstruktoru

PHP 8.4 mění (konečně) prioritu klíčového slova new, takže je možné nad novým objektem rovnou volat další metody.

//Závorky
(new Class($param))->process($data);

//Statický iniciátor
Class::init($param)->process($data);

//PHP 8.4
new Class($param)->process($data); 

Přepracovaná práce s HTML/XML

Starší verze PHP uměli pracovat s HTML a XML dokumenty, ale práce s nimi nebyla příliš snadná. PHP 8.4 zavádí nové třídy Dom\HTMLDocument a Dom\XmlDocument, které umožňují snadnější práci, jako například hledání pomocí CSS selektorů a práci s atributy: $html->querySelector('ul > li:last-child')->classList->contains('active');.

Nové funkce pro hledání v polích

PHP 8.4 přidává 4 nové funkce pro hledání v polích:

  • funkce array_find($array, $callback) vrátí hodnotu pole, pro kterou zadaný $callback vrátí TRUE. Na rozdíl od array_search() vrací hodnotu místo klíče, ale logicky potřebuje $callback, protože hledat přímo podle hodnoty nedává smysl.
  • funkce array_find_key($array, $callback) funguje stejně jako array_find(), ale vrátí klíč místo hodnoty. Pokud žádný prvek nesplní hledanou podmínku, vrátí NULL.
  • funkce array_all($array, $callback) funguje podobně jako Promise::all() – tedy zavolá $callback nad všemi hodnotami a vrátí TRUE, pokud $callback vrátil TRUE pro všechny prvky pole, jinak vrátí FALSE. Jinými slovy: vrátí FALSE pokud alespoň jeden prven nesplňuje podmínku $callbacku.
  • funkce array_any($array, $callback) funkce opačně než array_all() ve smyslu Promise::any() – tedy vrátí FALSE, pokud $callback vrátil FALSE pro všechny prvky pole, jinak vrátí TRUE. Jinými slovy: vrátí TRUE pokud alespoň jeden prvek splňuje podmínku $callbacku.

Všechny 4 funkce posílají do $callback jako první parametr porovnávanou hodnotu a jako druhý parametr její klíč. Vrácená hodnota může být cokoliv a funkce automaticky přetypuje návratovou hodnotu na BOOL (tedy prázdné hodnoty jsou FALSE, ne-prázdné hodnoty TRUE.

Všechny 4 funkce používají neúplné vyhodnocení, což znamená, že jakmile $callback vrátí TRUE (resp. FALSE u array_all()) poprvé, funkce se ukončí a vrátí očekávanou hodnotu (prvek pole, jeho klíč nebo TRUE či FALSE).

Při porovnání podle klíče u funkce array_find() je potřeba dát pozor na to, že hodnota může být NULL, takže pak nepoznáte, jestli se hodnota NULL vrátila záměrně nebo proto, že se nic nenašlo!

Pro prázdné pole vrací funkce array_find() a array_find_key() hodnotu NULL, funkce array_all() vrátí TRUE a array_any() vrátí FALSE! Hlavně u funkce array_all() je potřeba na to dát pozor, protože to může být trochu neočekávaný výsledek (který je ale plně v souladu v funkcí Promise::all(), protože pokud neběžel žádný proces, není potřeba čekat a je možno ihned pokračovat).

Nové funkce pro práci s UNICODE řetězci

PHP má funkce trim(), ltrim() a rtrim(), které ale pracují s ANSI řetězci. PHP 8.4 přidává jejich varianty mb_trim(), mb_ltrim() a mb_rtrim(), které dokáží pracovat s UNICODE řetězci. Rozdíl je v tom, že nové funkce dokáží poznat vícebajtové znaky v druhém parametru a dají se tak použít na oříznutí např. smajlíků (tedy pokud jsou na začátku nebo nakonci). Nevýhoda je, že tyto funkce nepodporují zadání rozsahu, takže není možné použít něco jako “😀…😔“ a bylo by nutné uvést všechny znaky, které chcete oříznout (a vzhledem k tomu, že UNICODE obsahuje tisíce smajlíků, tak pro jejich odstranění stále budete potřebovat jinou funkci). Hlavní použití tedy bude spíše pro odstranění neviditelné (ZWSP), volitelné (SHY) nebo nerozdělitelné (NBSP) mezery.

Obdobně přidává PHP vícebajtové alternativy pro funkce ucfirst() a lcfirst() jako mb_ucfirst() a mb_lcfirst(). Funkce se skutečně vyznají v UNICODE, takže například pro německé ostré S funkce mb_ucfirst('ß') vrátí ‚Ss‘.

Atribut Deprecated

PHP 8 představilo nové Atributy a PHP 8.4 toho využívá k tomu, že představuje atribut Deprecated, který automaticky vyvolá E_DEPRECATED výjimku, pokud použijete danou funkci, vlastnost nebo konstantu.

//Starý způsob bez výjimky
/** @Deprecated Replaces by XXX
public function YYY() { ... }

//Starý způsob s výjimkou
public function YYY {
    trigger_error('Use XXX instead', E_USER_DEPRECATED);
}

//Použití atributu
#[Deprecated]
public function YYY() { }

//Uvedení důvodu
#[Deprecated("use XXX instead")]
public function YYY() { }

//Uvedení důvodu a verze, od kdy
#[Deprecated("use XXX instead", "yesterday")]
public function YYY() { }

Atribut Deprecated není možno použít na celé třídy a také na parametry funkce. V PHP 8.4 nejde také použít na konstanty definované mimo třídy (od PHP 8.5 by to ale mělo být možné). Na vše ostatní použít jde, včetně – ne úplně zřejmého – použítí na jednotlivé hodnoty ENUMu.

Při uvedení důvodu je dobré začít malým písmenem, protože PHP zakomponuje text do své výjimky ve smyslu „Deprecated: <type> <name> is deprecated [since <since>], <reason> in $file on line $line.“. Při uvedení od kdy je funkce depreccated můžete použít buď verzi nebo datum – je to jedno, protože PHP prostě vloží hodnotu za slovo „since“. Pro hodnoty můžete použít i pojmenované parametry message a since.

DateTime z timestamp a mikrosekund

Od PHP 8.4 můžete konečně použít funkci DateTime::createFromTimeStamp() a není již potřeba používat hacky pomocí DateTime::createFromFormat('U') nebo new DateTime->setTimestamp(). Funkce také podporuje mikrosekundy ve formátu FLOAT.

Nově také můžete použít funkce $date->getMicrosecond() a $date->setMicrosecond(). Pozor na to, že microsekundy je potřeba nastavit přesně (v rozsahu 0 až 999_999) a není možno použít $date->setMicrosecond(1_000_000) pro přidání jedné sekundy).

U výše uvedených funkcí dejte pozor na to, že jsou uvedeny v jednotném čísle a mají trochu netypický CamelCase zápis (Timestamp s velkým S a naopak µs s malým).

Zákaz výchozí hodnoty NULL v typových parametrech

Od PHP 8.4 již není možné používat starý zápis výchozí hodnoty parametru NULL, pokud je uvedený ne-NULL-ový typ:

//PHP 8.3 a starší
abstract function(int $value = null);

//PHP 8.4+ (výše uvedený kód vyhodí výjimku)
abstract function(?int $value = null);

Tato změna rozbije váš kód, pokud stále používáte starý zápis volitelných parametrů. Nemusíte ale zoufat, protože stačí použít tento regulární výraz pro nahrazení:

//Hledejte:
(?&lt;!\ )(\(| )(?&lt;!\?)([^(\ \t\?\,\n\=]+?)( )(\$[^\ \n\t]+?)(\ \=\ null)
//Nahraďte za:
$1?$2$3$4$5

Kontrola správnosti CSV souborů

V PHP 8.3 bylo možno neuvádět parametr $escape a PHP automaticky escapovalo všechny CSV znaky pomocí zpětného lomítka. Od PHP 8.4 je potřeba specificky uvést, zda chcete použít zpětné lomítko nebo dvojité uvozovky (zadáním $escape:'').

Pokud parametr neuvedete, vyhodí se Deprecated výjimka. Paradoxně, PHP 9.0 by mělo tento parametr zcela odstranit a vždy použít pouze lomítko, takže nějak nechápu tento krok stranou, který vás donutí dvakrát měnit kód týkající se zápisu CSV souborů!?

Změna Session handleru

Od PHP 8.4 již není možno použít funkci session_set_save_handler() tak, že jí předáte 6 až 9 jednotlivých funkcí. Nově musíte funkci předat instanci třídy, která implementuje interface SessionHandlerInterface. Pokud tedy používáte vlastní úložiště session souborů, zkontrolujte si, že vám to PHP 8.4 nerozbije. Oprava by měla být jednoduchá – stačí vytvořit anonymní třídu, která obalí dané funkce.

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *

This site uses Akismet to reduce spam. Learn how your comment data is processed.