Understanding Enums in PHP 8.1

In PHP, an enum is a new data type that was introduced in version 8.1. An enum, short for "enumeration," is a set of named constants that represent distinct values that a variable of that enum type can take. Enums are especially useful when you have a set of values that are related and need to be grouped together under a single type.

To define an enum in PHP, you use the enum keyword, followed by the name of the enum, and then a list of constant names, each separated by a comma. You can optionally assign explicit integer values to the constants by using the = symbol.

Each constant in an enum has an implicit integer value assigned to it, starting at 0 for the first constant and incrementing by 1 for each subsequent constant.

Are you excited to know more about this new data type in PHP, and how does this actually works, without further delay, let's get started!

Table of Contents:

  1. Enum Methods
  2. Enum Interfaces
  3. Enum values-Backed Enums
  4. Backed Enums with Interfaces
  5. Serializing Backed enums
  6. Enums as Array Keys

Illustration: How Enums Work in PHP 8.1

Enums allow you to define a set of named constants that represent a finite list of possible values for a variable. Enums can help improve code readability, maintainability, and type-safety by providing a clear and consistent set of values that a variable can take.

Let us look into an example to understand enum and its usage. For example, consider that you are building a web application that allows customers to order coffee online, you can use an enum to represent the different sizes of coffee that customers can order.

enum CoffeeSize {
  SMALL,
  MEDIUM,
  LARGE
};

In this example, we define an enum called CoffeeSize that represents the size of a coffee. The enum has three constants: SMALL, MEDIUM, and LARGE.

$coffeeSize = CoffeeSize::MEDIUM;

We can use the enum to define a variable that represents the size of a coffee. In the above code example, we have defined a variable $coffeeSize of the CoffeeSize type and assigned a constant value MEDIUM to it.

if ($coffeeSize === CoffeeSize::SMALL) {
  echo "You ordered a small coffee";
} elseif ($coffeeSize === CoffeeSize::MEDIUM) {
  echo "You ordered a medium coffee";
} elseif ($coffeeSize === CoffeeSize::LARGE) {
  echo "You ordered a large coffee";
}

The variable is compared to the enum constants CoffeeSize::SMALL, CoffeeSize::MEDIUM, and CoffeeSize::LARGE using the === operator. The if-elseif block used above provides a clear and concise way to check the value of a variable that represents an enum type and execute code based on the value.

If the variable $coffeeSize is equal to the enum constant CoffeeSize::SMALL, the code block inside the first if statement is executed. This code block simply prints out the message "You ordered a small coffee" using the echo statement.

If the variable $coffeeSize is not equal to CoffeeSize::SMALL, the condition in the first if statement is false, and the program moves on to the next condition and so on.

If the variable $coffeeSize is not equal to any of the enum constants, none of the if-elseif statements are true, in that case the program moves on to the next line of code after the if-elseif block. For example, you could add an else statement at the end of the if-elseif block to handle this case, like this

if ($coffeeSize === CoffeeSize::SMALL) {
  echo "You ordered a small coffee";
} elseif ($coffeeSize === CoffeeSize::MEDIUM) {
  echo "You ordered a medium coffee";
} elseif ($coffeeSize === CoffeeSize::LARGE) {
  echo "You ordered a large coffee";
} else {
  echo "Sorry, we do not have that size of coffee available";
}

The else statement will be executed if none of the if-elseif statements are true, and will print out a message indicating that the requested coffee size is not available. This helps to ensure that your program handles all possible cases, and provides a clear message to the user if their input is invalid.

1. Enum Methods

Enums can also define methods, just like classes can. This means that you can define methods that are associated with a particular enum value. For example, if you have an enum representing different colors, you might define a method that calculates the brightness of a color.

enum Color {
  const RED = '#FF0000';
  const GREEN = '#00FF00';
  const BLUE = '#0000FF';

  public function brightness(): float {
    // Calculate brightness based on color value
    // ...
    return $brightness;
  }
}

In this example, we define an enum called Color with three possible values: RED, GREEN, and BLUE. We also define a method called brightness() that calculates the brightness of a color. This method can be called on any instance of the Color enum, and will return a float value representing the brightness.

This is a powerful feature of enums, especially when combined with the match operator. The match operator allows you to match an enum value to a particular case and perform an action based on that case. By defining methods on enums, you can perform complex actions based on the value of the enum.

$color = Color::RED;

$brightness = match ($color) {
  Color::RED => $color->brightness() * 0.8,
  Color::GREEN => $color->brightness() * 0.9,
  Color::BLUE => $color->brightness(),
};

echo "Brightness: $brightness\n";

In this example, we define a variable $color and set it to the RED value of the Color enum. We then use the match operator to match the value of $color to a particular case, and calculate the brightness of the color based on that case. Since $color is RED, we multiply the brightness by 0.8. We then print the resulting brightness to the console.

2. Enum Interfaces

In PHP, Enum interfaces are a way of defining an Enum using an interface instead of the enum keyword. Enum interfaces are not a language feature, but rather a pattern that has emerged in PHP as a way of achieving Enum-like behaviour in versions of PHP prior to 8.1, where the enum keyword was introduced.

interface Color {
    public const RED = '#FF0000';
    public const GREEN = '#00FF00';
    public const BLUE = '#0000FF';
}

In this example, we have defined an interface called Color with three constants that represent different colors. The constants are defined using the public const keywords. To use the Color interface, you can reference the constants like this.

echo Color::RED; // Output: #FF0000

You can also use the switch statement to handle different cases based on the value of the constant, just like you would with a regular Enum.

switch ($color) {
    case Color::RED:
        echo 'The color is red';
        break;
    case Color::GREEN:
        echo 'The color is green';
        break;
    case Color::BLUE:
        echo 'The color is blue';
        break;
}

While enum interfaces provide a way of achieving Enum-like behaviour in PHP versions prior to 8.1, they do have some limitations compared to the enum keyword introduced in PHP 8.1.

Enum interfaces do not provide any built-in validation of the values. This means that you can assign any value to an enum interface constant, even if it doesn't belong to the set of valid values for that enum. In contrast, the enum keyword introduced in PHP 8.1 provides built-in validation of the values, ensuring that only valid values can be assigned to an enum constant.

Check out our blog on What’s new in PHP 8.2 for more updates on PHP.

3. Enum values - Backed Enums

A backed enum is an enumeration type where each member is associated with a specific value, known as a "backing value". The backing value can be any primitive data type, such as an integer or a string, and is specified when the enum is defined.

Backed enums are useful because they provide a way to associate meaningful names with specific values, making code more readable and expressive. Additionally, backed enums can provide type safety, since the values you're working with are always of a specific type, as opposed to arbitrary constants or strings.

enum Fruit: int {
    APPLE = 1;
    BANANA = 2;
    ORANGE = 3;
}

In this example, Fruit is a backed enum that is backed by an int type. Each member of the enum (APPLE, BANANA, and ORANGE) is assigned a specific integer value (1, 2, and 3, respectively). This allows you to use the Fruit enum in your code instead of hardcoding integer values.

function eatFruit(Fruit $fruit) {
    switch ($fruit) {
        case Fruit::APPLE:
            echo "Mmm, apples are tasty!";
            break;
        case Fruit::BANANA:
            echo "Bananas are my favorite!";
            break;
        case Fruit::ORANGE:
            echo "I love oranges!";
            break;
    }
}

This function takes a Fruit parameter and uses a switch statement to print out a message based on the specific fruit that was passed in. By using the Fruit enum instead of hardcoded integer values, the code is more readable and less error-prone.

4. Backed Enums with Interfaces

Backed enums with interfaces are a way to simulate enums in PHP using interfaces and constants. The basic idea is to define a set of constants that represent the possible values of an enum, and then define an interface that provides methods for working with those values.

Here's an example of using backed enums with interfaces in PHP:

<?php

interface Fruit {
    public function getName(): string;
}

abstract class BackedEnum {
    private static $constCacheArray = null;
    private $value;

    protected function __construct($value) {
        $this->value = $value;
    }

    public function __toString() {
        return (string) $this->value;
    }

    public static function isValidName($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);
    }

    public static function isValidValue($value) {
        $values = array_values(self::getConstants());
        return in_array($value, $values, $strict = true);
    }

    private static function getConstants() {
        if (self::$constCacheArray == null) {
            self::$constCacheArray = [];
        }

        $calledClass = get_called_class();
        if (!array_key_exists($calledClass, self::$constCacheArray)) {
            $reflect = new ReflectionClass($calledClass);
            self::$constCacheArray[$calledClass] = $reflect->getConstants();
        }

        return self::$constCacheArray[$calledClass];
    }

    public static function toArray() {
        return self::getConstants();
    }

    public static function fromName($name) {
        if (!self::isValidName($name)) {
            throw new InvalidArgumentException("$name is not a valid name for this enum");
        }

        $constants = self::getConstants();
        return new static($constants[$name]);
    }

    public static function fromValue($value) {
        if (!self::isValidValue($value)) {
            throw new InvalidArgumentException("$value is not a valid value for this enum");
        }

        $keys = array_keys(self::getConstants());
        $values = array_values(self::getConstants());
        $index = array_search($value, $values, $strict = true);

        return new static($keys[$index]);
    }
}

final class Apple extends BackedEnum implements Fruit {
    const FUJI = 'fuji';
    const GRANNY_SMITH = 'granny smith';
    const HONEYCRISP = 'honeycrisp';

    public function getName(): string {
        return 'apple';
    }
}

final class Orange extends BackedEnum implements Fruit {
    const NAVEL = 'navel';
    const BLOOD = 'blood';
    const VALENCIA = 'valencia';

    public function getName(): string {
        return 'orange';
    }
}

$fuji = Apple::fromName('FUJI');
echo $fuji . "\n"; // outputs "fuji"
echo $fuji->getName() . "\n"; // outputs "apple"

$valencia = Orange::fromValue('valencia');
echo $valencia . "\n"; // outputs "VALENCIA"
echo $valencia->getName() . "\n"; // outputs "orange"

In this example, we have an abstract class BackedEnum that defines a set of methods that can be used to work with enums. The Apple and Orange classes extend this abstract class to define their own specific set of constants. Both classes also implement the Fruit interface, which requires the implementation of the getName() method.

We can then create instances of the Apple and Orange classes using the fromName() and fromValue() methods of the BackedEnum.

5. Serializing Backed Enums

An enum is a way to represent a group of related values. For example, you might have an enum for the different colors of a traffic light: RED, YELLOW, and GREEN.

Sometimes these enums have underlying values associated with each member. For example, the color RED might have an associated value of 0, YELLOW might have a value of 1, and GREEN might have a value of 2. These are called "backed enums".

Serializing backed enums is the process of converting these enums to a format that can be easily stored or transmitted, like a file or a network message. This involves converting the enum values (like RED or YELLOW) and their associated underlying values (like 0 or 1) to a standardized format that can be read and reconstructed later.

Serialization is useful when you need to save or transfer complex data structures, like enums with associated values, between different systems or applications.

// Define a backed enum for the colors of a traffic light
abstract class TrafficLightColor
{
    const RED = 0;
    const YELLOW = 1;
    const GREEN = 2;
}

// Serialize the enum to JSON
$traffic_light_colors = array(
    'RED' => TrafficLightColor::RED,
    'YELLOW' => TrafficLightColor::YELLOW,
    'GREEN' => TrafficLightColor::GREEN
);
$traffic_light_json = json_encode($traffic_light_colors);

// Print the serialized JSON string
echo $traffic_light_json;

// Deserialize the JSON back to an enum
$traffic_light_data = json_decode($traffic_light_json, true);
$traffic_light_enum = array();
foreach ($traffic_light_data as $key => $value) {
    $traffic_light_enum[$key] = $value;
}
var_dump($traffic_light_enum);

6. Enums as Array Keys

An array is a way to store a collection of values in a single variable. Each value in an array is assigned a key, which is used to identify the value. In PHP, enums can be used as keys in an array. This means that instead of using string or integer keys, you can use the enum members as keys. Using enums as array keys can make your code more readable and easier to understand, especially if the keys represent a specific set of options or states.

To use an enum as an array key, you need to define the enum first and then create an associative array with the enum members as keys and their associated values as values. You can then access values in the array using the enum member as the key.

For example, let us have an enum for different types of fruits:

enum Fruit {
    APPLE,
    BANANA,
    ORANGE
}

Let us create an associative array with the enum as the keys,

$fruit_colors = [
    Fruit::APPLE => 'red',
    Fruit::BANANA => 'yellow',
    Fruit::ORANGE => 'orange'
];

You can access values in the array using the enum member as the key.

echo $fruit_colors[Fruit::APPLE]; // Outputs "red"

Wrap Up

Enums in PHP 8.1 is a powerful addition to the language that enhances code readability, maintainability, and reduces the possibility of runtime errors. It is an essential feature that developers have long awaited and will greatly benefit from.

Enums provide a clean and concise way to define a set of named constants with an underlying type, making code more self-documenting and easier to understand. Additionally, Enums offer built-in support for operations such as iteration, comparison, and serialization, further increasing their usefulness.

And finally, it is a welcome addition that will help developers write more concise, reliable, and maintainable code, leading to more efficient and effective software development.


Atatus: PHP Performance Monitoring and Log Management

Atatus is an Application Performance Management (APM) solution that collects all requests to your PHP applications without requiring you to change your source code. However, the tool does more than just keep track of your application's performance.

Monitor logs from all of your PHP applications and systems into a centralized and easy-to-navigate user interface, allowing you to troubleshoot faster using PHP monitoring.

We give a cost-effective, scalable method to centralized PHP logging, so you can obtain total insight across your complex architecture. To cut through the noise and focus on the key events that matter, you can search the logs by hostname, service, source, messages, and more. When you can correlate log events with APM slow traces and errors, troubleshooting becomes easy.

Try your 14-day free trial of Atatus.

Atatus

#1 Solution for Logs, Traces & Metrics

tick-logo APM

tick-logo Kubernetes

tick-logo Logs

tick-logo Synthetics

tick-logo RUM

tick-logo Serverless

tick-logo Security

tick-logo More

Pavithra Parthiban

Pavithra Parthiban

A technical content writer specializing in monitoring and observability tools, adept at making complex concepts easy to understand.
Chennai