lvht / phpstan
PHPStan - PHP 静态分析工具
Requires
- php: ~7.0
- nikic/php-parser: ^2.1 || ^3.0.2
- php-di/php-di: ^5.4
- symfony/console: ~2.7 || ~3.0
- symfony/finder: ~2.7 || ~3.0
- tedivm/stash: ^0.14.1
Requires (Dev)
- jakub-onderka/php-parallel-lint: ^0.9
- phpunit/phpunit: ^6.0.7
- satooshi/php-coveralls: ^1.0
- squizlabs/php_codesniffer: ^2.8
This package is not auto-updated.
Last update: 2024-09-15 02:46:48 UTC
README
为什么我想创建自己的分支?请参阅 故事。
PHPStan 通过不实际运行代码来查找错误。它甚至在您为代码编写测试之前就捕捉到整个类别的错误。
在运行实际代码行之前,PHPStan 可以检查代码中每行的正确性,这使 PHP 更接近编译语言。
在 Medium.com 上了解更多关于 PHPStan 的信息 »
PHPStan 是否帮助您在生产中避免错误?
它目前在您的代码上执行以下检查:
- 在
instanceof
、catch
、类型提示、其他语言构造甚至注解中类和接口的存在。PHP 不做此操作,而是保持沉默。 - 在尊重分支和循环的作用域的情况下变量存在。
- 被调用方法和函数的存在和可见性。
- 访问的属性和常量的存在和可见性。
- 属性分配的正确类型。
- 构造函数、方法和函数传递的正确数量和类型参数。
- 方法和函数返回的正确类型。
- 基于格式字符串的
sprintf
/printf
调用的正确参数数量。 - 无用的类型转换,如
(string) 'foo'
。 - 未使用的构造函数参数 - 可以删除它们,或者作者忘记在类代码中使用它们。
- 只有对象才传递给
clone
关键字。
可扩展性
PHPStan 的独特功能是能够定义和静态检查类的“魔法”行为 - 访问在类中未定义但在 __get
和 __set
中创建的属性,以及使用 __call
调用方法。
您还可以安装已创建的特定框架的扩展
其他特定框架的扩展即将推出!
先决条件
PHPStan 需要 PHP >= 7.0。您必须在具有 PHP 7.x 的环境中运行它,但实际代码不必使用 PHP 7.x 特性。(为 PHP 5.6 及更早版本编写的代码在 7.x 上主要无需修改即可运行。)
PHPStan 与现代面向对象的代码配合得最好。您的代码类型越严格,您提供给 PHPStan 的工作信息就越多。
正确注释和类型提示的代码(类属性、函数和方法参数、返回类型)不仅有助于静态分析工具,还有助于其他与代码一起工作的人理解它。
安装
我们建议您下载 phpstan.phar
。
如果您想手动构建 phpstan.phar
,则需要克隆项目,然后运行 composer update --no-dev
,然后转到父目录并运行 phar-composer
。
要开始对您的代码进行分析,请在 Composer 中引入 PHPStan。
composer require --dev lvht/phpstan
Composer将在其bin-dir
目录中安装PHPStan的可执行文件,默认为vendor/bin
。
首次运行
要让PHPStan分析您的代码库,您需要使用analyse
命令,并将其指向正确的目录。
例如,如果您将类放在src
和tests
目录中,您可以这样运行PHPStan
vendor/bin/phpstan analyse src tests
PHPStan可能会找到一些错误,但请不要担心,您的代码可能没有问题。首次运行发现的错误通常包括
- 传递给函数的额外参数(例如,函数需要两个参数,代码传递了三个)
- 传递给print/sprintf函数的额外参数(例如,格式字符串包含一个占位符,代码传递了两个值来替换)
- 死代码中的明显错误
- 需要定义的魔法行为。请参阅扩展性。
在修复代码中的明显错误后,查看以下部分以获取所有配置选项,这将使报告的错误数量降至零,使PHPStan适合作为您持续集成脚本的一部分运行。
规则级别
规则级别。如果您想使用PHPStan,但您的代码库没有达到强类型和PHPStan严格检查的要求,您可以通过将--level
传递给analyse
命令来选择目前5个级别(0是最宽松的,4是最严格的)。默认级别为0
。
此功能使PHPStan检查的增量采用成为可能。您可以从较低的规则级别开始使用PHPStan,并在您觉得合适时增加它。
还有一个实验性
级别5,目前可以启用
- 联合类型(Foo|Bar将是一个指定类型,对其执行检查而不是混合类型)
- 调用函数和方法时检查参数类型
自动加载
PHPStan使用Composer自动加载器,因此通过composer.json中的autoload
/autoload-dev
部分自动加载类是最简单的方法。
全局安装的自动加载
PHPStan支持使用composer global
进行全局安装。在这种情况下,它不是项目自动加载器的一部分,但它支持从当前工作目录中的vendor/
自动发现Composer自动加载器。
cd /path/to/project phpstan analyse src tests # looks for autoloader at /path/to/project/vendor/autoload.php
如果您将依赖项安装在不同的路径或从不同的目录运行PHPStan,您可以使用--autoload-file|-a
选项指定自动加载器的路径。
phpstan analyse --autoload-file=/path/to/autoload.php --autoload-file=/path/to/another.php src tests
排除分析文件
如果您的代码库中包含一些故意损坏的文件(例如,为了测试应用程序在包含无效PHP代码的文件上的行为),您可以使用--ignore-path|-P
选项指定忽略路径:每行中的字符串用作fnmatch
函数的模式。
phpstan analyse --ignore-path='tests/*/data/*' src tests
通用对象容器[待办事项]
没有预定义结构的类在PHP应用程序中很常见。它们用作通用数据持有者 - 任何属性都可以在它们上设置和读取。显著的例子包括stdClass
、SimpleXMLElement
(这些默认启用),包含数据库查询结果的对象等。使用universalObjectCratesClasses
数组参数让PHPStan知道您的代码库中使用了哪些具有这些特征的类
这需要另一个选项。
将非显然分配的变量添加到作用域[待办事项]
如果您在catch块中使用来自try块的一些变量,请将布尔参数polluteCatchScopeWithTryAssignments
设置为true
。
try { $author = $this->getLoggedInUser(); $post = $this->postRepository->getById($id); } catch (PostNotFoundException $e) { // $author is probably defined here throw new ArticleByAuthorCannotBePublished($author); }
如果您正在if-elseif分支中枚举所有可能的情况,并且PHPStan在条件之后抱怨未定义的变量,您可以在else分支中抛出异常
if (somethingIsTrue()) { $foo = true; } elseif (orSomethingElseIsTrue()) { $foo = false; } else { throw new ShouldNotHappenException(); } doFoo($foo);
或者,您可以设置 defineVariablesWithoutDefaultBranch
布尔参数为 true
并保持代码如下:
if (somethingIsTrue()) { $foo = true; } elseif (orSomethingElseIsTrue()) { $foo = false; } doFoo($foo);
我建议将 polluteScopeWithLoopInitialAssignments
、polluteCatchScopeWithTryAssignments
和 defineVariablesWithoutDefaultBranch
设置为 false
,因为这会导致代码更清晰且易于维护。
自定义早期终止方法调用
前面的例子表明,如果某个条件分支以抛出异常结束,那么该分支不必定义条件分支结束后使用的变量。
但是,异常并不是提前终止方法执行的唯一方式。一些特定的方法调用也可能被项目开发者视为早期终止,例如停止执行的 redirect()
方法抛出内部异常。
if (somethingIsTrue()) { $foo = true; } elseif (orSomethingElseIsTrue()) { $foo = false; } else { $this->redirect('homepage'); } doFoo($foo);
使用正则表达式忽略错误信息
如果您的代码库中存在一些难以修复的问题,或者只是简单想要稍后处理,您可以使用 --ignore-error|-E
选项指定忽略模式。
phpstan analyse --ignore-error='Call to an undefined method [a-zA-Z0-9\\_]+::method\(\)' \ --ignore-error='Call to an undefined method [a-zA-Z0-9\\_]+::expects\(\)' \ --ignore-error='Access to an undefined property PHPUnit_Framework_MockObject_MockObject::\$[a-zA-Z0-9_]+' \ --ignore-error='Call to an undefined method PHPUnit_Framework_MockObject_MockObject::[a-zA-Z0-9_]+\(\)' \ src tests
自定义规则
您可以使用 --level
选项使用预置规则来检查源代码。但是,如果您想要使用所有级别 n 规则,但排除 SomeBuiltInRule,您可以指定 --ignore-rule|-R
选项。
phpstan analyse --level=5 --ignore-rule='Methods\ReturnTypeRule' src tests
请注意,前面有一个斜杠,内置规则名称应该是相对名称!
使用 phpstan analyse --help
可以获取所有内置规则列表。
PHPStan 允许编写自定义规则来检查您自己的代码库中的特定情况。您的规则类需要实现 PHPStan\Rules\Rule
接口,并使用 --rule|-r
选项指定规则。
phpstan analyse --rule='\MyApp\PHPStan\Rules\DefaultValueTypesAssignedToPropertiesRule' src tests
请注意,前面有一个斜杠,自定义规则名称应该是完全限定名称(FQCN)!
关于如何实现规则,请参考 src/Rules,查看许多内置规则。
类反射扩展
PHP 中的类可以通过 __get
、__set
和 __call
等类方法在运行时公开“魔法”属性和方法。因为 PHPStan 完全关于静态分析(在不运行代码的情况下测试代码错误),它必须事先知道这些属性和方法。
当 PHPStan 遇到一个内置类反射所不知道的属性或方法时,它会遍历所有注册的类反射扩展,直到找到一个定义了该属性或方法为止。
属性类反射扩展
此扩展类型必须实现以下接口
namespace PHPStan\Reflection; interface PropertiesClassReflectionExtension { public function hasProperty(ClassReflection $classReflection, string $propertyName): bool; public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection; }
您可能还需要实现一个新的 PropertyReflection
类
namespace PHPStan\Reflection; interface PropertyReflection { public function getType(): Type; public function getDeclaringClass(): ClassReflection; public function isStatic(): bool; public function isPrivate(): bool; public function isPublic(): bool; }
并且您可以使用 --extension|-x
来指定它
phpstan analyse --level=5 --extension='\App\PHPStan\PropertiesFromAnnotationsClassReflectionExtension' src tests
方法类反射扩展
此扩展类型必须实现以下接口
namespace PHPStan\Reflection; interface MethodsClassReflectionExtension { public function hasMethod(ClassReflection $classReflection, string $methodName): bool; public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection; }
您可能还需要实现一个新的 MethodReflection
类
namespace PHPStan\Reflection; interface MethodReflection { public function getDeclaringClass(): ClassReflection; public function getPrototype(): self; public function isStatic(): bool; public function isPrivate(): bool; public function isPublic(): bool; public function getName(): string; /** * @return \PHPStan\Reflection\ParameterReflection[] */ public function getParameters(): array; public function isVariadic(): bool; public function getReturnType(): Type; }
并且您可以使用 --extension|-x
来指定它
phpstan analyse --level=5 --extension='\App\PHPStan\EnumMethodsClassReflectionExtension' src tests
动态返回类型扩展
如果方法的返回类型不总是相同的,而是取决于传递给方法的方法参数,您可以通过编写和注册扩展来指定返回类型。
因为您必须编写包含类型解析逻辑的代码,所以它可以是您想要的任何复杂性。
编写示例扩展后,变量 $mergedArticle
将具有正确的类型。
$mergedArticle = $this->entityManager->merge($article); // $mergedArticle will have the same type as $article
这是动态返回类型扩展的接口
namespace PHPStan\Type; use PhpParser\Node\Expr\MethodCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\MethodReflection; interface DynamicMethodReturnTypeExtension { public static function getClass(): string; public function isMethodSupported(MethodReflection $methodReflection): bool; public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type; }
以下是正确解析 EntityManager::merge() 返回类型的扩展的编写方式
public static function getClass(): string { return \Doctrine\ORM\EntityManager::class; } public function isMethodSupported(MethodReflection $methodReflection): bool { return $methodReflection->getName() === 'merge'; } public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type { if (count($methodCall->args) === 0) { return $methodReflection->getReturnType(); } $arg = $methodCall->args[0]->value; $type = $scope->getType($arg); if ($type->getClass() !== null) { return $type; } return $methodReflection->getReturnType(); }
最后,您可以使用 --extension|-x
来指定它
phpstan analyse --level=5 --extension='\App\PHPStan\EntityManagerDynamicReturnTypeExtension' src tests
对于静态方法,也有类似的功能,使用 DynamicStaticMethodReturnTypeExtension
接口和 phpstan.broker.dynamicStaticMethodReturnTypeExtension
服务标签。
已知问题
- 如果分析代码中使用
include
或require
(而不是include_once
或require_once
),PHPStan 将抛出Cannot redeclare class
错误。使用_once
变体来避免此错误。 - 如果PHPStan在没有输出任何错误的情况下崩溃,很可能是因为您的系统设置了较低的内存限制。再次运行PHPStan,以获取一些提示,了解您可以采取哪些措施来防止崩溃。
- 如果您在系统上全局安装PHPStan,您可能会遇到由于依赖项版本不同而导致的错误。例如,如果PHPStan使用的Symfony Console版本与您的版本不同,并且您在分析代码中使用此方法,PHPStan可能会将其标记为错误。这将在未来通过给PHPStan依赖项的命名空间添加前缀来解决。
行为准则
本项目遵循贡献者行为准则。通过参与本项目及其社区,您应遵守此准则。