PHP 8.0: Novosti v podatkovnih vrstah (2/4)

pred 3 leti od David Grudl  

Pravkar je bila izdana različica PHP 8.0. Polna je novih funkcij, kot jih ni imela še nobena različica. Njihova predstavitev si zasluži štiri ločene članke. V drugem delu si bomo ogledali podatkovne vrste.

Vrnimo se v zgodovino. Uvedba skalarnih namigov na tipe je bila v PHP 7 pomembna prelomnica. Skoraj se ni zgodila. Andreu Faulds, avtor genialne rešitve, ki je bila zaradi spletne strani declare(strict_types=1) v celoti združljiva za nazaj in neobvezna , je skupnost ostro zavrnila. Na srečo je Anthony Ferrara takrat branil njo in predlog, sprožil kampanjo in RFC je bil sprejet zelo tesno. Uf. Za večino novosti v PHP 8 je zaslužen legendarni Nikita Popov in vse so šle skozi glasovanje kot nož skozi maslo. Svet se spreminja na bolje.

PHP 8 pripelje vrste do popolnosti. Veliko večino opomb phpDoc, kot so @param, @return in @var, bo nadomestil domači zapis. Najpomembneje pa je, da bo tipe preverjal gonilnik PHP. V komentarjih bodo ostali le opisi struktur, kot je string[], ali kompleksnejše opombe za PHPStan.

Tipi zvez

Tipi unije so naštevanje dveh ali več tipov, ki jih lahko sprejme spremenljivka:

class Button
{
	private string|object $caption;

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

Nekatere vrste unionov so bile v PHP uvedene že prej. Ničelne vrste, na primer. Na primer ?string, ki je enakovreden sindikalnemu tipu string|null. Zapis z vprašalnim znakom se lahko šteje za okrajšavo. Seveda deluje tudi v PHP 8, vendar ga ne morete kombinirati z navpičnimi črtami. Tako morate namesto ?string|object napisati string|object|null. Poleg tega je bil tip iterable vedno enakovreden tipu array|Traversable. Morda vas bo presenetilo, da je float prav tako sindikalni tip, saj sprejema oba tipa int|float, vendar vrednost odvrže v float.

Psevdotipov void in mixed ne morete uporabiti v unijah, saj bi bilo to nesmiselno.

Nette je pripravljen za tipe zvez. V shemi Expect::from() jih sprejema, prav tako jih sprejemajo predstavniki. Uporabljate jih lahko na primer v metodah upodabljanja in akcije:

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

Po drugi strani pa samodejno napeljevanje v Nette DI zavrača tipe zvez. Zaenkrat ni primera uporabe, v katerem bi bilo smiselno, da konstruktor sprejme en ali drug objekt. Seveda pa bo, če se tak primer uporabe pojavi, mogoče ustrezno prilagoditi obnašanje vsebnika.

Metode getParameterType(), getReturnType() in getPropertyType() v Nette\Utils\Reflection v primeru tipa union mečejo izjemo (to je v različici 3.1; v prejšnji različici 3.0 se te metode vrnejo zaradi ohranjanja ničelne združljivosti).

mixed

Psevdotip mixed sprejme katero koli vrednost.

V primeru parametrov in lastnosti povzroči enako obnašanje, kot če ne navedemo nobenega tipa. Kakšen je torej njegov namen? Razlikovati, kdaj tip zgolj manjka in kdaj je namerno mixed.

V primeru povratnih vrednosti funkcij in metod se neopredelitev tipa razlikuje od uporabe mixed. Pomeni nasprotno od void, saj zahteva, da funkcija nekaj vrne. Manjkajoča vrnitev takrat povzroči usodno napako.

V praksi bi jo morali redko uporabljati, saj lahko zaradi unionskih tipov vrednost natančno določimo. Zato je primerna le v edinstvenih primerih:

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

false

Za razliko od mixed lahko novi psevdotip false uporabljate izključno v tipih zvez. Nastal je zaradi potrebe po opisu vrnitvenega tipa izvornih funkcij, ki v preteklosti v primeru neuspeha vračajo false:

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

Zato tip true ne obstaja. Prav tako ne morete uporabiti samo tipa false, niti false|null, niti bool|false.

static

Psevdotip static je mogoče uporabiti samo kot povratni tip metode. Pove, da metoda vrne objekt istega tipa kot sam objekt (medtem ko self pove, da vrne razred, v katerem je metoda definirana). Odličen je za opisovanje tekočih vmesnikov:

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

Ta vrsta ne obstaja v PHP 8 in ne bo uvedena v prihodnosti. Viri so ostanek iz časov, ko PHP še ni imel predmetov. Postopoma bodo vire nadomestili predmeti. Sčasoma bodo popolnoma izginili. PHP 8.0 na primer nadomešča vir image z objektom GdImage, vir curl pa z objektom CurlHandle.

Stringable

To je vmesnik, ki ga vsak objekt samodejno implementira s čarobno metodo __toString().

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

function print(Stringable|string $s)
{
}

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

V definiciji razreda je mogoče izrecno navesti class Email implements Stringable, vendar to ni potrebno.

Nette\Utils\Html prav tako odraža to poimenovalno shemo z implementacijo vmesnika Nette\HtmlStringable namesto prejšnjega IHtmlString. Objektov te vrste na primer Latte ne pobegne.

Tip variance, kontravariance, kovariance

Liskovo načelo nadomeščanja (LSP) določa, da razširitveni razredi in izvedbe vmesnikov nikoli ne smejo zahtevati več in zagotavljati manj kot nadrejeni razred. To pomeni, da podrejena metoda ne sme zahtevati več argumentov ali sprejeti ožjega razpona tipov za parametre kot nadrejena, in obratno, ne sme vrniti širšega razpona tipov. Lahko pa jih vrne manj. Zakaj? Ker bi se sicer dedovanje prekinilo. Funkcija bi sprejela objekt določenega tipa, vendar ne bi vedela, katere parametre lahko posreduje svojim metodam in katere tipe bi te vrnile. Vsak otrok bi jo lahko prekinil.

V OOP lahko torej podrejeni razred:

  • sprejme širši razpon tipov v parametrih (to se imenuje kontravariantnost)
  • vrniti ožji razpon tipov (kovarianca)
  • in lastnosti ne morejo spremeniti tipa (so invariantne).

PHP to omogoča že od različice 7.4, vse novo uvedene vrste v PHP 8.0 pa podpirajo tudi kontravariantnost in kovariantnost.

V primeru mixed lahko otrok zoži povratno vrednost na katero koli vrsto, ne pa tudi void, saj ne predstavlja vrednosti, temveč njeno odsotnost. Otrok prav tako ne more izpustiti deklaracije tipa, saj to prav tako omogoča odsotnost vrednosti.

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

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

Tipe zvez je mogoče tudi razširiti pri parametrih in zožiti pri povratnih vrednostih:

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

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

Poleg tega je mogoče false razširiti na bool v parametru ali obratno bool na false v povratni vrednosti.

Vsi prekrški proti kovarianci/kontravarianci vodijo do usodne napake v PHP 8.0.

V naslednjih delih te serije bomo pokazali, kaj so atributi, katere nove funkcije in razredi so se pojavili v PHP in predstavili bomo Just in Time Compiler.