Introduction
The match
expression is a PHP feature that I love using. It was introduced in PHP 8.0 (released in November 2020), so it's been around for a while now. But I thought I'd put together a Quickfire article about it to help spread the word, for anyone (such as those new to PHP) who may not be aware of it.
The match
Expression in PHP
The match
expression allows you to compare a value against multiple conditions and return a given result (although a result doesn't necessarily have to be returned). It's similar to a switch
statement in the sense that you can define "branches" based on different conditions.
However, a key difference is that the match
expression uses strict comparison (===
), whereas a switch
statement uses loose comparison (==
). This means that with match
, the types of the values being compared must be the same for a match to occur.
I think the best way to understand what a match
expression is is to see it in action. So let's take a look at an example of how we might use it in a real-world scenario.
We'll imagine we have an App\Services\Git\RepositoryManager
class that allows us to interact with different Git repository hosting services, such as GitHub, GitLab, and Bitbucket. Each service has its own driver class that implements a common interface called App\Interfaces\Git\Drivers\RepositoryDriver
. Using if
/elseif
/else
, we might write something like this:
1namespace App\Services\Git; 2 3use App\Interfaces\Git\Drivers\RepositoryDriver; 4use App\Services\Git\Drivers\BitbucketDriver; 5use App\Services\Git\Drivers\GitHubDriver; 6use App\Services\Git\Drivers\GitLabDriver; 7 8final readonly class RepositoryManager 9{10 11 // ...12 13 public function driver(string $driver): RepositoryDriver14 {15 if ($driver === 'github') {16 return new GitHubDriver();17 } elseif ($driver === 'gitlab') {18 return new GitLabDriver();19 } elseif ($driver === 'bitbucket') {20 return new BitbucketDriver();21 } else {22 throw new \InvalidArgumentException("Unsupported driver: $driver");23 }24 }25}
Now, with the above code example, we can do something like this to get a driver instance for interacting with GitHub:
1$githubDriver = new RepositoryManager()->driver('github');
However, we can simplify our driver
method using a match
expression, like so:
1namespace App\Services\Git; 2 3use App\Interfaces\Git\Drivers\RepositoryDriver; 4use App\Services\Git\Drivers\BitbucketDriver; 5use App\Services\Git\Drivers\GitHubDriver; 6use App\Services\Git\Drivers\GitLabDriver; 7 8final readonly class RepositoryManager 9{10 11 // ...12 13 public function driver(string $driver): RepositoryDriver14 {15 return match ($driver) {16 'github' => new GitHubDriver(),17 'gitlab' => new GitLabDriver(),18 'bitbucket' => new BitbucketDriver(),19 default => throw new \InvalidArgumentException("Unsupported driver: $driver"),20 };21 }22}
In the code example above, we have used the match
expression to define 4 separate branches. The first 3 branches check if the $driver
variable matches one of the supported drivers, and if so, it returns a new instance of the corresponding driver class. The default
branch is used to handle any unsupported driver values by throwing an \InvalidArgumentException
.
In my personal opinion, I now find this function much easier to read and understand at a glance. I also like that it's reduced the number of return
statements in the method. Although there's nothing wrong with having multiple return
statements, I find it easier to understand the flow of the method when there's a single return
point.
Multiple Conditions per Branch
match
expressions can also have multiple conditions for a single branch.
For example, imagine we have our own self-hosted Git service that uses the same API as GitHub. As a result, we can use the App\Services\Git\Drivers\GitHubDriver
for both GitHub and our custom Git service. We'll assume that we are referring to this driver name as 'self-hosted'
. Let's update our driver
method to account for this:
1use App\Interfaces\Git\Drivers\RepositoryDriver; 2use App\Services\Git\Drivers\BitbucketDriver; 3use App\Services\Git\Drivers\GitHubDriver; 4use App\Services\Git\Drivers\GitLabDriver; 5 6namespace App\Services\Git; 7 8final readonly class RepositoryManager { 9 10 // ...11 12 public function driver(string $driver): RepositoryDriver13 {14 return match ($driver) {15 'github', 'self-hosted' => new GitHubDriver(),16 'gitlab' => new GitLabDriver(),17 'bitbucket' => new BitbucketDriver(),18 default => throw new \InvalidArgumentException("Unsupported driver: $driver"),19 };20 }21}
We can see in the code example above that we have added 'self-hosted'
as an additional condition for the first branch. This means that if the $driver
variable is either 'github'
or 'self-hosted'
, it will return a new instance of the App\Services\Git\Drivers\GitHubDriver
.
Passing true
to the match
Statement
A cool use case for the match
expression is to pass true
to it. This allows you to then evaluate conditions in the arms of the match
statement.
As a basic example, let's say we want to set a message based on a user's role:
1// Assume we have retrieved a user from the database 2// and that the user is an admin. 3$user = \App\Models\User::first(); 4 5$message = match (true) { 6 $user->isAdmin() => 'User is an admin', 7 $user->isEditor() => 'User is an editor', 8 $user->isSubscriber() => 'User is a subscriber', 9 default => 'User role is unknown',10};11 12// $message will be 'User is an admin'
Pairing Match with Enums
One of the places that I've found match
expressions pair really nicely is with PHP enums. For example, let's say we have an enum that defines different job types:
1enum JobType: string 2{ 3 case WebDeveloper = 'web_developer'; 4 5 case Designer = 'designer'; 6 7 case ProjectManager = 'project_manager'; 8 9 case SalesManager = 'sales_manager';10}
Although we might want to use the raw enum values in some places (such as the database and application code), we wouldn't want to display these to the user. For instance, if the user can select one of the job type options in a form, we'd much rather show them a more user-friendly label, such as "Web Developer" instead of "web_developer".
So let's add a toFriendly
method to our JobType
enum that uses a match
expression to return a user-friendly label for each job type:
1enum JobType: string 2{ 3 case WebDeveloper = 'web_developer'; 4 5 case Designer = 'designer'; 6 7 case ProjectManager = 'project_manager'; 8 9 case SalesManager = 'sales_manager';10 11 public function toFriendly(): string12 {13 return match ($this) {14 self::WebDeveloper => 'Web Developer',15 self::Designer => 'Designer',16 self::ProjectManager => 'Project Manager',17 self::SalesManager => 'Sales Manager',18 };19 }20}
You may have noticed that we've not defined a default
branch in our match
expression. This is because our expression is exhaustive, meaning that all the possible cases have been covered, so the match
expression can't evaluate to anything other than one of the defined cases.
Now we can use the toFriendly
method to get a user-friendly label for any JobType
enum value:
1$jobType = JobType::WebDeveloper;2 3echo $jobType->toFriendly(); // Outputs: Web Developer
In my opinion, pairing the match
expression with enums like this is a great way to keep your code clean and maintainable. I feel like they just work really well together.
An alternative approach you could take to adding helper methods, such as toFriendly
, to your enums could be to use PHP attributes. I have an article which covers how to do this, if you're interested: A Guide to PHP Attributes.
Conclusion
Hopefully, this article has given you a quick overview of the match
expression in PHP, along with some practical examples of how you can use it in your own code.
If you enjoyed reading this post, you might be interested in checking out my 220+ page ebook "Battle Ready Laravel" which covers similar topics in more depth.
Or, you might want to check out my other 440+ page ebook "Consuming APIs in Laravel" which teaches you how to use Laravel to consume APIs from other services.
If you're interested in getting updated each time I publish a new post, feel free to sign up for my newsletter below.
Keep on building awesome stuff! 🚀