成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

使用 Phan 為你的 PHP 項(xiàng)目保駕護(hù)航 - 代碼靜態(tài)掃描

array_huang / 2344人閱讀

摘要:比如上面的例子文件文件我們利用做了語(yǔ)法解析檢測(cè),代碼如下報(bào)錯(cuò)哪里類重復(fù)了不存在查看該屬性是否存在于父類中原理能就是對(duì)解析出來(lái)的繼續(xù)做分析,但是前人栽樹(shù)后人乘涼,這樣的完整工具已經(jīng)有大神幫我們做好了。

原文:我的個(gè)人博客 https://mengkang.net/1356.html
工作了兩三年,技術(shù)停滯不前,迷茫沒(méi)有方向,不如看下我的直播 PHP 進(jìn)階之路 (金三銀四跳槽必考,一般人我不告訴他)

很多時(shí)候,最大的優(yōu)勢(shì)在某些情況下就會(huì)變成最大的劣勢(shì)。PHP 語(yǔ)法非常靈活,也不用編譯。但是在項(xiàng)目比較復(fù)雜的時(shí)候,可能會(huì)導(dǎo)致一些意想不到的 bug。

背景分析

不知道你的項(xiàng)目是否有遇到過(guò)類似的線上故障呢?比如

繼承類語(yǔ)法錯(cuò)誤導(dǎo)致的故障

文件1

class Animal
{
    public $hasLeg = false;
}

文件2

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 并沒(méi)有提示有預(yù)發(fā)錯(cuò)誤的喲,我專門截圖)

今天在看代碼的時(shí)候看到一個(gè)變量一直重復(fù)查詢,就是用戶是否是管理員的身份。我想既然這樣,不然在第一次用的地方就放入到成員變量里,免得后面都重復(fù)查詢。

結(jié)果發(fā)現(xiàn)我在父類定義的變量名$isAdmin,之前的代碼已經(jīng)在某一個(gè)子類里面多帶帶定義過(guò)了。父類里是public屬性,而子類里是private導(dǎo)致了這個(gè)故障。

如果是 java 這種錯(cuò)誤,無(wú)法編譯通過(guò)。但是 php 不需要編譯,只要測(cè)試沒(méi)有覆蓋到剛剛修改的文件就不會(huì)發(fā)現(xiàn)這個(gè)問(wèn)題,既是優(yōu)勢(shì)也是弱勢(shì)。

參數(shù)不符合預(yù)期

有時(shí)候a.php,b.php,c.php三個(gè)文件都引用d.php的的一個(gè)函數(shù),但是修改了d.php里面的一個(gè)函數(shù)的參數(shù)個(gè)數(shù),如果前面使用的3個(gè)文件里面的沒(méi)有改全,只改了a.php,而測(cè)試的時(shí)候又沒(méi)有覆蓋到b.phpc.php,那么上線了,就會(huì)觸發(fā)bug和錯(cuò)誤了。

錯(cuò)把數(shù)組當(dāng)對(duì)象

你可能認(rèn)為這種錯(cuò)誤太低級(jí)了,不可能發(fā)生在自己身上,但是根據(jù)我的經(jīng)驗(yàn)的確會(huì)發(fā)生,高強(qiáng)度的需求之下,很容易復(fù)制粘貼一些東西,只復(fù)制一半。而且恰巧因?yàn)槟承┻壿嬇袛?,自己在日常環(huán)境開(kāi)發(fā)的時(shí)候,出現(xiàn)問(wèn)題的地方?jīng)]有被執(zhí)行到。
比如下面這段代碼:

$article = $this->getParam("article");

// 假設(shè)下面這段代碼是復(fù)制的
$isPowerEditer = "xxxxx 演示代碼";

if(!$isPowerEditer){
    if ($article->getUserId() != $uid)
    {
        ...
    }
}

因?yàn)閺?fù)制的來(lái)源處,$article是一個(gè)對(duì)象,所以調(diào)用了getUserId的方法。但是上面的$article是一個(gè)從客戶端獲取的參數(shù),不是對(duì)象。

Call to a member function getUserId() on a non-object

而自己測(cè)試的時(shí)候,因?yàn)?b>if(!$isPowerEditer)的判斷導(dǎo)致沒(méi)有執(zhí)行到里面去。直到上線之后才發(fā)現(xiàn)問(wèn)題。

錯(cuò)把對(duì)象當(dāng)數(shù)組

Cannot use object of type DataObjectArticle as array

不禁反思,如果這個(gè)項(xiàng)目是 java 的,肯定不會(huì)出現(xiàn)上面兩個(gè)問(wèn)題了,因?yàn)樵陧?xiàng)目構(gòu)建的時(shí)候就已經(jīng)沒(méi)法通過(guò)了。

不存在的數(shù)組


這也不飄紅?多寫了個(gè)s呢,可能因?yàn)橥饷姘艘粋€(gè)empty所以IDE沒(méi)有標(biāo)記為錯(cuò)誤吧。所以我們不能太相信IDE。

思考與改進(jìn) 自造輪子實(shí)驗(yàn)

進(jìn)一步思考,我們是否能夠做一個(gè)工具來(lái)自己模擬編譯呢?寫了一個(gè)小 demo ,依賴nikic/php-parser

https://github.com/nikic/PHP-...

PHP-Parser 可以把PHP代碼解析為AST,方便我們做語(yǔ)法分析。比如上面的例子
文件1

class Animal
{
    public $hasLeg = false;
}

文件2(Dog.php)

include "Animal.php";

class Dog extends Animal
{
    protected $hasLeg = false;
}

$dog = new Dog();

我們利用 PHP-Parser 做了語(yǔ)法解析檢測(cè),代碼如下:

include dirname(__DIR__)."/vendor/autoload.php";

use PhpParserError;
use PhpParserNodeStmtProperty;
use PhpParserParserFactory;
use PhpParserNodeStmtClass_;

$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()}
";
    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{
                    // 報(bào)錯(cuò)哪里類重復(fù)了
                    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()."
";
                    echo $property->getLine()."
";
                }
            }
        }
    }
}

原理能就是對(duì)解析出來(lái)的AST繼續(xù)做分析,但是前人栽樹(shù)后人乘涼,這樣的完整工具已經(jīng)有大神幫我們做好了。

使用現(xiàn)有工具
https://github.com/phan/phan

可以說(shuō)它與上面介紹的nikic/php-parser師出同門,依賴nikic/php-astPHP擴(kuò)展

先安裝php-ast擴(kuò)展

大概描述安裝步驟

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
# 引入擴(kuò)展
sudo vim ast.ini
# 就能看到擴(kuò)展啦
php -m | grep ast
安裝 composer

大概描述安裝步驟

curl -sS https://getcomposer.org/installer | php

安裝plan

mkdir test
cd test
~/composer.phar require --dev "phan/phan:1.x"
實(shí)驗(yàn) 實(shí)驗(yàn)1

新建個(gè)項(xiàng)目,隨便寫個(gè)有問(wèn)題的代碼

路徑是src/a.php

a2(1);
    }

    /**
     * @param array $b
     *
     * @return int
     */
    private function a2($b)
    {
        return $b + 1;
    }
}

寫個(gè)shell腳本

#!/bin/bash

function log()
{
    echo -e -n "