Enumerations (short: enums) were added in version 8.1 in 2021. In the journey of becoming a modern and object oriented programming language, enums in PHP were a cornerstone of writing cleaner, stricter and robust code. In this blog post, I want to address my experience so far with enums and give some advices, after having a little recap of how to use enums. We will briefly have a look at different aspects and argue why enums are better than simply working with constants.
Recap: What are enums?
Enums exist a very long time in the world of programming languages, such as in Java or MySQL. The basic idea is to limit a possible value to a given set of of values. Moreover, enums can also help in avoiding duplicates in certain scenarios.
Enums in PHP are very close to objects (technically, they are) and bring some possibilities reserved for classes. For instance, enums can use traits (read here why you should avoid traits), implement interfaces and provide methods. The following example is a fully valid, yet usable trait in PHP 8.1 and later:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | trait X { } interface StatusInterface { } enum Status implements StatusInterface{ use X; case ACTIVE; case INACTIVE; case PENDING; } |
Pure and Backed Enums
When coming to best practices for PHP enums, we must also address pure and backed enums. Consider the following enum:
1 2 3 4 5 | enum Status { case ACTIVE; case INACTIVE; case PENDING; } |
This enum is called pure enum since it simply declares it types without giving them a value. If enum cases hold a value, they are called „backed“ since they are backed by the values. Once introduced backed enums, you have to implement it for all cases. It is not possible to mix pure and backed enums. Modyfying the example above results in the following backed enum:
1 2 3 4 5 | enum Status: string { case ACTIVE = "active"; case INACTIVE = "inactive"; case PENDING = "pending"; } |
Comparing Enums
Enums not only limit the possible values, it enables also comparing them against each other. While before enums were introduced officially, we would use enum objects and instantiate separately which makes the following returning false:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | interface EnumInterface { public const ACTIVE = 'active'; public const INACTIVE = 'inactive'; public const PENDING = 'pending'; } class MyEnum implements EnumInterface { private string $value; public function __construct(string $value) { $this->value = $value; } } $a = new MyEnum(EnumInterface::ACTIVE); $b = new MyEnum(EnumInterface::ACTIVE); var_dump($a === $b); // false |
Whereas with enums, we can simply do:
1 2 3 4 5 6 7 8 9 10 | enum Status: string { case ACTIVE = "active"; case INACTIVE = "inactive"; case PENDING = "pending"; } $c = Status::ACTIVE; $d = Status::ACTIVE; var_dump($c === $d); // true |
Another point considering as best practice in PHP enums is the possibility of using the instanceof operator to proof whether a value is an enum or not.
How were enums simulated before PHP 8.1?
Before enums were introduced, there were different approaches to simulate them. The widely used approach is to work with constants, as we have seen above.
The code above serves as an enum, but has different disadvantages. First of all, the constants hold a value and viewing them isolated, they do not have a meaning. Another drawback is that implementing methods expect the value, not something that you declare as the enum. A method implementing the „enum“ above, would be like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | interface EnumInterface { public const ACTIVE = 'active'; public const INACTIVE = 'inactive'; public const PENDING = 'pending'; } class MyEnum implements EnumInterface { private string $value; public function __construct(string $value) { $this->value = $value; } } $a = new MyEnum('some invalid value'); workWithEnum($a); function workWithEnum(EnumInterface $enum):void { // do something with enum } |
The code above is valid PHP code, but incorrect in its meaning of an enum. You could pass one of the „enum“ values, but also something like some invalid value
, which is technically correct. This means, you either have to catch those cases -which leads to more boilerplate code – or have unexpected behavior.
Do you want a blog post addressing an in depth comparision between the “simulated enums” and the official enums?
Best Practices for PHP Enums from my Experience
As stated in the introduction, I gained a lot of experience with enums and want to give some insights to them. The last two years roughly, I was able to work in code bases of different companies like SCAYLE, The Quality Group or HPE/BMW and implement enums in different scenarios. Here are some points considered as best practices from my experience:
- Avoid using backed enums: Overusing backed enums will end up in the constant based approach of the previous approach. So whenever you find yourself introducing backed enums, hold a moment and think about whether you need this really. Instead, try to implement a very small method that translates your values or write an explicit service that does your work.
- Do not compare enums against scalar values: If you really need backed enums, do not compare the values of the enums against scalar values. Instead, try to create another enum given from your value and work with two enums.
- Do not blow your enum up with methods: While technically possible, try avoiding too much implementation in enums at all. Enums should just describe something, not provide business logic. An exception to this rule is having simple match/translate/transform methods.
- Do not make values part of your business logic: Backed values should ideally just used to create enums from values. Do not use the values in your logic as this would massively depend you on this specific value.
- Migrating from simulated enums to enums: You should be careful with migrating constants used as „enums“ to real enums. While 90% of them can possibly be just replaced, however, other may need some more attention.
Conclusion
In this blog post, we briefly went through the concept of enums in PHP and showed some best practices. While there is more in this context, I just limited this post to what I experienced in the past and think is meaningful to address. Did I forgot something? Let me know by using the contact form below.
Additionally, I will introduce an exclusive space for my readers. This space will serve as a place where you can address your questions to an experienced PHP developer with a 10+ years strong background in different industries. Register to my newsletter if you want to get notified once the platform is set up and you are able to register: