PHP Enums: Backed vs. Pure Enums im Vergleich
AI generated
<?php
8.4
PHP · Enums · Backed & Pure
PHP Enums: Backed vs. Pure Enums
wann welche Variante die richtige Wahl ist

Klassenkonstanten und magische Strings sind seit PHP 8.1 kein notwendiges Übel mehr. PHP Enums bringen echte Typsicherheit für endliche Wertemengen: Pure Enums für rein interne Zustände, Backed Enums für Persistenz und API-Kompatibilität, dazu Methoden, Interfaces und sichere Konvertierung mit from() und tryFrom().

14 Min. Lesezeit Enums · Backed & Pure · match · Interfaces PHP 8.1+

1. Warum Enums? Das Problem mit Klassenkonstanten und magischen Strings

Vor PHP 8.1 hat sich für endliche Wertemengen wie Bestellstatus, Rollen oder Zahlungsarten ein Muster durchgesetzt, das auf den ersten Blick funktioniert, aber strukturell fehleranfällig ist: Klassenkonstanten. Eine Klasse wie OrderStatus definiert dann const ACTIVE = 'active';, const CANCELLED = 'cancelled'; und so weiter. Das Problem: Der Rückgabetyp einer Methode, die einen solchen Status liefert, ist im besten Fall string, im schlimmsten Fall mixed. PHP kann zur Compile-Zeit nicht prüfen, ob 'aktiv' statt 'active' übergeben wurde, ob ein Tippfehler in der Konstante vorliegt oder ob überhaupt einer der gültigen Werte gemeint ist. Genau diese fehlende Typsicherheit ist der Ausgangspunkt, den PHP Enums lösen.

Magische Strings sind das zweite, eng verwandte Problem. Wird ein Status direkt als String-Literal durch die Codebasis gereicht, statt über eine benannte Konstante, gibt es keinerlei Schutznetz mehr. Ein Vertipper wie 'shiped' statt 'shipped' fällt weder der IDE noch PHPStan auf, weil beides syntaktisch gültige Strings sind. Erst zur Laufzeit, oft erst im Produktivbetrieb, fällt auf, dass ein Vergleich nie true zurückgibt. Enums schließen diese Lücke, weil ein Enum-Case ein eigener, benannter Typ ist und keine beliebige Zeichenkette an seiner Stelle akzeptiert wird.

Der dritte Aspekt ist Vollständigkeit: Klassenkonstanten geben keine Auskunft darüber, welche Werte überhaupt existieren, ohne die Klassendefinition zu lesen oder Reflection zu bemühen. PHP Enums bringen mit cases() eine eingebaute Möglichkeit, zur Laufzeit alle definierten Varianten aufzulisten, was für Validierung, Formulargenerierung und Testabdeckung ein direkter Gewinn ist. Damit sind Enums kein syntaktischer Zucker, sondern ein eigener Sprachmechanismus mit klaren Garantien, die Klassenkonstanten strukturell nicht bieten können.

2. Pure Enums: Syntax und Grundlagen

Ein Pure Enum ist die einfachste Form von PHP Enums: eine benannte Menge von Cases ohne zugrunde liegenden skalaren Wert. Die Syntax orientiert sich an Klassen, verwendet aber das Schlüsselwort enum statt class, und jeder Case wird mit case Name; deklariert. Jeder Case ist zur Laufzeit eine Singleton-Instanz des Enum-Typs. Das bedeutet konkret: Zwei Referenzen auf Status::Active sind immer identisch, ein Vergleich mit === ist damit sowohl korrekt als auch performant, weil PHP keine Objekt-Werte, sondern Objekt-Identitäten vergleicht.

Wichtig für das mentale Modell: Ein Pure Enum hat keinen impliziten Wert, den man serialisieren oder in eine Datenbank schreiben könnte. Es gibt keine ->value-Property. Wer versucht, json_encode() direkt auf ein Pure-Enum-Case anzuwenden, erhält ein leeres Objekt, keinen sinnvollen String. Das ist beabsichtigt: Pure Enums sind für rein interne Zustände gedacht, bei denen die Identität des Cases zählt, nicht sein externer Repräsentationswert. Genau diese Eigenschaft macht Pure Enums zum passenden Werkzeug für Zustandsmaschinen, interne Flags oder Strategie-Auswahl innerhalb einer Klasse.

Da jeder Case eine Singleton-Instanz ist, sind Enum-Cases von Natur aus unveränderlich: Es gibt keinen Konstruktor, den man von außen aufrufen könnte, und keine Möglichkeit, den Zustand eines Cases nachträglich zu verändern, ähnlich wie bei readonly-Properties, nur eben strukturell in der Sprache verankert statt über ein einzelnes Schlüsselwort. Damit entfällt auch die klassische Sorge um versehentliche Mutation eines geteilten Objekts, die man von normalen Klassen kennt.


declare(strict_types=1);

// Pure enum: no backing value, cases are compared by identity
enum OrderStatus
{
    case Pending;
    case Processing;
    case Shipped;
    case Delivered;
    case Cancelled;
}

function describeStatus(OrderStatus $status): string
{
    // Identity comparison works because every case is a singleton
    return match (true) {
        $status === OrderStatus::Pending => 'Awaiting confirmation',
        $status === OrderStatus::Cancelled => 'No further action possible',
        default => 'In progress',
    };
}

$current = OrderStatus::Processing;
var_dump($current === OrderStatus::Processing); // bool(true)

3. Backed Enums: int und string als Wertetyp

Backed Enums erweitern die Grundidee von Enums um einen skalaren Wert pro Case. Die Deklaration enum Status: string { case Active = 'active'; } bindet an jeden Case einen konkreten string- oder int-Wert, der über die schreibgeschützte ->value-Property abrufbar ist. Diese Bindung ist verpflichtend konsistent: Sobald ein Enum als Backed deklariert wird, muss jeder einzelne Case einen Wert desselben skalaren Typs erhalten, gemischte Typen innerhalb eines Enums sind nicht erlaubt. Der Compiler erzwingt das bereits beim Parsen.

Der praktische Nutzen liegt genau dort, wo Pure Enums an ihre Grenze stoßen: Persistenz und Serialisierung. Ein Backed Enum lässt sich verlustfrei in einer Datenbankspalte speichern, weil ->value exakt den skalaren Wert liefert, der auch in der Spalte steht. Bei einer JSON-API ist json_encode() auf ein Backed Enum das erwartete Verhalten: Es wird automatisch der Backing-Value serialisiert, kein internes Objekt. Diese beiden Eigenschaften machen Backed Enums zur naheliegenden Wahl, sobald Werte Systemgrenzen überschreiten, etwa Request-Payloads, Response-Bodies oder Zeilen aus einer relationalen Tabelle.

Ein wichtiges Detail: Der Backing-Value ist nicht dazu gedacht, im Code als Vergleichswert herumgereicht zu werden. Man sollte im Anwendungscode weiterhin mit dem Enum-Typ selbst arbeiten, etwa Status::Active, und den ->value nur an den Rändern des Systems auspacken, dort wo Persistenz oder Serialisierung tatsächlich stattfindet. Wer stattdessen ständig mit den rohen Strings vergleicht, gibt einen Großteil des Typsicherheitsgewinns wieder auf.


declare(strict_types=1);

// Backed enum: every case carries a scalar value of the same type
enum OrderStatus: string
{
    case Pending = 'pending';
    case Processing = 'processing';
    case Shipped = 'shipped';
    case Delivered = 'delivered';
    case Cancelled = 'cancelled';
}

// Persisting to a database column
$stmt = $pdo->prepare('UPDATE sales_order SET status = :status WHERE entity_id = :id');
$stmt->execute([
    'status' => OrderStatus::Shipped->value, // 'shipped'
    'id' => 4711,
]);

// Serialization to JSON: the backing value is used automatically
$payload = ['orderId' => 4711, 'status' => OrderStatus::Shipped];
echo json_encode($payload); // {"orderId":4711,"status":"shipped"}

4. Methoden in Enums: Verhalten kapseln

Ein Aspekt, der PHP Enums deutlich von klassischen Konstanten-Sets abhebt, ist die Fähigkeit, Instanzmethoden zu definieren. Ein Enum kann genau wie eine Klasse Methoden besitzen, die auf $this zugreifen und damit auf den aktuellen Case reagieren können. Eine Methode label() kann für jeden Case eine sprechende, übersetzbare Bezeichnung liefern, eine Methode color() einen Hex-Wert für die UI-Darstellung, eine Methode isFinal() einen Boolean, ob ein Status ein Endzustand ist. Der entscheidende Vorteil: Diese Logik lebt direkt beim Datentyp und nicht verstreut in Controllern, Templates oder Helper-Klassen.

Innerhalb einer Methode ist match ($this) das gängige Muster, um pro Case unterschiedliches Verhalten zu implementieren, weil $this in einer Enum-Methode immer der konkrete aufrufende Case ist. Das erlaubt es, komplexe Fallunterscheidungen, die sonst in einem separaten Service oder in wiederholten if-Kaskaden landen würden, direkt und exhaustiv im Enum selbst zu bündeln. Für Backed Enums lassen sich Methoden zusätzlich mit dem ->value kombinieren, etwa um einen Datenbankwert in eine formatierte Anzeige zu übersetzen, ohne dass der Aufrufer die Übersetzungslogik selbst kennen muss.

Methoden in PHP Enums können auch komplexere Rückgabetypen liefern als Skalare, etwa ein Array erlaubter Folgezustände einer Zustandsmaschine oder ein DTO mit Zusatzinformationen zum Case. Das macht Enums zu einem geeigneten Ort für sogenannte "smart enums", die nicht nur einen Wert repräsentieren, sondern das komplette fachliche Wissen über diesen Wert kapseln, eine Technik, die in Domain-Driven-Design-Kontexten bereits vor PHP 8.1 mit Klassenimplementierungen nachgebaut wurde und die native Enums heute deutlich vereinfachen.

5. Interfaces implementieren

Ein Enum in PHP kann ein oder mehrere Interfaces implementieren, exakt mit der gleichen Syntax wie eine Klasse: enum Status: string implements HasLabel, HasColor. Das eröffnet echten Polymorphismus für PHP Enums: Eine Funktion kann als Parametertyp das Interface erwarten, statt einen konkreten Enum-Typ, und funktioniert dann mit jedem Enum, das dieses Interface erfüllt. Das ist besonders wertvoll, wenn mehrere unabhängige Enums in einer Anwendung existieren, die alle eine gemeinsame Fähigkeit wie "hat ein Label" oder "hat eine Priorität" teilen, aber inhaltlich völlig unterschiedliche Wertemengen abbilden.

Eine wichtige Grenze: Enums können keine Klassen erweitern und auch nicht von einem anderen Enum erben, weder Pure noch Backed. Es gibt also keine klassische Vererbungshierarchie zwischen Enums. Das ist eine bewusste Design-Entscheidung der Sprache, weil Vererbung bei einer endlichen, geschlossenen Wertemenge konzeptionell wenig Sinn ergibt: Ein abgeleitetes Enum müsste entweder Cases ergänzen, was die "geschlossene Welt" des Basis-Enums verletzt, oder Cases einschränken, was ebenfalls inkonsistent wäre. Interfaces sind daher der einzige und zugleich ausreichende Mechanismus, um Verhalten zwischen mehreren Enums zu teilen.

In der Praxis kombiniert man Interfaces gerne mit abstrakten Methoden, die jeder implementierende Case zwingend füllen muss. Ein Interface HasLabel mit der Methode label(): string stellt sicher, dass PHPStan oder Psalm einen Fehler meldet, sobald ein neues Enum das Interface implementiert, aber die Methode vergisst. Diese Kombination aus Interface-Vertrag und enum-eigener Methode ist eines der stärksten Argumente für Enums gegenüber losen Konstanten, weil statische Analyse hier tatsächlich Vollständigkeit erzwingen kann.


declare(strict_types=1);

interface HasLabel
{
    public function label(): string;
}

interface HasColor
{
    public function color(): string;
}

// An enum can implement interfaces, but never extend a class
enum OrderStatus: string implements HasLabel, HasColor
{
    case Pending = 'pending';
    case Shipped = 'shipped';
    case Cancelled = 'cancelled';

    public function label(): string
    {
        return match ($this) {
            self::Pending => 'Awaiting confirmation',
            self::Shipped => 'On its way',
            self::Cancelled => 'Cancelled by customer or merchant',
        };
    }

    public function color(): string
    {
        return match ($this) {
            self::Pending => '#f59e0b',
            self::Shipped => '#3b82f6',
            self::Cancelled => '#ef4444',
        };
    }
}

// Polymorphic function: works for any enum implementing HasLabel
function renderBadge(HasLabel&HasColor $status): string
{
    return sprintf('<span style="color:%s">%s</span>', $status->color(), $status->label());
}

echo renderBadge(OrderStatus::Shipped);

6. Statische Methoden und cases()

Neben Instanzmethoden erlauben PHP Enums auch statische Methoden, genau wie Klassen. Ein häufiges Muster ist eine statische Factory-Methode, die aus einem externen, nicht direkt kompatiblen Wert den passenden Enum-Case ermittelt, etwa aus einem HTTP-Statuscode einen fachlichen Status ableitet, oder aus mehreren Legacy-Strings eines Altsystems auf einen einheitlichen Case abbildet. Das ist besonders nützlich, wenn die externe Datenquelle nicht exakt den ->value-Strings entspricht, die man selbst für das Backed Enum gewählt hat, sodass ein einfaches from() nicht ausreicht.

Die eingebaute statische Methode cases() gibt ein Array aller definierten Cases eines Enums in Deklarationsreihenfolge zurück, sowohl bei Pure als auch bei Backed Enums. Das ist der Mechanismus, mit dem sich Enums zur Laufzeit vollständig iterieren lassen, etwa um ein HTML-select-Element mit allen Statuswerten zu befüllen, eine Validierungsliste ohne Duplikation der Werte aufzubauen, oder in einem Unit-Test sicherzustellen, dass für jeden Case ein Verhalten definiert ist. Ohne cases() müsste man diese Vollständigkeit manuell pflegen, mit dem Risiko, dass ein neuer Case vergessen wird.

Kombiniert man cases() mit array_map() oder array_filter(), entstehen kompakte, deklarative Abfragen über die gesamte Wertemenge eines Enums, etwa "alle Cases, die kein Endzustand sind" oder "alle Labels für ein Dropdown". Diese Kombination ist einer der Gründe, warum Enums in der Praxis deutlich weniger Boilerplate erzeugen als äquivalente Klassenkonstanten-Lösungen, bei denen eine solche Liste separat gepflegt und bei jeder Änderung synchron gehalten werden müsste.


declare(strict_types=1);

enum OrderStatus: string
{
    case Pending = 'pending';
    case Processing = 'processing';
    case Shipped = 'shipped';
    case Delivered = 'delivered';
    case Cancelled = 'cancelled';

    /**
     * Custom static factory: maps a legacy status code to the enum case.
     */
    public static function fromLegacyCode(int $code): self
    {
        return match ($code) {
            0, 1 => self::Pending,
            2 => self::Processing,
            3 => self::Shipped,
            4 => self::Delivered,
            default => self::Cancelled,
        };
    }

    public function isFinal(): bool
    {
        return $this === self::Delivered || $this === self::Cancelled;
    }
}

// cases() lists every defined case at runtime, in declaration order
$options = array_map(
    fn (OrderStatus $status) => ['value' => $status->value, 'final' => $status->isFinal()],
    OrderStatus::cases()
);

$openStatuses = array_filter(OrderStatus::cases(), fn ($s) => !$s->isFinal());

7. from() und tryFrom(): sichere Konvertierung

Backed Enums bringen zwei eingebaute statische Methoden mit, um einen rohen Skalarwert zurück in einen Enum-Case zu konvertieren: from() und tryFrom(). Beide erwarten den skalaren Backing-Wert, etwa einen String aus einem Formularfeld oder einer Datenbankzeile, und liefern den passenden Case. Der Unterschied liegt im Fehlerfall: from() wirft einen ValueError, wenn kein Case zu dem übergebenen Wert existiert, während tryFrom() in diesem Fall stillschweigend null zurückgibt.

Diese Unterscheidung ist keine Geschmacksfrage, sondern folgt der Herkunft der Daten. Kommt der Wert aus einer Quelle, deren Gültigkeit bereits an anderer Stelle sichergestellt wurde, etwa einer eigenen Datenbankspalte mit Enum-Constraint, ist from() die richtige Wahl: Ein ungültiger Wert wäre dort ein echter, unerwarteter Fehlerzustand, der laut auffallen soll. Kommt der Wert dagegen aus einer externen, nicht vertrauenswürdigen Quelle, etwa einem Query-Parameter, einem Webhook-Payload oder Benutzereingaben, ist tryFrom() vorzuziehen, weil ein ungültiger Wert dort ein normaler, erwartbarer Fall ist, der kontrolliert behandelt werden soll, etwa mit einer 422-Antwort statt einem unabgefangenen Exception-Stacktrace.

Ein verbreiteter Fehler ist, from() unkontrolliert auf Benutzereingaben anzuwenden und den ValueError gar nicht abzufangen. Das führt im Produktivbetrieb zu 500er-Fehlern für eine völlig normale, erwartbare Situation: Ein Client sendet einen Wert, den es in dieser API-Version nicht mehr gibt. Der robustere Ansatz kombiniert tryFrom() mit einem expliziten Null-Check und einer sprechenden Fehlermeldung an den Aufrufer. So bleibt die Typsicherheit von PHP Enums erhalten, ohne dass externe, potenziell fehlerhafte Eingaben das System unkontrolliert zum Absturz bringen.


declare(strict_types=1);

enum OrderStatus: string
{
    case Pending = 'pending';
    case Processing = 'processing';
    case Shipped = 'shipped';
    case Delivered = 'delivered';
    case Cancelled = 'cancelled';
}

final class OrderStatusRequestHandler
{
    /**
     * Untrusted input (query parameter, webhook body): use tryFrom().
     */
    public function fromHttpRequest(string $rawStatus): OrderStatus
    {
        $status = OrderStatus::tryFrom($rawStatus);

        if ($status === null) {
            throw new InvalidArgumentException(
                sprintf('Unknown order status "%s" received from client', $rawStatus)
            );
        }

        return $status;
    }

    /**
     * Trusted internal source (own database column): from() is appropriate,
     * an invalid value here is a genuine, unexpected error condition.
     */
    public function fromDatabaseRow(string $dbValue): OrderStatus
    {
        return OrderStatus::from($dbValue); // throws ValueError on invalid data
    }
}

8. Enums im match-Ausdruck

Der match-Ausdruck und PHP Enums ergänzen sich besonders gut, weil match im Gegensatz zu switch strikte Vergleiche (===) verwendet und ohne break auskommt, dafür aber bei fehlendem Case und fehlendem default einen UnhandledMatchError wirft. Fehlt in einem match über ein Enum ein Case und gibt es keinen default-Zweig, bricht das Programm zur Laufzeit kontrolliert ab, statt den Fall stillschweigend zu ignorieren, wie es bei einem switch ohne passenden case und ohne default der Fall wäre.

Noch wichtiger für den Praxisalltag ist die statische Analyse: Werkzeuge wie PHPStan und Psalm kennen die vollständige Wertemenge eines Enums über cases() und können deshalb zur Analysezeit erkennen, wenn ein match über ein Enum nicht exhaustiv ist, also mindestens einen Case nicht abdeckt und keinen default-Zweig besitzt. Das verwandelt einen potenziellen Laufzeitfehler in einen sofort sichtbaren Analyse-Fehler, lange bevor der Code überhaupt deployt wird. Genau dieser Effekt ist einer der stärksten praktischen Vorteile von Enums in Kombination mit match gegenüber der alten Kombination aus String-Konstanten und switch.

Ein zusätzlicher, oft übersehener Nutzen: Wird einem bestehenden Enum ein neuer Case hinzugefügt, etwa ein zusätzlicher Bestellstatus, meldet die statische Analyse an jeder Stelle im Code einen Fehler, an der ein exhaustives match über dieses Enum existiert, aber den neuen Case nicht behandelt. Diese "Compiler hilft beim Refactoring"-Eigenschaft gibt es bei String-basierten Konstanten und switch-Anweisungen praktisch nicht, weil dort niemand automatisch weiß, an wie vielen Stellen im Code auf welche Werte reagiert wird.

9. Wann Backed, wann Pure: Entscheidungskriterien

Die Entscheidung zwischen Pure und Backed hängt in der Praxis fast immer von einer einzigen Frage ab: Muss der Wert die Grenzen des aktuellen PHP-Prozesses verlassen? Wird ein Zustand in einer Datenbank gespeichert, über eine REST- oder GraphQL-API ausgetauscht, in eine Konfigurationsdatei geschrieben oder in eine Queue-Message serialisiert, braucht er einen stabilen, expliziten skalaren Repräsentationswert, also ein Backed Enum. Bleibt ein Zustand dagegen vollständig innerhalb eines einzelnen Requests oder einer einzelnen Klasse, ohne dass er je persistiert oder über eine Schnittstelle transportiert wird, reicht ein Pure Enum, und man spart sich die Pflicht, für jeden Case einen sinnvollen String oder Integer zu erfinden.

Ein zweites Kriterium ist API-Kompatibilität über die Zeit. Der ->value eines Backed Enums wird zu einem stillschweigenden Vertrag: Ändert man ihn nachträglich, etwa von 'shipped' zu 'in_transit', brechen alle bereits gespeicherten Datensätze und alle externen Konsumenten, die den alten Wert erwarten. Der Case-Name selbst, also Shipped, kann dagegen im Code jederzeit umbenannt werden, ohne dass es die Persistenzschicht betrifft, solange der ->value stabil bleibt. Diese Trennung zwischen internem Case-Namen und externem Backing-Value ist einer der unterschätzten Vorteile von PHP Enums gegenüber Klassenkonstanten, bei denen Name und Wert oft identisch und damit gleichermaßen fragil sind.

Ein drittes Kriterium betrifft die Migration bestehenden Codes. Wer aktuell mit String-Konstanten arbeitet und auf Enums umstellt, sollte in aller Regel mit Backed Enums beginnen, weil die bisherigen String-Werte typischerweise bereits in Datenbanken, Logs und externen Systemen existieren. Ein Backed Enum mit denselben ->value-Strings wie die alten Konstanten erlaubt eine schrittweise Migration, bei der from() und tryFrom() als Brücke zwischen Alt- und Neucode dienen, ohne dass bestehende Daten migriert werden müssen. Pure Enums eignen sich dagegen besser für komplett neue, rein interne Konzepte, die von Anfang an ohne Altlasten entstehen.

Szenario Klassenkonstanten PHP Enums Vorteil
Typsicherheit string $status OrderStatus $status Ungültige Werte sind unmöglich, keine Tippfehler
IDE-Autovervollständigung Konstanten müssen bekannt sein OrderStatus:: listet alle Cases Schnellere, fehlerfreie Entwicklung
match-Exhaustiveness switch prüft nichts statisch PHPStan erkennt fehlende Cases Fehlende Fälle fallen vor dem Deploy auf
Datenbank-Persistenz Rohstring ohne Typgarantie Backed Enum mit ->value Klar typisiert, dennoch DB-kompatibel
Verhalten kapseln Separate Helper-Klasse nötig Methoden direkt im Enum Fachlogik lebt beim Datentyp
Rein interner Zustand (Pure) Willkürlicher Platzhalterwert nötig Pure Enum ohne Backing-Value Kein erfundener String für rein internen Case

Die Tabelle zeigt: Enums sind in jedem der aufgeführten Szenarien mindestens gleichwertig, meist deutlich überlegen gegenüber Klassenkonstanten. Der einzige Mehraufwand liegt in der initialen Modellierung, also der Frage, ob ein Enum Pure oder Backed sein sollte, und in der Migration bestehenden Codes. Dieser Aufwand amortisiert sich in der Regel bereits nach den ersten verhinderten Tippfehler-Bugs.

10. Zusammenfassung

PHP Enums ersetzen Klassenkonstanten und magische Strings durch einen echten, typsicheren Sprachmechanismus. Pure Enums bilden rein interne Zustände ohne Backing-Value ab, Backed Enums bringen einen stabilen ->value für Persistenz, Serialisierung und API-Austausch mit. Methoden direkt im Enum kapseln fachliche Logik am richtigen Ort, Interfaces erlauben Polymorphismus über mehrere Enums hinweg, auch wenn Enums selbst keine Klassen erweitern können. cases() liefert zur Laufzeit alle Varianten, from() und tryFrom() konvertieren sichere und unsichere Eingabequellen jeweils passend, und der match-Ausdruck macht fehlende Fälle für die statische Analyse sichtbar, statt sie zur Laufzeit zu verschlucken.

Wer heute noch mit String-Konstanten für endliche Wertemengen arbeitet, sollte die Migration zu Enums priorisieren, insbesondere dort, wo Status, Rollen, Zahlungsarten oder Versandmethoden im Code an vielen Stellen abgefragt werden. Der Umstieg ist inkrementell möglich, weil Backed Enums mit denselben Werten wie die alten Konstanten koexistieren können, während from() und tryFrom() als Brücke zwischen altem und neuem Code dienen. Der Gewinn an Typsicherheit, IDE-Unterstützung und statischer Analysefähigkeit rechtfertigt den überschaubaren Migrationsaufwand in praktisch jedem produktiven PHP-8.1-oder-neuer-Projekt.

PHP Enums: Backed vs. Pure Enums, das Wichtigste auf einen Blick

Pure vs. Backed

Pure Enums für rein interne Zustände ohne Backing-Value, Backed Enums für alles, was persistiert oder serialisiert wird.

Verhalten kapseln

Instanzmethoden wie label() oder isFinal() bündeln Fachlogik direkt am Enum, statt sie über Helper-Klassen zu verteilen.

Sichere Konvertierung

from() für vertrauenswürdige interne Quellen mit ValueError, tryFrom() für externe Eingaben mit kontrolliertem null.

match statt switch

Exhaustives match über Enum-Cases lässt PHPStan fehlende Fälle bereits vor dem Deploy erkennen.

11. FAQ: PHP Enums in der Praxis

1Was sind PHP Enums und seit welcher Version gibt es sie?
Ein eigener Sprachmechanismus seit PHP 8.1, mit dem sich eine endliche, benannte Menge von Werten typsicher abbilden lässt. Sie ersetzen Klassenkonstanten und magische Strings durch echte, vom Typsystem geprüfte Werte.
2Was ist der Unterschied zwischen Pure und Backed Enums?
Pure Enums haben keinen skalaren Wert, nur eine Case-Identität. Backed Enums binden an jeden Case einen int- oder string-Wert, abrufbar über die Property value, was Persistenz und Serialisierung ermöglicht.
3Können Enums Methoden haben?
Ja. Enums können Instanzmethoden und statische Methoden definieren, genau wie Klassen. Eine Methode kann mit match ($this) je nach Case unterschiedliches Verhalten liefern, etwa ein Label oder eine Farbe.
4Können Enums Interfaces implementieren?
Ja, mit derselben Syntax wie Klassen. Ein Enum kann aber keine Klasse erweitern und nicht von einem anderen Enum erben. Interfaces sind der einzige Weg, Verhalten zwischen mehreren Enums zu teilen.
5Was macht cases() bei Enums?
Eine eingebaute statische Methode, die ein Array aller definierten Cases eines Enums in Deklarationsreihenfolge zurückgibt, nützlich für Dropdowns, Validierungslisten und vollständige Testabdeckung.
6Was ist der Unterschied zwischen from() und tryFrom()?
Beide konvertieren einen Backing-Value in den passenden Enum-Case. from() wirft einen ValueError bei einem ungültigen Wert, tryFrom() gibt stattdessen null zurück. tryFrom() eignet sich für nicht vertrauenswürdige externe Eingaben.
7Warum ist match mit Enums sicherer als switch mit Konstanten?
match vergleicht strikt mit === und wirft ohne passenden Case und ohne default einen UnhandledMatchError. Statische Analyse-Tools wie PHPStan erkennen zudem, wenn ein match über ein Enum nicht alle Cases abdeckt.
8Wann sollte ich Backed statt Pure Enums verwenden?
Immer dann, wenn der Wert Systemgrenzen überschreitet: Datenbank-Persistenz, JSON-APIs, Konfigurationsdateien oder Queue-Nachrichten. Bleibt ein Zustand rein intern, reicht ein Pure Enum ohne Backing-Value.
9Sind Enum-Cases in PHP unveränderlich?
Ja. Jeder Case ist eine Singleton-Instanz ohne aufrufbaren Konstruktor und ohne Möglichkeit, den Zustand nachträglich zu ändern, vergleichbar mit der Unveränderlichkeit von readonly-Properties, nur strukturell im Sprachkern verankert.
10Kann ich bestehende String-Konstanten schrittweise durch Enums ersetzen?
Ja. Ein Backed Enum mit denselben value-Strings wie die alten Konstanten erlaubt eine inkrementelle Migration. from() und tryFrom() dienen dabei als Brücke zwischen bestehenden Daten und neuem, typsicherem Code.

Mironsoft

PHP Code-Reviews, Modernisierung und typsicheres Domain Modeling

Magische Strings und Klassenkonstanten im Code?

Wir prüfen bestehende PHP-Codebasen auf fragile Konstanten-Sets und ersetzen sie gezielt durch PHP Enums mit Methoden, Interfaces und sicherer Konvertierung, damit Zustände, Rollen und Statuswerte typsicher und wartbar bleiben.

Code-Review

Analyse bestehender Konstanten-Sets und magischer Strings auf Migrationspotenzial zu Enums

Modernisierung

Schrittweise Migration zu Backed und Pure Enums mit from()/tryFrom() als Kompatibilitätsbrücke

Domain Modeling

Typsichere Zustandsmodelle mit Enum-Methoden, Interfaces und PHPStan-Absicherung auf Level 5+