phpstan/phpstan-nette
PHPStan extension for the Nette Framework providing precise type inference for components, DI container getByType/createInstance, forms, dynamic Html methods, magic properties, and presenters, plus optional rules for regex validation and Nette\Object deprecation.
Architecture fit. phpstan/phpstan-nette is not application code — it is a dev-only static analysis extension that teaches PHPStan how Nette's dynamic, reflection-heavy runtime actually behaves. The value to a TPM is indirect but concrete: it converts a large class of Nette "magic" (component factories, DI container lookups, form value mapping, smart-object properties, on* event listeners, presenter @inject, and now ArrayHash collection access) from mixed/untyped territory into checked, inferable types. In other words, it is an analysis-fidelity dependency, not a runtime dependency. It only makes architectural sense if the portfolio contains Nette Framework applications or libraries; for a Laravel/Symfony/plain-PHP estate it is irrelevant and should be rejected on relevance grounds alone. Within a Nette shop, the fit is strong: it plugs into an existing PHPStan gate rather than requiring a new toolchain, and it bundles framework-specific rules (no Nette\Object, rethrow-always exceptions like AbortException, regex validation on Strings methods) that encode institutional Nette knowledge as automated checks.
Integration feasibility. Mechanically trivial: composer require --dev, and either phpstan/extension-installer auto-registers it or two includes lines point at extension.neon and optionally rules.neon. No runtime footprint, no deployment change, no service wiring, and no configuration change is required for the 2.0.15 patch line. The realistic effort is not installation but error triage: enabling the extension will surface a backlog of newly-typed findings that were previously invisible, and that backlog must be sized before commitment. The current release adds to that backlog in a narrow, predictable way — see risk notes below.
Technical risk. Three risks dominate. First, version triangulation: this package tracks both PHPStan majors (the 2.0.0 line moved to bleeding-edge behavior and dropped nikic/php-parser) and Nette releases (e.g. getUnsafeValues() → getUntrustedValues(), Nette 4 regex handling, and now ArrayHash iterator/offset typing). A project on an older Nette or an older PHPStan pin can end up with stubs that describe an API the codebase does not have — producing false positives that erode team trust in the gate. Second, ecosystem signal is weak: ~118 stars and 0 dependents on record suggests a narrow, maintainer-driven package with limited third-party validation; bus-factor and responsiveness should be probed directly. Note that this risk is partially offset by release cadence — 2.0.12 → 2.0.15 is an active, incremental patch line, and the 2.0.15 CI-workflow commit indicates routine maintenance rather than dormancy. Third, bleeding-edge coupling: features gated behind PHPStan's bleedingEdge flag mean analysis output can shift on a PHPStan upgrade alone, which is a CI-stability risk if versions are not pinned.
What 2.0.15 changes materially. The release is a patch bump with no breaking changes, no deprecations, and no new configuration surface. It refines ArrayHash typing: offset methods (offsetGet/offsetSet and related accessors) are now declared in the stub, and getIterator() return type is resolved by reflection. The TPM-relevant consequence is that ArrayHash — a ubiquitous Nette collection type that previously leaked mixed at access sites — now participates in typed analysis. That is a genuine (if narrow) gain in analysis fidelity for Nette codebases, and it will surface a small tranche of new findings at iteration/offset call sites on first run after upgrade. Treat it as a finding-delta event, not a compatibility event.
Key questions.
bleedingEdge enabled anywhere?ArrayHash iteration/offset access?rules.neon (behavioral/style enforcement) or only extension.neon (type inference)? They carry different social cost.~2.0.15 (patch-only, safest given the observed cadence) or a looser constraint that could pull a future minor with new stubs?Stack fit. This belongs in the CI/quality layer alongside PHPStan core, PHPUnit static analysis, and coding standards — not in the application dependency graph. It should be declared in require-dev with a committed composer.lock, and configuration should live in the same phpstan.neon that already governs the project. For multi-repo estates, treat the extension NEON configuration as a shared, versioned artifact (a small internal "phpstan-config" package or a template repo) so the compatibility matrix is expressed once. 2.0.15 requires no config edits, so the upgrade is a lockfile change only.
Migration path. Adopt in three steps. (1) Discovery/no-op: install the extension with extension.neon only, run PHPStan at the project's current level, and diff the report against the pre-install run. This produces the "new findings" inventory without changing the pass/fail bar. (2) Absorb: add genuinely new findings to the existing baseline or fix them in a bounded cleanup sprint; the target is a green build with the extension on. (3) Ratchet: bump PHPStan level or enable rules.neon, one increment per iteration, never both at once.
Compatibility. Establish an explicit support matrix before rollout — extension version × PHPStan major × Nette major/minor × PHP version. Given the release history (PHP 8.4 nullable-parameter fixes, Nette 4 regex support, PHPStan 2.x migrations, and now ArrayHash reflection-based typing), expect the extension's supported window to lag neither tool by much, but to be intolerant of being behind. The getIterator() reflection change in particular implies the stub assumes a reasonably current ArrayHash shape; verify against the Nette version actually deployed rather than assuming. Pin the extension to a minor range and let Renovate/Dependabot open PRs rather than floating it. Where a project is stranded on an old Nette, either upgrade it or exclude it from the rollout — do not run mismatched stubs.
Sequencing. Pilot on the single most-modern Nette service with the cleanest PHPStan baseline — that is where the extension will show immediate value (component factory typing, getByType, form getValues, ArrayHash traversal) with the least triage noise. Measure the finding delta and fix cost. Only then extend to the next project, and only after the matrix is documented. Defer rules.neon to a second wave, since behavioral rules generate author-facing friction and need team buy-in. For Laravel/PHPStan-only services, skip entirely. For projects already on 2.0.x, queue the 2.0.15 bump as an ordinary dependency PR, run it in isolation from any PHPStan or level change, and expect a small ArrayHash-related finding delta that should be reviewed rather than blanket-baselined.
Maintenance. Low ongoing operational load but non-zero governance load. Runtime maintenance is zero (dev-only, no deployment, no attack surface). What must be maintained is the pin: this package's changelog shows it moves in lockstep with both PHPStan and Nette, so "upgrade PHPStan" and "upgrade Nette" both become multi-repo change events that include this dependency. Budget a small recurring allowance for dependency-PR review and stub-mismatch debugging rather than a one-off install. The 2.0.12 → 2.0.15 cadence — three patches with stub refinements and a CI workflow refresh — is a useful calibration datum: expect frequent, small, low-risk bumps, which argues for automation over manual review theatre.
Support. No commercial support; MIT, community/maintainer-driven, with a documented issue tracker and active CI badge. The practical support model is "we pin and we test," not "we escalate." Given ~118 stars and 0 recorded dependents, the TPM should not assume responsive upstream fixes for edge cases — internal capability to read the NEON/stub files and, if necessary, fork or locally override configuration is the real mitigation. The recent commit history is a modest positive signal (work is landing), but it does not change the bus-factor exposure. Document who on the team can author a custom PHPStan extension or stub if the package misses a case.
Scaling. Scales well across services because it is configuration, not code: N projects cost N config entries and one compatibility matrix, not N integrations. The failure mode at scale is configuration drift — divergent PHPStan levels and extension pins across repos making findings incomparable. Counter this by centralizing the PHPStan config template and treating level changes as a portfolio-wide decision. With patch-level stubs like the ArrayHash change, drift also means two services report different errors on identical code; that is a comparability problem, not a correctness problem, but it undermines portfolio-level metrics.
Failure modes.
ArrayHash code; mitigation: align versions or suppress the specific identifiers.ArrayHash findings are a likely candidate for lazy baselining.bleedingEdge toggle changes results with no application change; mitigation: pin PHPStan, upgrade deliberately, and never combine a PHPStan bump with a level bump in one PR.require-dev line and two includes). Current cadence suggests this risk is not imminent.getIterator() typing resolved at analysis time assumes a ArrayHash implementation shape; if an internal subclass overrides iteration with a different generic type, expect incorrect (not merely missing) types. Mitigation: spot-check typed results on ArrayHash subclasses during the pilot.Ramp-up. Developer learning curve is near zero — engineers keep writing PHPStan commands as before; they simply see better types. The real ramp-up is on whoever owns the static-analysis configuration: they must understand the Nette↔PHPStan↔extension version matrix, know why extension.neon and rules.neon are separate, and be able to explain a newly-typed error to a skeptical developer. Expect one to two days of a senior engineer's time for pilot, triage, and matrix documentation; thereafter it is routine dependency hygiene. The value proposition is straightforward to state to stakeholders: fewer mixed escapes and fewer runtime surprises in Nette's magic-heavy corners — now including ArrayHash traversal — purchased for a pinned dev dependency and a modest, one-time cleanup.
How can I help you explore Laravel packages today?