How To Use Enumerations In PHP?

Published October 3, 2024

Problem: Understanding Enumerations in PHP

Enumerations in PHP let you define a set of named constants. They offer a way to represent a group of related values, but some developers may find their usage unclear.

Implementing Enumerations in PHP 8.1 and Later

Syntax and Structure

PHP 8.1 added native enumerations. The basic syntax for declaring an enumeration is:

enum Status
{
    case Active;
    case Inactive;
    case Pending;
}

PHP supports two types of enumerations:

  1. Pure enumerations: Simple enumerations without associated values.
  2. Backed enumerations: Enumerations with associated values of a specific type (string or int).

Here's an example of a backed enumeration:

enum Status: string
{
    case Active = 'active';
    case Inactive = 'inactive';
    case Pending = 'pending';
}

Tip: Using Backed Enumerations

Backed enumerations are useful when you need to store enum values in a database or serialize them. They allow you to map enum cases to specific string or integer values, making it easier to work with external data sources.

Working with Enumeration Cases

To define cases within an enumeration, list them inside the enum block. Each case represents a distinct value of the enumeration.

You can access enumeration cases using this syntax:

$status = Status::Active;

For backed enumerations, you can access the associated value:

$value = Status::Active->value; // Returns 'active'

Enumeration Methods and Properties

PHP enumerations have built-in methods:

  • cases(): Returns an array of all cases in the enumeration.
  • from($value): Creates an enum instance from a backed value.
  • tryFrom($value): Similar to from(), but returns null for invalid values.

You can also create custom methods within enumerations:

enum Status: string
{
    case Active = 'active';
    case Inactive = 'inactive';
    case Pending = 'pending';

    public function getColor(): string
    {
        return match($this) {
            self::Active => 'green',
            self::Inactive => 'red',
            self::Pending => 'yellow',
        };
    }
}

$color = Status::Active->getColor(); // Returns 'green'

This approach lets you add functionality specific to your enumeration, making your code more organized and easier to maintain.

Example: Using Enums in Switch Statements

Enums work well with switch statements, providing type safety and clear code:

function handleStatus(Status $status): void
{
    switch ($status) {
        case Status::Active:
            echo "The status is active";
            break;
        case Status::Inactive:
            echo "The status is inactive";
            break;
        case Status::Pending:
            echo "The status is pending";
            break;
    }
}

handleStatus(Status::Active); // Outputs: The status is active

Enumerations in PHP 8.0 and Earlier

Class-based Enumeration Alternatives

Before PHP 8.1, developers used abstract classes to mimic enumerations. This method involves creating a class with constants to represent the enumeration values:

abstract class Status
{
    const ACTIVE = 'active';
    const INACTIVE = 'inactive';
    const PENDING = 'pending';
}

You can use these constants like this:

$status = Status::ACTIVE;

This approach groups related constants and offers some type hinting, but it lacks the full functionality of true enumerations.

Tip: Using PHPDoc for Better IDE Support

When using class-based enumerations, you can improve IDE support by adding PHPDoc comments to your methods. For example:

/**
 * @param string $status One of Status::ACTIVE, Status::INACTIVE, or Status::PENDING
 */
public function setStatus(string $status) {
    // Method implementation
}

This provides better autocomplete and type checking in modern IDEs.

Advanced Enumeration Workarounds

For more advanced enum-like behavior, you can create a basic enumeration class with validation methods:

abstract class BasicEnum
{
    private static $constCache = [];

    private static function getConstants()
    {
        $calledClass = get_called_class();
        if (!isset(self::$constCache[$calledClass])) {
            $reflect = new ReflectionClass($calledClass);
            self::$constCache[$calledClass] = $reflect->getConstants();
        }
        return self::$constCache[$calledClass];
    }

    public static function isValid($name, $strict = false)
    {
        $constants = self::getConstants();

        if ($strict) {
            return array_key_exists($name, $constants);
        }

        $keys = array_map('strtolower', array_keys($constants));
        return in_array(strtolower($name), $keys);
    }
}

You can extend this class to create specific enumerations:

abstract class Status extends BasicEnum
{
    const ACTIVE = 'active';
    const INACTIVE = 'inactive';
    const PENDING = 'pending';
}

This setup allows you to validate enum values:

$isValid = Status::isValid('ACTIVE'); // Returns true
$isInvalid = Status::isValid('COMPLETE'); // Returns false

While these workarounds provide some enum-like functionality, they lack the type safety and other benefits of native enumerations introduced in PHP 8.1.

Best Practices for Using Enumerations in PHP

When to Use Enumerations

Enumerations are useful in cases where you need to represent a fixed set of related constants. Here are some situations where enumerations are helpful:

  • Representing states or statuses (e.g., order statuses, user roles)
  • Defining a set of options (e.g., menu items, configuration settings)
  • Modeling business logic concepts (e.g., days of the week, card suits)

Enumerations offer these advantages over other data structures:

  • Type safety: Enums provide strict type checking, reducing errors.
  • Self-documentation: Enum cases clearly define the possible values.
  • IDE support: Modern IDEs offer better autocomplete and type hinting for enums.
  • Code organization: Enums group related constants, improving code structure.

You can use arrays or constants for similar purposes, but enums offer better type safety and are more self-documenting.

Example: Using Enums for Order Status

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

function updateOrderStatus(Order $order, OrderStatus $status)
{
    $order->status = $status;
    $order->save();
}

// Usage
updateOrderStatus($order, OrderStatus::Shipped);

Performance Considerations

When using enum-like structures in PHP versions before 8.1, consider these performance aspects:

  • Reflection: Using reflection to implement enum-like behavior can be slow if not optimized.
  • Caching: Cache the results of reflection calls to improve performance.

Here's an example of caching reflection results:

abstract class BasicEnum
{
    private static $constCache = [];

    protected static function getConstants()
    {
        $calledClass = get_called_class();
        if (!isset(self::$constCache[$calledClass])) {
            $reflect = new ReflectionClass($calledClass);
            self::$constCache[$calledClass] = $reflect->getConstants();
        }
        return self::$constCache[$calledClass];
    }
}

For PHP 8.1 and later, native enumerations are optimized for performance. To further optimize enum usage:

  • Use backed enums when you need to work with databases or external APIs.
  • Avoid unnecessary enum instantiations in performance-critical loops.
  • Consider using static analysis tools to catch enum-related issues early in development.

Tip: Optimize Enum Comparisons

When comparing enum values in performance-critical code, use identity comparison (===) instead of equality comparison (==). This is faster because it doesn't involve type coercion.

// Faster
if ($status === OrderStatus::Shipped) {
    // Handle shipped orders
}

// Slower
if ($status == OrderStatus::Shipped) {
    // Handle shipped orders
}

By following these practices, you can use enumerations to create better and maintainable code while keeping performance in mind.