etsy/phan

此包已被弃用,不再维护。作者建议使用 phan/phan 包。

PHP 静态分析器

维护者

详细信息

github.com/phan/phan

源代码

问题

安装数: 207,496

依赖者: 4

建议者: 0

安全性: 0

星标: 5,519

关注者: 108

分支: 362

开放问题: 944

类型:项目

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

入门指南

使用 Phan 最简单的方法是通过 Composer。

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 可以通过使用 CLI 选项 --allow-polyfill-parser 而不使用 php-ast 来使用,但文档注释的解析会有一些差异)

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

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

功能

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

  • 检查所有方法、函数、类、特性、接口、常量、属性和变量是否已定义且可访问。
  • 检查方法/函数/闭包调用中的类型安全和参数数量问题。
  • 检查 PHP8/PHP7/PHP5 向后兼容性。
  • 检查在旧版 PHP 7.x 次版本中不受支持的功能(例如 objectvoiditerable?T[$x] = ...;、负字符串偏移量、多个异常捕获等)。
  • 检查数组访问的合理性。
  • 检查二进制运算中的类型安全。
  • 检查方法、函数和闭包的有效和类型安全的返回值。
  • 检查数组、闭包、常量、属性、变量、一元运算符和二进制运算符上的 No-Ops。
  • 检查未使用/死代码。 (传递 --dead-code-detection
  • 检查未使用的变量和参数。 (传递 --unused-variable-detection
  • 检查冗余或不合理的条件和无意义的类型转换。 (传递 --redundant-condition-detection
  • 检查未使用的 use 语句。这些和其他一些问题类型可以自动修复,使用 --automatic-fix
  • 检查类、函数和方法是否被重新定义。
  • 检查类继承的合理性(例如,检查方法签名兼容性)。Phan 还会检查最终类/方法是否被重写、抽象方法是否已实现,以及实现的接口是否真的是接口(等等)。
  • 支持命名空间、特性和可变参数。
  • 支持联合类型
  • 支持泛型类型(即 @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/config.php 文件 来告诉 Phan 在哪里找到源代码。一个简单的 .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版本以及至少4GB的空闲RAM。(这是一个15MB的下载)

在浏览器中完全运行Phan.

Preview of analyzing PHP