PHP 8.0: Adattípusok (2/4)

5 éve írta David Grudl  

Megjelent a PHP 8.0-s verziója. Annyira tele van újdonságokkal, mint még egyetlen verzió sem korábban. Bemutatásukhoz egyenesen négy különálló cikkre volt szükség. Ebben a másodikban az adattípusokat nézzük meg.

Térjünk vissza a történelembe. A PHP 7 alapvető áttörése a skaláris type hintek bevezetése volt. Majdnem nem történt meg. A csodálatos megoldás szerzőjét, Andrea Faulds, amely a declare(strict_types=1)-nek köszönhetően teljesen visszamenőlegesen kompatibilis és opcionális volt, a közösség csúnyán elutasította. Szerencsére akkoriban Anthony Ferrara mellé állt, kampányt indított, és az RFC nagyon szűken átment. Húha. A PHP 8 legtöbb újdonsága a legendás Nikita Popov nevéhez fűződik, és a szavazáson simán átmentek neki. A világ jobbá válik.

A PHP 8 tökéletesíti a típusokat. A kód phpDoc annotációinak (@param, @return és @var) túlnyomó többsége eltűnik, és helyüket natív írásmód és főleg a PHP motor általi ellenőrzés veszi át. A kommentekben csak a struktúrák leírásai maradnak, mint a string[] vagy bonyolultabb annotációk a PHPStan számára.

Union típusok

Az union típusok két vagy több olyan típus felsorolása, amelyeket egy érték felvehet:

class Button
{
	private string|object $caption;

	public function setCaption(string|object $caption)
	{
		$this->caption = $caption;
	}
}

Néhány speciális union típust a PHP már korábban is ismert. Például a nullable típusokat, mint a ?string, ami a string|null union típus ekvivalense, és a kérdőjeles írásmódot csak rövidítésnek tekinthetjük. Természetesen ez PHP 8-ban is működik, de nem kombinálható függőleges vonalakkal, tehát a ?string|object helyett a teljes string|object|null-t kell írni. Továbbá az iterable mindig is az array|Traversable ekvivalense volt. Talán meglepő, hogy az union típus tulajdonképpen a float is, amely valójában int|float-ot fogad el, de float-ra konvertálja.

Az unionokban nem használhatók a void és mixed pszeudotípusok, mert ennek semmi értelme nem lenne.

A Nette készen áll az union típusokra. A Schemában a Expect::from() elfogadja őket, és a prezenterek is elfogadják őket. Használhatod őket például a render és action metódusokban:

public function renderDetail(int|array $id)
{
	...
}

Ezzel szemben a Nette DI autowiring elutasítja az union típusokat. Hiányzik még a use case, ahol értelme lenne annak, hogy például a konstruktor vagy ezt, vagy azt az objektumot fogadja el. Természetesen, ha megjelenik, a konténer viselkedését ennek megfelelően lehet majd módosítani.

A getParameterType(), getReturnType() és getPropertyType() metódusok a Nette\Utils\Reflection-ben kivételt dobnak union típus esetén (a 3.1-es verzióban, a régebbi 3.0-ban a kompatibilitás miatt null-t adnak vissza).

mixed

A mixed pszeudotípus azt jelenti, hogy az érték bármi lehet.

Paraméterek és property-k esetén ez tulajdonképpen ugyanaz a viselkedés, mintha nem adnánk meg típust. Mire jó tehát? Hogy meg lehessen különböztetni, mikor hiányzik egyszerűen a típus, és mikor valóban mixed.

Függvények és metódusok visszatérési értékénél a típus meg nem adása eltér a mixed típus megadásától. Ez tulajdonképpen a void ellentéte, mivel azt mondja, hogy a függvénynek valamit vissza kell adnia. A hiányzó return ekkor fatális hiba.

A gyakorlatban ritkán kellene használni, mert az union típusoknak köszönhetően pontosabban meg lehet határozni az értéket. Tehát kivételes helyzetekben hasznos:

function dump(mixed $var): mixed
{
	// változó kiírása
	return $var;
}

false

Az új false pszeudotípust ezzel szemben csak union típusokban lehet használni. Azért jött létre, hogy natívan le lehessen írni a natív függvények visszatérési értékének típusát, amelyek történelmileg sikertelenség esetén false-t adnak vissza:

function strpos(string $haystack, string $needle): int|false
{
}

Ebből az okból nem létezik true típus, nem használható önmagában a false, sem a false|null, sem a bool|false.

static

A static pszeudotípust csak metódus visszatérési típusaként lehet használni. Azt mondja, hogy a metódus ugyanolyan típusú objektumot ad vissza, mint maga az objektum (míg a self azt mondja, hogy azt az osztályt adja vissza, amelyben a metódus definiálva van). Ami kiválóan alkalmas a fluent interface-ek leírására:

class Item
{
	public function setValue($val): static
	{
		$this->value = $val;
		return $this;
	}
}

class ItemChild extends Item
{
	public function childMethod()
	{
	}
}

$child = new ItemChild;
$child->setValue(10)
	->childMethod();

resource

Ez a típus PHP 8-ban nem létezik, és a jövőben sem kerül bevezetésre. Az erőforrások (resources) történelmi maradványok abból az időből, amikor a PHP-nak még nem voltak objektumai. Fokozatosan az erőforrásokat objektumokra cserélik, és idővel ez a típus teljesen eltűnik. Például a PHP 8.0 a képet reprezentáló erőforrást a GdImage objektumra, a curl kapcsolat erőforrását pedig a CurlHandle objektumra cseréli.

Stringable

Ez egy interfész, amelyet automatikusan implementál minden objektum a mágikus __toString() metódussal.

class Email
{
	public function __toString(): string
	{
		return $this->value;
	}
}

function print(Stringable|string $s)
{
}

print('abc');
print(new Email);

Az osztálydefinícióban explicit módon meg lehet adni a class Email implements Stringable-t, de ez nem szükséges.

Ez az elnevezési stílus tükröződik a Nette\Utils\Html-ben is, amely a Nette\HtmlStringable interfészt implementálja a korábbi IHtmlString helyett. Az ilyen típusú objektumokat például a Latte nem escapeli.

Típusvariancia, kontravariancia, kovariancia

A Liskov-féle helyettesítési elv (Liskov Substitution Principle – LSP) kimondja, hogy az osztály leszármazottai és az interfész implementációi soha nem követelhetnek többet és nem nyújthatnak kevesebbet, mint a szülő. Tehát a leszármazott metódusa nem követelhet több argumentumot, vagy nem fogadhat el szűkebb típus tartományt a paramétereknél, mint az ős, és fordítva, nem adhat vissza szélesebb típus tartományt. De adhat vissza szűkebbet. Miért? Mert különben az öröklődés egyáltalán nem működne. A függvény ugyan elfogadna egy bizonyos típusú objektumot, de nem tudná, milyen paramétereket lehet átadni a metódusoknak, és mit fognak valójában visszaadni, mert bármelyik leszármazott ezt felboríthatná.

Tehát az OOP-ban érvényes, hogy a leszármazott:

  • a paraméterekben szélesebb típus tartományt fogadhat el (ezt nevezik kontravarianciának)
  • szűkebb típus tartományt adhat vissza (kovariancia)
  • és a property-k nem változtathatják meg a típust (invariánsak)

A PHP ezt a 7.4-es verziótól tudja, és a PHP 8.0-ban bevezetett összes új típus is támogatja a kontravarianciát és a kovarianciát.

A mixed esetén a leszármazott szűkítheti a visszatérési értéket bármilyen típusra, de nem void-ra, mert az nem az érték típusa, hanem annak hiánya. A leszármazott sem hagyhatja el a típust, mert az is megengedi a hiányt.

class A
{
    public function foo(mixed $foo): mixed
    {}
}

class B extends A
{
    public function foo($foo): string
    {}
}

Az union típusokat is lehet bővíteni a paraméterekben és szűkíteni a visszatérési értékekben:

class A
{
    public function foo(string|int $foo): string|int
    {}
}

class B extends A
{
    public function foo(string|int|float $foo): string
    {}
}

Továbbá a false a paraméterben bővíthető bool-ra, vagy fordítva a bool a visszatérési értékben szűkíthető false-ra.

Minden kovariancia/kontravariancia elleni vétség PHP 8.0-ban fatális hibához vezet.

A következő részekben megmutatjuk, mik azok az attribútumok, milyen új függvények és osztályok jelentek meg a PHP-ban, és bemutatjuk a Just in Time Compilert.

David Grudl A web developer since 1999 who now specializes in artificial intelligence. He's the creator of Nette Framework and libraries including Texy!, Tracy, and Latte. He hosts the Tech Guys podcast and covers AI developments on Uměligence. His blog La Trine earned a Magnesia Litera award nomination. He's dedicated to AI education and approaches technology with pragmatic optimism.