X
Popular Searches

How to Use Enums in PHP 8.1

PHP Logo

PHP 8.1 will finally add language support for enums. Enums, short for enumerations, are types which can only be instantiated with specific values. They’re commonly found in other object-oriented languages but have previously required userland workarounds to implement in PHP.

Basic Syntax

Here’s what a simple enum looks like:

enum PostStatus {
    case Published;
    case InReview;
    case Draft;
}

The case keyword, previously part of switch statements, is used to delineate the specific values the enum accepts. Values are referenced in the same way as class constants:

$published = PostStatus::Published;

Enums behave similarly to classes and interfaces. They’re fully compatible with the type system, so you can typehint that a function only accepts a value defined in an enum:

class BlogPost {
 
    public function __construct(
        public string $Headline,
        public string $Content,
        public PostStatus $Status=PostStatus::Draft) {}
 
}

Here’s an example of using the BlogPost class:

// OK
$post = new BlogPost(
    "Example Post",
    "An example",
    PostStatus::Draft
);
 
// TypeError: Argument #3 ($Status) must be of type PostStatus
$post = new BlogPost(
    "Broken Example",
    "A broken example",
    "Submitted"
);

The first instance works because its $Status is a valid value from the PostStatus enum. In the second case, a plain string is passed as $Status, which is prohibited as the value must be defined within PostStatus.

Enum cases are represented as constants on the enum object. This means you can use them as static values and as part of constant expressions. The BlogPost constructor shows an enum case being used as a default parameter value, where $Status is automatically set to Draft when no value is supplied by the caller.

Advertisement

You can access all the available values in an enum using its cases method:

PostStatus::cases();
// [PostStatus::Published, PostStatus::InReview, PostStatus::Draft]

Pure vs Backed Enums

The PostStatus enum above is a pure enum. It contains only case statements, with no extra data. PHP also lets you attach a value to enum cases, creating a backed enum.

enum PostStatus : string {
    case Published = "S1";
    case InReview = "S2";
    case Draft = "S3";
}

Here the PostStatus enum has been modified to create a backed enum. The typehint in the enum’s definition stipulates that each case has a string value assigned to it. In this example, we’re assuming each named post status has an associated short identifier. It might be this identifier that gets saved into the database when posts are persisted.

You can access backed values via the value property on case instances:

class BlogPostRepository {
 
    public function save(BlogPost $Post) : void {
        $this -> insert(
            "blog_posts",
            [
                "headline" => $Post -> Headline,
                "content" => $Post -> Content,
                "status" => $Post -> Status -> value
            ]
        );
    }
 
}
 
$post = new BlogPost("Example", "Demo", PostStatus::Published);
(new BlogPostRepository()) -> save($post);

This example would set the value of the persisted status field to S1, based on the backed version of the PostStatus enum shown above.

Backed enums only accept strings and integers as values. It’s not possible to use the union type string|int either. In addition, each case needs a unique value – the following example isn’t permissible:

enum PostStatus : string {
 
    case Published = "S1";
    case Draft = "S1";
 
}
Advertisement

PHP provides a utility method on enums to create an instance from a backed value:

// fetch the blog post from earlier from the database
// the "status" field = S1
$status = PostStatus::from($record["status"]);

The from() method will hydrate instances from value cases. In this example, S1 is mapped back to the Published case, and your code receives an instance of PostStatus::Published.

from() throws a ValueError if the input value is invalid; in scenarios where you know the value might not be usable, the alternative tryFrom() method can be used instead. This returns null when there’s no match, instead of throwing the error.

Adding Methods to Enums

As enums are based on classes, you can also add methods to them!

enum PostStatus {
 
    case Published;
    case Draft;
 
    public function isPubliclyAccessible() : bool {
        return ($this instanceof self::Published);
    }
 
}

This lets you keep case-specific behavior within your enum, instead of duplicating it across your codebase.

Enums can implement interfaces too:

enum PostStatus implements PublicAccessGatable {
 
    case Published;
    case Draft;
 
    public function isPubliclyAccessible() : bool {
        return ($this instanceof self::Published);
    }
 
}

Now you can pass a PostStatus instance to anything which accepts a PublicAccessGatable:

class UserAuthenticator {
 
    function shouldAllowAccess(PublicAccessGatable $Resource) : bool {
        return ($this -> User -> isAdmin() || $Resource -> isPubliclyAccessible());
    }
 
}
 
$auth = new UserAuthenticator();
 
// get a blog post from the database
if (!$auth -> shouldAllowAccess($post -> Status)) {
    http_response_code(403);
}
Advertisement

There are no restrictions on what you can do with enum methods – they are regular PHP methods, after all – but in general you’d expect them to perform some kind of comparison against the instance’s case, then return a static value. Enums can use traits, so you can pull in existing methods which you’ve abstracted in this way too.

You can use public, protected and private methods in enums, although protected and private have the same effect. Enums can’t extend each other so private is effectively redundant. You can’t add a constructor or destructor either. Static methods are supported and can be called on the enum class or its case instances.

Constants

Enums can also have their own constants, either as regular literal values or a reference to an enum case:

enum PostStatus {
 
    case Published;
    case Draft;
 
    public const Live = self::Published;
    public const PlainConstant = "foobar";
 
}

This holds the potential to create confusion as the same syntax is used to access cases (enum instances) and constants:

$published = PostStatus::Published;
$plain = PostStatus::PlainConstant;

Only $published would satisfy a PostStatus typehint, as $plain refers to a simple scalar value.

When to Use Enums?

Enums are for occasions where you need flexibility in the value a variable can take, but only among a predetermined set of possible cases.

Advertisement

The blog post class that’s run through this post is a classic example. Posts can only be in one of a known set of states but PHP previously had no straightforward way of achieving this.

In older versions, you might have used this approach:

class PostStatus {
    const Published = 0;
    const Draft = 1;
}
 
class BlogPost {
    public function __construct(
        public string $Headline,
        public int $Status
    ) {}
}
 
$post = new BlogPost("My Headline", PostStatus::Published);

The problem here is that $Status actually accepts any integer, so the following call would be perfectly valid:

$post = new BlogPost("My Headline", 9000);

Furthermore, BlogPost and PostStatus are completely detached – there’s no way somebody reading BlogPost can learn the range of values that $Status actually accepts. While these issues can be mitigated by using appropriate docblock typehints, or third-party “fake enum” packages, they’re all adding extra layers around a concept other programming languages make simple.

Adding native enums to PHP is a step that helps to round off the language’s type system. You can finally typehint permissible values in a way that keeps everyone on the same page. If you pass an invalid value, you’ll get a runtime error. Your IDE can better assist you in supplying correct values, as it will know that $Status only accepts three options, instead of “any integer.”

Conclusion

Enums address some common developer pain points when working in PHP. They make it possible to typehint that parameters, return values, and properties must be one of a set of predetermined options.

Enums are flexible codebase entities which you can keep simple in pure form, or extend with backed values, interface implementations, and custom methods. Enums behave similarly to regular objects in most cases and support class features such as __call(), __invoke, and ::class.

Advertisement

You can introspect enums with the new enum_exists() function and ReflectionEnum Reflection class. In addition, enums implement two new interfaces, UnitEnum (in the case of pure enums), and BackedEnum (for enums with backed values). These can be used in generic framework code which works with any enum. The interfaces cannot be manually implemented by userland code.

Enums will land in PHP as part of the 8.1 release in November 2021. They’re already available in the latest beta builds. PHP 8.1 will ship several other convenience features too, including readonly properties and intersection types.

James Walker James Walker
James Walker is a CloudSavvy IT contributor. He is the founder of Heron Web, a UK-based digital agency providing bespoke software development services to SMEs. He has experience managing complete end-to-end web development workflows with DevOps, CI/CD, Docker, and Kubernetes. Read Full Bio »

The above article may contain affiliate links, which help support CloudSavvy IT.