Weave Code
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

Plugin Laravel Laravel Package

psalm/plugin-laravel

Psalm plugin for Laravel that adds deep framework-aware static analysis plus taint-based security scanning. Detects SQL injection, XSS, SSRF, shell injection, file traversal, and open redirects by tracking user input flows across functions and services.

View on GitHub
Deep Wiki
Context7

title: Upgrading to v4 nav_order: 5

Upgrading from v3 to v4

Requirements

Dependency v3 v4
PHP ^8.2 ^8.3
Laravel 11, 12 12, 13
Psalm 6, 7 (beta) 7 only

Laravel 11 and Psalm 6 are no longer supported. If you need them, stay on v3.

Breaking changes

Psalm 7 is required

v4 requires vimeo/psalm ^7.0.0-beta17 or later. If your project still uses Psalm 6, upgrade Psalm first:

composer require --dev vimeo/psalm:^7.0.0-beta17

Psalm 7 is still in beta. You may need to add this to your project's composer.json:

{
    "minimum-stability": "dev",
    "prefer-stable": true
}

Psalm 7 introduces new issue types that may surface in your codebase. These catch real design problems – fixing them improves your code, but you can suppress them during the upgrade and address them later:

  • MissingPureAnnotation -- a method has no side effects but lacks [@psalm-pure](https://github.com/psalm-pure). Adding it lets Psalm verify the method stays side-effect-free and enables callers to use it in pure contexts.
  • MissingAbstractPureAnnotation -- an abstract method should be declared [@psalm-pure](https://github.com/psalm-pure) so all implementations are guaranteed pure.
  • MissingImmutableAnnotation -- a class has no mutable state but lacks [@psalm-immutable](https://github.com/psalm-immutable). Marking it immutable prevents accidental mutation in future changes.
  • MissingInterfaceImmutableAnnotation -- an interface should be [@psalm-immutable](https://github.com/psalm-immutable) so all implementations are guaranteed immutable.
<issueHandlers>
    <MissingPureAnnotation errorLevel="suppress" />
</issueHandlers>

Eloquent relation generics now require a declaring model parameter

All Eloquent relation stubs gained additional template parameters. If your codebase has [@psalm-return](https://github.com/psalm-return) (or [@return](https://github.com/return)) annotations with relation generics, they must be updated:

Relation type v3 signature v4 signature
BelongsTo BelongsTo<TRelated> BelongsTo<TRelated, TDeclaringModel>
HasOne HasOne<TRelated> HasOne<TRelated, TDeclaringModel>
HasMany HasMany<TRelated> HasMany<TRelated, TDeclaringModel>
BelongsToMany BelongsToMany<TRelated> BelongsToMany<TRelated, TDeclaringModel>
MorphOne MorphOne<TRelated> MorphOne<TRelated, TDeclaringModel>
MorphMany MorphMany<TRelated> MorphMany<TRelated, TDeclaringModel>
MorphTo MorphTo<TRelated> MorphTo<TRelated, TDeclaringModel>
MorphToMany MorphToMany<TRelated> MorphToMany<TRelated, TDeclaringModel>
HasOneThrough HasOneThrough<TRelated> HasOneThrough<TRelated, TIntermediate, TDeclaring>
HasManyThrough HasManyThrough<TRelated> HasManyThrough<TRelated, TIntermediate, TDeclaring>

Collection, EloquentCollection, and Builder are unchanged.

Taint analysis runs automatically

In Psalm 6 you had to pass --taint-analysis as a separate flag. Psalm 7 combines type analysis and taint analysis into a single run by default. No flags needed — just run ./vendor/bin/psalm.

New features in v4

New issue types

Plugin issues (suppressible via <PluginIssue>):

  • InvalidConsoleArgumentName -- argument() references an undefined name in the command's $signature
  • InvalidConsoleOptionName -- option() references an undefined name in the command's $signature
  • NoEnvOutsideConfig -- env() called outside the config/ directory (env() returns null when the config is cached)
<issueHandlers>
    <PluginIssue name="InvalidConsoleArgumentName" errorLevel="suppress" />
    <PluginIssue name="InvalidConsoleOptionName" errorLevel="suppress" />
    <PluginIssue name="NoEnvOutsideConfig" errorLevel="suppress" />
</issueHandlers>

Psalm built-in issues (new detections via taint analysis):

  • TaintedSql -- where(), orWhere(), and other query builder methods now have [@psalm-taint-sink](https://github.com/psalm-taint-sink) sql annotations, catching SQL injection via dynamic column names

Other improvements

  • #[Scope] attribute support -- Laravel 12+ scope detection alongside the traditional scope method prefix
  • AST-based cast parsing (reads casts() method without executing it)
  • Write-type registration (pseudo_property_set_types) for model properties
  • Support for Attribute<TGet, TSet> accessor templates
  • after() closures, Blueprint::rename(), addColumn(), and more migration methods supported
  • Auto-discovery of migration directories registered via loadMigrationsFrom()

Upgrade steps

# 1. Update PHP to 8.3+ and Laravel to 12+ if needed

# 2. Upgrade Psalm to v7
composer require --dev vimeo/psalm:^7.0.0-beta17

# 3. Upgrade the plugin
composer require --dev psalm/plugin-laravel:^4.0

# 4. Update relation generic annotations (add declaring model parameter)
#    BelongsTo<Foo>        → BelongsTo<Foo, self>
#    HasMany<Foo>          → HasMany<Foo, self>
#    HasOne<Foo>           → HasOne<Foo, self>
#    BelongsToMany<Foo>    → BelongsToMany<Foo, self>
#    MorphOne<Foo>         → MorphOne<Foo, self>
#    MorphMany<Foo>        → MorphMany<Foo, self>
#    HasManyThrough<Foo>   → HasManyThrough<Foo, Intermediate, self>
#    HasOneThrough<Foo>    → HasOneThrough<Foo, Intermediate, self>
#
#    Quick sed for the common cases (run from project root):
find app -name '*.php' -exec grep -l '[@psalm-return](https://github.com/psalm-return) \(BelongsTo\|HasMany\|HasOne\|BelongsToMany\|MorphOne\|MorphMany\|MorphTo\|MorphToMany\)<' {} \; \
  | xargs sed -i 's/[@psalm-return](https://github.com/psalm-return) \(BelongsTo\|HasMany\|HasOne\|BelongsToMany\|MorphOne\|MorphMany\|MorphTo\|MorphToMany\)<\([^>]*\)>/[@psalm-return](https://github.com/psalm-return) \1<\2, self>/g'
#    HasManyThrough / HasOneThrough need manual edits (add intermediate model).

# 5. Run Psalm and update your baseline
./vendor/bin/psalm --set-baseline=psalm-baseline.xml

# 6. Review new issues
#    - InvalidConsoleArgumentName / InvalidConsoleOptionName are real bugs — fix them
#    - NoEnvOutsideConfig — move env() calls into config files
#    - TaintedSql on Builder::where() — review for actual SQL injection risk
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.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport