| Name | Modified | Size | Downloads / Week |
|---|---|---|---|
| Parent folder | |||
| phpstan.phar | 2026-07-26 | 27.8 MB | |
| phpstan.phar.asc | 2026-07-26 | 833 Bytes | |
| 2.2.6 source code.tar.gz | 2026-07-26 | 11.9 MB | |
| 2.2.6 source code.zip | 2026-07-26 | 11.9 MB | |
| README.md | 2026-07-26 | 13.1 kB | |
| Totals: 5 Items | 51.6 MB | 2 | |
This release fixes 33 issues! 🎉
Major new features 🚀
- PHPStan Turbo (https://github.com/phpstan/turbo-ext/)
- Optional native PHP extension (PHP 8.3+) written in C++ that makes running PHPStan 10-30 % faster (depending on project specifics)
- Users installing PHPStan through Composer with
phpstan/phpstanget it automatically. Composer package ships prebuilt binaries for the most common platforms — Linux (glibc and musl, x86_64 and arm64), macOS, and Windows (x86_64) - Users running standalone
phpstan.pharcan install the extension withpie install phpstan/turbo - The fact that it's enabled can be verified with
vendor/bin/phpstan diagnoseor runninganalyse -vvv - There's room to improve performance with this technique in the future, but we're shipping it now because it brings real benefits already.
Bleeding edge 🔪
- Report finite-typed values in a constant array haystack that can never be the in_array()/array_search()/array_keys() needle (#6054), [#14960]
- Report
?? null/??= nullon an always-set left side as an unnecessary null coalesce (#5865), [#4337], [#12179], [#9966], thanks @VincentLanglet and @staabm! - Type
DOMDocumentload/save/validate string arguments asnon-empty-stringon PHP 8 (#6075), [#14979], thanks @VincentLanglet and @staabm!
If you want to see the shape of things to come and adopt bleeding edge features early, you can include this config file in your project's phpstan.neon:
includes:
- vendor/phpstan/phpstan/conf/bleedingEdge.neon
Of course, there are no backwards compatibility guarantees when you include this file. The behaviour and reported errors can change in minor versions with this file included. Learn more
Improvements 🔧
- Implement
@pure-unless-callable-is-impure(#3482), [#11101], [#11100], thanks @zonuexe! - Report invalid
$flagsconstants forpreg_replace_callback()andpreg_replace_callback_array()(#6019), [#11437], [#14910], thanks @staabm and @VincentLanglet! - Validate callback signatures for curl_setopt callback options like CURLOPT_PREREQFUNCTION (#6050), [#14956], thanks @staabm and @VincentLanglet!
Bugfixes 🐛
- Do not narrow the left side of
??to non-null when the coalesce context still permits a falsey value (#6000), [#13488], thanks @VincentLanglet and @staabm! - Add
UnaryOperatorTypeSpecifyingExtensionforBcMath\Numberso unary+/-inferBcMath\Number(#5994), [#13965], thanks @VincentLanglet and @staabm! - Narrow
explode()result for limit 0, 1, and -1 (#5993), thanks @paulbalandan! - Accept
decimal-int-stringarguments for%dprintf placeholders in strict mode (#5997), [#13609], thanks @VincentLanglet and @staabm! - Resolve the base of
$prop[] = ...to its readable type instead of its writable type (#5996), [#14054], thanks @VincentLanglet and @staabm! - Invalidate tracked superglobal narrowings after impure function, method, static and constructor calls (#6007), [#12799], thanks @VincentLanglet and @staabm!
- Invalidate volatile expressions on eval/include (#6009), thanks @VincentLanglet!
- Do not treat offset writes on ArrayAccess properties as reassignments of readonly properties (#5995), [#13856], thanks @VincentLanglet and @staabm!
- Forget negative
function_exists()/class_exists()narrowings when global state may change (#5998), [#13608], [#12094], thanks @VincentLanglet and @staabm! - Reanalyse trait-consuming files after a body-only trait change even when the trait shares its file with another symbol (#6033), [#14943]
- Negating the smallest integer results in a float (#6028), [#14946], [#14947], thanks @zonuexe!
- Fix 'path ... does not exist' false-positive for expr containing DIRECTORY_SEPARATOR (#6035), [#14944], thanks @staabm!
- Track file dependencies for global constant fetches (#6023), thanks @SanderMuller!
- Track define()'d constants as exported nodes (#6036), thanks @SanderMuller!
- Fix filter_var throw-on-failure detection (#6042), thanks @simPod!
- Make Type objects behave as the immutable values they are (#6037)
- Account for subclasses in get_parent_class() of a $this/static value (#6048), [#14951], thanks @zonuexe!
- Treat all in-scope variables as used when a closure or constructor body contains
include/require/eval(#6056), [#14962], thanks @staabm and @VincentLanglet! - Treat all in-scope variables as used when an unused-parameter body accesses a variable variable (
$$name) (#6058), [#13960], thanks @staabm and @VincentLanglet! - Resolve static/self/parent relative to
@param-closure-thisbound class (#6061), [#11010], thanks @calebdw! - Do not report abstract interface methods provided by a non-abstract built-in parent class in
MissingMethodImplementationRule(#6059), [#14964], thanks @VincentLanglet! - Preserve maybe in BitwiseFlagHelper for bitwise-or with dynamic operands (#6080), thanks @vrana!
- Treat trait-declared properties as initialized when promoted by an inherited constructor (#6085), [#14983], thanks @VincentLanglet and @staabm!
- Keep the result cache when composer.lock changes but no package version did (#6086), [#14984], thanks @SanderMuller!
- Fix "
ob_get_*() === falsewill always evaluate to false" false positive (#6097), [#14985], thanks @staabm! - Invalidate the whole result cache when a changed package registers a container class (#6088), [#14986], thanks @SanderMuller!
Performance 🏎️
- Cache ObjectType instead of re-creating it over and over (#6006), thanks @staabm!
- Single-flight cold directory scans in OptimizedDirectorySourceLocatorFactory (#5846), thanks @SanderMuller!
- Disable zend.assertions as suggested by PHP-Parser (#6021), thanks @staabm!
- Faster DuplicateClassDeclarationRule (#6038), thanks @staabm!
- Faster DuplicateFunctionDeclarationRule (#6039), thanks @staabm!
- Memoize
ExprHandlerdispatch inMutatingScope::resolveType()(#6040), thanks @staabm! - Replace
NodeFinderwith direct recursion inshouldInvalidateExpression()(#6041), thanks @staabm! - Return early from
ClassReflection::getMethod()cache (#6043), thanks @staabm! - Index PSR autoloader locators by symbol instead of sweeping them per lookup (#6037)
- Short-circuit no-op remove() comparisons by identity before equals() (#6037)
- Cache reflections built from the PhpStorm stubs (https://github.com/phpstan/phpstan-src/commit/ae5fd3163f507b5091ff19c0e96d7ab3c8d3e4c0)
- Do not park fibers on expressions that can never receive a before-scope (#6064)
- Extend the compositional-key shortcut to more invalidation scans (#6064)
- Prevent unnecessary method calls on Type (#6076), thanks @staabm!
- Prevent reflection when Property Hooks not supported (#6082), thanks @staabm!
- Iterate through nullsafe expression chains (#6084), thanks @realFlowControl!
- Iterate in
collectInterfaces()(#6092), thanks @staabm! - Iterate in
getBooleanExpressionDepth()(#6091), thanks @staabm! - Prevent duplicate
filterByTruthyValue()work inNodeScopeResolver(#6103), thanks @staabm! - Run indentation detection, node cloning and node replacing in a single AST traversal and cache per-file fixing data (#6101), [#14996], thanks @staabm and @VincentLanglet!
- Remove unnecessary work in
BooleanOrHandler(#6106), thanks @staabm! - Iterative NullsafeOperatorHelper (#6105), thanks @staabm!
- Simplify
AccessoryArrayListType(#6107), thanks @staabm!
Function signature fixes 🤖
- Use list-shapes for functionMap return types that are guaranteed lists (#6024), thanks @zonuexe!
- Add array-shape return type for
localeconv()and shape-basedlocaltime()return type extension (#6031), [#14949], thanks @staabm and @VincentLanglet!
Internals 🔍
- Refactor: extract ConfiguredPhpVersionRangeHelper (#6014), thanks @staabm!
- Utilize ConfiguredPhpVersionRangeHelper (#6017), thanks @staabm!
- Add named argument tests for
@pure-unless-callable-is-impure(#6022), thanks @zonuexe! - Rename Test-classes colliding with builtin classes (#6032), thanks @staabm!
- Upgrade PHPBench to 1.7.0 and use aggregate-preview report (#6067), thanks @dantleech!
- Introduce
#[ExtensionInterface]attribute, derive the interface-tag mapping from it (#6096) - Introduce
ExtensionsCollection,#[AutowiredExtensions]andContainer::getExtensionsCollection(), replace extension providers (#6098) - Add autoTag parameter to #[AutowiredService], migrate services that must not be auto-tagged (#6096)
- Introduce
#[InstanceofDeprecated]attribute, derive ApiInstanceofTypeRule map from it (#6096) - Update composer-attribute-collector: collect attributes without autoloading under PHP < 8 (#6096)
- Remove obsolete PDO.patch for phpstorm-stubs (#6073), thanks @vrana!
- Compile
#[AutowiredParameter]references into deferred getParameter() lookups (#6102) - Validate service tags in afterCompile() so tags added by any extension are covered (#6102)
- Discover compiler extensions from the
#[ContainerExtension]attribute (#6102) - Upgrade nette/di to 3.1.10 and drop the deleted-tag pin (#6102)