PHP 8.0: Datové typy (2/4)

před 7 měsíci od David Grudl     edit

Vyšlo PHP verze 8.0. Je tak nadupané novinkami, jako nebyla žádná verze předtím. Jejich představení si vyžádalo rovnou čtyři samostatné články. V tomto druhém se podíváme datové typy.

Vraťme se do historie. Zásadním průlomem PHP 7 bylo zavedení skalárních type hintů. Málem k tomu nedošlo. Autorku úžasného řešení Andreu Faulds, které bylo díky declare(strict_types=1) zcela zpětně kompatibilní a volitelné, komunita ošklivě odmítla. Naštěstí se jí i jejího návrhu tehdy zastal Anthony Ferrara, spustil kampaň a RFC velmi těsně prošlo. Ufff. Většinu novinek v PHP 8 má na svědomí legendární Nikita Popov a v hlasování mu prošly jako po másle. Svět se mění k lepšímu.

PHP 8 dotahuje typy k dokonalosti. Zmizí naprostá většina phpDoc anotací @param, @return a @var v kódu a nahradí je nativní zápis a hlavně kontrola enginem PHP. V komentářích zůstanou jen popisy struktur jako string[] nebo složitější anotace pro PHPStan.

Union Types

Union typy jsou výčtem dvou nebo více typů, kterých může hodnota nabývat:

class Button
{
	private string|object $caption;

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

Některé speciální union typy znalo PHP už dříve. Například nullable typy jako ?string, což je ekvivalent union typu string|null a otazníkový zápis lze považovat jen za zkratku. Pochopitelně funguje i v PHP 8, ale nelze jej kombinovat se svislítky, tedy místo ?string|object je potřeba psát plné string|object|null. Dále iterable bylo vždy ekvivalentem array|Traversable. Možná vás překvapí, že union typem je vlastně i float který ve skutečnosti akceptuje int|float, ovšem přetypovává na float.

V unionech nelze používat pseudotypy void a mixed, protože by to nedávalo žádný smysl.

Nette je na union typy připraveno. Ve Schema si s nimi rozumí Expect::from(), rozumí si s nimi i presentery, takže je můžete používat třeba v render a action metodách:

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

Naopak autowiring v Nette DI union typy odmítá. Chybí zatím use case, kde by dávalo smysl, aby třeba konstruktor přijímal buď ten nebo onen objekt. Samozřejmě když se objeví, bude možné podle toho upravit chování kontejneru.

Metody getParameterType(), getReturnType() a getPropertyType() v Nette\Utils\Reflection vyhazují v případě union typu výjimku (ve verzi 3.1, ve starší 3.0 vracejí kvůli kompatibilitě null). S union typy umí pracovat jejich noví sourozenci getParameterTypes(), getReturnTypes() a getPropertyTypes(), viz dokumentace.

mixed

Pseudotyp mixed říká, že hodnota může být úplně cokoliv.

V případě parametrů a properties jde vlastně o stejné chování, jako když žádný typ neuvedeme. K čemu je tedy dobrý? Aby se dalo rozlišit, kdy typ jednoduše chybí a kdy je opravdu mixed.

V případě návratové hodnoty funkce a metody se neuvedení typu od uvedení typu mixed liší. Jde vlastně o opak void, jelikož říká, že funkce musí něco vrátit. Chybějící return je pak fatální chybou.

V praxi byste jej měli užívat vzácně, protože díky union typům můžete hodnotu přesněji specifikovat. Hodí se tedy ve výjimečných situacích:

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

false

Nový pseudotyp false lze naopak používat jen v union typech. Vznikl z potřeby nativně popsat typ návratové hodnoty u nativních funkcí, které historicky v případě neúspěchu vracejí false:

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

Z toho důvodu neexistuje typ true, nelze používat ani samotné false nebo false|null či bool|false.

static

Pseudotyp static lze použít jen jako návratový typ metody. Říká, že metoda vrací objekt stejného typu, jako je objekt samotný (zatímco self říká, že vrací třídu, ve které je metoda definována). Což se výborně hodí pro popis fluent interfaces:

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

Tento typ v PHP 8 neexistuje a ani v budoucnu zaveden nebude. Resources jsou historickým reliktem z dob, kdy PHP ještě nemělo objekty. Postupně se budou resources za objekty nahrazovat a časem tento typ úplně zmizí. Například PHP 8.0 nahrazuje resource představující obrázek za objekt GdImage a resource spojení curl za objekt CurlHandle.

Stringable

Jde o interface, který automaticky implementuje každý objekt s magickou metodou __toString().

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

function print(Stringable|string $s)
{
}

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

V definici třídy je možné explicitně uvést class Email implements Stringable, ale není to nutné.

Tento styl pojmenování reflektuje i Nette\Utils\Html, které implementuje rozhraní Nette\HtmlStringable namísto předchozího IHtmlString Objekty toto typu pak např. Latte neescapuje.

Type variance, contravariance, covariance

Liskovové princip zaměnitelnosti (Liskov Substitution Principle – LSP) říká, že potomci třídy a implementace rozhraní nesmí nikdy vyžadovat více a poskytovat méně než rodič. Tedy, že metoda potomka nesmí vyžadovat víc argumentů nebo akceptovat u parametrů užší rozpětí typů než předek a naopak nesmí vracet širší rozpětí typů. Ale může vracet užší. Proč? Protože jinak by dědičnost vůbec nefungovala. Funkce by sice přijala objekt určitého typu, ale netušila by, jaké parametry lze metodám předávat a co budou skutečně vracet, protože jakýkoliv potomek by to mohl nabourat.

Takže v OOP platí, že potomek může:

  • v parametrech akceptovat širší rozpětí typů (tomu se říká kontravariance)
  • vracet užší rozpětí typů (kovariance)
  • a properties nemohou měnit typ (jsou invariantní)

PHP tohle umí od verze 7.4 a všechny nově zavedené typy v PHP 8.0 také podporují kontravarianci a kovarianci.

V případě mixed může potomek zúžit návratovou hodnotu na jakýkoliv typ, nikoliv však void, protože nejde o typ hodnoty, ale o její absenci. Ani potomek nemůže neuvést typ, protože to také připouští absenci.

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

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

Také union typy lze v parametrech rozšiřovat a v návratových hodnotách zužovat:

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

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

Dále false může být v parametru rozšířeno na bool nebo naopak bool v návratové hodnotě zúžen na false.

Všechny prohřešky proti kovarianci/kontravarianci vedou v PHP 8.0 k fatal error.

V příštích dílech si ukážeme co jsou to atributy, jaké se v PHP objevily nové funkce a třídy a představíme si Just in Time Compiler.

Další čtení

Komentáře (RSS)

  1. Díky za další skvělý článek. Návratový typ false jsem přidal do PHP manuálu k funkcím, které ho vrací: https://github.com/…mit/c80da7c0. Když by ještě někde chyběl, tak mi prosím dejte vědět.

    před 7 měsíci · replied [2] vrana
  2. před 7 měsíci
  3. Ty union typy, to je zlo. Místo toho, aby se jednalo o dočasné řešení, které „pomůže“, začnou to zneužívat a dostaneme se tam, odkud jsme vzešli – do dob bez typování.

    před 4 měsíci

Chcete-li odeslat komentář, přihlaste se