Match and Enums have become a happy place for me writing modern PHP. For those that don’t know, the match
expression was added in PHP 8.0 as an alternative to switch
. As per the docs:
The match
expression is similar to a switch
statement but has some key differences:
- A match arm compares values strictly (===) instead of loosely as the switch statement does.
- A match expression returns a value.
- match arms do not fall-through to later cases the way switch statements do.
- a match expression must be exhaustive
Enums were added in PHP 8.1 “indented to provide a way to define a closed set of possible values for the type” and they can be plain:
or backed
with integer or string values:
This gives us more options for encapsulating state and, with PHP’s type system improvements, we can pass around enum values rather than integer and string literals and we get some nice hand holding from our tools:
Bonus: Laravel handles casting model properties to enums really nicely.
Playing nicely together
As Match expressions are exhaustive and Enums are a closed set of values, if we don’t handle all the cases like this:
Again our IDE / Static Analysis will shout at us “Match expression does not handle remaining value: DoorState::Locked”. You can fix this by handling the missing case or adding a default arm:
Always another thing to learn
These two features have been in PHP for awhile so using them has become commonplace for me, however there’s always something new to learn in programming. In a recent project with a multi-step upload where each step will call different actions depending on the file type I started using this approach where a related action could be handled by another method on the action class:
This works but we love writing tests for our code right? These actions can be quite complicated and testing individual class methods can require a lot of setup related to other things on that class that a unit test for that specific method isn’t actually testing. For example we don’t want to fake a file or file upload in a unit test that is only testing specific data flows from the bigger upload process. So it was better to refactor to instantiating separate actions in each arm instead like this:
Now we can have simpler unit tests for the separate action classes, nice. But… in some cases I wanted to do more before calling another action. For example, create a DTO from validated file data specific to the action to process and save it, something like:
As PHP doesn’t [yet] support multi-line short closures I didn’t think it was possible to inline this in the match arm. I was wrong as PHP has supported Immediately Invoked Function Execution aka IFFE (Iffy!) since PHP 7, so we can combine this functionality to do this
Whilst the syntax is a little verbose, this feels like a really nice approach to me and great to have another option to reach for.
Wishful thinking
That being said I do still hope that some day we will be able to do this:
Or even this:
Blurred lines
As much as I love the Match + Enum combination I recently found a case where I preferred not to use it. Here we have a Laravel collection and are filtering it. The filter method will keep the values that return true from the callback:
It is possible to group the conditions that will return false by comma separating them but this felt like a step too far as more conditions and more complicated conditions may have to be added. Therefore I fell back to using a standard guard clause like this:
What do you think? What are your favourite ways to use Enums and Match in PHP? What’s your favourite modern PHP feature?