编程

PHP 中的命名参数

43 2025-01-08 00:57:00

介绍

命名参数是 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 之外的所有默认值创建一个用户。使用位置参数,我们必须传递 sendWelcomeEmailisVerifiedrole 参数的默认值:

use App\Services\UserService;
 
$userService = new UserService();
 
$user = $userService->createUser(
    'Ash',
    'mail@ashallendesign.co.uk',
    true,
    false,
    'user',
    'es',
);

而使用命名参数,如果我们想覆盖默认值,我们只需要将值传递给可选参数。因此,我们可以跳过 sendWelcomeEmailisVerifiedrole 参数:

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 代码中使用命名参数的好处。本文同时还展示使用命名参数时需要注意的事项。

 

PHP