PHP 8.0: Neuigkeiten bei Datentypen (2/4)
Die PHP-Version 8.0 wurde soeben veröffentlicht. Sie ist voll von neuen Funktionen, wie keine Version zuvor. Ihre Einführung verdient vier separate Artikel. Im zweiten Teil werden wir einen Blick auf die Datentypen werfen.
Gehen wir in der Geschichte zurück. Die Einführung von skalaren
Typ-Hinweisen war ein bedeutender Durchbruch in PHP 7. Fast wäre es nicht dazu
gekommen. Andreu Faulds, der Autor der
genialen Lösung, die dank declare(strict_types=1)
vollständig
rückwärtskompatibel und optional war, wurde von der Community harsch
abgelehnt. Glücklicherweise hat Anthony
Ferrara sie und den Vorschlag damals verteidigt, eine Kampagne gestartet und
den RFC sehr knapp verabschiedet. Uff. Die meisten Neuerungen in PHP 8 sind dem
legendären Nikita Popov zu verdanken und
sie alle gingen durch die Abstimmung wie ein Messer durch Butter. Die Welt
verändert sich zum Besseren.
PHP 8 bringt Typen zur Perfektion. Die überwiegende Mehrheit der
phpDoc-Annotationen wie @param
, @return
und
@var
werden durch native Notation ersetzt. Vor allem aber werden
die Typen von der PHP-Engine überprüft. Nur Beschreibungen von Strukturen wie
string[]
oder komplexere Anmerkungen für PHPStan werden in den
Kommentaren verbleiben.
Union-Typen
Vereinigungstypen sind eine Aufzählung von zwei oder mehr Typen, die eine Variable annehmen kann:
class Button
{
private string|object $caption;
public function setCaption(string|object $caption)
{
$this->caption = $caption;
}
}
Bestimmte Union-Typen wurden bereits in PHP eingeführt. Nullbare Typen, zum
Beispiel. Zum Beispiel ?string
, was dem Union-Typ
string|null
entspricht. Die Fragezeichenschreibweise kann als
Abkürzung betrachtet werden. Natürlich funktioniert sie auch in PHP 8, aber
man kann sie nicht mit vertikalen Balken kombinieren. Anstelle von
?string|object
müssen Sie also string|object|null
schreiben. Außerdem war iterable
immer äquivalent zu
array|Traversable
. Es mag Sie überraschen, dass float
auch ein Union-Typ ist, da er sowohl int|float
akzeptiert, aber den
Wert in float
umwandelt.
Sie können die Pseudotypen void
und mixed
nicht in
Unions verwenden, 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)
{
...
}
Auf der anderen Seite lehnt das Autowiring in Nette DI Union-Typen ab. Bislang gibt es keinen Anwendungsfall, bei dem es sinnvoll wäre, dass der Konstruktor entweder das eine oder das andere Objekt akzeptiert. Sollte ein solcher Anwendungsfall eintreten, kann das Verhalten des Containers natürlich entsprechend angepasst werden.
Die Methoden getParameterType()
, getReturnType()
und getPropertyType()
in Nette\Utils\Reflection
lösen
im Falle des Union-Typs eine Ausnahme aus (das ist in Version 3.1; in der
früheren Version 3.0 kehren diese Methoden zurück, um die
Null-Kompatibilität zu wahren).
mixed
Der Pseudotyp mixed
akzeptiert jeden Wert.
Im Falle von Parametern und Eigenschaften führt er zu demselben Verhalten,
als wenn wir keinen Typ angeben. Was ist also sein Zweck? Um zu unterscheiden,
wann ein Typ lediglich fehlt und wann er absichtlich
mixed
ist.
Im Falle von Funktions- und Methodenrückgabewerten unterscheidet sich die
Nichtangabe des Typs von der Verwendung von mixed
. Es bedeutet das
Gegenteil von void
, da es voraussetzt, dass die Funktion etwas
zurückgibt. Ein fehlender Rückgabewert führt dann zu einem fatalen
Fehler.
In der Praxis sollten Sie es nur selten verwenden, da Sie dank der Union-Typen den Wert genau angeben können. Sie ist daher nur in besonderen Situationen geeignet:
function dump(mixed $var): mixed
{
// print variable
return $var;
}
false
Im Gegensatz zu mixed
können Sie den neuen Pseudotyp
false
ausschließlich in Unionstypen verwenden. Er entstand aus der
Notwendigkeit, den Rückgabetyp von nativen Funktionen zu beschreiben, die im
Falle eines Fehlers historisch gesehen false zurückgeben:
function strpos(string $haystack, string $needle): int|false
{
}
Daher gibt es keinen Typ true
. Sie können weder
false
allein noch false|null
oder
bool|false
verwenden.
static
Der Pseudotyp static
kann nur als Rückgabetyp einer Methode
verwendet werden. Er besagt, dass die Methode ein Objekt desselben Typs wie das
Objekt selbst zurückgibt (während self
besagt, dass sie die
Klasse zurückgibt, in der die Methode definiert ist). Er eignet sich
hervorragend für die Beschreibung von fließenden Schnittstellen:
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 nicht in PHP 8 und wird auch in Zukunft nicht
eingeführt werden. Ressourcen sind ein Relikt aus der Zeit, als es in PHP noch
keine Objekte gab. Nach und nach werden Ressourcen durch Objekte ersetzt werden.
Letztendlich werden sie ganz verschwinden. Zum Beispiel ersetzt PHP 8.0 die
Ressource image durch das Objekt GdImage
und die Ressource curl
durch das Objekt CurlHandle
.
Stringable
Es ist eine Schnittstelle, die automatisch von jedem Objekt mit einer
magischen Methode __toString()
implementiert wird.
class Email
{
public function __toString(): string
{
return $this->value;
}
}
function print(Stringable|string $s)
{
}
print('abc');
print(new Email);
Es ist möglich, class Email implements Stringable
in der
Klassendefinition explizit anzugeben, aber es ist nicht notwendig.
Nette\Utils\Html
Auch die Klasse Latte spiegelt dieses
Namensschema wider, indem sie die Schnittstelle
Nette\HtmlStringable
anstelle der früheren
IHtmlString
implementiert. Objekte dieses Typs werden zum Beispiel
von Latte nicht escaped.
Typ Varianz, Kontravarianz, Kovarianz
Das Liskov-Substitutionsprinzip (LSP) besagt, dass die Erweiterungsklassen und Schnittstellenimplementierungen niemals mehr verlangen und weniger liefern dürfen als die Eltern. Das heißt, die untergeordnete Methode darf nicht mehr Argumente verlangen oder einen engeren Bereich von Parametertypen akzeptieren als die übergeordnete Methode, und umgekehrt darf sie nicht einen breiteren Bereich von Typen zurückgeben. Aber sie kann weniger zurückgeben. Und warum? Weil sonst die Vererbung zusammenbrechen würde. Eine Funktion würde ein Objekt eines bestimmten Typs akzeptieren, aber sie hätte keine Ahnung, welche Parameter sie an ihre Methoden übergeben kann und welche Typen sie zurückgeben würden. Jedes Kind könnte sie unterbrechen.
In der OOP kann das die untergeordnete Klasse:
- einen breiteren Bereich von Typen in den Parametern akzeptieren (dies wird Kontravarianz genannt)
- einen engeren Bereich von Typen zurückgeben (Kovarianz)
- und Eigenschaften können ihren Typ nicht ändern (sie sind invariant)
PHP kann dies seit Version 7.4, und alle neu eingeführten Typen in PHP 8.0 unterstützen auch Kontravarianz und Kovarianz.
Im Fall von mixed
kann das Kind den Rückgabewert auf einen
beliebigen Typ eingrenzen, aber nicht void
, da es keinen Wert,
sondern dessen Abwesenheit repräsentiert. Das Kind kann auch keine
Typdeklaration auslassen, da dies ebenfalls das Fehlen eines Wertes
ermöglicht.
class A
{
public function foo(mixed $foo): mixed
{}
}
class B extends A
{
public function foo($foo): string
{}
}
Union-Typen können auch in den Parametern erweitert und in den Rückgabewerten eingegrenzt werden:
class A
{
public function foo(string|int $foo): string|int
{}
}
class B extends A
{
public function foo(string|int|float $foo): string
{}
}
Außerdem kann false
im Parameter zu bool
erweitert
werden oder umgekehrt bool
zu false
im
Rückgabewert.
Alle Verstöße gegen die Kovarianz/Kontravarianz führen in PHP 8.0 zu einem fatalen Fehler.
*In den nächsten Teilen dieser Serie werden wir zeigen, was die Attribute sind, welche neuen Funktionen und Klassen in PHP erschienen sind und wir werden den Just in Time Compiler vorstellen.
Um einen Kommentar abzugeben, loggen Sie sich bitte ein