原文:个人我的博客 https://mengkang.net/1356.html
工做了两三年,技术停滞不前,迷茫没有方向,不如看下个人直播 PHP 进阶之路 (金三银四跳槽必考,通常人我不告诉他)
不少时候,最大的优点在某些状况下就会变成最大的劣势。PHP 语法很是灵活,也不用编译。可是在项目比较复杂的时候,可能会致使一些意想不到的 bug。php
不知道你的项目是否有遇到过相似的线上故障呢?好比html
文件1java
class Animal { public $hasLeg = false; }
文件2node
include "Animal.php"; class Dog extends Animal { protected $hasLeg = false; } $dog = new Dog();
php Dog.php Fatal error: Access level to Dog::$hasLeg must be public (as in class Animal) in /Users/mengkang/vagrant-develop/project/untitled1/Dog.php on line 5
(注意 IDE 并无提示有预发错误的哟,我专门截图)git
今天在看代码的时候看到一个变量一直重复查询,就是用户是不是管理员的身份。我想既然这样,否则在第一次用的地方就放入到成员变量里,省得后面都重复查询。github
结果发现我在父类定义的变量名$isAdmin
,以前的代码已经在某一个子类里面单独定义过了。父类里是public
属性,而子类里是private
致使了这个故障。shell
若是是 java 这种错误,没法编译经过。可是 php 不须要编译,只要测试没有覆盖到刚刚修改的文件就不会发现这个问题,既是优点也是弱势。json
有时候a.php
,b.php
,c.php
三个文件都引用d.php
的的一个函数,可是修改了d.php
里面的一个函数的参数个数,若是前面使用的3个文件里面的没有改全,只改了a.php
,而测试的时候又没有覆盖到b.php
和c.php
,那么上线了,就会触发bug
和错误了。vim
你可能认为这种错误过低级了,不可能发生在本身身上,可是根据个人经验的确会发生,高强度的需求之下,很容易复制粘贴一些东西,只复制一半。并且恰巧由于某些逻辑判断,本身在平常环境开发的时候,出现问题的地方没有被执行到。
好比下面这段代码:segmentfault
$article = $this->getParam('article'); // 假设下面这段代码是复制的 $isPowerEditer = "xxxxx 演示代码"; if(!$isPowerEditer){ if ($article->getUserId() != $uid) { ... } }
由于复制的来源处,$article
是一个对象,因此调用了getUserId
的方法。可是上面的$article
是一个从客户端获取的参数,不是对象。
Call to a member function getUserId() on a non-object
而本身测试的时候,由于if(!$isPowerEditer)
的判断致使没有执行到里面去。直到上线以后才发现问题。
Cannot use object of type DataObject\Article as array
不由反思,若是这个项目是 java 的,确定不会出现上面两个问题了,由于在项目构建的时候就已经无法经过了。
这也不飘红?多写了个s
呢,可能由于外面包了一个empty
因此IDE没有标记为错误吧。因此咱们不能太相信IDE。
进一步思考,咱们是否可以作一个工具来本身模拟编译呢?写了一个小 demo ,依赖nikic/php-parser
https://github.com/nikic/PHP-...
PHP-Parser 能够把PHP代码解析为AST,方便咱们作语法分析。好比上面的例子
文件1
class Animal { public $hasLeg = false; }
文件2(Dog.php)
include "Animal.php"; class Dog extends Animal { protected $hasLeg = false; } $dog = new Dog();
咱们利用 PHP-Parser 作了语法解析检测,代码以下:
include dirname(__DIR__)."/vendor/autoload.php"; use PhpParser\Error; use PhpParser\Node\Stmt\Property; use PhpParser\ParserFactory; use PhpParser\Node\Stmt\Class_; $code = file_get_contents("Dog.php"); $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP5); try { $ast = $parser->parse($code); } catch (Error $error) { echo "Parse error: {$error->getMessage()}\n"; return; } $classCheck = new ClassCheck($ast); $classCheck->extendsCheck(); class ClassCheck{ /** * @var Class_[]|null */ private $classTable; public function __construct($nodes) { foreach ($nodes as $node){ if ($node instanceof Class_){ $name = $node->name; if (!isset($this->classTable[$name])) { $this->classTable[$name] = $node; }else{ // 报错哪里类重复了 echo $node->getLine(); } } } } public function extendsCheck(){ foreach ($this->classTable as $node){ if (!$node->extends){ continue; } $parentClassName = $node->extends->getFirst(); if (!isset($this->classTable[$parentClassName])) { exit($parentClassName."不存在"); } $parentNode = $this->classTable[$parentClassName]; foreach ($node->stmts as $stmt){ if ($stmt instanceof Property){ // 查看该属性是否存在于父类中 $this->propertyCheck($stmt,$parentNode); } } } } /** * @param Property $property * @param Class_ $parentNode */ private function propertyCheck($property,$parentNode){ foreach ($parentNode->stmts as $stmt){ if ($stmt instanceof Property){ if ($stmt->props[0]->name != $property->props[0]->name){ continue; } if ($stmt->isProtected() && $property->isPrivate()) { echo $stmt->getLine()."\n"; echo $property->getLine()."\n"; } } } } }
原理能就是对解析出来的AST继续作分析,可是前人栽树后人乘凉,这样的完整工具已经有大神帮咱们作好了。
https://github.com/phan/phan
能够说它与上面介绍的nikic/php-parser
师出同门,依赖nikic/php-ast
PHP扩展
php-ast
扩展大概描述安装步骤
git clone https://github.com/nikic/php-ast cd php-ast/ phpize sudo ./configure --enable-ast sudo make sudo make install cd /etc/php.d # 引入扩展 sudo vim ast.ini # 就能看到扩展啦 php -m | grep ast
大概描述安装步骤
curl -sS https://getcomposer.org/installer | php
安装plan
mkdir test cd test ~/composer.phar require --dev "phan/phan:1.x"
新建个项目,随便写个有问题的代码
路径是src/a.php
<?php class A extends B { public function a1() { return $this->a2(1); } /** * @param array $b * * @return int */ private function a2($b) { return $b + 1; } }
写个shell脚本
#!/bin/bash function log() { echo -e -n "\033[01;35m[YUNQI] \033[01;31m" echo $@ echo -e -n "\033[00m" } Color_Text() { echo -e " \e[0;$2m$1\e[0m" } Echo_Red() { echo $(Color_Text "$1" "31") } Echo_Green() { echo $(Color_Text "$1" "32") } Echo_Yellow() { echo $(Color_Text "$1" "33") } : > file.list for file in $(ls src/*) do echo $file >> file.list done Echo_Green "file list:\n" Echo_Green "========================\n" cat file.list Echo_Green "========================\n" Echo_Yellow "Phan run\n" Echo_Yellow "========================\n" ./vendor/bin/phan -f file.list -o res.out Echo_Yellow "========================\n" Echo_Red "error log\n" Echo_Red "========================\n" cat res.out Echo_Red "========================\n"
案例中的错误
新增一个src/b.php
<?php class B{ }
能过自动查找到class B
了,不用咱们作自动加载规则的指定
刚刚两个都是测试的单独的脚本,没有测试项目,其实Plan
已经支持了。假如我有一个项目以下
我在composer.json里面指定自动加载规则
{ "require-dev": { "phan/phan": "1.x" }, "autoload": { "psr-4": { "Mk\\": "src" } } }
而后在项目根目录执行
./vendor/bin/phan --init --init-level=3
而后就会生成默认的配置文件在.phan
目录里,最后就能够执行静态检测命令了
./vendor/bin/phan --progress-bar
如图所示呢,说明根据项目的自动加载规则A
,B
,C
三个类呢都被扫描到了。
看到这里,是否是有想把本身项目上线流程里面加上静态语法检测呢?心动不如行动。
也欢迎你们关注个人公众号,不发骚扰,只发干货原创文章