PHP hat in seinen neueren Versionen signifikante Verbesserungen im Typsystem eingeführt, um die Codequalität und -sicherheit zu erhöhen. Besonders hervorzuheben sind die Union Types (ab PHP 8.0) und die Intersection Types (ab PHP 8.1), die eine präzisere Typangabe ermöglichen.
Union Types erlauben es, mehrere mögliche Typen für einen Parameter,
Rückgabewert oder eine Eigenschaft zu definieren. Diese Typen werden mit
dem Pipe-Symbol (|) verknüpft.
function verarbeiteWert(int|float $zahl): string|bool {
if ($zahl > 0) {
return "Positive Zahl: " . $zahl;
}
return false;
}In diesem Beispiel: - Der Parameter $zahl kann entweder
vom Typ int oder float sein - Die Rückgabe
kann entweder ein string oder ein bool
sein
Vor PHP 8.0 wurden nullable Typen mit einem vorangestellten Fragezeichen markiert:
// Vor PHP 8.0
function getName(?string $name): ?string {
return $name;
}Mit Union Types ist dies äquivalent zu:
// Ab PHP 8.0
function getName(string|null $name): string|null {
return $name;
}Beide Schreibweisen sind gültig und führen zum gleichen Ergebnis. Das
Fragezeichen ist eine kürzere Syntax für |null.
function konfigurationsWert(string|int|float|bool $wert): void {
// Verarbeite Konfigurationswert unterschiedlicher Typen
}function verarbeiteZahlung(Kreditkarte|PayPal|Rechnung $zahlungsmethode): Transaktion {
return $zahlungsmethode->prozessieren();
}function filterListe(array $liste): array<int|string> {
// Filtert eine Liste und gibt nur Elemente zurück, die int oder string sind
}Union Types können alle in PHP verfügbaren Typen kombinieren:
int, float,
string, boolarrayiterablecallablenull, false,
mixedself, static,
parentSeit PHP 8.2 ist es auch möglich, null und
false separat zu definieren, während vorher nur der
komplette bool-Typ verwendet werden konnte.
// Ab PHP 8.2
function findElement(array $haystack, mixed $needle): int|false {
$index = array_search($needle, $haystack);
return $index;
}Intersection Types wurden in PHP 8.1 eingeführt und ermöglichen es,
Typen zu definieren, die gleichzeitig mehrere Typen (Interfaces oder
Klassen) implementieren müssen. Sie werden mit dem Ampersand-Symbol
(&) verknüpft.
function prozessiereSpezialObjekt(Serializable&Countable $objekt): void {
// $objekt muss sowohl Serializable als auch Countable implementieren
echo "Anzahl der Elemente: " . count($objekt);
$serialized = serialize($objekt);
}int, string, etc.) können
nicht mit Intersection Types kombiniert werdenfunction speichereLogEintrag(JsonSerializable&Stringable $logEintrag): void {
file_put_contents(
'log.txt',
(string)$logEintrag . PHP_EOL,
FILE_APPEND
);
file_put_contents(
'log.json',
json_encode($logEintrag->jsonSerialize()) . PHP_EOL,
FILE_APPEND
);
}Obwohl PHP keine echten Generics unterstützt, können Intersection Types dazu beitragen, die Typensicherheit zu verbessern:
/**
* @template T of Iterator&Countable
* @param T $collection
* @return int
*/
function countIterator(Iterator&Countable $collection): int {
return count($collection);
}interface Repository {
public function findById(int $id): ?object;
}
trait Cacheable {
private array $cache = [];
public function isCached(int $id): bool {
return isset($this->cache[$id]);
}
}
class UserRepository implements Repository {
use Cacheable;
// ...
}
function mitCacheArbeiten(Repository&Cacheable $repo, int $id): ?object {
if ($repo->isCached($id)) {
return $repo->findById($id);
}
return null;
}Ab PHP 8.1 können Union Types und Intersection Types miteinander
kombiniert werden. Dabei hat das &-Zeichen Vorrang vor
dem |-Zeichen:
// Eine Funktion, die entweder ein Objekt akzeptiert, das Countable und Iterator implementiert,
// oder ein einfaches Array
function zähleElemente(array|(Countable&Iterator) $items): int {
if (is_array($items)) {
return count($items);
}
// $items implementiert sowohl Countable als auch Iterator
return count($items);
}Wenn die Reihenfolge der Auswertung unklar sein könnte, sollten Klammern verwendet werden, um die Gruppierung explizit zu machen.
Sobald Union oder Intersection Types definiert sind, führt PHP Typprüfungen zur Laufzeit durch:
function process(int|string $value): void {
// ...
}
process(42); // OK
process("Hello"); // OK
process(null); // TypeError
process([]); // TypeErrorWenn ein Wert nicht einem der erwarteten Typen entspricht, wirft PHP
eine TypeError-Exception.
Union Types sollten spezifisch genug sein, um Typfehler zu erkennen, aber nicht so restriktiv, dass sie legitime Anwendungsfälle verhindern.
// Besser:
function getId(int|string $id): void { /* ... */ }
// Statt:
function getId(mixed $id): void { /* ... */ }Union Types sind besonders nützlich für öffentliche APIs, um Kompatibilität zu gewährleisten und gleichzeitig Typ-Sicherheit zu bieten:
public function setConfig(array|ConfigObject $config): void {
if (is_array($config)) {
$config = new ConfigObject($config);
}
$this->config = $config;
}Mit Union Types können oft Typumwandlungen vermieden werden:
// Ohne Union Types
function processValue($value): string {
return (string) $value;
}
// Mit Union Types
function processValue(int|float|string $value): string {
return is_string($value) ? $value : (string) $value;
}Ergänzen Sie Union Types mit expliziten Typprüfungen:
function process(int|float|string $value): string {
if (is_string($value)) {
return $value;
}
if (is_int($value)) {
return "Ganzzahl: $value";
}
return "Fließkommazahl: $value";
}PHP unterstützt immer noch keine echten Generics, was die Verwendung von typisierten Arrays einschränkt. PHPDoc-Annotationen können diese Lücke teilweise schließen:
/**
* @param array<int|string> $values
* @return array<int, string>
*/
function transformValues(array $values): array {
// ...
}PHP kann nicht ausdrücken, dass ein Typ genau eine von mehreren Möglichkeiten sein muss, ohne die anderen zu sein (disjunkte Unions):
// Es gibt keine Möglichkeit zu definieren, dass ein Wert entweder ein int ODER ein string sein muss,
// aber nicht beides (was mit "mixed" möglich wäre)Bei der Verwendung von Pipe-Zeichen in PHPDoc-Blöcken muss beachtet werden, dass diese eine andere Bedeutung haben als in Typdeklarationen:
/**
* @param int|string $value // In PHPDoc ist dies eine Union
*/
function example(int|string $value): void { // Tatsächliche PHP-Union-Typdeklaration
// ...
}