PHP 8.0: Újdonságok az adattípusokban (2/4)

4 éve A címről David Grudl  

A PHP 8.0-ás verziója nemrég jelent meg. Tele van új funkciókkal, mint egyetlen korábbi verzió sem. Bemutatásuk négy külön cikket érdemel. A második részben az adattípusokkal foglalkozunk.

Menjünk vissza a történelemben. A skalár típusú tippek bevezetése jelentős áttörést jelentett a PHP 7-ben. Majdnem meg sem történt. Andreu Faulds, a zseniális megoldás szerzője, aki a declare(strict_types=1)-nak köszönhetően teljesen visszafelé kompatibilis és opcionális volt, keményen elutasította a közösség. Szerencsére Anthony Ferrara annak idején megvédte őt és a javaslatot, kampányt indított, és az RFC nagyon szorosan átment. Huhh. A PHP 8 legtöbb újdonsága a legendás "Nikita Popov:https://nikic.github.io " jóvoltából született, és mind úgy ment át a szavazáson, mint kés a vajon. A világ jobbra változik.

A PHP 8 tökéletessé teszi a típusokat. A phpDoc annotációk nagy többségét, mint a @param, @return és @var natív jelölés váltja fel. De ami a legfontosabb, a típusokat a PHP motorja fogja ellenőrizni. Csak az olyan struktúrák leírásai, mint a string[] vagy a PHPStan összetettebb megjegyzései maradnak meg a megjegyzésekben.

Uniós típusok

Az union típusok két vagy több olyan típus felsorolása, amelyet egy változó elfogadhat:

class Button
{
	private string|object $caption;

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

Bizonyos union típusok már korábban is megjelentek a PHP-ban. Például a nullázható típusok. Ilyen például a ?string, amely egyenértékű a string|null union típussal. A kérdőjeles jelölés rövidítésnek tekinthető. Természetesen a PHP 8-ban is működik, de nem kombinálható függőleges vonalakkal. Tehát a ?string|object helyett a string|object|null kell írni. Továbbá a iterable mindig is egyenértékű volt a array|Traversable-vel . Meglepő lehet, hogy a float is union típus, mivel elfogadja a int|float-at is, de az értéket a float-re dobja.

A void és a mixed áltípusokat nem használhatja unióban, mert annak nem lenne értelme.

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

Másrészt a Nette DI-ben az automatikus kapcsolás elutasítja az union típusokat. Eddig nincs olyan felhasználási eset, ahol a konstruktornak lenne értelme elfogadni akár az egyik, akár a másik objektumot. Természetesen, ha ilyen felhasználási eset 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 -ban az union típus esetén kivételt dobnak (ez a 3.1-es verzióban van így; a korábbi 3.0-s verzióban ezek a metódusok a nullkompatibilitás fenntartása érdekében visszatérnek).

mixed

A mixed áltípus bármilyen értéket elfogad.

A paraméterek és tulajdonságok esetében ugyanazt a viselkedést eredményezi, mintha nem adnánk meg semmilyen típust. Mi tehát a célja? Megkülönböztetni, hogy mikor hiányzik csupán egy típus, és mikor szándékosan mixed.

A függvények és metódusok visszatérési értékei esetében a típus meg nem adása különbözik a mixed használatától. Ez a void ellentétét jelenti, mivel megköveteli, hogy a függvény valamit visszaadjon. A hiányzó return ekkor végzetes hibát eredményez.

A gyakorlatban ritkán kell használni, mert az union típusoknak köszönhetően pontosan meg lehet adni az értéket. Ezért csak egyedi helyzetekben alkalmas:

function dump(mixed $var): mixed
{
	// print variable
	return $var;
}

false

A mixed-től eltérően a false új áltípust kizárólag union típusokban használhatja. Ez a natív függvények visszatérési típusának leírási igénye miatt alakult ki, amelyek történelmileg hiba esetén false-t adnak vissza:

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

Ezért nincs true típus. Nem használható sem a false önmagában, sem a false|null, sem a bool|false.

static

A static áltípus csak egy metódus visszatérési típusaként használható. Ez azt mondja, hogy a metódus egy olyan típusú objektumot ad vissza, amely azonos típusú magával az objektummal (míg a self azt mondja, hogy azt az osztályt adja vissza, amelyben a metódus definiálva van). Kiválóan alkalmas folyékony interfészek 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 nem létezik a PHP 8-ban, és a jövőben sem lesz bevezetve. Az erőforrások egy reliktum abból az időből, amikor a PHP még nem rendelkezett objektumokkal. Az erőforrásokat fokozatosan objektumok fogják felváltani. Végül teljesen el fognak tűnni. A PHP 8.0 például a kép erőforrást a GdImage objektummal, a curl erőforrást pedig a CurlHandle objektummal helyettesíti.

Stringable

Ez egy olyan interfész, amelyet minden objektum automatikusan megvalósít egy mágikus metódussal __toString().

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

function print(Stringable|string $s)
{
}

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

Lehetőség van a class Email implements Stringable kifejezett megadására az osztály definíciójában, de ez nem szükséges.

Nette\Utils\Html szintén tükrözi ezt az elnevezési sémát azáltal, hogy a korábbi IHtmlString helyett a Nette\HtmlStringable interfészt valósítja meg. Az ilyen típusú objektumokat például a Latte nem szökteti.

Típus variancia, kontravariancia, kovariancia

A Liskov-féle helyettesítési elv (LSP) kimondja, hogy a kiterjesztő osztályok és az interfészek implementációi soha nem igényelhetnek többet és nem nyújthatnak kevesebbet, mint a szülői osztály. Vagyis a gyermek metódus nem igényelhet több argumentumot, és nem fogadhat el szűkebb típustartományt paraméterként, mint a szülő, és fordítva, nem adhat vissza szélesebb típustartományt. De kevesebbet is visszaadhat. Hogy miért? Mert különben az öröklés megszakadna. Egy függvény elfogadna egy adott típusú objektumot, de fogalma sem lenne arról, hogy milyen paramétereket adhat át a metódusainak, és azok milyen típusokat adnának vissza. Bármelyik gyerek megtörhetné.

Tehát az OOP-ban a gyermekosztály is:

  • a típusok szélesebb körét fogadhatja el a paraméterekben (ezt nevezzük kontravariációnak).
  • a típusok szűkebb körét adja vissza (kovariancia)
  • és a tulajdonságok nem változtathatják meg a típust (invariánsak).

A PHP a 7.4-es verzió óta képes erre, és a PHP 8.0-ban minden újonnan bevezetett típus támogatja a kontravariánsságot és a kovarianciát is.

A mixed esetében a gyermek a visszatérési értéket bármilyen típusra szűkítheti, de a void nem, mivel az nem értéket, hanem annak hiányát reprezentálja. A gyermek szintén nem hagyhatja el a típusdeklarációt, mivel ez szintén érték hiányát teszi lehetővé.

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

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

Az unió típusok a paraméterekben is bővíthetők, a visszatérési értékekben pedig szűkíthetők:

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 bővíthető bool -re a paraméterben, vagy fordítva bool -re false -ra a visszatérési értékben.

A kovariancia/kontravariancia megsértése a PHP 8.0-ban végzetes hibához vezet.

*A sorozat következő részeiben bemutatjuk, hogy mik az attribútumok, milyen új függvények és osztályok jelentek meg a PHP-ben, és bemutatjuk a Just in Time Compilert.