Abschnitt 13 · Lektion 2
Statisch, Konstanten, Enums
Bisher gehörte alles, was in einer Klasse stand, zu einem einzelnen Objekt: Jede Notiz hat ihren eigenen Titel, jeder Posten seinen eigenen Preis. Manches gehört aber nicht zu einem Objekt, sondern zur Klasse als Ganzes.
Konstanten und static
<?php
class Notiz
{
public const MAX_LAENGE = 20;
private static int $angelegt = 0;
public function __construct(public readonly string $titel)
{
self::$angelegt++;
}
public function kurz(): string
{
return substr($this->titel, 0, self::MAX_LAENGE);
}
public static function anzahl(): int
{
return self::$angelegt;
}
}
$eins = new Notiz("Milch kaufen");
$zwei = new Notiz("Brot holen und Kaffee mitnehmen");
echo $eins->kurz(), "\n";
echo $zwei->kurz(), "\n";
// Beides ohne ein Objekt in der Hand: die Konstante und der Zaehler.
echo "Obergrenze: ", Notiz::MAX_LAENGE, "\n";
echo "Angelegt: ", Notiz::anzahl(), "\n"; Eine Klassenkonstante steht mit const in der Klasse und ändert sich nie. Von innen liest du
sie mit self::MAX_LAENGE, von außen mit Notiz::MAX_LAENGE, und dafür brauchst du kein Objekt.
Der Doppelpunkt-Doppelpunkt ist dabei das Gegenstück zum Pfeil: Der Pfeil geht an ein Objekt, die
zwei Doppelpunkte gehen an die Klasse.
Eine statische Eigenschaft gehört ebenfalls der Klasse, kann sich aber ändern. Im Beispiel zählt
self::$angelegt mit, wie viele Notizen es insgesamt gibt. Das ist genau eine Zahl für alle Objekte
zusammen, nicht eine je Objekt.
Und in einer statischen Methode gibt es kein $this. Sie wird ja an der Klasse aufgerufen, und
da ist kein Objekt, auf das $this zeigen könnte.
Im Reiter Debug ist das wörtlich zu sehen. Nach Aufzeichnen steht an der Zeile in anzahl()
„Noch keine Variablen”, denn dort gibt es weder Parameter noch $this. Und self::$angelegt
taucht in keiner der Listen auf, auch nicht im Konstruktor, wo $this->titel steht: Die Liste zeigt
Variablen und das Objekt, und der Zähler gehört keinem von beiden.
Wofür static wirklich gut ist
<?php
class Notiz
{
public function __construct(
public readonly string $titel,
public readonly bool $erledigt,
) {
}
// Ein zweiter Konstruktor waere praktisch. Den gibt es in PHP
// nicht, eine statische Methode tut dieselbe Arbeit.
public static function ausZeile(string $zeile): self
{
$teile = explode("|", $zeile);
return new self(trim($teile[0]), trim($teile[1] ?? "") === "x");
}
}
$eins = new Notiz("Milch kaufen", false);
$zwei = Notiz::ausZeile("Brot holen | x");
foreach ([$eins, $zwei] as $notiz) {
echo $notiz->titel, ": ", $notiz->erledigt ? "erledigt" : "offen", "\n";
} PHP erlaubt genau einen Konstruktor je Klasse. Wenn ein Objekt auf zwei Wegen entstehen soll, einmal
aus Einzelwerten und einmal aus einer Zeile Text, ist eine statische Erzeugermethode die
Antwort. Sie baut das Objekt und gibt es zurück, und der Rückgabetyp self heißt „ein Objekt dieser
Klasse”.
Das ist der Fall, für den static gedacht ist. Der Kurs benutzt es sonst sparsam, und der Grund ist
derselbe wie bei global in Lektion 6.4: Eine statische Eigenschaft ist ein Wert, an den jede
Stelle im Programm herankommt und den jede Stelle ändern kann. Für einen Zähler ist das in Ordnung.
Für alles, was das Programm steuert, wird es der Ort, an dem am Ende niemand mehr sagen kann, wer
den Wert eigentlich gesetzt hat.
Der Fall, für den man früher Konstanten genommen hat
<?php
const STATUS_OFFEN = "offen";
const STATUS_ERLEDIGT = "erledigt";
const STATUS_VERWORFEN = "verworfen";
function beschriftung(string $status): string
{
if ($status === STATUS_OFFEN) {
return "noch zu tun";
} elseif ($status === STATUS_ERLEDIGT) {
return "fertig";
}
return "unbekannt";
}
// Die Konstante ist nur ein Angebot. Der Parameter nimmt jede
// Zeichenkette, und niemand haelt einen davon ab, eine andere
// hinzuschreiben.
echo beschriftung(STATUS_OFFEN), "\n";
echo beschriftung("Offen"), "\n";
echo beschriftung("gestern erledigt"), "\n"; Sehr oft gibt es nicht eine Zahl, sondern eine kleine Liste erlaubter Werte: offen, erledigt, verworfen. Drei Konstanten, und alle sind froh.
Das Beispiel zeigt, warum sie nicht reichen. beschriftung(string $status) nimmt jede beliebige
Zeichenkette entgegen, denn STATUS_OFFEN ist ja nur ein anderer Name für "offen". "Offen" mit
großem O geht durch, "gestern erledigt" auch, und beide landen still im letzten Zweig. Kein
Fehler, keine Warnung, ein falsches Ergebnis. Genau die Sorte Problem aus Lektion 7.1.
Ein Enum ist dieselbe Liste, nur als Typ
<?php
enum Status: string
{
case Offen = "offen";
case Erledigt = "erledigt";
case Verworfen = "verworfen";
}
function beschriftung(Status $status): string
{
return match ($status) {
Status::Offen => "noch zu tun",
Status::Erledigt => "fertig",
Status::Verworfen => "weggelegt",
};
}
echo beschriftung(Status::Offen), "\n";
// Die ganze Liste, ohne sie ein zweites Mal aufzuschreiben.
foreach (Status::cases() as $fall) {
echo " ", $fall->name, " steht fuer ", $fall->value, "\n";
}
// Aus Text wird ein Fall, wenn es einen gibt.
var_dump(Status::tryFrom("erledigt"));
var_dump(Status::tryFrom("Offen"));
try {
Status::from("Offen");
} catch (ValueError $fehler) {
echo "from() sagt: ", $fehler->getMessage(), "\n";
} Ein Enum (seit PHP 8.1) schreibt die Liste einmal auf und macht daraus einen Typ. Status
steht dann in der Typangabe, und was kein Status ist, kommt gar nicht erst herein. Der Teil
: string dahinter heißt „jeder Fall hat einen Textwert”, den du über ->value bekommst. Ohne ihn
gäbe es die Fälle trotzdem, nur eben ohne Wert zum Speichern.
Vier Dinge brauchst du im Alltag:
Status::Offenist ein Fall, und zwar immer derselbe. Ein Vergleich mit===funktioniert deshalb.Status::cases()gibt alle Fälle als Array, in der Reihenfolge, in der sie in der Klasse stehen. Damit kannst du über die Liste laufen, ohne sie abzuschreiben.Status::tryFrom("erledigt")sucht den Fall zu einem Textwert und liefertnull, wenn es keinen gibt. Das ist der Weg für Daten von außen, etwa aus einem Formular.Status::from("Offen")tut dasselbe, wirft aber einenValueError, stattnullzu liefern. Das ist der Weg für Daten, denen du vertraust, weil ein Fehler dort ein echter Fehler ist.
Der ->name daneben ist der Name des Falls im Code (Offen), nicht sein Wert. Praktisch für
Protokolle, ungeeignet zum Speichern: Ein Wert in der Datenbank soll sich nicht ändern, nur weil du
einen Fall umbenennst.
Im Reiter Debug siehst du beides nebeneinander: In der Schleife über cases() steht fall als
Status {name: "Offen", value: "offen"}, ein Fall ist also ein Objekt mit genau diesen zwei
Angaben, und eben kein Text.
Was „vollständig” bei einem match wirklich heißt
<?php
enum Status: string
{
case Offen = "offen";
case Erledigt = "erledigt";
case Verworfen = "verworfen";
}
// Hier fehlt Status::Verworfen, und zwar mit Absicht.
function beschriftung(Status $status): string
{
return match ($status) {
Status::Offen => "noch zu tun",
Status::Erledigt => "fertig",
};
}
echo beschriftung(Status::Offen), "\n";
echo beschriftung(Status::Erledigt), "\n";
try {
echo beschriftung(Status::Verworfen), "\n";
} catch (\UnhandledMatchError $fehler) {
echo "Aufgefallen: ", $fehler->getMessage(), "\n";
} Über ein Enum lässt sich ein match schreiben, das jeden Fall behandelt und kein default
braucht. Genau darauf zielt der ganze Aufwand.
Sei dabei genau: PHP prüft die Vollständigkeit nicht beim Einlesen der Datei. Es merkt die Lücke
erst, wenn wirklich ein Fall vorbeikommt, für den nichts dasteht, und wirft dann einen
UnhandledMatchError. Der Gewinn ist also kein Fehler vor dem Start, sondern etwas fast genauso
Wertvolles: Der vergessene Fall wird laut, statt still in ein default zu rutschen und dort
eine harmlos aussehende Antwort zu bekommen.
Ein default in einem match über ein Enum nimmt dir genau diesen Alarm wieder weg. Schreib es
deshalb nur dort hin, wo du wirklich einen Sammelfall meinst.
Die Faustregel
Drei Konstanten sind drei Zeichenketten mit besseren Namen. Ein Enum ist ein Typ, und der Unterschied zeigt sich nicht beim Aufschreiben, sondern ein halbes Jahr später, wenn ein vierter Zustand dazukommt und dich jede Stelle im Programm darauf hinweist, dass sie ihn noch nicht kennt.
Zum Mitnehmen
Manches gehört zur Klasse und nicht zum einzelnen Objekt: eine Obergrenze, ein Umrechnungsfaktor, die Liste der erlaubten Zustände. Für die ersten beiden gibt es Konstanten und static. Für die dritte gibt es seit PHP 8.1 etwas Besseres.
Jetzt du
Basis Konto, kostenlosZu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.
Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.
In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.
Was in dieser Lektion steckt
-
Artikel mit 5 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe, dein Code läuft auf einem Server
Öffnet sich mit dem Basis Konto.