PHP 中的命名参数
介绍
命名参数是 PHP 8.0 版本中新增的功能之一。我觉得它们真的有助于提高我的代码的可读性,让我更容易理解发生了什么。
本文中,我们将看看什么是命名参数及其提供的好处。我们还将快速查看使用它们时需要注意的几个问题。
命名参数简述
简单地说,命名参数允许你通过指定参数名称,并在其后紧跟冒号和值将参数传递给函数或方法。例如,假设我们有一个名为 greet
的函数,它有两个参数:
function greet(string $name, string $greeting): string
{
return "{$greeting}, {$name}!";
}
使用位置参数(通常会这么用),我们可以这样调用此函数:
echo greet('Ash', 'Hello');
而使用命名参数,我们可以这样调用该函数:
echo greet(name: 'Ash', greeting: 'Hello');
通过使用命名参数,我们可以立即看到每个参数的意图。
命名参数解决了什么问题?
我认为,要真正理解使用命名参数的好处及其解决的问题,最好的方法是看一个稍微深入一点的例子。
假设有一个服务类,它允许你创建一个新用户。我们可以在服务类上调用 createUser
方法,如下所示:
use App\Services\UserService;
$userService = new UserService();
$user = $userService->createUser(
'Ash',
'mail@ashallendesign.co.uk',
true,
);
通过查看上面的代码,您可能可以猜测第一个和第二个参数是什么(姓名和邮箱地址)。但第三个参数是什么?
如果你在项目中遇到了这段代码,你现在可能需要在集成开发环境(IDE)中点击查看第三个参数是什么。必须这样做会给开发过程增加摩擦,减缓你的速度,因为参数是什么并不明显。
这就是命名参数发挥作用的地方。
让我们来看看 createUser
方法签名:
app/Services/UserService.php
namespace App\Services;
use App\Models\User;
class UserService
{
public function createUser(
string $name,
string $email,
bool $sendWelcomeEmail,
): User {
// Create the user
}
}
我们可以在方法签名中看到,第三个参数用于指定是否向用户发送欢迎电子邮件。通过使用命名参数,我们可以使代码更具可读性和自解释性:
use App\Services\UserService;
$userService = new UserService();
$user = $userService->createUser(
name: 'Ash',
email: 'mail@ashallendesign.co.uk',
sendWelcomeEmail: true,
);
正如我们在上面的代码中看到的,我们使用了命名参数来更清楚地说明每个参数的用途。这意味着代码乍一看更容易阅读,并且有助于减少以后再次使用时理解它所需的时间。
命名参数真正的迷人之处在于,你可以混合使用命名参数和位置参数(只要命名参数在位置参数之后)。所以,假设我们只想为第三个参数使用命名参数,我们可以这样更新我们的函数调用:
use App\Services\UserService;
$userService = new UserService();
$user = $userService->createUser(
'Ash',
'mail@ashallendesign.co.uk',
sendWelcomeEmail: true,
);
命名参数的益处
在代码中使用命名参数有以下几个好处:
提升可读性
如前所述,使用命名参数最大的好处是是提高了可读性。通过使用命名参数,可以使代码更加不言自明,乍一看更容易理解。
当你重新审视你前一段时间写的代码,或者当你和一个开发团队一起工作时,这可能非常有用。
它几乎可以作为一种代码形式的文档,因为你可以向阅读代码的开发人员提供有关每个参数用途的提示。
跳过可选参数
命名参数的另一个好处是,如果它们与默认值匹配,你可以使用它们来减少需要传递给方法的可选参数的数量。
例如,假设我们有以下 createUser
方法:
app/Services/UserService.php
namespace App\Services;
use App\Models\User;
class UserService
{
public function createUser(
string $name,
string $email,
bool $sendWelcomeEmail = true,
bool $isVerified = false,
string $role = 'user',
string $locale = 'en',
): User {
// Create the user
}
}
顺便说一句,这个方法签名是一种代码异味(它有太多的参数),可以重构以提高可维护性,特别是通过向方法传递数据传输对象(DTO)。但就本例而言,让我们保持原样。
在该方法中,我们实际上只强制执行前两个参数 ($name
和 $email
)。其余参数具有默认值,因此它们是可选的.
让我们想象一下,我们想用除 locale
之外的所有默认值创建一个用户。使用位置参数,我们必须传递 sendWelcomeEmail
、isVerified
和 role
参数的默认值:
use App\Services\UserService;
$userService = new UserService();
$user = $userService->createUser(
'Ash',
'mail@ashallendesign.co.uk',
true,
false,
'user',
'es',
);
而使用命名参数,如果我们想覆盖默认值,我们只需要将值传递给可选参数。因此,我们可以跳过 sendWelcomeEmail
、isVerified
和 role
参数:
use App\Services\UserService;
$userService = new UserService();
$user = $userService->createUser(
name: 'Ash',
email: 'mail@ashallendesign.co.uk',
locale: 'es',
);
需要注意的事
值得注意的是,命名参数在方法签名中应该与函数调用中完全匹配。这意味着,如果你拼错了命名参数或重命名了参数,将抛出错误。
比如,让我们采用前面的 createUser
方法。想象一下,sendWelcomeEmail
参数被重命名为 sendWelcomeNotification
,如下所示:
app/Services/UserService.php
namespace App\Services;
use App\Models\User;
class UserService
{
public function createUser(
string $name,
string $email,
bool $sendWelcomeNotification,
): User {
// Create the user
}
}
如果我们没有更新之前的的代码,将会抛出错误如下:
Unknown named parameter $sendWelcomeEmail
因此,我们需要更新函数调用以匹配新的方法签名:
use App\Services\UserService;
$userService = new UserService();
$user = $userService->createUser(
name: 'Ash',
email: 'mail@ashallendesign.co.uk',
sendWelcomeNotification: true,
);
因此,了解方法签名的更改非常重要。如果你拥有你正在改变的方法,那么发现这些变化就容易得多。但是,如果使用的是第三方库,则需要查看文档或更改日志,以查看方法签名是否已更改。如果你没有注意到这一变化,你可能会在应用出现错误。
另一个捕捉这些错误的好方法是准备一个好的测试套件,或者使用像 PHPTan 这样的静态分析工具。因此,你可能会发现,在更新 Composer 依赖关系后运行测试套件以捕获任何突破性更改是有益的。
小结
希望通过本文你能了解在 PHP 代码中使用命名参数的好处。本文同时还展示使用命名参数时需要注意的事项。