使用 git-hooks + phpcs + composer-scripts 践行PHP编码规范

Published on Jul 22, 2019

背景

关于代码规范的重要性,诸如促进团队合作、降低维护成本、有助于代码审查、有助于自身成长等,这里就不详细赘述了,直接进入正题。

PHP编码规范

当前主流的PHP编码规范是由 PHP FIG 组织制定的 PSR 系列规范,如 PSR-1 , PSR-2 , PSR-4 。 当然还是有其他一些规范的存在,如 PEAR规范Symfony规范。我们使用的主要是 PSR-2 规范。

用于检查和修复代码规范的工具主要有 PHP_CodeSnifferPHP-CS-Fixer。 如果你还想了解这两个工具的差异,可以看下这个 ISSUE。我们本次使用的是 PHP_CodeSniffer

Git钩子

和其它版本控制系统一样,Git 能在特定的重要动作发生时触发自定义脚本。 有两组这样的钩子:客户端的和服务器端的。 客户端钩子由诸如提交和合并这样的操作所调用,而服务器端钩子作用于诸如接收被推送的提交这样的联网操作。 钩子都被存储在 Git 目录下的 hooks 子目录中。 也即绝大部分项目中的 .git/hooks 。 查看一下你项目中的该目录, 就会看到诸如 pre-commit.sample, pre-push.sample, pre-receive.sample 等文件。 要使用他们,只需要把 .sample 后缀去掉,然后编辑自己的代码即可。 这里我们用的是 pre-commit 钩子,即在键入提交信息 git commit 前运行。 它一般用于检查即将提交的快照,例如,检查是否有所遗漏,确保测试运行,以及核查代码。 如果该钩子以非零值退出,Git 将放弃此次提交,不过也可以用 git commit --no-verify 来绕过这个环节。

Show me the code

废话不多说,直接上代码吧。 以下为 .git/hooks/pre-commit 的代码:

#!/bin/sh

PROJECT=`php -r "echo dirname(dirname(dirname(realpath('$0'))));"`
STAGED_FILES_CMD=`git diff --cached --name-only --diff-filter=ACMR HEAD | grep \\\\.php`

# Determine if a file list is passed
if [ "$#" -eq 1 ]
then
    oIFS=$IFS
    IFS='
    '
    SFILES="$1"
    IFS=$oIFS
fi
SFILES=${SFILES:-$STAGED_FILES_CMD}

echo "Checking PHP Lint..."
for FILE in $SFILES
do
    php -l -d display_errors=0 $PROJECT/$FILE
    if [ $? != 0 ]
    then
        echo "Fix the error before commit."
        exit 1
    fi
    FILES="$FILES $PROJECT/$FILE"
done

if [ "$FILES" != "" ]
then
    echo "Running Code Sniffer..."
    ./vendor/bin/phpcs $FILES
    if [ $? != 0 ]
    then
        echo "Fix the error before commit!"
        echo "Run"
        echo "  ./vendor/bin/phpcbf $FILES"
        echo "for automatic fix or fix it manually."
        exit 1
    fi
fi

exit $?

通过代码也能看出,我们直接使用了当前项目中的 ./vendor/bin/phpcs, 即需要我们在项目中加载 PHP_CodeSniffer 包。 执行 composer require squizlabs/php_codesniffer --dev 即可。 另外,在hook中我们也没有具体指明phpcs要使用哪种规范标准, 因为这更多是取决于我们自己的项目,具体的规则定义在项目根目录下的 phpcs.xml 配置文件中即可。 如果你想参考的话,这里 是一个我们使用的关于 yii2 的规则。

安装的Git钩子无法加入版本库, 每个成员,每个环境都要重复装一遍?

.git 目录下的文件无法加入版本库,这就导致了团队成员中的每个人都要手动安装一次钩子,或者你的多套环境也要重复安装钩子,这当然是很不方便的。 为了解决这个问题,我们可以利用 composer 的钩子来自动完成安装git钩子的过程。

Composer 在执行的过程中会触发一些事件,我们可以通过监控这些事件来自动触发一些脚本的执行。 就自动安装git钩子这个需求,我们可以使用 post-install-cmd 钩子,即当项目里有 composer.lock 文件的情况下调用 install 命令后执行安装脚本。 安装脚本就用最简单的单行PHP脚本即可。 同样,直接上代码:

  1. 将git的pre-commit钩子脚本放在项目根目录下,命名为 .git-pre-commit , 并加入版本库;
  2. 编辑项目中的 composer.json 文件:

    {
        "scripts": {
            "post-install-cmd": [
                "php -r \"is_dir('.git/hooks') && !is_file('.git/hooks/pre-commit') && copy('.git-pre-commit', '.git/hooks/pre-commit') && chmod('.git/hooks/pre-commit', 0755);\""
            ]
        }
    }
    

    加入如上代码后,当执行 composer install 后,会自动执行那行php代码, 即简单的将 .git-pre-commit 钩子脚本复制到 .git/hooks/pre-commit

    当然了,借助 composer 的脚本特性,你也可以继续定义其他的一些常用命令,比如:执行编码规范检查、执行编码规范修复等。 代码简单如下:

    {
        "scripts": {
            "cs": "phpcs .",
            "cbf": "phpcbf ."
        }
    }
    

    定义好之后,在项目根目录下执行 composer cs 即可对全项目进行编码规范检查,执行 composer cbf 即可对全项目进行编码规范修复。

结束语

我们日常用到的一些工具类确实都很强大,只是平时我们都只满足于最简单的日常使用。也许多看一眼文档,就能学习一些更高阶的用法。 希望自己能对日常使用的一些工具能较深入的了解学习一下,也许学习会花一些时间,但是更能为以后的开发节约不少时间,避免重复劳动。