phan/phan

PHP 静态分析工具

维护者

详细信息

github.com/phan/phan

源代码

问题

安装数: 8,210,537

依赖: 1,052

建议者: 10

安全性: 0

星标: 5,530

关注者: 108

分支: 359

公开问题: 945

类型:项目

5.4.5 2024-08-13 21:41 UTC

README

Phan 是一个 PHP 静态分析工具,旨在尽量减少误报。Phan 尝试证明代码的错误性,而不是正确性。

Phan 寻找常见问题,并在类型信息可用或可推断时验证各种操作的类型兼容性。Phan 对流程控制有很好的理解(但并非全面),可以在一些用例中跟踪值(例如数组、整数和字符串)。

Build Status Build Status (Windows) Gitter Latest Stable Version License

入门指南

使用 Composer 是使用 Phan 的最简单方式。

composer require phan/phan

安装 Phan 后,您需要在项目中创建一个 .phan/config.php 文件,以告诉 Phan 如何分析您的源代码。配置完成后,您可以通过 ./vendor/bin/phan 运行它。

Phan 5 需要 PHP 7.2+ 及其 php-ast 扩展(推荐 1.1.1+),并支持分析 PHP 7.0-8.2 语法。php-ast 的安装说明可以在 此处 找到。(Phan 可以不使用 php-ast 使用,通过 CLI 选项 --allow-polyfill-parser,但文档注释的解析会有细微差异)

  • 其他安装方法
    有关使用 Phan 的其他方法和如何为您的项目配置 Phan 的详细信息,请参阅 入门指南
  • 逐步增强分析
    有关如何随着代码质量的提高逐渐增加分析严格性的提示,请参阅 逐步增强分析
  • 安装依赖项
    有关在您的系统上安装 Phan 依赖项的帮助,请参阅 安装 Phan 依赖项

Wiki 中有更多关于使用 Phan 的信息。

功能

Phan 能够执行以下类型的分析

  • 检查所有方法、函数、类、特性、接口、常量、属性和变量是否已定义且可访问。
  • 检查方法/函数/闭包调用中的类型安全性和参数数量问题。
  • 检查 PHP8/PHP7/PHP5 的向后兼容性。
  • 检查旧版 PHP 7.x 小版本中不支持的功能(例如 objectvoiditerable?T[$x] = ...;、负字符串偏移量、多个异常捕获等)。
  • 检查数组访问的合理性。
  • 检查二进制操作中的类型安全性。
  • 检查方法、函数和闭包的有效和类型安全的返回值。
  • 检查数组、闭包、常量、属性、变量、一元运算符和二元运算符上的无操作。
  • 检查未使用/已死/不可达 代码。(使用 --dead-code-detection 传递)
  • 检查未使用的变量和参数。(使用 --unused-variable-detection 传递)
  • 检查冗余或不合理的条件和无意义的类型转换。(使用 --redundant-condition-detection 传递)
  • 检查未使用的 use 语句。这些以及其他一些问题类型可以通过 --automatic-fix 自动修复。
  • 检查类、函数和方法是否被重新定义。
  • 检查类继承的合理性(例如,检查方法签名兼容性)。Phan 还检查是否重写了 final 类/方法,抽象方法是否已实现,以及实现的接口是否确实是接口(等等)。
  • 支持命名空间、特质和变长参数。
  • 支持联合类型
  • 支持泛型类型(即 @template
  • 支持泛型数组,如 int[]UserObject[]array<int,UserObject> 等。
  • 支持数组形状,如 array{key:string,otherKey:?stdClass} 等。(内部和在 PHPDoc 标签中)这也支持通过 array{requiredKey:string,optionalKey?:string} 标记数组形状的字段为可选(对于 @param 很有用)
  • 支持 phpdoc 类型注解
  • 支持继承 phpdoc 类型注解。
  • 支持检查 phpdoc 类型注解是否为实际类型签名的缩窄形式(例如,子类/子类型)
  • 支持从 assert() 语句 和 if 元素/循环的条件中推断类型。
  • 支持为弃用的类、方法和函数使用 @deprecated 注解
  • 支持为定义在包内的元素(如常量、函数、类、类常量、属性或方法)使用 @internal 注解
  • 支持用于 抑制问题@suppress <ISSUE_TYPE> 注解。
  • 支持 魔术 @property 注解@property <union_type> <variable_name>
  • 支持 魔术 @method 注解@method <union_type> <method_name>(<union_type> <param1_name>)
  • 支持 class_alias 注解(实验性,默认关闭)
  • 支持通过 @phan-closure-scope 指定闭包将要绑定的类(示例
  • 支持分析传递给 array_maparray_filter 和其他内部数组函数的闭包和返回类型。
  • 提供广泛的配置,以弱化分析,使其适用于大型混乱的代码库。
  • 可以在多个核心上运行。(需要 pcntl
  • 输出以文本、checkstyle、json、pylint、csv 或 codeclimate 格式发出。
  • 可以在源上运行 用户插件,以检查特定于您代码的问题。Phan 包含了您可能希望启用的一些插件

请参阅 Phan 问题类型,了解 Phan 可以检测的所有问题的描述和示例。查看 \Phan\Issue,以查看每个错误类型的定义。

请参阅 分析大型混乱代码库教程,了解您进行持续分析的过程可能是什么样子。

Phan 可以从 各种编辑器和 IDE 中使用,用于错误检查、“转到定义”支持等,通过 语言服务器协议。编辑器和工具也可以使用更简单的 守护进程模式 来请求对项目中的单个文件进行分析。

有关各种检查的示例,请参阅 测试目录

Phan 并不完美,不应用来证明您的基于 PHP 的火箭制导系统没有缺陷。

插件提供的功能

插件已提供额外的分析功能,请参阅 插件

示例: Phan 的自我分析插件

用法

安装 Phan 后,Phan 需要配置分析代码的位置以及如何分析它。告诉 Phan 在何处找到源代码的最简单方法就是 创建一个 .phan/config.php 文件。一个简单的 .phan/config.php 文件可能看起来像以下这样。

<?php

/**
 * This configuration will be read and overlaid on top of the
 * default configuration. Command line arguments will be applied
 * after this file is read.
 */
return [

    // Supported values: `'5.6'`, `'7.0'`, `'7.1'`, `'7.2'`, `'7.3'`, `'7.4'`,
    // `'8.0'`, `'8.1'`, `'8.2'`, `'8.3'`, `null`.
    // If this is set to `null`,
    // then Phan assumes the PHP version which is closest to the minor version
    // of the php executable used to execute Phan.
    "target_php_version" => null,

    // A list of directories that should be parsed for class and
    // method information. After excluding the directories
    // defined in exclude_analysis_directory_list, the remaining
    // files will be statically analyzed for errors.
    //
    // Thus, both first-party and third-party code being used by
    // your application should be included in this list.
    'directory_list' => [
        'src',
        'vendor/symfony/console',
    ],

    // A directory list that defines files that will be excluded
    // from static analysis, but whose class and method
    // information should be included.
    //
    // Generally, you'll want to include the directories for
    // third-party code (such as "vendor/") in this list.
    //
    // n.b.: If you'd like to parse but not analyze 3rd
    //       party code, directories containing that code
    //       should be added to the `directory_list` as
    //       to `exclude_analysis_directory_list`.
    "exclude_analysis_directory_list" => [
        'vendor/'
    ],

    // A list of plugin files to execute.
    // Plugins which are bundled with Phan can be added here by providing their name
    // (e.g. 'AlwaysReturnPlugin')
    //
    // Documentation about available bundled plugins can be found
    // at https://github.com/phan/phan/tree/v5/.phan/plugins
    //
    // Alternately, you can pass in the full path to a PHP file
    // with the plugin's implementation.
    // (e.g. 'vendor/phan/phan/.phan/plugins/AlwaysReturnPlugin.php')
    'plugins' => [
        // checks if a function, closure or method unconditionally returns.
        // can also be written as 'vendor/phan/phan/.phan/plugins/AlwaysReturnPlugin.php'
        'AlwaysReturnPlugin',
        'DollarDollarPlugin',
        'DuplicateArrayKeyPlugin',
        'DuplicateExpressionPlugin',
        'PregRegexCheckerPlugin',
        'PrintfCheckerPlugin',
        'SleepCheckerPlugin',
        // Checks for syntactically unreachable statements in
        // the global scope or function bodies.
        'UnreachableCodePlugin',
        'UseReturnValuePlugin',
        'EmptyStatementListPlugin',
        'LoopVariableReusePlugin',
    ],
];

有关更多详细信息,请参阅 创建配置文件逐步增强分析

运行 phan --help 将显示 使用信息以及命令行选项

注释您的源代码

Phan 可以读取并理解大多数 PHPDoc 类型注释,包括 联合类型(例如 int|MyClass|string|null)和泛型数组类型(例如 int[]string[]|MyClass[]array<int,MyClass>)。

请参阅 注释您的源代码关于联合类型,以了解如何开始在代码中定义类型。

Phan 支持 (int|string)[] 风格的注释,并在内部将它们表示为 int[]|string[](两种注释都被视为数组,可以包含整数和/或字符串)。当您有混合类型的数组时,只需使用 array

以下代码展示了支持的各种注释。

/**
 * @return void
 */
function f() {}

/** @deprecated */
class C {
    /** @var int */
    const C = 42;

    /** @var string[]|null */
    public $p = null;

    /**
     * @param int|null $p
     * @return string[]|null
     */
    public static function f($p) {
        if (is_null($p)) {
            return null;
        }

        return array_map(
            /** @param int $i */
            function($i) {
                return "thing $i";
            },
            range(0, $p)
        );
    }
}

与 PHP 一样,任何类型都可以在函数声明中被置为 null,这也意味着可以为该参数传递 null。

Phan 检查数组中每个元素的类型(包括键和值)。从实际应用的角度来看,这意味着 [$int1=>$int2,$int3=>$int4,$int5=>$str6] 被视为 array<int,int|string>,Phan 表示为 array<int,int>|array<int,string>[$strKey => new MyClass(), $strKey2 => $unknown] 将被表示为 array<string,MyClass>|array<string,mixed>

  • 字面量,如 [12,'myString'],将内部表示为数组形式,如 array{0:12,1:'myString'}

生成文件列表

这个静态分析器不跟踪包含或尝试找出自动加载的魔法。它将您扔给它的所有文件视为一个大的应用程序。对于封装在类中的代码,这工作得很好。对于在全局范围内运行的代码,这有点棘手,因为顺序很重要。如果您有一个 index.php 包含一个设置许多全局变量的文件,然后您在 index.php 中的 include(...) 之后尝试访问这些变量,静态分析器将一无所知。

从实际应用的角度来看,这仅仅意味着您应该在文件列表的顶部放置您的入口点和任何设置全局变量的文件。如果您有一个 config.php 设置了所有其他东西都需要的全局变量,那么您应该将它放在列表的第一位,然后是您的各种入口点,然后是包含您的类的所有库文件。

开发

请参阅Phan 开发者指南以获取有关如何开始对 Phan 进行黑客攻击的帮助。

当您发现问题时,请花时间创建一个小的复现代码片段,以说明错误。一旦您做到了这一点,修复它。然后将您的代码片段转换为测试并将其添加到tests,然后运行 ./test 并提交一个包含修复和测试的PR。或者,您可以打开一个包含详细信息的Issue。

要运行 Phan 的单元测试,只需运行 ./test

要运行 Phan 的所有单元测试和集成测试,运行 ./tests/run_all_tests.sh

行为准则

我们致力于营造一个欢迎的社区。任何参与者和贡献者都必须遵守我们的行为准则

在线演示

这需要一个最新的 Firefox/Chrome 版本以及至少 4 GB 的空闲 RAM。(这是一个 15 MB 的下载文件)

在浏览器中完全运行 Phan.

Preview of analyzing PHP