【Phan】代码静态扫描-php教程

资源魔 52 0
不少时分,最年夜的劣势正在某些状况下就会变为最年夜的优势。PHP 语法十分灵敏,也不必编译。然而正在名目比拟复杂的时分,可能会招致一些意想没有到的 bug。

布景剖析

没有晓得你的名目能否有遇到过相似的线上毛病呢?比方

承继类语法谬误招致的毛病

文件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

09cbff756d0da9edde2a9a9e294f121.png

(留意 IDE 并无提醒有预发谬误的哟,我专门截图)

明天正在看代码的时分看到一个变量不断反复查问,就是用户能否是治理员的身份。我想既然这样,否则正在第一次用之处就放入到成员变量里,省得前面都反复查问。

后果发现我正在父类界说的变量名$isAdmin,以前的代码曾经正在某一个子类外面独自界说过了。父类里是public属性,而子类里是private招致了这个毛病。

假如是 java 这类谬误,无奈编译经过。然而 php 没有需求编译,只需测试不笼罩到刚刚修正的文件就没有会发现这个成绩,既是劣势也是弱势。

参数没有合乎预期

159fc171249eb29153d0b2426c2d91e.png

有时分a.php,b.php,c.php三个文件都援用d.php的的一个函数,然而修正了d.php外面的一个函数的参数个数,假如后面应用的3个文件外面的不改全,只改了a.php,而测试的时分又不笼罩到b.php以及c.php,那末上线了,就会触发bug以及谬误了。

错把数组当工具

你可能以为这类谬误过低级了,不成能发作正在本人身上,然而依据我的经历确实会发作,高强度的需要之下,很容易复制粘贴一些货色,只复制一半。并且凑巧由于某些逻辑判别,本人正在一样平常环境开发的时分,呈现成绩之处不被执行到。

比方上面这段代码:

$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)的判别招致不执行到外面去。直到上线之后才发现成绩。

错把工具当数组

ac3ac228a321cf3233e87e9bcaa3947.png

Cannot use object of type DataObject\Article as array

不由反思,假如这个名目是 java 的,一定没有会呈现下面两个成绩了,由于正在名目构建的时分就曾经没法经过了。

没有存正在的数组

4b26208a24c34bb8b7d445a1a664134.png

这也没有飘红?多写了个s呢,可能由于里面包了一个empty以是IDE不标志为谬误吧。以是咱们不克不及太置信IDE。

考虑与改良

自造轮籽实验

进一步考虑,咱们能否可以做一个对象来本人模仿编译呢?写了一个小 demo ,依赖nikic/php-parser

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

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-astPHP扩大

先装置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

装置 composer

大略形容装置步骤

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

装置plan

mkdir test
cd test
~/composer.phar require --dev "phan/phan:1.x"

试验

试验1

新建个名目,随意写个有成绩的代码

门路是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"

执行后果

案例中的谬误

1.类没有存正在

2.参数类型谬误

3.语法运算类型揣度

1794fb71fadacc2d91df8da0b124c56.png

试验2

新增一个src/b.php

<?php
class B{
}

执行后果

能过主动查找到class B了,不必咱们做主动加载规定的指定

d2f7a8f2dca8da87f24775cec9e45b2.png

试验3

刚刚两个都是测试的独自的剧本,不测试名目,其实Plan曾经支持了。如果我有一个名目以下

656645af70b2035eb8bf1707986baef.png

我正在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

d033fb376300e7d98bdfc33700a3382.png

如图所示呢,阐明依据名目的主动加载规定A,B,C三个类呢都被扫描到了。

以上就是【Phan】代码动态扫描的具体内容,更多请存眷资源魔其它相干文章!

标签: php开发教程 php开发资料 php开发自学 Phan

抱歉,评论功能暂时关闭!