match
表达式 match
表达式是我非常喜欢使用的 PHP 功能。它是在 PHP 8.0(2020 年 11 月发布)中引入的,所以已经存在一段时间了。但我想写一篇关于它的快速文章来帮助宣传,让可能还不了解它的人(比如 PHP 新手)知道这个功能。
match
表达式 match
表达式允许你将一个值与多个条件进行比较并返回给定的结果(尽管不一定非要返回结果)。在定义基于不同条件的"分支"这一点上,它类似于 switch
语句。
然而,一个关键区别是 match
表达式使用严格比较(===
),而 switch
语句使用松散比较(==
)。这意味着使用 match
时,被比较值的类型必须相同才能匹配。
我认为理解 match
表达式的最佳方式是看它在实际场景中的应用。让我们来看一个在真实场景中如何使用它的例子。
假设我们有一个 App\Services\Git\RepositoryManager
类,允许我们与不同的 Git 代码仓库托管服务(如 GitHub、GitLab 和 Bitbucket)进行交互。每个服务都有其自己的驱动类,这些类实现了一个名为 App\Interfaces\Git\Drivers\RepositoryDriver
的通用接口。使用 if/elseif/else
,我们可能会这样写:
namespace App\Services\Git;
use App\Interfaces\Git\Drivers\RepositoryDriver;
use App\Services\Git\Drivers\BitbucketDriver;
use App\Services\Git\Drivers\GitHubDriver;
use App\Services\Git\Drivers\GitLabDriver;
final readonly class RepositoryManager
{
// ...
public function driver(string $driver): RepositoryDriver
{
if ($driver === 'github') {
return new GitHubDriver();
} elseif ($driver === 'gitlab') {
return new GitLabDriver();
} elseif ($driver === 'bitbucket') {
return new BitbucketDriver();
} else {
throw new \InvalidArgumentException("不支持的驱动: $driver");
}
}
}
使用上面的代码示例,我们可以这样做来获取一个用于与 GitHub 交互的驱动实例:
$githubDriver = new RepositoryManager()->driver('github');
但是,我们可以使用 match
表达式来简化我们的 driver
方法,如下所示:
namespace App\Services\Git;
use App\Interfaces\Git\Drivers\RepositoryDriver;
use App\Services\Git\Drivers\BitbucketDriver;
use App\Services\Git\Drivers\GitHubDriver;
use App\Services\Git\Drivers\GitLabDriver;
final readonly class RepositoryManager
{
// ...
public function driver(string $driver): RepositoryDriver
{
return match ($driver) {
'github' => new GitHubDriver(),
'gitlab' => new GitLabDriver(),
'bitbucket' => new BitbucketDriver(),
default => throw new \InvalidArgumentException("不支持的驱动: $driver"),
};
}
}
在上面的代码示例中,我们使用 match
表达式定义了 4 个独立的分支。前 3 个分支检查 $driver
变量是否匹配支持的驱动之一,如果匹配,则返回相应驱动类的新实例。default
分支用于处理任何不受支持的驱动值,抛出一个 \InvalidArgumentException
。
就我个人而言,我现在发现这个函数更容易一目了然地阅读和理解。我也喜欢它减少了方法中的 return
语句数量。虽然拥有多个 return
语句并没有错,但我发现当只有一个返回点时,更容易理解方法的流程。
match
表达式也可以让单个分支拥有多个条件。
例如,假设我们有自己的自托管 Git 服务,它使用与 GitHub 相同的 API。因此,我们可以将 App\Services\Git\Drivers\GitHubDriver
用于 GitHub 和我们的自定义 Git 服务。我们假设将此驱动名称称为 'self-hosted'
。让我们更新我们的 driver
方法来考虑这一点:
use App\Interfaces\Git\Drivers\RepositoryDriver;
use App\Services\Git\Drivers\BitbucketDriver;
use App\Services\Git\Drivers\GitHubDriver;
use App\Services\Git\Drivers\GitLabDriver;
namespace App\Services\Git;
final readonly class RepositoryManager {
// ...
public function driver(string $driver): RepositoryDriver
{
return match ($driver) {
'github', 'self-hosted' => new GitHubDriver(),
'gitlab' => new GitLabDriver(),
'bitbucket' => new BitbucketDriver(),
default => throw new \InvalidArgumentException("不支持的驱动: $driver"),
};
}
}
我们可以在上面的代码示例中看到,我们为第一个分支添加了 'self-hosted'
作为附加条件。这意味着如果 $driver
变量是 'github'
或 'self-hosted'
,它将返回一个 App\Services\Git\Drivers\GitHubDriver
的新实例。
match
语句传递 true
match
表达式的一个很酷的用例是向它传递 true
。这允许你在 match
语句的分支中评估条件。
作为一个基本示例,假设我们想根据用户的角色设置一条消息:
// 假设我们从数据库中检索到了一个用户
// 并且该用户是管理员。
$user = \App\Models\User::first();
$message = match (true) {
$user->isAdmin() => '用户是管理员',
$user->isEditor() => '用户是编辑',
$user->isSubscriber() => '用户是订阅者',
default => '用户角色未知',
};
// $message 将是 '用户是管理员'
match
与枚举配对 我发现 match
表达式与 PHP 枚举配合得非常好的一个地方是使用枚举。例如,假设我们有一个定义了不同工作类型的枚举:
enum JobType: string
{
case WebDeveloper = 'web_developer';
case Designer = 'designer';
case ProjectManager = 'project_manager';
case SalesManager = 'sales_manager';
}
虽然我们可能在某些地方(例如数据库和应用程序代码中)使用原始的枚举值,但我们不会希望将这些值显示给用户。例如,如果用户可以在表单中选择一个工作类型选项,我们更愿意向他们显示更用户友好的标签,比如 "Web Developer" 而不是 "web_developer"。
所以让我们向我们的 JobType
枚举添加一个 toFriendly
方法,该方法使用 match
表达式为每个工作类型返回一个用户友好的标签:
enum JobType: string
{
case WebDeveloper = 'web_developer';
case Designer = 'designer';
case ProjectManager = 'project_manager';
case SalesManager = 'sales_manager';
public function toFriendly(): string
{
return match ($this) {
self::WebDeveloper => 'Web Developer',
self::Designer => 'Designer',
self::ProjectManager => 'Project Manager',
self::SalesManager => 'Sales Manager',
};
}
}
你可能已经注意到,我们在 match
表达式中没有定义 default
分支。这是因为我们的表达式是详尽的,意味着所有可能的情况都已覆盖,所以 match
表达式只能计算为已定义的情况之一。
现在我们可以使用 toFriendly
方法为任何 JobType
枚举值获取用户友好的标签:
$jobType = JobType::WebDeveloper;
echo $jobType->toFriendly(); // 输出: Web Developer
在我看来,像这样将 match
表达式与枚举配对是保持代码清洁和可维护的好方法。我觉得它们配合得非常好。
向枚举添加辅助方法(如 toFriendly
)的另一种替代方法是使用 PHP 属性。如果你感兴趣,我之前有一篇文章 PHP 属性指南 介绍了如何做到这一点