原文:個人我的博客 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
三個類呢都被掃描到了。
看到這裏,是否是有想把本身項目上線流程裏面加上靜態語法檢測呢?心動不如行動。
也歡迎你們關注個人公衆號,不發騷擾,只發乾貨原創文章