cyberangles blog

Enumerations in PHP: A Complete Technical Guide

Enumerations, commonly known as enums, were introduced in PHP 8.1 as a way to define a fixed set of named values. Before enums, developers used class constants, arrays, or database lookup tables to represent fixed sets of values, which often led to validation issues and lacked type safety. PHP enums provide a robust, type-safe solution for working with predefined values.

2026-06

Table of Contents#

What are Enumerations?#

Enumerations are a special kind of class that represents a group of related constants. Unlike simple constants, enums can have methods, implement interfaces, and leverage PHP's type system for better code reliability and maintainability.

Key characteristics:

  • Type-safe way to represent fixed sets of values
  • Can have methods and implement interfaces
  • Automatically final (cannot be extended)
  • Cases are singleton instances

Basic Enum Syntax#

Pure Enumerations#

Pure enums are the simplest form, containing only case names without associated values.

<?php
enum Status {
    case PENDING;
    case APPROVED;
    case REJECTED;
}
 
// Usage
class Order {
    public function __construct(
        public Status $status = Status::PENDING
    ) {}
}
 
$order = new Order(Status::APPROVED);
 
// Type-safe comparison
if ($order->status === Status::APPROVED) {
    echo "Order is approved!";
}

Backed Enumerations#

Backed enums associate scalar values (int or string) with each case, making them useful for database storage or API responses.

<?php
enum HttpStatus: int {
    case OK = 200;
    case CREATED = 201;
    case BAD_REQUEST = 400;
    case NOT_FOUND = 404;
    case SERVER_ERROR = 500;
}
 
// Usage in API response
class ApiResponse {
    public function __construct(
        public mixed $data,
        public HttpStatus $status
    ) {}
    
    public function toArray(): array {
        return [
            'data' => $this->data,
            'status' => $this->status->value
        ];
    }
}
 
$response = new ApiResponse(['user_id' => 123], HttpStatus::CREATED);
echo $response->status->value; // Output: 201

Pure vs. Backed Enumerations#

FeaturePure EnumBacked Enum
ValuesNo associated valuesScalar values (int/string)
Use CaseSimple state representationDatabase/API integration
MemoryLighterSlightly heavier
FlexibilityLess external integrationEasy serialization

Enum Methods#

Enums can contain methods, making them more powerful than simple constants.

<?php
enum UserRole: string {
    case ADMIN = 'admin';
    case EDITOR = 'editor';
    case VIEWER = 'viewer';
    
    public function getPermissions(): array {
        return match($this) {
            self::ADMIN => ['read', 'write', 'delete', 'manage_users'],
            self::EDITOR => ['read', 'write'],
            self::VIEWER => ['read']
        };
    }
    
    public function hasPermission(string $permission): bool {
        return in_array($permission, $this->getPermissions());
    }
    
    public static function fromDisplayName(string $displayName): self {
        return match($displayName) {
            'Administrator' => self::ADMIN,
            'Content Editor' => self::EDITOR,
            'Content Viewer' => self::VIEWER,
            default => throw new ValueError("Invalid display name: $displayName")
        };
    }
}
 
// Usage
$adminRole = UserRole::ADMIN;
$permissions = $adminRole->getPermissions(); // ['read', 'write', 'delete', 'manage_users']
$canDelete = $adminRole->hasPermission('delete'); // true
 
$editorFromDisplay = UserRole::fromDisplayName('Content Editor'); // UserRole::EDITOR

Enum Interfaces#

Enums can implement interfaces, allowing them to adhere to specific contracts while maintaining enum semantics.

<?php
interface Describable {
    public function getDescription(): string;
}
 
enum Color: string implements Describable {
    case RED = '#ff0000';
    case GREEN = '#00ff00';
    case BLUE = '#0000ff';
    
    public function getDescription(): string {
        return match($this) {
            self::RED => 'Primary color representing passion and energy',
            self::GREEN => 'Color of nature and growth',
            self::BLUE => 'Color of stability and trust'
        };
    }
}
 
// Usage with interface
function describeColor(Describable $color): void {
    echo $color->getDescription();
}
 
describeColor(Color::RED); // Outputs description for red

Best Practices#

1. Use Descriptive Case Names#

// Good
enum OrderStatus {
    case PENDING_PAYMENT;
    case PROCESSING;
    case SHIPPED;
    case DELIVERED;
}
 
// Avoid
enum OrderStatus {
    case PEND;
    case PROC;
    case SHIP;
    case DELV;
}

2. Leverage Type Safety#

// Good - type safety
function updateOrderStatus(OrderStatus $status): void {
    // Implementation
}
 
// Avoid - string validation needed
function updateOrderStatus(string $status): void {
    if (!in_array($status, ['pending', 'processing', 'shipped'])) {
        throw new InvalidArgumentException("Invalid status");
    }
    // Implementation
}

3. Use Backed Enums for Persistence#

// Good for database storage
enum UserType: string {
    case CUSTOMER = 'customer';
    case VENDOR = 'vendor';
    case ADMIN = 'admin';
}
 
class User {
    public function __construct(
        public string $name,
        public UserType $type
    ) {}
    
    public function save(): void {
        // $this->type->value can be stored in database
        DB::insert('users', [
            'name' => $this->name,
            'type' => $this->type->value
        ]);
    }
    
    public static function find(int $id): self {
        $data = DB::select('users', $id);
        return new self(
            $data['name'],
            UserType::from($data['type']) // Convert back to enum
        );
    }
}
enum FileType: string {
    case IMAGE = 'image';
    case DOCUMENT = 'document';
    case VIDEO = 'video';
    
    public function getAllowedExtensions(): array {
        return match($this) {
            self::IMAGE => ['jpg', 'png', 'gif', 'webp'],
            self::DOCUMENT => ['pdf', 'doc', 'docx', 'txt'],
            self::VIDEO => ['mp4', 'avi', 'mov', 'webm']
        };
    }
    
    public function getMaxSize(): int {
        return match($this) {
            self::IMAGE => 5 * 1024 * 1024, // 5MB
            self::DOCUMENT => 10 * 1024 * 1024, // 10MB
            self::VIDEO => 100 * 1024 * 1024 // 100MB
        };
    }
}

Common Use Cases#

1. State Management#

enum ProjectStatus {
    case DRAFT;
    case IN_REVIEW;
    case APPROVED;
    case PUBLISHED;
    case ARCHIVED;
    
    public function canTransitionTo(self $newStatus): bool {
        return match($this) {
            self::DRAFT => in_array($newStatus, [self::IN_REVIEW, self::ARCHIVED]),
            self::IN_REVIEW => in_array($newStatus, [self::APPROVED, self::DRAFT]),
            self::APPROVED => in_array($newStatus, [self::PUBLISHED, self::DRAFT]),
            self::PUBLISHED => in_array($newStatus, [self::ARCHIVED]),
            self::ARCHIVED => false
        };
    }
}

2. API Response Handling#

enum ApiErrorCode: int {
    case VALIDATION_ERROR = 1001;
    case AUTHENTICATION_ERROR = 1002;
    case AUTHORIZATION_ERROR = 1003;
    case NOT_FOUND = 1004;
    
    public function getHttpStatusCode(): int {
        return match($this) {
            self::VALIDATION_ERROR => 400,
            self::AUTHENTICATION_ERROR => 401,
            self::AUTHORIZATION_ERROR => 403,
            self::NOT_FOUND => 404
        };
    }
    
    public function getMessage(): string {
        return match($this) {
            self::VALIDATION_ERROR => 'The request data is invalid',
            self::AUTHENTICATION_ERROR => 'Authentication required',
            self::AUTHORIZATION_ERROR => 'Insufficient permissions',
            self::NOT_FOUND => 'Resource not found'
        };
    }
}

3. Configuration Options#

enum CacheDriver: string {
    case REDIS = 'redis';
    case MEMCACHED = 'memcached';
    case FILE = 'file';
    
    public function getConfig(): array {
        return match($this) {
            self::REDIS => [
                'host' => env('REDIS_HOST'),
                'port' => env('REDIS_PORT')
            ],
            self::MEMCACHED => [
                'servers' => explode(',', env('MEMCACHED_SERVERS'))
            ],
            self::FILE => [
                'path' => storage_path('cache')
            ]
        };
    }
}

Advanced Patterns#

1. Enum Collections with ArrayAccess#

<?php
/**
 * @template T of \BackedEnum
 */
final class EnumCollection implements \Countable, \IteratorAggregate, \ArrayAccess {
    /** @var array<T> */
    private array $enums;
    
    /**
     * @param class-string<T> $enumClass
     * @param array<T|string|int> $values
     */
    public function __construct(
        private string $enumClass,
        array $values = []
    ) {
        $this->enums = array_map(
            fn($value) => $value instanceof $enumClass ? $value : $enumClass::from($value),
            $values
        );
    }
    
    public function add(\BackedEnum $enum): void {
        if (!$enum instanceof $this->enumClass) {
            throw new \InvalidArgumentException('Invalid enum type');
        }
        $this->enums[] = $enum;
    }
    
    public function getValues(): array {
        return array_map(fn($enum) => $enum->value, $this->enums);
    }
    
    // Implement Countable, IteratorAggregate, ArrayAccess methods...
}
 
// Usage
$statusCollection = new EnumCollection(OrderStatus::class, [
    OrderStatus::PENDING,
    OrderStatus::PROCESSING
]);

2. Enum with Custom Attributes (PHP 8.0+)#

<?php
#[\Attribute]
class Description {
    public function __construct(public string $text) {}
}
 
enum PaymentMethod: string {
    #[Description('Credit Card payment')]
    case CREDIT_CARD = 'cc';
    
    #[Description('PayPal digital payment')]
    case PAYPAL = 'pp';
    
    #[Description('Bank transfer')]
    case BANK_TRANSFER = 'bt';
    
    public function getDescription(): string {
        $reflection = new \ReflectionEnumBackedCase($this, $this->name);
        $attributes = $reflection->getAttributes(Description::class);
        
        if (count($attributes) > 0) {
            return $attributes[0]->newInstance()->text;
        }
        
        return ucfirst(str_replace('_', ' ', strtolower($this->name)));
    }
}

Conclusion#

PHP enumerations provide a robust, type-safe way to work with fixed sets of values. They eliminate many common bugs associated with using strings or integers directly and make code more readable and maintainable. By leveraging enum methods, interfaces, and advanced patterns, you can create sophisticated, self-documenting code that's easier to test and maintain.

Remember to:

  • Use enums for any fixed set of related values
  • Prefer backed enums when persistence is needed
  • Leverage enum methods for behavior related to the enum values
  • Implement interfaces when enums need to adhere to contracts
  • Follow best practices for naming and organization

References#