在PHP 8.0和8.1版本中的一些特性浅析示例及对旧版的对比

2023-06-01 00:00:00 示例 浅析 旧版

PHP8版本是2020年发布的,看看下面的例子来介绍所有最新的功能,说明我可能会选择使用它们。

让我们来回顾一下PHP 8.0版本中的几个突出特点及跟之前版本的示例代码对比。


构造函数属性推广

这一定是我最常用的php8.0功能之一,它为我节省了许多击键。

示例代码:

// Before PHP 8.0
class Client
{
    private string $url;
 
    public function __construct(string $url)
    {
        $this->url = $url;
    }
}

// PHP 8.0
class Client
{
    public function __construct(
        private string $url,
    ) {}
}

我们现在可以直接在构造函数中作为参数设置对象的属性,而不是手动分配它们。

我现在几乎一直在使用这个方法,因为它节省了精力,而且它使属性包含在构造函数中

--所以你可以直接了解你的对象的更多信息,不需要滚动。


联盟类型

这就是一个类型提示的变量或返回类型可以是一个或多个类型。

这对静态分析很有帮助,因为你可能在一个方法内有条件返回。

示例代码:

// Before PHP 8.0
class PostService
{
    public function all(): mixed
    {
        if (! Auth::check()) {
            return [];
        }
 
        return Post::query()->get();
    }
}

// PHP 8.0
class PostService
{
    public function all(): array|Collection
    {
        if (! Auth::check()) {
            return [];
        }
 
        return Post::query()->get();
    }
}

这个新增加的功能让我们在静态分析和自己理解我们的代码时可以做到超级具体--即使只是粗略的看一眼。我们知道all方法将返回一个数组或一个集合,这意味着我们的代码更可预测,我们知道如何处理。


命名的论据

这是我最近可能过度使用的另一个功能。我发现使用命名参数可以使我们的代码具有声明性

--不再需要猜测那个函数的第三个参数在你的代码库中意味着什么。

示例代码:

// Before PHP 8.0
class ProcessImage
{
    public static function handle(string $path, int $height, int $width, string $type, int $quality, int $compression): void
    {
        // logic for handling image processing
    }
}
 
ProcessImage::handle('/path/to/image.jpg', 500, 300, 'jpg', 100, 5);

// PHP 8.0
class ProcessImage
{
    public static function handle(string $path, int $height, int $width, string $type, int $quality, int $compression): void
    {
        // logic for handling image processing
    }
}
 
ProcessImage::handle(
    path: '/path/to/image.jpg',
    height: 500,
    width: 300,
    type: 'jpg',
    quality: 100,
    compression: 5,
);

正如你在上面的例子中所看到的--把高度和宽度弄错了,会产生与你预期不同的效果。

由于类和实现是紧挨着的,这就相对容易了。

现在想象一下,这个方法来自你安装的一个包,这个包可能没有最好的文档--使用命名的参数可以让你和其他使用你的代码库的人了解这个方法的这些参数的顺序。然而,这仍然应该谨慎使用,因为库的作者往往更频繁地改变参数名称,而且并不总是被认为是破坏性的变化。


匹配的表达方式

这是我接触过的所有人都喜欢的一项改进,是一项重大的改进。

在过去,我们使用了一个带有多个案例的大开关语句,让我们诚实地讲--它不是最好看的东西,也不是最容易处理的东西。

示例代码:

// Before PHP 8.0
switch (string $method) {
    case 'GET':
        $method = 'GET';
        break;
    case 'POST':
        $method = 'POST';
        break;
    default:
        throw new Exception("$method is not supported yet.");
}

// PHP 8.0
match (string $method) {
    'GET' => $method = 'GET',
    'POST' => $method = 'POST',
    default => throw new Exception(
        message: "$method is not supported yet.",
    ),
};

匹配语句允许一个更简洁的语法,并且更易读。

我不能说这可能会增加任何性能改进,但我知道在工作起来要容易得多。


在对象上使用::class

在过去,当你想把一个类的字符串传递给一个方法时,你必须使用像get_class这样的东西,这总是让人觉得有点毫无意义。

当时系统已经知道了这个类,因为你已经自动加载了它或者创建了一个新的实例。

示例代码:

// Before PHP 8.0
$commandBus->dispatch(get_class($event), $payload);

// PHP 8.0
$commandBus->dispatch(
    event: $event::class,
    payload: $payload,
);

就功能而言,这可能不是一个引人注目的东西,但它绝对是我使用的东西,并且在需要时总是会伸手去拿。


无捕获块

有时在构建一个应用程序时,你不需要访问可能被抛出的异常。不过对我来说,这种情况很少发生。

不过,你的里程可能会有所不同。

示例代码:

// Before PHP 8.0
try {
    $response = $this->sendRequest();
} catch (RequestException $exception) {
    Log::error('API request failed to send.');
}

// PHP 8.0
try {
    $response = $this->sendRequest();
} catch (RequestException) {
    Log::error('API request failed to send.');
}

我们不需要捕捉异常,因为我们在这种情况下并没有使用它。

如果我们想包含一个来自异常的消息,那么也许要确保你捕获了这个异常。

正如我所说,这不是我使用的东西,因为我通常想使用抛出的异常。


枚举

可爱的Enums,全世界代码库中无意义的数据库表和浮动常数的救星。

Enums已经迅速成为PHP 8.1中我最喜欢的功能之一--我现在可以把我的角色推到Enums中,而不是把它们放在一个永远不会改变的表中。

我可以将HTTP方法设置为Enums,而不是常量或我从未真正想使用的类的公共静态属性。

示例代码:

// before PHP 8.1
class Method
{
    public const GET = 'GET';
    public const POST = 'POST';
    public const PUT = 'PUT';
    public const PATCH = 'PATCH';
    public const DELETE = 'DELETE';
}
// PHP 8.1
enum Method: string
{
    case GET = 'GET';
    case POST = 'POST';
    case PUT = 'PUT';
    case PATCH = 'PATCH';
    case DELETE = 'DELETE';
}

上面的例子强调了语法上的差异,这一点已经得到了改善,但实际使用情况如何呢?

让我们举个简单的例子,我通常会在API集成中使用一个特质。

// Before PHP 8.1
trait SendsRequests
{
    public function send(string $method, string $uri, array $options = []): Response
    {
        if (! in_array($method, ['GET', 'POST', 'PATCH', 'PUT', 'DELETE'])) {
            throw new InvalidArgumentException(
                message: "Method [$method] is not supported.",
            );
        }
 
        return $this->buildRequest()->send(
            method: $method,
            uri: $uri,
            options: $options,
        );
    }
}

// PHP 8.1
trait SendsRequests
{
    public function send(Method $method, string $uri, array $options = []): Response
    {
        return $this->buildRequest()->send(
            method: $method->value,
            uri: $uri,
            options: $options,
        );
    }
}

它允许我的方法从类型的角度准确地知道传递进来的是什么--而且由于不支持的类型而抛出异常的机会更少。

如果我们想扩展支持,现在我们可以给我们的Enum添加一个新的案例--而不是添加一个新的常量,

并且不得不重构所有我们可能要检查支持的条件。


解压数组

这个功能是我不确定是否会使用的东西,直到我使用。以前,我们总是不得不复制东西或合并数组来获得我们需要的东西。

现在我们可以直接解压数组,而且行为也是一样的。

我在我的代码中经常使用DTO,所有的DTO都有一个叫做toArray的方法,这是我将DTO转化为Eloquent为我处理的一种简单方法。

让我们看看一个例子。

// Before PHP 8.1
final class CreateNewClient implements CreateNewClientContract
{
    public function handle(DataObjectContract $client, int $account): Model|Client
    {
        return Client::query()->create(
            attributes: array_merge(
                $client->toArray(),
                [
                    'account_id' => $account,
                ],
            ),
        );
    }
}

// PHP 8.1
final class CreateNewClient implements CreateNewClientContract
{
    public function handle(DataObjectContract $client, int $account): Model|Client
    {
        return Client::query()->create(
            attributes: [
                ...$client->toArray(),
                'account_id' => $account,
            ],
        );
    }
}

正如你所看到的,这只是一个小的代码变化,但它意味着我不必担心合并数组的问题,可以简单地在原地解包,建立我需要的数组。

它更干净,更容易管理。

我无法评论性能,因为这是一个很小的操作,但我很想听听谁对这种不同的方法进行了基准测试,看看是否有任何差异。


构造函数中的新内容

在 PHP 8.1 之前,由于各种原因,有时你可能不会向构造函数传递一个新的类的实例,而有时你会这样做。

这就造成了这样一种情况:你永远无法确定是否需要传递一个实例。

有那么一刻--我只是传递null,看看会发生什么--是一个希望最好的时刻。

示例代码:

// Before PHP 8.1
class BuyerWorkflow
{
    public function __construct(
        private null|WorkflowStepContract $step = null
    ) {
        $this->step = new InitialBuyerStep();
    }
}

// PHP 8.1
class BuyerWorkflow
{
    public function __construct(
        private WorkflowStepContract $step = new InitialBuyerStep(),
    ) {}
}

因此,至少在我看来,这里的主要胜利是代码的清洁。使用新的构造函数功能,我们可以不再担心可能传递null的问题--而只是让类来处理它。

上面的例子有点简单。说实话,这可能是由于我以前没有真正遇到过这些问题。

然而,我知道你们中的许多人都会这样做,希望能看到使用这个新特性的好处。


只读属性

它让我可以轻松地在编程中加入不变性,而不必降低可见性。

以前,我不得不把我想公开的属性改为保护或私有--这意味着我不得不在类中添加获取器--这感觉就像添加了真正不需要的模板。

示例代码:

// Before PHP 8.1
class Post
{
    public function __construct() {
        protected string $title,
        protected string $content,
    }
 
    public function getTitle(): string
    {
        return $this->title;
    }
 
    public function getContent(): string
    {
        return $this->content;
    }
}

// PHP 8.1
class Post
{
    public function __construct() {
        public readonly string $title,
        public readonly string $content,
    }
}

看看这个代码例子,你可以因为这个新的语言特性而增加的改进是令人印象深刻的。

很明显,我们可以看到只读属性给你带来的好处--你的代码不那么冗长了,你可以在保持不变性的同时放松可见性和访问。


当然,这并不是一个详尽的清单--它只是使这些版本脱颖而出的几个关键的东西。

在PHP 8.0和8.1中还有很多我没有提到的东西。

如果你想更深入地了解所有增加的东西,我强烈推荐你去看Brent Roose的blog:

https://stitcher.io/

相关文章