Mit PHP 8.1 wurden Enumerations (Enums) als ein neues Sprachelement eingeführt, das eine typsichere Möglichkeit bietet, eine Menge von benannten Konstanten zu definieren. Enums adressieren ein langjähriges Problem in PHP und bringen die Sprache näher an die modernen Typkonzepte anderer Programmiersprachen.
Eine Enumeration ist eine spezielle Art von Klasse, die eine feste, begrenzte Menge von möglichen Werten definiert. Jeder dieser Werte ist eine benannte Konstante, die als Instanz der Enum-Klasse behandelt wird.
enum Status
{
case Pending;
case Active;
case Suspended;
case Cancelled;
}
function updateStatus(Status $newStatus): void
{
// Verarbeite den Status...
echo "Status aktualisiert auf: " . $newStatus->name;
}
// Verwendung der Enum
updateStatus(Status::Active); // Status aktualisiert auf: Active
// Typeprüfung verhindert ungültige Werte
updateStatus("Active"); // TypeError: updateStatus(): Argument #1 ($newStatus) must be of type StatusVor PHP 8.1 wurden Mengen von zusammengehörigen Konstanten typischerweise als Klassen- oder Interfacekonstanten definiert:
// Vor PHP 8.1
class StatusConstants
{
public const PENDING = 'pending';
public const ACTIVE = 'active';
public const SUSPENDED = 'suspended';
public const CANCELLED = 'cancelled';
}
function updateStatus(string $status): void
{
// Manuelle Validierung erforderlich
if (!in_array($status, [
StatusConstants::PENDING,
StatusConstants::ACTIVE,
StatusConstants::SUSPENDED,
StatusConstants::CANCELLED
])) {
throw new InvalidArgumentException('Ungültiger Status');
}
// Status verarbeiten...
}
// Kann mit beliebigen Strings aufgerufen werden
updateStatus(StatusConstants::ACTIVE); // Funktioniert
updateStatus('unknown'); // Fehler zur LaufzeitEnumerations bieten gegenüber diesem Ansatz mehrere Vorteile:
PHP unterstützt zwei Arten von Enumerations:
Pure Enums definieren nur die möglichen Fälle ohne zugeordnete Werte:
enum Direction
{
case North;
case East;
case South;
case West;
}
function move(Direction $direction): void
{
match ($direction) {
Direction::North => echo "Bewege nach Norden",
Direction::East => echo "Bewege nach Osten",
Direction::South => echo "Bewege nach Süden",
Direction::West => echo "Bewege nach Westen",
};
}
move(Direction::East); // Bewege nach OstenBacked Enums ordnen jedem Fall einen primitiven Wert (string oder int) zu:
enum HttpStatus: int
{
case OK = 200;
case Created = 201;
case BadRequest = 400;
case Unauthorized = 401;
case Forbidden = 403;
case NotFound = 404;
case ServerError = 500;
}
function handleResponse(HttpStatus $status): void
{
if ($status->value >= 400) {
echo "Fehler: HTTP-Status {$status->value}";
} else {
echo "Erfolg: HTTP-Status {$status->value}";
}
}
handleResponse(HttpStatus::NotFound); // Fehler: HTTP-Status 404
// Konvertierung von int zu Enum ist möglich
$status = HttpStatus::from(404); // Gibt HttpStatus::NotFound zurückBei Backed Enums können Sie: - Mit ->value auf den
zugrunde liegenden Wert zugreifen - from() verwenden, um
einen primitiven Wert in den entsprechenden Enum-Fall zu konvertieren -
tryFrom() verwenden, um einen Wert zu konvertieren, der
möglicherweise nicht existiert (gibt null zurück, wenn kein passender
Fall existiert)
Enumerationen können wie Klassen Methoden und Eigenschaften enthalten:
enum PaymentStatus: string
{
case Pending = 'pending';
case Authorized = 'authorized';
case Completed = 'completed';
case Failed = 'failed';
case Refunded = 'refunded';
// Konstante für alle Zustände
public const FINAL_STATUSES = [
self::Completed,
self::Failed,
self::Refunded
];
// Methode zur Überprüfung, ob der Status endgültig ist
public function isFinal(): bool
{
return in_array($this, self::FINAL_STATUSES);
}
// Statische Factory-Methode
public static function fromDbValue(?string $value): ?self
{
return match ($value) {
'PEND' => self::Pending,
'AUTH' => self::Authorized,
'COMP' => self::Completed,
'FAIL' => self::Failed,
'RFND' => self::Refunded,
default => null
};
}
// Anwendungsspezifische Logik
public function getNextPossibleStatuses(): array
{
return match($this) {
self::Pending => [self::Authorized, self::Failed],
self::Authorized => [self::Completed, self::Failed, self::Refunded],
self::Completed => [self::Refunded],
default => []
};
}
}
// Verwendung der Methoden
$status = PaymentStatus::Authorized;
echo $status->value; // 'authorized'
if ($status->isFinal()) {
echo "Status kann nicht mehr geändert werden";
} else {
echo "Mögliche nächste Status: ";
foreach ($status->getNextPossibleStatuses() as $nextStatus) {
echo $nextStatus->value . " ";
}
}Enums können Interfaces implementieren und Traits verwenden:
interface Processable
{
public function canProcess(): bool;
public function getDescription(): string;
}
trait StatusColorTrait
{
public function getColor(): string
{
return match($this) {
OrderStatus::New => 'blue',
OrderStatus::Processing => 'orange',
OrderStatus::Shipped => 'purple',
OrderStatus::Delivered => 'green',
OrderStatus::Cancelled => 'red',
OrderStatus::Returned => 'gray',
};
}
}
enum OrderStatus: string implements Processable
{
use StatusColorTrait;
case New = 'new';
case Processing = 'processing';
case Shipped = 'shipped';
case Delivered = 'delivered';
case Cancelled = 'cancelled';
case Returned = 'returned';
public function canProcess(): bool
{
return match($this) {
self::New, self::Processing => true,
default => false
};
}
public function getDescription(): string
{
return match($this) {
self::New => 'Bestellung eingegangen',
self::Processing => 'Bestellung wird bearbeitet',
self::Shipped => 'Bestellung wurde versandt',
self::Delivered => 'Bestellung wurde zugestellt',
self::Cancelled => 'Bestellung wurde storniert',
self::Returned => 'Bestellung wurde zurückgegeben'
};
}
}
// Verwendung der Interface-Methoden und Traits
$status = OrderStatus::Shipped;
echo "Status: " . $status->getDescription() . "\n";
echo "Farbe: " . $status->getColor() . "\n";
if ($status->canProcess()) {
echo "Status kann noch verarbeitet werden";
} else {
echo "Status kann nicht mehr verarbeitet werden";
}Enums eignen sich hervorragend für die Implementierung von Zustandsautomaten:
enum DocumentState: string
{
case Draft = 'draft';
case Review = 'review';
case Approved = 'approved';
case Published = 'published';
case Archived = 'archived';
public function canTransitionTo(self $target): bool
{
return match ($this) {
self::Draft => in_array($target, [self::Review]),
self::Review => in_array($target, [self::Draft, self::Approved]),
self::Approved => in_array($target, [self::Review, self::Published]),
self::Published => in_array($target, [self::Archived]),
self::Archived => false
};
}
public static function getInitialState(): self
{
return self::Draft;
}
}
class Document
{
private DocumentState $state;
public function __construct()
{
$this->state = DocumentState::getInitialState();
}
public function getState(): DocumentState
{
return $this->state;
}
public function transitionTo(DocumentState $newState): bool
{
if (!$this->state->canTransitionTo($newState)) {
throw new InvalidArgumentException(
"Kann nicht von {$this->state->value} zu {$newState->value} wechseln"
);
}
$this->state = $newState;
return true;
}
}
// Verwendung des Zustandsautomaten
$doc = new Document();
echo "Initialer Status: " . $doc->getState()->value . "\n";
$doc->transitionTo(DocumentState::Review);
echo "Neuer Status: " . $doc->getState()->value . "\n";
// Dieser Übergang würde fehlschlagen
try {
$doc->transitionTo(DocumentState::Published);
} catch (InvalidArgumentException $e) {
echo "Fehler: " . $e->getMessage() . "\n";
}Backed Enums bieten eingebaute Validierung für eingehende Daten:
enum UserRole: string
{
case Admin = 'admin';
case Editor = 'editor';
case Author = 'author';
case Subscriber = 'subscriber';
public function hasPermission(string $permission): bool
{
$rolePermissions = [
self::Admin->value => ['create', 'read', 'update', 'delete', 'publish', 'manage_users'],
self::Editor->value => ['create', 'read', 'update', 'delete', 'publish'],
self::Author->value => ['create', 'read', 'update'],
self::Subscriber->value => ['read']
];
return in_array($permission, $rolePermissions[$this->value]);
}
}
function assignRole(User $user, string $roleName): void
{
// Sichere Konvertierung mit Validierung
$role = UserRole::tryFrom($roleName);
if ($role === null) {
throw new InvalidArgumentException("Ungültige Rolle: $roleName");
}
$user->setRole($role);
// Wir können jetzt typsicher mit der Rolle arbeiten
if ($role->hasPermission('manage_users')) {
echo "Benutzer hat Admin-Rechte erhalten";
}
}Enums sind ideal für die Definition von Konfigurationsoptionen:
enum LogLevel: string
{
case Debug = 'debug';
case Info = 'info';
case Warning = 'warning';
case Error = 'error';
case Critical = 'critical';
public function shouldLog(self $minimumLevel): bool
{
$levels = [
self::Debug->value => 0,
self::Info->value => 1,
self::Warning->value => 2,
self::Error->value => 3,
self::Critical->value => 4
];
return $levels[$this->value] >= $levels[$minimumLevel->value];
}
}
class Logger
{
private LogLevel $minimumLevel;
public function __construct(LogLevel $minimumLevel = LogLevel::Info)
{
$this->minimumLevel = $minimumLevel;
}
public function log(LogLevel $level, string $message): void
{
if ($level->shouldLog($this->minimumLevel)) {
echo "[$level->value] $message\n";
}
}
}
// Verwendung des Loggers
$logger = new Logger(LogLevel::Warning);
$logger->log(LogLevel::Debug, "Dies wird nicht angezeigt");
$logger->log(LogLevel::Info, "Dies wird nicht angezeigt");
$logger->log(LogLevel::Warning, "Dies wird angezeigt");
$logger->log(LogLevel::Error, "Dies wird angezeigt");Enums lassen sich gut mit Datenbanken kombinieren:
enum SubscriptionType: string
{
case Free = 'free';
case Basic = 'basic';
case Premium = 'premium';
case Enterprise = 'enterprise';
public function getMonthlyPrice(): int
{
return match($this) {
self::Free => 0,
self::Basic => 999, // 9.99€
self::Premium => 1999, // 19.99€
self::Enterprise => 4999, // 49.99€
};
}
public function hasFeature(string $feature): bool
{
$features = [
self::Free->value => ['basic_content'],
self::Basic->value => ['basic_content', 'full_content', 'download'],
self::Premium->value => ['basic_content', 'full_content', 'download', 'offline', 'priority_support'],
self::Enterprise->value => ['basic_content', 'full_content', 'download', 'offline', 'priority_support', 'white_label', 'api_access'],
];
return in_array($feature, $features[$this->value]);
}
}
class SubscriptionRepository
{
private PDO $pdo;
public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
}
public function findByUser(int $userId): ?Subscription
{
$stmt = $this->pdo->prepare("SELECT id, user_id, type, status, expires_at FROM subscriptions WHERE user_id = :user_id");
$stmt->execute(['user_id' => $userId]);
$data = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$data) {
return null;
}
return new Subscription(
$data['id'],
$data['user_id'],
// Konvertiere den String aus der Datenbank in einen Enum
SubscriptionType::from($data['type']),
// Weitere Felder...
);
}
public function save(Subscription $subscription): void
{
$stmt = $this->pdo->prepare("
INSERT INTO subscriptions (user_id, type, status, expires_at)
VALUES (:user_id, :type, :status, :expires_at)
ON DUPLICATE KEY UPDATE type = :type, status = :status, expires_at = :expires_at
");
$stmt->execute([
'user_id' => $subscription->getUserId(),
'type' => $subscription->getType()->value, // Speichere den Enum als String
// Weitere Felder...
]);
}
}PHP bietet einige eingebaute Funktionen zur Arbeit mit Enumerationen:
enum Size
{
case Small;
case Medium;
case Large;
case ExtraLarge;
}
// Alle Fälle einer Enumeration auflisten
$cases = Size::cases();
foreach ($cases as $case) {
echo $case->name . "\n";
}
// Namen eines Enum-Falles abrufen
$size = Size::Medium;
echo $size->name; // "Medium"
// Mit Strings vergleichen (falls notwendig)
$userInput = "Medium";
$selectedSize = null;
foreach (Size::cases() as $size) {
if ($size->name === $userInput) {
$selectedSize = $size;
break;
}
}
// Bessere Methode mit einer statischen Hilfsmethode
enum Size
{
case Small;
case Medium;
case Large;
case ExtraLarge;
public static function fromName(string $name): ?self
{
foreach (self::cases() as $case) {
if ($case->name === $name) {
return $case;
}
}
return null;
}
}
$selectedSize = Size::fromName($userInput);Aussagekräftige Namen verwenden: Wählen Sie für Enums und ihre Fälle Namen, die klar und selbsterklärend sind.
Dokumentation hinzufügen: Nutzen Sie PHPDoc, um Ihre Enums zu dokumentieren, besonders wenn sie komplexe Zustände oder Geschäftsregeln repräsentieren.
/**
* Repräsentiert den Status einer Bestellung im System.
*/
enum OrderStatus: string
{
/**
* Die Bestellung wurde gerade erstellt, aber noch nicht bezahlt.
*/
case New = 'new';
/**
* Die Bezahlung wurde erfolgreich verarbeitet.
*/
case Paid = 'paid';
// ...
}Vermeiden Sie zu viele Fälle: Eine Enumeration mit zu vielen Fällen kann schwer zu verwalten sein. Wenn Sie mehr als etwa 10-15 Fälle haben, überlegen Sie, ob Sie die Enum aufteilen oder einen anderen Ansatz verwenden können.
Nutzen Sie Backed Enums für Datenbankinteraktion: Wenn Ihre Enums mit Datenbanken interagieren, verwenden Sie vorzugsweise Backed Enums, um die Zuordnung zu Datenbankwerten zu erleichtern.
Verwenden Sie Enums statt Konstanten für verwandte Werte: Wenn Sie eine Gruppe von zusammengehörigen Konstanten haben, verwenden Sie Enums anstelle von Klassenkonstanten, um Typsicherheit zu gewährleisten.
Keine dynamische Erzeugung: Enumerationsfälle müssen zur Kompilierzeit definiert werden und können nicht dynamisch zur Laufzeit erstellt werden.
Keine Vererbung zwischen Enums: Eine Enumeration kann nicht von einer anderen Enumeration erben.
Keine Klassen als Backed-Type: Nur primitive Typen (int, string) können als Backing-Types verwendet werden.
Keine instanziierbaren Enums: Es ist nicht
möglich, Enums mit new zu instanziieren. Sie können nur auf
die vordefinierten Fälle zugreifen.