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

Larastan Laravel Package

nunomaduro/larastan

Larastan is a PHPStan extension for Laravel that adds static typing and catches bugs before you write tests. It understands Laravel's magic and boots your app container to resolve runtime types, supporting Laravel 9 through 11+.

View on GitHub
Deep Wiki
Context7

Getting Started

Larastan is a PHPStan extension that boots your Laravel container, so it can type-check things plain PHPStan can't see: Eloquent attributes, relations, collections, facades, config, translations, Blade views.

Day-one setup (3 commands, 1 file):

composer require --dev "larastan/larastan:^3.0"   # PHP 8.2+, Laravel 11.15+
# phpstan.neon
includes:
    - vendor/larastan/larastan/extension.neon
    - vendor/nesbot/carbon/extension.neon

parameters:
    paths:
        - app/
    level: 5
./vendor/bin/phpstan analyse --memory-limit=2G

Where to look first, in this order:

Doc Why you care
docs/features.md What magic is actually understood (relations, facades, models, views)
docs/rules.md Laravel-specific rules you can toggle — many are opt-in
docs/custom-types.md model-property<...> and friends for precise PHPDoc
docs/custom-config-parameters.md checkModelProperties, viewDirectories, migration scan knobs
docs/errors-to-ignore.md Copy-paste ignores for known-too-magical Laravel corners

First use case to chase: an undefined/nonexistent model attribute or a wrong relation type — e.g. $user->proflie or $post->comments()->first()->body where comments() returns HasMany<Comment> and you forgot the ?. Those are the bugs that cost you a 3am production page and that Larastan finds in under a second.

For an existing codebase, don't start at level 5. Do:

./vendor/bin/phpstan analyse --generate-baseline

commit phpstan-baseline.neon, include it in phpstan.neon, and enforce the full level on new code only. That is the whole ratchet.

Implementation Patterns

Ratchet the level per-directory instead of globally. Raise level one notch at a time, fix, commit, repeat. When app/ hits level 8 but database/ and routes/ are feral, split paths:

includes:
    - vendor/larastan/larastan/extension.neon
    - phpstan-baseline.neon

parameters:
    level: 0
    paths: [app, database, routes, config]
    ignoreErrors:
        - identifier: missingType.generics
          paths: [database/migrations/*]

CI job (the highest-leverage five lines you'll write this quarter):

- run: composer install --prefer-dist --no-interaction   # NOT --no-dev
- run: ./vendor/bin/phpstan analyse --no-progress --error-format=github --memory-limit=2G

--error-format=github turns findings into inline PR annotations, which is what actually makes developers fix them. Add the same command to a composer lint / pre-commit hook for the fast local loop.

Model typing is the core daily workflow. Larastan infers column types from your migrations, but relations need explicit generics in v3 (the automatic relation generics from 2.x were dropped — read UPGRADE.md before upgrading):

/**
 * @property int $id
 * @property string $email
 * @property-read Carbon $created_at
 * @property-read Collection<int, Post> $posts
 */
class User extends Model
{
    /** @return HasMany<Post, $this> */
    public function posts(): HasMany { return $this->hasMany(Post::class); }
}

That one @return HasMany<Post, $this> propagates through ->posts()->where(...)->get() and gives you Collection<int, Post> end to end. $this as the second template arg keeps fluent chaining accurate.

Turn on full model property checking only where it pays:

parameters:
    checkModelProperties: true
    databaseMigrationsPath: [database/migrations]

This resolves attributes from the actual schema rather than @property docblocks — powerful, but slow and requires a reachable database, so keep it out of the default local run and put it in a nightly CI job.

Precise types at the boundaries. Larastan gives you helpers to stop mixed leaking out of Eloquent and requests:

/** @param model-property<User> $email */
function sendTo(string $email): void {}

/** @var array{name: string, email: string} $data */
$data = $request->validate([...]);

Prefer error identifiers over message regexes (PHPStan 2 / Larastan 3 support both):

$foo->bar(); // @phpstan-ignore method.notFound
ignoreErrors:
    - identifier: larastan.noUnnecessaryCollectionCall
      count: 3   # fails if the count changes — keeps ignores from rotting

count: on an ignore is underused: it makes an ignored error a tracked debt rather than a permanent blind spot.

Package development: install orchestra/testbench and point Larastan at it, otherwise the container boot fails and you get a wall of nonsense.

Extension points: add your own stubFiles to teach PHPStan about local magic; write custom PHPStan\Rules\Rule implementations for house conventions (no DB::raw, no env() outside config); and use larastan's existing extensions as templates — src/Extensions/ is the best-documented part of the codebase.

Gotchas and Tips

It boots your app — this is not pure static analysis. That means .env must exist, APP_KEY must be set, and config must be consistent. The number-one false-positive source is a stale bootstrap/cache/config.php: run php artisan config:clear before phpstan analyse in CI, or you'll analyse yesterday's config forever.

Database access is conditional but real. Migration scanning needs to parse migrations, not connect — but checkModelProperties and anything resolving the schema does connect. If your CI has no DB service, either add one or leave checkModelProperties off in that job.

Memory. Default PHP memory limits die on mid-size apps. Always pass --memory-limit=2G (or -1 in CI). Also cap parallelism: parallel: maximumNumberOfProcesses: 4 prevents a 16-core CI runner from OOMing itself.

Squashed migrations are a trap. Larastan reads migrations to infer column types; if you squash old migrations into schema:dump, the original Schema::create calls vanish. Either keep them in database/schema/ and set squashedMigrationsPath, or accept degraded inference. Raw DB::statement migrations are invisible to it entirely — annotate those models with @property by hand.

env() return types are generalized to string|null by default. If you're comparing env('X') === true, that's a bug and Larastan will tell you. You can opt out, but don't — move the call into config/ instead. That's the correct Laravel pattern anyway.

$request->input() is mixed, $request->validated() is array. Both are honest. Widen them with @var shapes or FormRequest rules() inference rather than reaching for assert().

Ignoring is a one-way door if you're sloppy. @phpstan-ignore-next-line attaches to the next line and silently detaches when someone inserts a comment above it. Prefer inline @phpstan-ignore <identifier> on the same line, and in config prefer identifier: + count: over '#Call to an undefined method .*badMethod\(\)#'. Regex ignores with line numbers are the classic thing that breaks on every refactor.

Level 3 → 6 is where the real value is; level 10 is a lifestyle choice. max forces mixed everywhere (checkExplicitMixed), which on a framework full of __get is often performable fiction. Level 6–8 is the sane target for application code.

Version pinning matters. Larastan tracks PHPStan majors tightly: 3.x requires PHPStan 2 and Laravel 11.15+. If you pin phpstan/phpstan too loosely you'll get an extension/API mismatch. Pin both, upgrade together, and read both upgrade guides. Also note the 3.9.5 fix for an infinite loop when the Redis extension is missing — pin >=3.9.5 if you have Redis in config/database.php but not installed locally.

Don't analyse vendor/, and don't composer install --no-dev in the analysis job — you'll get "class not found" avalanches from dev-only stubs and test helpers.

Facades you wrote yourself need @method docblocks on the class; Larastan only knows about Laravel's own facades out of the box.

It doesn't replace tests. It finds type errors, dead code, wrong-collection-method misuse, missing translations, unused Blade views — not business logic errors. The teams that get value from it treat it as a fast pre-test filter, not a substitute.

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.
unabomber/timezone
delatbabel/contacts
astritzeqiri/g-recaptcha
kevinongko/laravel-genode
developeruz/laravel-analytics
robbiep/afterthedeadline
frankkessler/incontact-laravel-oauth2-rest
jlxh/laravel-userlog
davidmgilo/timetables
qylinfly/role-permission
reflect-digital/conduit
takashato/laravel-vietqr
aitor24/laralang
kraenzle-ritter/entity-relations
dindigital/din-image
pomirleanu/image-colors
sawmainek/laravel-api-generator
delatbabel/jsonresponses
mouhamedfd/permissions-generator
eduplus/qr