Enums arrived in PHP 8.1 and are much more than a typed list of constants. Backed enums with string or int values, methods on enums, interface implementation, cases as function arguments – these are tools that eliminate an entire class of errors related to magic strings and unvalidated values. I show all the capabilities with practical examples from Magento 2.
Pure enum vs Backed enum
<?php
declare(strict_types=1);
// Pure enum - names only, no values
enum Direction
{
case North;
case South;
case East;
case West;
}
// Backed int enum - case has an assigned int value
enum HttpStatus: int
{
case Ok = 200;
case Created = 201;
case NoContent = 204;
case BadRequest = 400;
case Unauthorized = 401;
case Forbidden = 403;
case NotFound = 404;
case UnprocessableEntity = 422;
case InternalServerError = 500;
}
// Backed string enum - case has an assigned string value
enum OrderStatus: string
{
case Pending = 'pending';
case Processing = 'processing';
case Complete = 'complete';
case Cancelled = 'cancelled';
case Closed = 'closed';
case Holded = 'holded';
}
// Conversion to/from backing value
$status = OrderStatus::from('complete'); // OrderStatus::Complete
$status = OrderStatus::tryFrom('unknown'); // null (does not throw)
$value = OrderStatus::Complete->value; // 'complete'
$name = OrderStatus::Complete->name; // 'Complete'
// List all cases
$allStatuses = OrderStatus::cases();
// [OrderStatus::Pending, OrderStatus::Processing, ...]
Methods on enums
<?php
declare(strict_types=1);
enum OrderStatus: string
{
case Pending = 'pending';
case Processing = 'processing';
case Complete = 'complete';
case Cancelled = 'cancelled';
case Holded = 'holded';
public function label(): string
{
return match($this) {
self::Pending => 'Pending payment',
self::Processing => 'Processing',
self::Complete => 'Complete',
self::Cancelled => 'Cancelled',
self::Holded => 'On hold',
};
}
public function color(): string
{
return match($this) {
self::Pending => 'yellow',
self::Processing => 'blue',
self::Complete => 'green',
self::Cancelled => 'red',
self::Holded => 'gray',
};
}
public function allowedTransitions(): array
{
return match($this) {
self::Pending => [self::Processing, self::Cancelled],
self::Processing => [self::Complete, self::Holded, self::Cancelled],
self::Holded => [self::Processing, self::Cancelled],
self::Complete,
self::Cancelled => [],
};
}
public function canTransitionTo(self $newStatus): bool
{
return in_array($newStatus, $this->allowedTransitions(), true);
}
public function isFinal(): bool
{
return empty($this->allowedTransitions());
}
}
// Usage
$status = OrderStatus::Processing;
echo $status->label(); // "Processing"
echo $status->color(); // "blue"
if ($status->canTransitionTo(OrderStatus::Complete)) {
// allowed transition
}
$current = OrderStatus::Pending;
$next = OrderStatus::Complete;
if (!$current->canTransitionTo($next)) {
throw new \RuntimeException(
"Invalid transition: {$current->label()} => {$next->label()}"
);
}
Enums implementing interfaces
<?php
declare(strict_types=1);
interface HasLabel
{
public function label(): string;
}
interface HasIcon
{
public function icon(): string;
}
enum PaymentMethod: string implements HasLabel, HasIcon
{
case CreditCard = 'checkmo';
case BankTransfer = 'banktransfer';
case PayPal = 'paypal_express';
case CashOnDelivery = 'cashondelivery';
public function label(): string
{
return match($this) {
self::CreditCard => 'Credit card',
self::BankTransfer => 'Bank transfer',
self::PayPal => 'PayPal',
self::CashOnDelivery => 'Cash on delivery',
};
}
public function icon(): string
{
return match($this) {
self::CreditCard => 'icon-credit-card',
self::BankTransfer => 'icon-bank',
self::PayPal => 'icon-paypal',
self::CashOnDelivery => 'icon-cash',
};
}
public function requiresOnlineCapture(): bool
{
return match($this) {
self::CreditCard, self::PayPal => true,
default => false,
};
}
}
// Function accepts interface - works with any enum implementing it
function renderPaymentBadge(HasLabel&HasIcon $method): string
{
return sprintf(
'<span class="%s">%s</span>',
$method->icon(),
$method->label()
);
}
echo renderPaymentBadge(PaymentMethod::PayPal);
Constants on enums
<?php
declare(strict_types=1);
enum Priority: int
{
case Low = 1;
case Medium = 5;
case High = 10;
case Critical = 100;
// Constants on enum (PHP 8.3+: can be typed)
const int DEFAULT = 5;
const int MIN = 1;
const int MAX = 100;
// Constant can be a case
const self FALLBACK = self::Low;
public function isUrgent(): bool
{
return $this->value >= self::High->value;
}
}
Priority::from(Priority::DEFAULT); // Priority::Medium
Enums in Magento 2
<?php
declare(strict_types=1);
// Replace magic strings in observers and plugins
enum ProductType: string
{
case Simple = 'simple';
case Configurable = 'configurable';
case Bundle = 'bundle';
case Virtual = 'virtual';
case Downloadable = 'downloadable';
case Grouped = 'grouped';
public function isPhysical(): bool
{
return match($this) {
self::Simple, self::Configurable, self::Bundle, self::Grouped => true,
self::Virtual, self::Downloadable => false,
};
}
public function supportsQty(): bool
{
return $this !== self::Grouped;
}
}
class ProductProcessor
{
public function process(\Magento\Catalog\Api\Data\ProductInterface $product): void
{
$type = ProductType::from($product->getTypeId());
if ($type->isPhysical()) {
$this->calculateShipping($product);
}
if (!$type->supportsQty()) {
return;
}
$this->updateInventory($product);
}
}
Enums in arrays and collections
<?php
// Enum as array key (PHP 8.1+)
$labels = [
OrderStatus::Pending => 'Awaiting payment',
OrderStatus::Processing => 'Processing in progress',
OrderStatus::Complete => 'Shipped',
];
echo $labels[OrderStatus::Pending]; // "Awaiting payment"
// Filter collection by enum
$orders = $this->orderRepository->getList($searchCriteria)->getItems();
$pending = array_filter(
$orders,
fn($order) => OrderStatus::tryFrom($order->getStatus()) === OrderStatus::Pending
);
// Group by status
$grouped = [];
foreach ($orders as $order) {
$status = OrderStatus::tryFrom($order->getStatus()) ?? OrderStatus::Pending;
$grouped[$status->value][] = $order;
}
Summary
PHP 8.1 enums are more than typed constants. Backed enums eliminate magic strings and provide safe conversion to/from the backing value. Methods on enums move logic related to a given type to where it belongs. Interfaces on enums enable polymorphism. In Magento 2 enums work best everywhere you previously used class-of-constants – order statuses, product types, payment and shipping method codes.
