PHP静态代码检查工具的使用

Posted by jintang on 2017-12-21

前言

如何保证PHP的代码质量?我们首先想到的是要遵循代码规范,良好的代码风格和良好的代码习惯,虽然说PSR给出了良好的规范标准,但是光靠人工遵守难免会把握不到位。

本篇来介绍一下PHP可用的静态代码质量检查工具,可以提前在你本地编辑器编写代码的时候就对你的”Bad Coding”代码做出提醒和预警,防止代码部署后,在运行时才发现问题,造成事故。

另外,推荐一个工具:grumphp能够在代码提交时强制性地拦截下来,保证代码版本库的代码质量。

代码质量的检查层次

我把代码质量划分了四个检查层次:基础语法检查、代码规范检查、Bad Coding检查和深层检查

1. 基础语法检查

有基本语法错误(syntax errors)的代码,在编译阶段就会报致命的错误,所以是必须检查出来,并由程序员手动进行改正。

因为这是最基的层次,所以大部分IDE都支持错误语法检查,包括php自身也带有语法检查的命令

但是由于php -l的反馈不太友好,推荐使用PHPLint的工具。

2. 代码规范检查

编译器对编码规范及风格是无感的,只要无语法错误,编译都可以通过。但是规范的代码和统一的风格有助于提高代码的可读性,提高编码效率和协作水平。



代码规范有许多工具可用,功能包括规范检测和自动格式化,PHPStorm也支持自动格式化代码:Reformat Code,下面推荐几种进阶工具:

phpcs (php code sniffer) PHP代码嗅探器

phpcbf (php code beautiful fixer) PHP代码美化器

php-cs-fixer,另一个PHP代码规范修复器

3. Bad coding检查

Bad Coding“意思为没有语法问题,也遵循了代码规范,但写法不太好,有可能诱发潜在问题的一部分代码。
经过语法检查和编码规范修正,代码质量水平的下限已经提高了许多,但是仍然有可能因为不好的编程习惯和一些bad coding导致出现一些潜在的问题,当程序运行时才会遇到。我们希望是可以在代码未运行时就在静态代码中发现。


4. 深层检查

如果已经把控好前三个层次,其实已经是非常合格的代码了。但是我们仍然可以做的更多,比如检查代码的复用率、代码复杂程度(作为代码重构的指标)和代码运行时的性能优化。


推荐工具:
phpcpd 代码复用率检查
churn 代码复杂度检查
xhprof 性能分析工具

工具的安装与使用

下面介绍phplint、phpcs、phpcbf、php-cs-fixer和grumphp的安装和使用,与PHPStorm整合的过程。

环境依赖

  • php
  • composer
  • git

准备工作

本文所有用到的工具,都可以通过composer来安装,为了把工具集中起来,我们建立一个composer project来统一存放

按照个人喜好,我创建一个名为”php-quality-tools”的工程

script
1
2
3
> mkdir php-quality-tools
> cd php-quality-tools
> composer init # 会有个交互过程,一直回车就行

加入环境变量:

script
1
2
3
4
5
6
# linux/mac环境
> cd /path/to/php-qulity-tools
> echo "export PATH=\$PATH:`pwd`/vendor/bin" >> ~/.bash_profile
> source ~/.bash_profile # 马上生效

# windows请百度一下"windows环境变量设置"

加入环境变量的解释:

根据composer工程的特征,vendor目录用于存放依赖的包,我们的所有工具(如phplint、phpcs等)都使用依赖包的方式进行安装,而相应的二进制文件(windows为.bat)文件会存放在各自的依赖包目录下的bin文件夹中,同时会通过ln的方式汇总一份到vendor/bin目录下,所以/path/to/php-quality-tools/vendor/bin加入环境变量就可以直接使用工具命令了。

检查工具安装及使用

PHP命令

其实PHP自带有语法检查的命令

script
1
> php -l xxx.php

但是输出比较简单,使用并不是很友好

PHPLint

语法检测器

功能

用于检测php是否有语法错误

安装

script
1
2
3
4
5
> cd /path/to/php-quality-tools
> composer require --dev overtrue/phplint
# 安装成功后,验证一下
> phplint -V
phplint 2.0.2

使用

有以下代码:test.php

1
2
3
4
5
6
7
8
<?php 
$var = 1;
foo($var)

function foo($var)
{
echo $var, "\n";
}

结果:

script
1
2
3
4
5
6
7
8
9
10
11
12
13

> phplint test.php

There was 1 errors:
1. /Users/jaych/Codes/projects/blake/src/test/test.php:5
2|
3| $var = 1;
4|
> 5| foo($var;
6|
7| function foo($var)
8| {
unexpected ';', expecting ')' in line 5

phpcs与phpcbf

  • phpcs: PHP代码嗅探器
  • phpcbf: PHP代码美化器

安装

两个工具都在同一个包中,所以一起安装就可以了

script
1
2
> cd /path/to/php-quality-tools
> composer require --dev squizlabs/php_codesniffer

使用

查看支持的代码规范标准

script
1
2
3
4
> phpcs -i
The installed coding standards are PEAR, Zend, PSR2, MySource, Squiz, PSR1 and PSR12
> phpcbf -i
The installed coding standards are PEAR, Zend, PSR2, MySource, Squiz, PSR1 and PSR12

检查代码,有如下代码:test.php

1
2
3
4
5
6
7
8
9
10
11
12
<?php 

class Foo {
private $foo1 = 100;
private $foo2 = 200;
public function hello(){
if($this->foo1>$this->foo2){
$foo3 = "good!";
}
echo "say hello!";
}
}

结果:

script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
> phpcs --extensions=php --standard=PSR2,PSR1 test.php

FILE: /Users/jaych/Codes/projects/blake/src/test/test.php
---------------------------------------------------------------------------------------------------
FOUND 5 ERRORS AFFECTING 3 LINES
---------------------------------------------------------------------------------------------------
3 | ERROR | [ ] Each class must be in a namespace of at least one level (a top-level vendor name)
3 | ERROR | [x] Opening brace of a class must be on the line after the definition
8 | ERROR | [x] Opening brace should be on a new line
9 | ERROR | [x] Expected 1 space(s) after IF keyword; 0 found
9 | ERROR | [x] Expected 1 space(s) after closing parenthesis; found 0
---------------------------------------------------------------------------------------------------
PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY
---------------------------------------------------------------------------------------------------

Time: 232ms; Memory: 6MB

> phpcbf --extensions=php --standard=PSR2,PSR1 test.php

PHPCBF RESULT SUMMARY
------------------------------------------------------------------------
FILE FIXED REMAINING
------------------------------------------------------------------------
/Users/jaych/Codes/projects/blake/src/test/test.php 4 1
------------------------------------------------------------------------
A TOTAL OF 4 ERRORS WERE FIXED IN 1 FILE
------------------------------------------------------------------------

Time: 355ms; Memory: 6MB

与PHPStorm的整合

强大的IDE编辑器都会有提供一些对应的辅助功能来做代码规范提示,下面来展示phpcs与phpstorm的整合方法

PHPStorm点开配置 > languages & frameworks > PHP > Quality Tools 第一栏 php_codesniffer (已更新为2019.03版本的phpstorm的配置方法)



配置”inspection”: 配置 > Editor > PHP > Inspections

PHPStorm中的Inspection提示效果

PHPStorm IDE编辑器自带的格式化工具

首先设置Code Style为 PSR1/PSR2

其实PHPStorm格式化代码的功能”Reformat code”和”Optimize imports”

使用”Marco”做动作打包,并且加入到保存快捷键中,这样保存的时候就自动做好code reformat和optimize imports了

进阶代码规范修复器:php-cs-fixer

与phpcbf类似,但是能够修复的内容会更多

安装

script
1
> composer require --dev friendsofphp/php-cs-fixer

与PHPStorm整合

效果

phpmd

针对有可能因为不好的编程习惯和一些bad coding导致出现一些潜在的问题给出优化建议

安装

script
1
> composer require --dev phpmd/phpmd

使用

script
1
> phpmd /path/to/sources text design,cleancode

与PHPStorm整合

效果

Grumphp

GrumPHP 是通过挂在git hook上的一款PHP代码检测工具,人称”愤怒的老头”。
GrumPHP可以整合多个工具,并且编排成task,在git commit的时候,对暂存区stage的代码按照编排好的task进行检查,如果不通过的话,git commit会失败。

原理 :利用git hooks钩子脚本pre-commit,当进行git commit动作时,会先调用pre-commit脚本执行
grumphp git:commit命令

安装

script
1
composer require --dev phpro/grumphp

配置

需要编辑grumphp.yml文件,并且放在代码的根目录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
grumphp:
hooks_dir: .git
hooks_preset: local
git_hook_variables:
stop_on_failure: false
ignore_unstaged_changes: false
hide_circumvention_tip: false
process_timeout: 60
parallel:
enabled: true
max_workers: 32
fixer:
enabled: true
fix_by_default: false
environment:
files: []
variables: {}
paths: []
tasks:
phplint:
short_open_tag: false
ignore_patterns: ['/vendor']
triggered_by: ['php']
phpcs:
standard: [PSR1,PSR2]
ignore_patterns: ['/vendor']
triggered_by: ['php']
testsuites: []
extensions: []

配置文件中的重点几个配置:

hooks_dir: 指定git hooks的所在的目录,一般都是站点代码的更目录下的.git
tasks: 指定需要完成的任务清单,可以支持上面介绍过的工具,包括phplint,phpcs

更多的配置说明,参考:https://github.com/phpro/grumphp

从上面的grumphp.yml配置的tasks看出,我只是指定了phplint,phpcs两个工具,正好对应的是代码检查层次的第一层基础语法层和第二层代码规范层,因为我认为能够提交到代码版本库中的代码,应该至少符合第一二层的要求。为了权衡开发效率和代码质量,我认为到第二层即可,第三层可以作为进阶要求。

使用

把grumphp注册git hooks中:

script
1
> grumphp git:init

完成以后可以看到.git/hooks/pre-commit脚本多了几行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/sh

#
# Run the hook command.
# Note: this will be replaced by the real command during copy.
#

# Fetch the GIT diff and format it as command input:
DIFF=$(git -c diff.mnemonicprefix=false --no-pager diff -r -p -m -M --full-index --no-color --staged | cat)

# Grumphp env vars
export GRUMPHP_GIT_WORKING_DIR=$(git rev-parse --show-toplevel)
# Run GrumPHP
(cd "./" && printf "%s\n" "${DIFF}" | exec '/path/to/phpro/grumphp/bin/grumphp' 'git:pre-commit' '--skip-success-output')

注意最后一行的exec ‘/path/to/phpro/grumphp/bin/grumphp’,是你实际grumphp所在的目录,一般都是自动生成。

配置完成后,只要我们提交代码,就会对做出修改的文件进行tasks指定的检测。

总结

本篇探讨了代码质量检查的四个层次,介绍了前三层的工具的安装和使用:

基础语法检查 对应工具:php -l,phplint
代码规范检查 对应工具:phpcs,phpcbf和php-cs-fixer,与PHPStorm整合
Bad Coding检查 对应工具:phpmd、phpstan
深层检查 对应工具:phpcpd、chrun和xhprof (没有介绍,感兴趣可以搜索这些工具)

介绍了PHPStorm的Reformat Code功能与自定义宏的配合使用,达到保存时自动格式化代码。

最后介绍了grumphp工具怎么做到代码提交时拦截和检查。

希望对你有帮助,学艺不精,欢迎讨论指正。