Phpstan - Php Static Analysis Tool (Discover Bugs Inward Your Code Without Running It!)
Read to a greater extent than almost PHPStan on Medium.com
Try out PHPStan on the on-line playground!
Prerequisites
PHPStan requires PHP >= 7.1. You receive got to run it inwards surroundings alongside PHP 7.x but the actual code does non receive got to work PHP 7.x features. (Code written for PHP 5.6 together with before tin run on 7.x generally unmodified.)
PHPStan industrial plant best alongside modern object-oriented code. The to a greater extent than strongly-typed your code is, the to a greater extent than information y'all give PHPStan to piece of work with.
Properly annotated together with typehinted code (class properties, business office together with method arguments, render types) helps non alone static analysis tools but also other people that piece of work alongside the code to sympathise it.
Installation
To start performing analysis on your code, require PHPStan inwards Composer:
composer require --dev phpstan/phpstan
Composer volition install PHPStan's executable inwards its bin-dir
which defaults to vendor/bin
.If y'all receive got conflicting dependencies or y'all desire to install PHPStan globally, the best agency is via a PHAR archive. You volition e'er regain the latest stable PHAR archive below the release notes. You tin also work the phpstan/phpstan-shim parcel to install PHPStan via Composer without the take a opportunity of conflicting dependencies.
You tin also work PHPStan via Docker.
First run
To allow PHPStan analyse your codebase, y'all receive got to work the
analyse
dominance together with signal it to the right directories.So, for instance if y'all receive got your classes inwards directories
src
together with tests
, y'all tin run PHPStan similar this:vendor/bin/phpstan analyse src tests
- Extra arguments passed to functions (e. g. business office requires 2 arguments, the code passes three)
- Extra arguments passed to print/sprintf functions (e. g. format string contains i placeholder, the code passes 2 values to replace)
- Obvious errors inwards dead code
- Magic conduct that needs to last defined. See Extensibility.
Rule levels
If y'all desire to work PHPStan but your codebase isn't upwards to speed alongside potent typing together with PHPStan's strict checks, y'all tin take from currently 8 levels (0 is the loosest together with vii is the strictest) yesteryear passing
--level
to analyse
command. Default bird is 0
.This characteristic enables incremental adoption of PHPStan checks. You tin start using PHPStan alongside a lower dominion bird together with growth it when y'all experience similar it.
You tin also work
--level max
every bit an alias for the highest level. This volition ensure that y'all volition e'er work the highest bird when upgrading to novel versions of PHPStan. Please authorities annotation that this tin practise a pregnant obstruction when upgrading to a newer version because y'all mightiness receive got to prepare a lot of code to convey the number of errors downwards to zero.Extensibility
Unique characteristic of PHPStan is the powerfulness to define together with statically banking enterprise check "magic" conduct of classes - accessing properties that are non defined inwards the bird but are created inwards
__get
together with __set
together with invoking methods using __call
.See Class reflection extensions, Dynamic render type extensions together with Type-specifying extensions.
You tin also install official framework-specific extensions:
- Doctrine
- PHPUnit
- Nette Framework
- Dibi - Database Abstraction Library
- PHP-Parser
- beberlei/assert
- webmozart/assert
- Symfony Framework
- Mockery
- Phony
- Prophecy
- Laravel
- myclabs/php-enum
- Yii2
- PhpSpec
- TYPO3
- moneyphp/money
- Drupal
- WordPress
- Zend Framework
- thecodingmachine / phpstan-strict-rules
- localheinz / phpstan-rules
- pepakriz / phpstan-exception-rules
- Slamdunk / phpstan-extensions
- ekino / phpstan-banned-code
Configuration
Config file is passed to the
phpstan
executable alongside -c
option:vendor/bin/phpstan analyse -l four -c phpstan.neon src tests
--level
(-l
) selection to analyse
dominance (default value does non apply here).If y'all practise non render config file explicitly, PHPStan volition seem for files named
phpstan.neon
or phpstan.neon.dist
inwards electrical current directory.The resolution priority is every bit such:
- If config file is provided on dominance line, it is used.
- If config file
phpstan.neon
exists inwards electrical current directory, it volition last used. - If config file
phpstan.neon.dist
exists inwards electrical current directory, it volition last used. - If none of the inwards a higher identify is true, no config volition last used.
parameters
section.Configuration variables
%rootDir%
- root directory where PHPStan resides (i.e.vendor/phpstan/phpstan
inwards Composer installation)%currentWorkingDirectory%
- electrical current working directory where PHPStan was executed
Configuration options
tmpDir
- specifies the temporary directory used yesteryear PHPStan cache (defaults tosys_get_temp_dir() . '/phpstan'
)level
- specifies analysis bird - if specified,-l
selection is non requiredpaths
- specifies analysed paths - if specified, paths are non required to last passed every bit arguments
Autoloading
PHPStan uses Composer autoloader thus the easiest agency how to autoload classes is through the
autoload
/autoload-dev
sections inwards composer.json.Specify paths to scan
If PHPStan complains almost about non-existent classes together with you're surely the classes be inwards the codebase AND y'all don't desire to work Composer autoloader for about reason, y'all tin specify directories to scan together with concrete files to include using
autoload_directories
together with autoload_files
array parameters:parameters: autoload_directories: - %rootDir%/../../../build autoload_files: - %rootDir%/../../../generated/routes/GeneratedRouteList.php
%rootDir%
is expanded to the root directory where PHPStan resides.Autoloading for global installation
PHPStan supports global installation using
composer global
or via a PHAR archive. In this case, it's non component of the projection autoloader, but it supports autodiscovery of the Composer autoloader from electrical current working directory residing inwards vendor/
:cd /path/to/project phpstan analyse src tests # looks for autoloader at /path/to/project/vendor/autoload.php
--autoload-file|-a
option:phpstan analyse --autoload-file=/path/to/autoload.php src tests
Exclude files from analysis
If your codebase contains about files that are broken on purpose (e. g. to bear witness conduct of your application on files alongside invalid PHP code), y'all tin exclude them using the
excludes_analyse
array parameter. String at each trouble is used every bit a blueprint for the fnmatch
function.parameters: excludes_analyse: - %rootDir%/../../../tests/*/data/*
Include custom extensions
If your codebase contains php files alongside extensions other than the measure .php extension together with thus y'all tin add together them to the
fileExtensions
array parameter:parameters: fileExtensions: - php - module - inc
Universal object crates
Classes without predefined construction are mutual inwards PHP applications. They are used every bit universal holders of information - whatever belongings tin last laid together with read on them. Notable examples include
stdClass
, SimpleXMLElement
(these are enabled yesteryear default), objects alongside results of database queries etc. Use universalObjectCratesClasses
array parameter to allow PHPStan know which classes alongside these characteristics are used inwards your codebase:parameters: universalObjectCratesClasses: - Dibi\Row - Ratchet\ConnectionInterface
Add non-obviously assigned variables to scope
If y'all work about variables from a endeavour block inwards your take handgrip of blocks, laid
polluteCatchScopeWithTryAssignments
boolean parameter to true
.try { $author = $this->getLoggedInUser(); $post = $this->postRepository->getById($id); } take handgrip of (PostNotFoundException $e) { // $author is in all probability defined hither throw novel ArticleByAuthorCannotBePublished($author); }
if (somethingIsTrue()) { $foo = true; } elseif (orSomethingElseIsTrue()) { $foo = false; } else { throw novel ShouldNotHappenException(); } doFoo($foo);
polluteCatchScopeWithTryAssignments
laid to false
because it leads to a clearer together with to a greater extent than maintainable code.Custom early on terminating method calls
Previous instance showed that if a status branches goal alongside throwing an exception, that branch does non receive got to define a variable used after the status branches end.
But exceptions are non the alone agency how to terminate execution of a method early. Some specific method calls tin last perceived yesteryear projection developers also every bit early on terminating - similar a
redirect()
that stops execution yesteryear throwing an internal exception.if (somethingIsTrue()) { $foo = true; } elseif (orSomethingElseIsTrue()) { $foo = false; } else { $this->redirect('homepage'); } doFoo($foo);
parameters: earlyTerminatingMethodCalls: Nette\Application\UI\Presenter: - redirect - redirectUrl - sendJson - sendResponse
Ignore error messages alongside regular expressions
If about number inwards your code base of operations is non tardily to prepare or exactly but desire to bargain alongside it later, y'all tin exclude error messages from the analysis consequence alongside regular expressions:
parameters: ignoreErrors: - '#Call to an undefined method [a-zA-Z0-9\\_]+::method\(\)#' - '#Call to an undefined method [a-zA-Z0-9\\_]+::expects\(\)#' - '#Access to an undefined belongings PHPUnit_Framework_MockObject_MockObject::\$[a-zA-Z0-9_]+#' - '#Call to an undefined method PHPUnit_Framework_MockObject_MockObject::[a-zA-Z0-9_]+\(\)#'
To exclude an error inwards a specific directory or file, specify a path
or paths
along alongside the message
:parameters: ignoreErrors: - message: '#Call to an undefined method [a-zA-Z0-9\\_]+::method\(\)#' path: %currentWorkingDirectory%/some/dir/SomeFile.php - message: '#Call to an undefined method [a-zA-Z0-9\\_]+::method\(\)#' paths: - %currentWorkingDirectory%/some/dir/* - %currentWorkingDirectory%/other/dir/* - '#Other error to take handgrip of anywhere#'
If about of the patterns practise non occur inwards the consequence anymore, PHPStan volition allow y'all know together with y'all volition receive got to withdraw the blueprint from the configuration. You tin plough off this conduct yesteryear setting reportUnmatchedIgnoredErrors
to false
inwards PHPStan configuration.Bootstrap file
If y'all ask to initialize something inwards PHP runtime before PHPStan runs (like your ain autoloader), y'all tin render your ain bootstrap file:
parameters: bootstrap: %rootDir%/../../../phpstan-bootstrap.php
Custom rules
PHPStan allows writing custom rules to banking enterprise check for specific situations inwards your ain codebase. Your dominion bird needs to implement the
PHPStan\Rules\Rule
interface together with registered every bit a service inwards the configuration file:services: - class: MyApp\PHPStan\Rules\DefaultValueTypesAssignedToPropertiesRule tags: - phpstan.rules.rule
For inspiration on how to implement a dominion plough to src/Rules to run across a lot of built-in rules.Check out also phpstan-strict-rules repository for extra strict together with opinionated rules for PHPStan!
Check every bit good phpstan-deprecation-rules for rules that honor usage of deprecated classes, methods, properties, constants together with traits!
Custom error formatters
PHPStan outputs errors via formatters. You tin customize the output yesteryear implementing the
ErrorFormatter
interface inwards a novel bird together with add together it to the configuration. For existing formatters, run across side yesteryear side chapter.interface ErrorFormatter { /** * Formats the errors together with outputs them to the console. * * @param \PHPStan\Command\AnalysisResult $analysisResult * @param \Symfony\Component\Console\Style\OutputStyle $style * @return int Error code. */ world business office formatErrors( AnalysisResult $analysisResult, \Symfony\Component\Console\Style\OutputStyle $style ): int; }
phpstan.neon
:services: errorFormatter.awesome: class: App\PHPStan\AwesomeErrorFormatter
Use the mention component after errorFormatter.
every bit the CLI selection value:vendor/bin/phpstan analyse -c phpstan.neon -l four --error-format awesome src tests
Existing error formatters to last used
You tin exceed the next keywords to the
--error-format=X
parameter inwards lodge to impact the output:table
: Default. Grouped errors yesteryear file, colorized. For human consumption.raw
: Contains i error per line, alongside path to file, trouble number, together with error descriptioncheckstyle
: Creates a checkstyle.xml compatible output. Note that you'd receive got to redirect output into a file inwards lodge to capture the results for afterwards processing.json
: Creates minified .json output without whitespaces. Note that you'd receive got to redirect output into a file inwards lodge to capture the results for afterwards processing.prettyJson
: Creates human readable .json output alongside whitespaces together with indentations. Note that you'd receive got to redirect output into a file inwards lodge to capture the results for afterwards processing.
Class reflection extensions
Classes inwards PHP tin expose "magical" properties together with methods decided inwards run-time using bird methods similar
__get
, __set
together with __call
. Because PHPStan is all almost static analysis (testing code for errors without running it), it has to know almost those properties together with methods beforehand.When PHPStan stumbles upon a belongings or a method that is unknown to built-in bird reflection, it iterates over all registered bird reflection extensions until it finds i that defines the belongings or method.
Class reflection extension cannot receive got
PHPStan\Broker\Broker
(service for obtaining bird reflections) injected inwards the constructor due to circular reference issue, but the extensions tin implement PHPStan\Reflection\BrokerAwareExtension
interface to obtain Broker via a setter.Properties bird reflection extensions
This extension type must implement the next interface:
namespace PHPStan\Reflection; interface PropertiesClassReflectionExtension { world business office hasProperty(ClassReflection $classReflection, string $propertyName): bool; world business office getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection; }
PropertyReflection
class:namespace PHPStan\Reflection; interface PropertyReflection { world business office getType(): Type; world business office getDeclaringClass(): ClassReflection; world business office isStatic(): bool; world business office isPrivate(): bool; world business office isPublic(): bool; }
services: - class: App\PHPStan\PropertiesFromAnnotationsClassReflectionExtension tags: - phpstan.broker.propertiesClassReflectionExtension
Methods bird reflection extensions
This extension type must implement the next interface:
namespace PHPStan\Reflection; interface MethodsClassReflectionExtension { world business office hasMethod(ClassReflection $classReflection, string $methodName): bool; world business office getMethod(ClassReflection $classReflection, string $methodName): MethodReflection; }
MethodReflection
class:namespace PHPStan\Reflection; interface MethodReflection { world business office getDeclaringClass(): ClassReflection; world business office getPrototype(): self; world business office isStatic(): bool; world business office isPrivate(): bool; world business office isPublic(): bool; world business office getName(): string; /** * @return \PHPStan\Reflection\ParameterReflection[] */ world business office getParameters(): array; world business office isVariadic(): bool; world business office getReturnType(): Type; }
services: - class: App\PHPStan\EnumMethodsClassReflectionExtension tags: - phpstan.broker.methodsClassReflectionExtension
Dynamic render type extensions
If the render type of a method is non e'er the same, but depends on an declaration passed to the method, y'all tin specify the render type yesteryear writing together with registering an extension.
Because y'all receive got to write the code alongside the type-resolving logic, it tin last every bit complex every bit y'all want.
After writing the sample extension, the variable
$mergedArticle
volition receive got the right type:$mergedArticle = $this->entityManager->merge($article); // $mergedArticle volition receive got the same type every bit $article
namespace PHPStan\Type; work PhpParser\Node\Expr\MethodCall; work PHPStan\Analyser\Scope; work PHPStan\Reflection\MethodReflection; interface DynamicMethodReturnTypeExtension { world business office getClass(): string; world business office isMethodSupported(MethodReflection $methodReflection): bool; world business office getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type; }
public business office getClass(): string { render \Doctrine\ORM\EntityManager::class; } world business office isMethodSupported(MethodReflection $methodReflection): bool { render $methodReflection->getName() === 'merge'; } world business office getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type { if (count($methodCall->args) === 0) { render \PHPStan\Reflection\ParametersAcceptorSelector::selectFromArgs( $scope, $methodCall->args, $methodReflection->getVariants() )->getReturnType(); } $arg = $methodCall->args[0]->value; render $scope->getType($arg); }
services: - class: App\PHPStan\EntityManagerDynamicReturnTypeExtension tags: - phpstan.broker.dynamicMethodReturnTypeExtension
There's also an analogous functionality for:- static methods using
DynamicStaticMethodReturnTypeExtension
interface together withphpstan.broker.dynamicStaticMethodReturnTypeExtension
service tag. - functions using
DynamicFunctionReturnTypeExtension
interface together withphpstan.broker.dynamicFunctionReturnTypeExtension
service tag.
Type-specifying extensions
These extensions allow y'all to specify types of expressions based on surely pre-existing conditions. This is best illustrated alongside twosome examples:
if (is_int($variable)) { // hither nosotros tin last surely that $variable is integer }
// using PHPUnit's asserts self::assertNotNull($variable); // hither nosotros tin last surely that $variable is non null
PHPStan\Analyser\TypeSpecifier
injected inwards the constructor due to circular reference issue, but the extensions tin implement PHPStan\Analyser\TypeSpecifierAwareExtension
interface to obtain TypeSpecifier via a setter.This is the interface for type-specifying extension:
namespace PHPStan\Type; work PhpParser\Node\Expr\StaticCall; work PHPStan\Analyser\Scope; work PHPStan\Analyser\SpecifiedTypes; work PHPStan\Analyser\TypeSpecifierContext; work PHPStan\Reflection\MethodReflection; interface StaticMethodTypeSpecifyingExtension { world business office getClass(): string; world business office isStaticMethodSupported(MethodReflection $staticMethodReflection, StaticCall $node, TypeSpecifierContext $context): bool; world business office specifyTypes(MethodReflection $staticMethodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes; }
public business office getClass(): string { render \PHPUnit\Framework\Assert::class; } world business office isStaticMethodSupported(MethodReflection $staticMethodReflection, StaticCall $node, TypeSpecifierContext $context): bool; { // The $context declaration tells us if we're inwards an if status or non (as inwards this case). render $staticMethodReflection->getName() === 'assertNotNull' && $context->null(); } world business office specifyTypes(MethodReflection $staticMethodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes { // Assuming extension implements \PHPStan\Analyser\TypeSpecifierAwareExtension. render $this->typeSpecifier->create($node->var, \PHPStan\Type\TypeCombinator::removeNull($scope->getType($node->var)), $context); }
services: - class: App\PHPStan\AssertNotNullTypeSpecifyingExtension tags: - phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension
There's also an analogous functionality for:- dynamic methods using
MethodTypeSpecifyingExtension
interface together withphpstan.typeSpecifier.methodTypeSpecifyingExtension
service tag. - functions using
FunctionTypeSpecifyingExtension
interface together withphpstan.typeSpecifier.functionTypeSpecifyingExtension
service tag.
Building
You tin either run the whole build including linting together with coding standards using
vendor/bin/phing
vendor/bin/phing tests