语法检查和代码样式

我们要讨论的第一组工具可以帮助我们保持代码的语法正确性(即可以被 PHP 正确执行)和格式的结构化。代码必须写得没有错误,这似乎是显而易见的,但最好还是仔细检查一下,因为有些工具会主动更改你的代码。当我们在本书稍后部分将整个代码质量流程自动化时,有一个简单快速的方法来确保这一点将是至关重要的。

按照通用的风格指南格式化代码,可以减少阅读和理解自己的代码以及他人代码所需的工作量。特别是当你在一个团队中工作时,一个公认的风格指南可以让你省去几个小时讨论如何正确格式化代码的时间。

我们将学习以下工具:

  • A PHP built-in linter

  • The PHP CS Fixer tool

PHP 内置 linter

我们要了解的第一个工具实际上并不是一个代码质量工具,而是 PHP 二进制文件本身的一个内置选项:Linter。它可以在不执行代码的情况下检查代码是否存在语法错误。这对于确保代码在重构后或代码被外部工具修改后仍能正常运行特别有用。

安装和使用

由于 Linter 已经是 PHP 安装的一部分,我们可以通过一个示例立即开始使用它。如果仔细观察,你可能会发现作者在下面的类示例中犯了一个错误:

<?php

class Example
{
    public function doSomething() bool
    {
        return true;
    }
}

如果你没有立即发现错误,也不必担心,这正是 Linter 的作用所在!只需使用 -l 选项将要检查的文件的全名和路径传递给 PHP 二进制程序即可。通过添加 -f 选项,PHP 还将检查致命错误,这正是我们想要的。这两个选项可以合并为 -lf

假设前面提到的类可以在当前文件夹下的 example.php 文件中找到,那么我们只需键入以下内容:

$ php -lf example.php

输出结果如下:

PHP Parse error: syntax error, unexpected identifier "bool", expecting ";" or "{" in example.php on line 5 Errors parsing example.php

你也可以让打印机检查完整的目录:

$ php -lf src/*

内置的 PHP 检查程序会在第一次出错时停止运行,也就是说,它不会给出所有检测到的错误的完整列表。因此,最好确保在解决问题后再次运行该命令。

PHP 内置接口概述

内置的 PHP linter 是快速检查代码的便捷工具,但功能远不止于此。还有其它更复杂的检查程序,如 https://github.com/overtrue/phplint 。它不仅能返回完整的错误列表,还能并行运行多个进程,在大型代码库中速度明显更快。不过,其它代码质量工具也已经包含了 linter,例如我们将在下一节中的检查工具。

PHP CS Fixer:代码嗅探器

另一个必不可少的工具是代码嗅探器。它可以扫描 PHP 代码,找出违反编码标准的地方和其它不良做法。PHP CS Fixer (https://github.com/FriendsOfPHP/PHP-CS-Fixer) 是一个不错的选择,因为顾名思义,它不仅能报告发现的问题,还能立即修复。

其它代码嗅探器

PHP CS Fixer 不是唯一可用的代码嗅探器。 另一个著名的工具是 PHP_CodeSniffer (https://github.com/squizlabs/PHP_CodeSniffer ),我们也完全推荐使用它。

安装和使用

使用 Composer,安装非常简单:

$ composer require friendsofphp/php-cs-fixer --dev
Composer 的替代品

有多种方法可以安装我们将在本书中介绍的工具。我们还将在本书后面查看更多选项。

代码嗅探器的典型用例是注意括号的位置和缩进的数量,无论是空格还是制表符。让我们看看下面这个格式难看的 PHP 文件:

<?php

class Example
{
    public function doSomething(): bool { return true; }
}

如果我们使用默认设置运行代码嗅探器,命令会非常简短:

$ vendor/bin/php-cs-fixer fix example.php

这将一次性扫描并修复 example.php 文件,使我们的代码整洁光亮:

<?php

class Example
{
    public function doSomething(): bool
    {
        return true;
    }
}

如果不想立即修复文件,可以使用 --dry-run 选项,只扫描问题。还可以添加 -v 选项来显示结果:

$ vendor/bin/php-cs-fixer fix example.php --dry-run -v

与所有代码质量工具一样,你也可以在文件夹中的所有文件上运行它。以下命令将递归扫描 src 文件夹,因此所有子文件夹也会被扫描:

$ vendor/bin/php-cs-fixer fix src

规则和规则集

到目前为止,我们使用的是 PHP CS Fixer 的默认设置。在更改这些默认设置之前,让我们仔细看看它是如何知道要检查和修复什么的。

代码质量工具的一个常见模式是在规则集中组织规则。规则是一个简单的指令,它告诉 PHP CS Fixer 我们的代码在某个方面应该如何格式化。例如,如果我们想在 PHP 中使用严格类型,那么每个 PHP 文件都应包含 declare(strict_types=1); 指令。

PHP CS Fixer 中有一条规则可以强制这样做:

$ vendor/bin/php-cs-fixer fix src --rules=declare_strict_types

该命令将检查 src 中的每个文件,并在开头的 PHP 标记后添加 declare(strict_types=1);

由于像 PSR-12 (https://www.php-fig.org/psr/psr-12/) 这样的编码标准包含了许多关于代码格式的说明,如果将所有这些规则都添加到前面的命令中,将会非常麻烦。因此,我们引入了规则集,它是规则甚至其它规则集的简单组合。

如果我们想明确地按照 PSR-12 格式化代码,只需运行以下命令即可:

$ vendor/bin/php-cs-fixer fix src --rules=@PSR12

正如你所看到的,规则集由 @ 符号表示。

规则和规则集文档

在本书的范围内讨论 PHP CS Fixer 的每条规则和规则集是不可能的。 如果你对它还提供什么感到好奇,请查看官方 GitHub 存储库: https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/master/doc

配置

开始时手动执行命令还可以,但到了一定时候,我们就不想每次都记住所有选项了。这就是配置文件发挥作用的地方:大多数 PHP 代码质量工具都允许我们以 YAML、XML 或纯 PHP 等各种格式在一个或多个文件中存储所需的配置。

对于 PHP CS Fixer,所有相关设置都可以通过 .php-cs-fixer.dist.php 配置文件来控制。这里有一个示例:

<?php

$finder = PhpCsFixer\Finder::create()
    ->in(__DIR__)
    ->exclude('templates');

$config = new PhpCsFixer\Config();
return $config->setRules([
    '@PSR12' => true,
    'declare_strict_types' => true,
    'array_syntax' => ['syntax' => 'short'],
])->setFinder($finder);

这里发生了许多事情。首先,创建了一个 PhpCsFixer\Finder 实例,该实例被配置为使用相同的目录来查找配置文件所在的 PHP 文件。由于应用程序的根目录通常位于此处,我们可能希望将某些子目录(如本例中的 templates)排除在扫描范围之外。

其次,创建 PhpCsFixer\Config 实例。在这里,我们要告诉 PHP CS Fixer 应用哪些规则和规则集。我们已经讨论过 @PSR-12 规则集和 declare_strict_ types 规则。array_syntax 规则强制使用短数组语法。

你可能已经注意到,配置文件的名称 .php-cs-fixer.dist.php 包含一个缩写 dist。这代表分发,通常表示该文件是项目分发的文件。换句话说,该文件会被添加到 Git 仓库,并在签出后立即可用。

如果想在本地系统中使用自己的配置,可以创建一个副本并重命名为 .php-cs-fixer.php。如果该文件存在,PHP-CS Fixer 将使用它而不是 dist-file。好的做法是让 Git 忽略该文件。否则可能会不小心将本地设置添加到版本库中。

高级使用

PHP CS Fixer 的功能不仅限于自动修复违反编码标准的问题。它还可用于执行小型重构任务。例如,一个很好的用例就是自动迁移到更高的 PHP 版本:PHP CS Fixer 随附的迁移规则集可以为代码库引入一些新的语言特性。

例如,在 PHP 8.0 中,可以使用 class 关键字来代替 get_class() 函数。PHP CS Fixer 可以扫描你的代码并替换某些行,例如,请参阅以下内容:

$class = get_class($someClass);

可以用这句话替换前一行:

$class = $someClass::class;

迁移规则集分为非风险规则集和风险规则集。风险规则集可能会导致副作用,而非风险规则集通常不会造成任何问题。我们之前讨论过的 declare_strict_types 规则就是风险变更的一个很好的例子。应用这些规则后,请务必彻底测试应用程序。

这些迁移的功能是有限的—​你的代码不会突然包含所有新的 PHP 版本特性。

代码修复程序无法为我们修复语法错误。例如,我们在上一节中使用 PHP 内置指针检查过的 Example 类仍然需要开发人员先手动修正。

检查

PHP CS Fixer 的第一步是检查要检测的文件是否有语法错误,如果发现语法错误,它不会进行任何修改。这意味着你不必再额外运行 PHP 内置的语法检查程序(linter)。

PHP CS 修复程序概述

像 PHP CS Fixer 这样的代码嗅探器应该成为每个严肃的 PHP 项目的一部分。自动修复违反规则的功能将为你节省许多工作时间。如果你选择不应用任何有风险的修复,那么几乎不会造成任何问题。

我们现在已经学会了如何确保代码格式正确、语法正确。虽然这是高质量代码的基础,但它并不能帮助我们避免错误或可维护性问题。此时,静态代码分析工具便开始发挥作用。