Exploring the Power of PHP Enums in Domain-Driven Design

Exploring the Power of PHP Enums in Domain-Driven Design

Last Updated: July 16, 2026By Tags: , , , , , ,

Introduction

For years, PHP developers had to rely on cumbersome workarounds to represent a fixed set of values. We used class constants, arrays, or third-party packages like myclabs/php-enum to mock enumerations. This lack of native support often led to leaky domain models, excessive validation logic, and magic strings scattered across codebases.

With the release of PHP 8.1, native Enums finally arrived, fundamentally changing how we construct domain models. Enums in PHP aren’t just simple scalar values; they are full-fledged objects capable of holding state, implementing interfaces, and defining methods. In this article, we’ll dive into how you can leverage PHP Enums to build robust, expressive applications using Domain-Driven Design (DDD) principles.

Core Concepts: Why Enums Matter

In Domain-Driven Design, the concept of a “Value Object” is critical. A Value Object represents a descriptive aspect of the domain with no conceptual identity—it is defined entirely by its attributes. For example, an order’s status (e.g., Pending, Shipped, Delivered) is a perfect candidate for a Value Object.

Before PHP 8.1, validating an order status meant checking a string against a predefined array of allowed statuses. If a typo occurred, it could easily slip past static analysis tools, causing runtime errors. Native Enums solve this by providing type-safe, built-in validation. If a function expects an OrderStatus enum, you cannot accidentally pass it the string "pending" or "shippd". The PHP engine will throw a TypeError.

The Anatomy of PHP Enums

PHP offers two types of Enums: Pure Enums and Backed Enums.

Pure Enums

A Pure Enum is an enumeration that does not map to any underlying scalar value (like a string or integer). It exists purely as a singleton object in memory. They are useful for internal domain logic where the value doesn’t need to be serialized to a database.

Backed Enums

Backed Enums map directly to a string or integer value. This is crucial for web applications because state usually needs to be saved to a database or sent over an API. A Backed Enum ensures that the internal representation is type-safe, while the serialized representation is a standard scalar type.

Enums as Rich Domain Models

Because PHP Enums can contain methods, they can encapsulate business rules related to their specific state. Instead of writing massive switch statements in your services or controllers, you can delegate that logic directly to the Enum itself.

For instance, an OrderStatus enum can contain a method that determines if the order is eligible for a refund. This encapsulates the logic exactly where it belongs, ensuring your services remain clean and focused on orchestration.

Code Example: Implementing a Rich Backed Enum

Let’s look at an example of a Backed Enum representing an Order Status, complete with encapsulated business logic.

<?php

namespace App\Domain\Order;

enum OrderStatus: string
{
    case PENDING = 'pending';
    case PROCESSING = 'processing';
    case SHIPPED = 'shipped';
    case DELIVERED = 'delivered';
    case CANCELLED = 'cancelled';

    /**
     * Encapsulated domain logic: Can the order be cancelled?
     */
    public function canBeCancelled(): bool
    {
        return match($this) {
            self::PENDING, self::PROCESSING => true,
            self::SHIPPED, self::DELIVERED, self::CANCELLED => false,
        };
    }

    /**
     * Generate a human-readable label for the UI.
     */
    public function getLabel(): string
    {
        return match($this) {
            self::PENDING => 'Awaiting Payment',
            self::PROCESSING => 'Preparing for Shipment',
            self::SHIPPED => 'On the Way',
            self::DELIVERED => 'Arrived',
            self::CANCELLED => 'Order Cancelled',
        };
    }
    
    /**
     * Determine the allowed transitions to prevent invalid state changes.
     */
    public function canTransitionTo(OrderStatus $newStatus): bool
    {
        $allowedTransitions = [
            self::PENDING->value => [self::PROCESSING, self::CANCELLED],
            self::PROCESSING->value => [self::SHIPPED, self::CANCELLED],
            self::SHIPPED->value => [self::DELIVERED],
            self::DELIVERED->value => [],
            self::CANCELLED->value => [],
        ];

        return in_array($newStatus, $allowedTransitions[$this->value], true);
    }
}

Common Mistakes / Pitfalls

  • Using Enums for Frequently Changing Data: Enums are hardcoded into your PHP files. If your system requires dynamic statuses that users can create via an admin panel, do not use Enums. Enums are strictly for values that require code changes to modify.
  • Forgetting the Backing Type: When working with databases, developers sometimes forget to specify the backing type (e.g., enum Status: string). If you don’t define the backing type, you cannot easily serialize the enum to save it, leading to ORM errors.
  • Overcomplicating the Match Expression: While match is powerful, if your Enum methods require complex external dependencies (like checking a user’s permissions via a repository), that logic belongs in a domain service, not the Enum itself. Enums should remain pure and unaware of external systems.

Best Practices

  • Implement Interfaces: Enums can implement interfaces. This is highly useful for applying the Strategy Pattern, where different Enums implement the same interface and provide different calculation behaviors based on their state.
  • Leverage Framework Casting: If you are using frameworks like Laravel or Symfony, take advantage of native Enum casting. In Laravel, you can cast an Eloquent model attribute directly to an Enum, and the framework will automatically serialize and deserialize it for you.
  • Use Static Methods for Factory Logic: If you need to construct an Enum from complex external data or provide a default fallback, create static factory methods directly on the Enum class instead of cluttering your controllers.

Conclusion

PHP 8.1 Enums are a massive leap forward for the language, especially for developers practicing Domain-Driven Design. By moving validation and state-specific logic out of services and into type-safe Enum objects, we can build more expressive, resilient, and maintainable applications. They eliminate magic strings and make your domain rules explicit.

FAQ

Can Enums have properties?

No, PHP Enums cannot have properties (state) other than their backing value. They act as singletons. If you need an object with mutable state, you should use a standard class instead.

Can I extend an Enum?

No, Enums in PHP cannot be extended or inherit from other classes or Enums. However, they can use traits and implement interfaces to share behavior.

How do I extract all values from a Backed Enum?

You can use the built-in cases() method. For example, array_column(OrderStatus::cases(), 'value') will return an array of all the underlying scalar values, which is perfect for building validation rules.

editor's pick

latest video

news via inbox

Nulla turp dis cursus. Integer liberos  euismod pretium faucibua

Leave A Comment