PHP 8.0: Datentypen (2/4)
PHP Version 8.0 ist erschienen. Sie ist so vollgepackt mit Neuerungen wie keine Version zuvor. Ihre Vorstellung erforderte gleich vier separate Artikel. In diesem zweiten Teil werfen wir einen Blick auf die Datentypen.

Gehen wir zurück in die Geschichte. Der entscheidende Durchbruch von PHP
7 war die Einführung skalarer Type Hints. Fast wäre es dazu nicht gekommen.
Die Autorin der erstaunlichen Lösung, Andrea
Faulds, die dank declare(strict_types=1)
vollständig
abwärtskompatibel und optional war, wurde von der Community hässlich
abgelehnt. Glücklicherweise setzte sich damals Anthony Ferrara für sie und ihren
Vorschlag ein, startete eine Kampagne und das RFC ging sehr knapp durch. Ufff.
Die meisten Neuerungen in PHP 8 gehen auf das Konto des legendären Nikita Popov und gingen bei der Abstimmung
wie Butter durch. Die Welt verändert sich zum Besseren.
PHP 8 bringt die Typen zur Perfektion. Die absolute Mehrheit der
phpDoc-Annotationen @param
, @return
und
@var
im Code wird verschwinden und durch native Schreibweise und
vor allem durch die Kontrolle der PHP-Engine ersetzt. In Kommentaren bleiben nur
Beschreibungen von Strukturen wie string[]
oder komplexere
Annotationen für PHPStan übrig.
Union-Typen
Union-Typen sind eine Aufzählung von zwei oder mehr Typen, die ein Wert annehmen kann:
class Button
{
private string|object $caption;
public function setCaption(string|object $caption)
{
$this->caption = $caption;
}
}
Einige spezielle Union-Typen kannte PHP schon früher. Zum Beispiel nullable
Typen wie ?string
, was dem Union-Typ string|null
entspricht, und die Fragezeichen-Schreibweise kann nur als Abkürzung betrachtet
werden. Natürlich funktioniert sie auch in PHP 8, kann aber nicht mit
senkrechten Strichen kombiniert werden, d.h. anstelle von
?string|object
muss das vollständige
string|object|null
geschrieben werden. Weiterhin war
iterable
immer ein Äquivalent zu array|Traversable
.
Vielleicht überrascht es Sie, dass auch float
eigentlich ein
Union-Typ ist, der int|float
akzeptiert, jedoch zu
float
umwandelt.
In Union-Typen können die Pseudotypen void
und
mixed
nicht verwendet werden, da dies keinen Sinn
ergeben würde.
Nette ist bereit für Union-Typen. In Schema, Expect::from()
werden sie akzeptiert, und auch Presenter akzeptieren sie. Sie können sie z.B.
in Render- und Action-Methoden verwenden:
public function renderDetail(int|array $id)
{
...
}
Im Gegensatz dazu lehnt Autowiring in Nette DI Union-Typen ab. Es fehlt bisher ein Anwendungsfall, bei dem es sinnvoll wäre, dass beispielsweise ein Konstruktor entweder das eine oder das andere Objekt akzeptiert. Natürlich, wenn sich ein solcher Fall ergibt, kann das Verhalten des Containers entsprechend angepasst werden.
Die Methoden getParameterType()
, getReturnType()
und getPropertyType()
in Nette\Utils\Reflection werfen im Falle
eines Union-Typs eine Ausnahme (in Version 3.1, in der älteren Version
3.0 geben sie aus Kompatibilitätsgründen null zurück).
mixed
Der Pseudotyp mixed
besagt, dass der Wert absolut alles
sein kann.
Im Falle von Parametern und Properties ist dies eigentlich dasselbe
Verhalten, als ob wir keinen Typ angeben würden. Wozu ist er also gut? Um
unterscheiden zu können, wann der Typ einfach fehlt und wann er wirklich
mixed
ist.
Im Falle des Rückgabewerts einer Funktion oder Methode unterscheidet sich
die Nichtangabe des Typs von der Angabe des Typs mixed
. Es ist
eigentlich das Gegenteil von void
, da es besagt, dass die Funktion
etwas zurückgeben muss. Ein fehlendes return ist dann ein fataler Fehler.
In der Praxis sollten Sie ihn selten verwenden, da Sie dank Union-Typen den Wert genauer spezifizieren können. Er eignet sich also in Ausnahmesituationen:
function dump(mixed $var): mixed
{
// Variable ausgeben
return $var;
}
false
Der neue Pseudotyp false
kann hingegen nur in Union-Typen
verwendet werden. Er entstand aus der Notwendigkeit, den Typ des Rückgabewerts
bei nativen Funktionen nativ zu beschreiben, die historisch im Fehlerfall false
zurückgeben:
function strpos(string $haystack, string $needle): int|false
{
}
Aus diesem Grund existiert kein Typ true
, es kann auch nicht
false
allein oder false|null
oder
bool|false
verwendet werden.
static
Der Pseudotyp static
kann nur als Rückgabetyp einer Methode
verwendet werden. Er besagt, dass die Methode ein Objekt desselben Typs
zurückgibt wie das Objekt selbst (während self
besagt, dass sie
die Klasse zurückgibt, in der die Methode definiert ist). Dies eignet sich
hervorragend zur Beschreibung von 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
Dieser Typ existiert in PHP 8 nicht und wird auch in Zukunft nicht
eingeführt werden. Ressourcen sind ein historisches Relikt aus Zeiten, als PHP
noch keine Objekte hatte. Nach und nach werden Ressourcen durch Objekte ersetzt
und irgendwann wird dieser Typ vollständig verschwinden. Zum Beispiel ersetzt
PHP 8.0 die Ressource, die ein Bild darstellt, durch das Objekt
GdImage
und die Ressource der curl
-Verbindung durch
das Objekt CurlHandle
.
Stringable
Dies ist ein Interface, das automatisch von jedem Objekt mit der magischen
Methode __toString()
implementiert wird.
class Email
{
public function __toString(): string
{
return $this->value;
}
}
function print(Stringable|string $s)
{
}
print('abc');
print(new Email);
In der Klassendefinition kann explizit
class Email implements Stringable
angegeben werden, dies ist jedoch
nicht notwendig.
Dieser Benennungsstil spiegelt sich auch in Nette\Utils\Html
wider, das das Interface Nette\HtmlStringable
anstelle des
vorherigen IHtmlString
implementiert. Objekte dieses Typs werden
dann z.B. von Latte nicht escaped.
Typvarianz, Kontravarianz, Kovarianz
Das Liskovsche Substitutionsprinzip (LSP) besagt, dass Nachkommen einer Klasse und Implementierungen eines Interfaces niemals mehr verlangen und weniger bereitstellen dürfen als der Elternteil. Das heißt, dass die Methode eines Nachkommen nicht mehr Argumente verlangen oder bei Parametern einen engeren Typbereich akzeptieren darf als der Vorfahre und umgekehrt keinen breiteren Typbereich zurückgeben darf. Sie kann aber einen engeren zurückgeben. Warum? Weil sonst die Vererbung überhaupt nicht funktionieren würde. Eine Funktion würde zwar ein Objekt eines bestimmten Typs akzeptieren, wüsste aber nicht, welche Parameter an die Methoden übergeben werden können und was sie tatsächlich zurückgeben werden, da jeder beliebige Nachkomme dies untergraben könnte.
Daher gilt in OOP, dass ein Nachkomme:
- in Parametern einen breiteren Typbereich akzeptieren kann (dies nennt man Kontravarianz)
- einen engeren Typbereich zurückgeben kann (Kovarianz)
- und Properties den Typ nicht ändern können (sie sind invariant)
PHP kann dies seit Version 7.4 und alle neu eingeführten Typen in PHP 8.0 unterstützen ebenfalls Kontravarianz und Kovarianz.
Im Falle von mixed
kann der Nachkomme den Rückgabewert auf
jeden beliebigen Typ einschränken, jedoch nicht auf void
, da es
sich nicht um einen Werttyp, sondern um dessen Abwesenheit handelt. Auch der
Nachkomme kann den Typ nicht weglassen, da dies ebenfalls die Abwesenheit
zulässt.
class A
{
public function foo(mixed $foo): mixed
{}
}
class B extends A
{
public function foo($foo): string
{}
}
Auch Union-Typen können in Parametern erweitert und in Rückgabewerten eingeschränkt werden:
class A
{
public function foo(string|int $foo): string|int
{}
}
class B extends A
{
public function foo(string|int|float $foo): string
{}
}
Weiterhin kann false
im Parameter auf bool
erweitert werden oder umgekehrt bool
im Rückgabewert auf
false
eingeschränkt werden.
Alle Verstöße gegen Kovarianz/Kontravarianz führen in PHP 8.0 zu einem fatalen Fehler.
In den nächsten Teilen zeigen wir, was Attribute sind, welche neuen Funktionen und Klassen in PHP aufgetaucht sind und stellen den Just-in-Time-Compiler vor.
Um einen Kommentar abzugeben, loggen Sie sich bitte ein