PHP 8.0: Datové typy (2/4)
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).
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.
Komentáře
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.
#1 vrana Hm, mi to smázli: https://news-web.php.net/…oc.cvs/17645
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í.
Chcete-li odeslat komentář, přihlaste se