Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Phpstan Nette Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

This is the Larastan of the Nette world: a PHPStan extension that teaches static analysis the runtime magic Nette relies on (component factories, DI autowiring, form value hydration, SmartObject properties, ArrayHash iteration). If you already have PHPStan wired into CI, adoption is roughly two commands and one config file.

Minimal install:

composer require --dev phpstan/phpstan-nette phpstan/extension-installer

With phpstan/extension-installer present, extension.neon is registered automatically — nothing else to do. Without it, include it manually:

# phpstan.neon
includes:
    - vendor/phpstan/phpstan-nette/extension.neon

One important asymmetry: the installer only pulls in extension.neon (the reflection/type layer). The framework-specific rules are opt-in and must be added by hand:

includes:
    - vendor/phpstan/phpstan-nette/extension.neon
    - vendor/phpstan/phpstan-nette/rules.neon   # DoNotExtendNetteObject, rethrow, regex validation

Where to look first: extension.neon lists every registered service (type extensions, dynamic method return type extensions, stubs); stubs/ shows which Nette classes get their signatures overridden. When a type resolves unexpectedly, that pair tells you whether the guess came from a stub or a dynamic extension.

First use case: open any presenter with a createComponent* method and call $this->getComponent('grid') (or $this['grid']). Without the extension you get IComponent; with it you get your concrete Grid type and the following ->setDataSource() call type-checks. That single check is usually the "oh, this is worth it" moment — it turns the most stringly-typed part of a Nette codebase into something PHPStan can follow end to end.

Implementation Patterns

Factory-driven component typing. The extension reads the return type of createComponent<Name>() to type getComponent('name') and $this['name']. Write the factory methods with explicit return types (not IComponent) or the whole chain collapses back to the interface:

protected function createComponentUserGrid(): UserGrid
{
    return new UserGrid($this->userRepository);
}

public function renderDefault(): void
{
    $this->template->users = $this['userGrid']->getDataSource(); // typed as UserGrid
}

DI container resolution. Container::getByType(Foo::class) and createInstance(Foo::class) return Foo. This makes service-locator-ish code analysable, but the real win is in bootstrap/tests where getByType is unavoidable. Prefer constructor injection anywhere the type can be expressed statically.

Form value hydration. getValues() is the biggest API in the package and the one to learn precisely:

$array = $form->getValues(true);       // array<string, mixed> — no per-field types
$typed = $form->getValues();           // typed DTO when the form maps to a class
$dto   = $form->getUntrustedValues(MyFormData::class); // mapping-class based

If you map a form to a DTO via setMappedType()/onSuccess conventions, getValues() gives you the object type and catches field-name typos in $form['email'] lookups for free.

ArrayHash is typed end to end (2.0.15). The stub now declares the offset methods (ArrayAccess contract) and the iterator return type is inferred by reflection from the generic/@template annotation on the subclass. That means foreach over an ArrayHash<Foo> yields Foo, $hash['key'] reads back the element type, and isset($hash['key']) no longer trips an "offset on mixed" warning. Because the iterator type comes from reflection over your own class, parameterised subclasses (/** @extends ArrayHash<Item> */) benefit automatically — but only if you actually annotate the generics. Bare ArrayHash still degrades to mixed.

Fluent chains actually stay fluent. BaseControl methods (setRequired(), setDefaultValue(), addRule()) return static in the extension's view, so chained form definitions keep the concrete control type instead of degrading to BaseControl — important when you assign the control to a property and later call setItems() on it.

Optional-return APIs honour their flags. lookup($throw), getPresenter($throw), and getSession($namespace) are typed per-argument, so $this->getPresenter(false) is nullable and getSession('cart') is a SessionSection, not Session.

Regex shapes. Strings::match()/matchAll() produce array shapes derived from the pattern, and Strings::replace() infers the callback parameter type. This only fires for literal, constant, or preg_quote'd patterns — keep named groups literal and consider const patterns if you want the shape to survive refactoring.

Presenter conventions. @inject properties are treated as initialized (no false "possibly null" on first use), magic SmartObject properties resolve from @property docs, and on* event properties are callable. Early-terminating presenter methods (sendJson, redirect, error) are marked as never-returning, which is what finally kills a whole class of "undefined variable after $this->redirect()" false positives.

CI workflow. Run the usual level ladder, but generate a baseline once on adoption: vendor/bin/phpstan analyse --generate-baseline. Keep rules.neon enabled in a second, stricter CI job if you want the legacy Nette\Object and swallowed-AbortException checks without blocking the main pipeline on day one.

Gotchas and Tips

  • rules.neon is not auto-installed. The single most common "the extension does nothing" report. If DoNotExtendNetteObjectRule never fires, you forgot the include.
  • Bleeding edge. As of 2.0.0 several behaviours sit behind PHPStan's bleedingEdge flag. If a documented feature (e.g. a stub shape) doesn't show up, enable it in phpstan.neon before filing an issue.
  • Version lockstep. 2.0.x targets PHPStan 2.x and modern Nette; 1.3.x is the fallback for older PHPStan. A mismatched pair usually manifests as a container compile error in extension.neon, not a useful message — check the PHPStan version constraint first. ArrayHash iterator/offset typing landed in 2.0.15, so anything below that gives you the old untyped iteration.
  • getValues(true) is deliberately untyped. It returns array<string, mixed> (fixed in 2.0.6). Do not expect per-field array shapes; use a mapping class/DTO if you want real field-level safety.
  • Generics on ArrayHash are load-bearing. The 2.0.15 iterator inference reads the @template/@extends annotation by reflection; unannotated hashes silently stay mixed with no warning. If foreach ($hash as $item) gives you mixed, add /** @extends ArrayHash<Item> */ to your subclass before suspecting the extension.
  • Dynamic patterns degrade silently. Strings::match($text, $this->pattern) yields the generic array<int|string, string>|null — no error, just less precision. Hoist patterns into constants when the shape matters.
  • Stub collisions. If you maintain your own stubs for Nette classes, they compete with the package's. Prefer stubFiles: in your config over patching vendor code, and keep your overrides narrow — this matters more since the ArrayHash stub is now substantive.
  • Extension points: write your own PHPStan\Type\DynamicMethodReturnTypeExtension when a project-level convention isn't covered (custom createComponent* variants, custom base presenters that wrap lookup). Register it in your own phpstan.neon alongside the package's — the two coexist fine.
  • Debugging a wrong type: vendor/bin/phpstan analyse --debug -v shows which extensions were consulted; clear-result-cache after changing stubs or extension registrations — mandatory after bumping to 2.0.15 if you still see untyped ArrayHash iteration. Large DI graphs can push memory — bump --memory-limit=1G in CI.
  • Silencing individual framework rules is best done by error identifier (ignoreErrors: - identifier: …) rather than message regex, so upstream wording changes don't break your config.
  • Scope check: this buys you nothing on a Laravel app. It's the direct analogue of Larastan (larastan/larastan) for Nette — same architecture, same install shape, zero overlap. For a Laravel team evaluating a Nette codebase's tooling, treat it as the framework-specific layer on top of a base PHPStan install, not a replacement for it.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel