Extensions and framework awareness
Point PHPStan at a Laravel app without any help and it reports things like:
Access to an undefined property App\Models\Order::$reference.
Call to an undefined method Illuminate\Database\Eloquent\Builder<App\Models\Order>::unpaid().
Cannot call method orderBy() on mixed.
None of them are bugs. $order->reference is a column, which Eloquent reads through __get(). unpaid() is a scope, reached through __call(). PHPStan reads code without running it, so it can't see what a database table or a magic method will provide. These are false positives: errors about code that works.
Most teams meet a few hundred of them on the first run.
The fix that makes it worse
The quick way to a green build is to ignore them by pattern:
parameters: ignoreErrors: - '#^Access to an undefined property App\\Models\\#' - '#, mixed given\.$#'
That first pattern matches every undefined property on every model, including $order->totl, the typo that is about to break every invoice headline. The second hides every mixed argument in the project. Ignore patterns can't tell a false positive from a bug with the same wording, so they hide both, for good.
Extensions explain the magic
An extension teaches PHPStan a framework's rules, so it can see what the magic provides and report only what's genuinely wrong. For Laravel, that's Larastan:
composer require --dev larastan/larastan
includes: - vendor/larastan/larastan/extension.neon
Larastan boots your application from bootstrap/app.php, then:
- reads
database/migrationsto learn every model's columns, their types, and whether they can benull - knows that scopes, relations and query builder calls return typed builders and collections, so
Order::query()->unpaid()->get()is aCollection<int, Order> - understands facades,
config(),app(), and the container
Run it on a project that hid its false positives and you'll see the hidden errors come back, now with only the real ones left. It does more than silence noise. delivery_note is a nullable column, so Larastan knows $order->delivery_note is string|null, which no hand-written @property tag ever said.
There are extensions for most of the libraries that rely on magic. phpstan/phpstan-phpunit understands mocks, and phpstan/phpstan-webmozart-assert understands Assert::string(). When a library makes PHPStan complain about code that works, search for its extension before you reach for ignoreErrors.
Power-up
<?php use Illuminate\Database\Eloquent\Model; /** * Written by hand in 2022, before delivery_note became nullable * and total was renamed total_pence. * * @property int $id * @property string $reference * @property int $total * @property string $delivery_note */ final class Order extends Model { }
<?php use Illuminate\Database\Eloquent\Model; // No @property tags. Larastan reads every column, its type and // whether it can be null from database/migrations, so they can't drift. final class Order extends Model { protected function casts(): array { return ['paid_at' => 'immutable_datetime']; } }