Skip to content
Choose a tag to compare


@ondrejmirtes ondrejmirtes released this
· 6142 commits to master since this release
Choose a tag to compare

This is a big one, 3½ months in the making. It packs so many new and useful features that this release deserves its own article with a little bit more space for explaining them 😊

PHPStan 0.9: A Huge Leap Forward -

This release is sponsored by Roave:

Roave is a full-service web development firm, offering services such as consulting, training, software development, and more. Roave employs some of the most recognized and accomplished experts in the industry to ensure that organizations have access to the talent they need, when they need it.

I'm really lucky that I'm able to work on something I really love and that I can justify spending time on open-source instead of traditional paid work. And it's thanks to sponsors like Roave and also my patrons on Patreon.

Did you know that I offer issuing invoices for sponsorships of releases and for contributions either on Patreon or via PayPal? Contact me for details.

Here's a comprehensive list of all changes in PHPStan 0.9:

Major new features:

  • Intersection types - #410, thanks @JanTvrdik!
  • Improved support of union types
  • Full support of PHP 7.2
    • object typehint (164a5ad) - #367
    • Check for get_class calls with nullable argument, specifically disallowed in PHP 7.2 (74c3cf8)
  • Next-gen phpDoc parser - #606, thanks @JanTvrdik!
    • Support intersection types (Connection&MockInterface)
    • Support complex array types ((string|int)[])
    • Support specifying array/iterable key types (array<KeyType, ValueType>, iterable<KeyType, ValueType>)
    • Support any combination of all of the above using parentheses (((Connection & MockInterface) | iterable<array<string | int>>)[])
    • Rule for checking invalid phpDoc syntax (level 2)
    • Rule for checking incompatible types in phpDocs when compared to native ones (level 2)
  • New 1st party package with extra-strict rules for strongly-typed codebases: phpstan/phpstan-strict-rules
  • PHAR (distributed in phpstan/phpstan-shim package) can be installed alongside extensions! (phpstan/phpstan-shim#2)
  • Checking for various dead conditions
    • Undefined and always-defined not-nullable variables in isset() - level 1 (6a3d4c9) - #455
    • Impossible instanceof condition - level 4 (01c912f)
    • Impossible check type function call like is_int etc. - level 4 (8ae2a43)
  • Check correct case of referenced class names
    • Although class names are case-insensitive, it's dangerous to reference them with an incorrect case because of autoloading on case-sensitive filesystems.
    • Places in code that cause autoloading are checked on level 0, other places like instanceof and phpDocs on level 2
  • Check arguments passed to foreach, only iterables are supported - level 3 (c391153)


  • Differentiate between undefined and might-be-undefined variables (5aa10a6)
  • Specify known variable type when isset() is used instead of mixed (4c51312)
  • Use specific union type of union exception types in catch block instead of using mixed (ba190f0)
  • Always check nullables in StrictComparisonOfDifferentTypesRule (9d03413)
  • Add support for number type (fef68c1) - #469, thanks @PurpleBooth!
  • Simplify and improve ImpossibleInstanceOfRule (0c4e802) - #491, thanks @JanTvrdik!
  • Function is_numeric leads to string|int|float type (c8807bc) - #364, #369, thanks @pepakriz!
  • Check existence of all referenced classes when accessing a property (d56453f)
  • Check existence of all referenced classes when calling a method (e99eadc)
  • Scope - get type of constants accessed on parent (63f3d37)
  • Better message when accessing constant on an unknown class (b22a6fc)
  • AccessStaticPropertiesRule - check properties accessed on expressions (344f4b2)
  • AccessStaticPropertiesRule - check whether the type supports properties (7a14b5e)
  • Allow overriding native methods and properties with @method and @property annotations (77a92fc, a7421ce) - #76, #244, #414, #431
  • Support Symfony 4.x (c30fdd2) - #547
  • Combine types in for/foreach/while/do-while loops with assignments from previous iteration (dc592c4, fa989c1, 4fcd11c) - #77, #176
  • Fixed type of variable after complex while and foreach loops (613ddaa)
  • Implement DynamicFunctionReturnTypeExtension interface (00ffe78) - #476, thanks @pepakriz!
  • Narrowing type of an array with instanceof in array_filter function (808ead8) - #482, #561, thanks @JanTvrdik!
  • It's possible to register a rule for the Trait_ root node (e6b037c) - #565
  • Process rules registered to ElseIf_ and Case_ nodes (6d5b613)
  • Support global keyword (b42c749) - #580
  • Introducing ContainerFactory that can be used in 3rd party integration tests (4ff66ee)
  • Moved AbstractRuleTest to src/ and renamed to PHPStan\Testing\RuleTestCase to be reused in other 1st and 3rd party repositories (fddde6d)
  • --debug option for finding out on which file PHPStan crashes; rethrow internal errors (999d2c5)
  • Lazy creation of class properties and methods - prevents autoloading of untouched properties types (8055761, 3e0c8ba) - #576
  • Check scope visibility of constructors when instantiating an object (2e96c3e)
  • Check scope visibility when calling private constructor (d7c08e5)
  • Check what else is wrong with static/instance method call even if it cannot be called because of scope visibility (77c8dd8, 6dbee10)
  • Allow setting unlimited memory (95dea67) - #607, thanks @AJenbo!
  • Rule can now be registered for a node interface (PHPStan\Rules\Rule::getNodeType() can return an interface name) (b738f1b) - thanks @JanTvrdik!
  • Support for property_exists() (85c1735) - #611
  • Detect BrokerAwareClassReflectionExtension for more kinds of extensions (aa83bab)
  • Deduce key types of literal arrays (199c5be)
  • Allow overriding BrokerFactory in the configuration (8aad71a) - #615
  • Internal errors - request to post the stack trace as a GitHub issue (444f4aa)
  • Internal errors - stop after 50 occurences (8a3ea10)

BC breaks:

  • If the variable has absolutely no chance of existing in the current scope, it's reported on level 0 instead of level 1. (5aa10a6)
  • Drop support for Symfony 2.x (b57c6bf) and require at least 3.2 (f334167) - #547
  • Specific kinds of errors that originate from a wrong configuration can no longer be ignored - use excludes_analyse instead (05172a8)
  • polluteScopeWithLoopInitialAssignments is now on by default - it matches PHP behaviour. Initial assignments of variables in for loop and while loop condition can be used after the loop (2c659f8). - #339
  • Invalid forms of @var phpDoc tags are no longer supported
    • /** @var $int int */ (Type must come first)
      • To convert the non-standard @var PHPDoc tag to the standard one, you can use "Replace in Path" in PhpStorm. Select the "Regexp" option and replace (/\*\*\s*+@var\s++)(\$[\w\d]++)\s++([^\s*]++) with $1$3 $2
    • /* @var int $int */ (/** must be used)
  • Arrays and iterables with non-mixed keys and values are now described differently using for example array<int, Foo> - #619, thanks @JanTvrdik!

BC breaks (for rules & extensions developers):

  • TrinaryLogic is no longer just an integer, but an object with convenient methods (d7cfd30)
  • Many changes on PHPStan\Type\Type interface:
    • Removed getClass() - use hasMethod(), getMethod(), hasProperty(), getProperty(), hasConstant(), getConstant() when applicable, they account for compound types too. If you still need to know the class name, use getReferencedClasses(). (5aaab92). (Using getReferencedClasses() is better than checking for the new TypeWithClassName interface.)
    • isIterable() now returns TrinaryLogic object instead of int.
    • ArrayType and IterableIterableType constructors now accept two arguments - key type and value type.
    • CommonUnionType was renamed to UnionType
  • TypeCombinator::combine() was replaced with union() and intersect() methods
  • ClassReflection::getMethod() requires Scope, added hasNativeMethod() and getNativeMethod(), dtto for properties #522
    • Turns out that in most cases where the second $scope argument was forgotten, the user was better off by calling getNative* variants.
    • Requiring the $scope argument allows for more consistent class reflection extension selection.
    • Related commits: 4fd5cf0, 3511ceb
  • Scope::hasVariableType now return TrinaryLogic instead of bool
  • Changed dynamic return type interfaces getClass() methods to be non-static. (bdc424a)
  • BrokerAwareClassReflectionExtension renamed to BrokerAwareExtension - with compatibility alias (2fc4664, 0c678a6)


  • FileTypeMapper: fix type pattern to support class names with length 1 (022f4a8) - #483, thanks @JanTvrdik!
  • FloatType: fix accepting int|float (adb4fdd) - #492, thanks @JanTvrdik!
  • Variable type in catch always conforms to Throwable (480e9c8) - #91
  • TypeSpecifier: fix negated identity comparison with true/false (45fa647) - #479, #480, thanks @JanTvrdik!
  • Consistent case-insensitive handling of ::class (7e7eb91)
  • Fixed resolving static return types from static method calls (17a8ae7)
  • $this variable can be referenced outside of class in Closure::bind (f4e0b9b) - #529
  • TypeSpecifier: support is_a() (33737cd, 1a91dc9) - #504, #507, thanks @JanTvrdik!
  • TypeSpecifier: support assign operation (3113103) - #457, #384, #584
  • Fix resolving self/static/parent in switch(get_class($foo)) (81ef733)
  • Fix resolving new variable assigned by reference when calling static method via self/static/parent (3d5a377)
  • Looking for variable assignments in require/include expressions (bc51719) - #453
  • Fixed incorrect argument number for mysqli_fetch_all (3c3ebdf) - #562, #549, thanks @lookyman!
  • Fixed wrong parameter number for openssl_open (da31968) - #527, #563, thanks @lookyman!
  • Fixed undefined properties in libXMLError (192bbac) - #526, #566, thanks @lookyman!
  • Fix support for return and continue inside switch (ec8d06c)
  • Fix filtering of truthy values in while condition (6ceb82e)
  • Fixed internal error with a @property from a trait (c0df313) -
  • Fix variables assigned in switch without early termination statement (5b2f59b) - #498
  • Fix continue handling in loops (9b17568)
  • Correctly evaluate variable types in for loop (e939100) - #206
  • Fixed issue with a variable defined in all switch branches (27c15e7)
  • Correctly resolve self in files with multiple classes (9884937) - #303
  • Null-coalesce protects from possibly null accesses and calls (752dca1) - #589
  • Fix anonymous class with a final __construct in parent class (9613b14) - #382, thanks @rasj!
  • Specify type of $this in anonymous class to avoid wrong type resolution in TypeSpecifier; register anonymous class in Broker (9284168)
  • Check that filename reported by reflection really exists (2d042fa) - #598
  • Do not report "return" keyword in generator functions (5b940d5) - #574
  • FileTypeMapper: handle dependencies among PHPDocs (ea6d62e) - #616, #591, thanks @JanTvrdik!
  • Call to function is_array() will always evaluate to false (f8cccde) - #624, #625, thanks @JanTvrdik!