Search by

nette / phpstan-rules

david@grudl.com

🐘 PHPStan Rules: makes PHPStan understand Nette, with precise return types, typed database rows and fewer false positives.

Package info

github.com/nette/phpstan-rules

Homepage

Type:phpstan-extension

pkg:composer/nette/phpstan-rules

Statistics

Installs: 49 341

Dependents: 48

Suggesters: 0

Stars: 10

Open Issues: 0

v1.0.7 2026-10-22 02:05 UTC

This package is auto-updated.

Last update: 2026-10-28 02:33:41 UTC


README

Nette PHPStan Rules

Downloads this Month Tests Latest Stable Version License

 

Makes PHPStan smarter about Nette code. Install, and it just works — more precise types, fewer false positives.

 

Installation

Install via Composer:

composer require --dev nette/phpstan-rules

Requirements: PHP 8.1 or higher and PHPStan 2.2.6+.

If you use phpstan/extension-installer, the extension is registered automatically. Otherwise add to your phpstan.neon:

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

 

What's Included

 

Precise return types — narrows return types of Strings::match(), matchAll(), split(), Helpers::falseToNull(), Expect::array(), Expect::type(), Arrays::invoke(), and Arrays::invokeMethod() based on the arguments you pass. For Expect::type() with a constant expression you get the element it actually builds, so Expect::type('int') is a NumberType and its min()/max() are known to exist. For Strings::match() and matchAll() with a constant pattern, the exact array shape is derived from the regular expression — e.g. Strings::match($s, '#(\d+)-(\w+)#') returns array{string, string, string}|null with one element per capture group (named groups included). The same shape is inferred for the $matches argument of a Strings::replace() callback. Also narrows Container::getComponent() and $container['...'] to match the corresponding createComponent*() factory return type. For forms, $form['name'] returns the specific control type (e.g. TextInput, SelectBox) based on the addText(), addSelect(), etc. call in the same function.

Database row mapping — narrows return types of Explorer::table(), ActiveRow::related(), ActiveRow::ref(), and Selection::insert() based on a configurable table-to-entity-class convention. For example, $explorer->table('booking') returns Selection<BookingRow> instead of Selection<ActiveRow>, and a single-row $bookings->insert([...]) returns BookingRow instead of the wide schema-dependent union. Configure via:

parameters:
    nette:
        database:
            mapping:
                tables:
                	*: App\Entity\*Row                    # * = PascalCase table name
                    special_table: App\Entity\SpecialRow  # optional explicit overrides

Asset type narrowing — narrows return types of Registry::getMapper() to the specific mapper class, and Registry::getAsset() / tryGetAsset() to the specific asset type (e.g. ImageAsset, ScriptAsset) based on file extension. Also narrows FilesystemMapper::getAsset() and ViteMapper::getAsset() directly. Configure via:

parameters:
    nette:
        assets:
            mapping:
                default: file                   # FilesystemMapper
                images: file                    # FilesystemMapper
                vite: vite                      # ViteMapper
                custom: App\MyMapper            # custom class (FQCN)

Interface @property tags — makes @property-read/@property annotations on interfaces work. PHPStan core ignores them there, so e.g. $asset->url on Nette\Assets\Asset reports property.notFound; with this extension the read gets the annotated type (nullsafe access included) and the false error is suppressed. Property writes are intentionally left unsupported.

Html magic methods — resolves $html->getXxx(), setXxx(), and addXxx() calls on Nette\Utils\Html that go through __call() but aren't declared via @method annotations.

Runtime-validated callbacks — Nette\Utils\Callback::toReflection() accepts any callback and escalates the validity check to a ReflectionException at runtime, so passing a value that can't be statically proven callable (typically a [class-string, method-name] tuple) no longer reports a false argument.type error.

Removes |false and |null from PHP functions — many native functions like getcwd, json_encode, preg_split, preg_replace, and many more include false or null in their return type even though these error values are unrealistic on modern systems. For preg_* functions this applies only to constant patterns. With the u modifier, false can also mean an invalid UTF-8 subject — this is stripped by design too, so that every regex call doesn't have to double as input validation. Validate untrusted input explicitly with preg_match('##u', $s), whose false return type is preserved.

Injected properties — properties marked with the #[Nette\DI\Attributes\Inject] attribute are treated as initialized and written, so PHPStan won't report them as uninitialized or never written.

Invalid regex detection — reports invalid regular expression patterns passed to Strings::match(), matchAll(), split(), and replace(), so a malformed pattern is caught during analysis instead of at runtime.

AbortException safety — warns when a broad catch (\Throwable) or catch (\Exception) in a try block that calls redirect(), forward(), terminate(), etc. swallows Nette\Application\AbortException instead of rethrowing it — a mistake that silently breaks redirects.

Subject narrowing after match — inside if (Strings::match($s, '#\d+#')) the subject $s is narrowed (e.g. to non-empty-string) based on the pattern.

Assert type narrowing — PHPStan understands type guarantees after Tester\Assert calls like notNull(), type(), true(), etc.

False positive suppression — silences known PHPStan false positives in Nette patterns (arrow functions passed as void callbacks, runtime type validation closures, callbacks assigned to Form event-handler properties like $onSuccess, $onClick).

Old class names — the deprecated names Nette keeps as aliases (e.g. Nette\DI\Statement for Nette\DI\Definitions\Statement) are understood as the classes they stand for, so code still using them is analysed without false errors such as a missing constructor.

 

Type Assertion Testing Helper

For Nette package developers: TypeAssert lets you verify type inference in tests using Nette Tester:

use Nette\PHPStan\Tester\TypeAssert;

TypeAssert::assertTypes(__DIR__ . '/data/types.php');
TypeAssert::assertNoErrors(__DIR__ . '/data/clean.php');

The data file uses assertType() from PHPStan:

use function PHPStan\Testing\assertType;

assertType('non-empty-string', getcwd());
assertType('string', Normalizer::normalize('foo'));

 

Support Me

Do you like Nette? Are you looking forward to the new features?

Buy me a coffee

Thank you!