PackageTrack
Sign in Get early access

psalm/plugin-laravel

Psalm plugin for Laravel

v4.15.6 5.5M downloads/mo #2317 most downloaded on Packagist psalm/psalm-plugin-laravel

What this package is like to depend on

Last release 2 days ago

21 Aug 2026

Ships fairly regularly

a new release about every 9 days

Rarely documented

notes for 10 of 169 stable releases

Nothing withdrawn

no release was ever pulled

8 years old

177 releases · first in 2019

104 releases in the last 12 months

see the full history below

Release timeline

174 releases · Feb 2019 to Aug 2026
2020 2021 2022 2023 2024 2025 2026
Release Pre-release

Releases

latest 60 of 177
  1. v4.15.6 21 Aug 2026
    Release notes

    Two new inference sources for Eloquent-backed code, one new opt-in queue-safety rule, and three false-positive fixes across migrations, model attribute helpers, and pipelines.

    Features

    • Add opt-in SerializedQueuedModel rule for ShouldQueue classes that hold an Eloquent model without reaching Illuminate\Queue\SerializesModels, where the whole model is written into the queue payload instead of a ModelIdentifier (#1385). Enable with <findSerializedQueuedModels value="true" />. Detection resolves the flattened __serialize() / __sleep() rather than matching the trait name, so framework bases that already pull the trait in (Illuminate\Foundation\Queue\Queueable, Illuminate\Notifications\Notification) and classes that hand-write their own serialization stay silent.
     final class ReconcileLedger implements ShouldQueue
     {
         public function __construct(private Customer $customer) {}
    -    // silent: the entire Customer row is written into the queue payload
    +    // SerializedQueuedModel: $customer will be serialized whole into the queue payload
     }
    • Infer Arr::pluck() value and key types from the element model's @property annotations, matching what Collection::pluck() and Builder::pluck() already do (#1383).
     /** @param list<Customer> $rows */   // Customer has @property string $id
    -Arr::pluck($rows, 'id');        // array<array-key, mixed>
    +Arr::pluck($rows, 'id');        // list<string>

    Fixes

    • Support $schema = Schema::connection(...) in migrations, so columns declared through a variable-held builder (#1382).
    • Narrow Model::getAppends() and Model::getMutatedAttributes() to list<string>, so callers no longer have to re-assert the element type (#1381).
    • Type Pipeline::then() from its destination closure instead of leaving it mixed (#1376).

    Full Changelog: v4.15.5...v4.15.6

    Open source →
    Release notes

    v4.15.6 Latest

    Latest

    Compare

    Choose a tag to compare

    Open source →
  2. v4.15.5 18 Aug 2026
    Release notes

    Taint precision and Eloquent type narrowing. Two taint fixes remove a duplicate finding and close a missed SQL injection, and four type fixes sharpen inference on facades, collections, and the query builder.

    Fixes

    • Restore plugin facade stub precedence over Laravel's generated @method tags, so templated facade methods resolve their real return type (#1370)
     $value = Cache::remember('key', 60, fn (): User => User::first());
    -// mixed — the generated @method tag outranked the plugin's stub
    +// User
    • Narrow groupBy() and keyBy() keys for model columns instead of widening to array-key (#1369)
     $byId = User::all()->keyBy('id');
    -// Collection<array-key, User>
    +// Collection<int, User>
    • Type the chunkById() callback like chunk(), so the chunk is a templated collection rather than mixed (#1373)
     Article::query()->chunkById(100, function ($chunk) { ... });
    -// $chunk: mixed
    +// $chunk: Collection<int, Article>
    • Accept Laravel 13's fourth $fetchUsing argument on DB::select(), DB::selectResultSets(), DB::cursor(), and their Connection counterparts, while keeping the three-argument signature an error on Laravel 12 (#1374)
     DB::cursor('select * from users', [], true, [PDO::FETCH_ASSOC]);
    -// TooManyArguments — the stub declared three parameters
    +// accepted on Laravel 13; still TooManyArguments on Laravel 12

    Row types follow the fetch mode, so a custom $fetchUsing no longer claims stdClass:

     $rows = DB::cursor('select 1');                              // Generator<int, stdClass>
     $assoc = DB::cursor('select 1', [], true, [PDO::FETCH_ASSOC]);
    -// Generator<int, stdClass>
    +// Generator<int, mixed>
    • 🛡️ Report a tainted view name as TaintedInclude only, not also TaintedFile (#1358). A view name selects which template executes; it cannot reach an arbitrary path, because the view finder rewrites . to / and appends a fixed extension
    • 🛡️ Key WhereColumnTaintHandler's removal bridge on the AST node itself rather than spl_object_id, closing a missed SQL injection (#1366). Psalm frees foreign ASTs mid-file, so a reissued object handle could hit a stale record and strip sql taint from an unrelated expression
    • Ship the Psalm cache in the generated CI template, pass both thread flags, and install igbinary (#1347)

    Full Changelog: v4.15.4...v4.15.5

    Open source →
    Release notes

    v4.15.5

    Compare

    Choose a tag to compare

    Open source →
  3. v4.15.4 16 Aug 2026
    Release notes

    Extends taint reporting to the call forms Laravel applications actually write — facade statics, response() on its contract, and view() names — and clears the false positives that surfaced alongside it.

    Features

    • 🛡️ Report taint sinks on facade static calls, response() contract methods, and view() names (#1318). A facade's surface is @method pseudo-methods resolved through __callStatic, whose parameters have no docblock to carry a sink, so the static form was silent while the chained form fired.
     Redirect::to($request->input('next'));
    -// silent: the facade pseudo-parameter carried no sink
    +// TaintedHeader: Detected tainted header
    • Detect undefined relations in model eager-load defaults $with and $withCount (#1321). Supports dotted paths, column selectors, and the $withCount alias grammar; defers when an intermediate related model cannot be resolved.
     class Post extends Model
     {
         protected $with = ['auther'];
    -    // silent: eager-load defaults were never validated
    +    // UndefinedModelRelation: relation 'auther' is not defined on Post
     }

    Fixes

    • Honour @psalm-taint-escape on closure validation rules (#1352), in inline validate() arrays, in FormRequest rules(), and when the closure is the field's whole rule. Previously the only way to assert a rule made a value safe was to extract it into a dedicated Rule class.
     $request->validate([
         'path' => ['required', /** @psalm-taint-escape file */ static fn ($attr, $value, $fail) => /* ... */],
     ]);
    -// TaintedFile: a closure body is opaque, so no rule could assert safety
    +// clean
    • Fix TaintedSql false positives on where() array values for nullable, template-bounded, and intersection builder receivers (#1338, #1350). Values in the map form are PDO-bound, but a receiver typed Builder|null, @template T of Builder, or T&Builder declined the strip.
     /** @param Builder|null $query */
     $query->where(['status' => $request->input('status')]);
    -// TaintedSql: Detected tainted SQL
    +// clean
    • Stop reporting TaintedFile on uploaded-file extensions (#1324, #1325). getClientOriginalExtension() is the tail after the final dot of a normalized basename and cannot introduce a path segment, and clientExtension() returns a value from Symfony's MIME registry rather than raw client input. All other taint kinds, including include, are unchanged.
     Storage::putFileAs('uploads', $file, Str::ulid() . '.' . $file->getClientOriginalExtension());
    -// TaintedFile: Detected tainted file handling
    +// clean
    • Suppress cross-class taint flow through the Dispatchable traits (#1334). Psalm conflated taint nodes from the shared trait bodies, so an argument dispatched to one job appeared to reach an unrelated job's constructor sink. Genuine Bus and Event taint is still reported.

    • Fingerprint the migration schema cache on file contents instead of modification times (#1346). A git clone stamps every file with the checkout time, so the fingerprint changed on every CI run and the cache never hit even when the restored schema was still valid.

    Full Changelog: v4.15.3...v4.15.4

    Open source →
    Release notes

    v4.15.4

    Compare

    Choose a tag to compare

    Open source →
  4. v4.15.3 24 Jul 2026
    Release notes

    Taint precision for where() array forms and redirect responses.

    Fixes

    1. Fix TaintedSql false positives on where([[...]]) nested-condition arrays: a static receiver spelling (Model::where(...)) skipped the strip entirely, and the inner condition value was scalar-gated so any mixed-typed request value (the common case) kept the sink. Also closes a false negative where a Model subclass with its own concrete where() override had its real sink stripped (#1314).
     $term = (string) $request->input('term');
    -User::where([['name', '=', $term]])->get();
    -// TaintedSql (false positive) -- static receiver wasn't recognized as PDO-bound
    
     User::where([['code', '=', $request->keyword]])->first();
    -// TaintedSql (false positive) -- mixed-typed value failed the scalar gate
    +// clean -- both forms are PDO-bound, matching the instance-receiver behavior
    1. Fix TaintedSSRF incorrectly firing alongside TaintedHeader on redirect() and the Redirector/ResponseFactory redirect family. A redirect sets a Location header for the browser to follow; the server makes no outbound request, so it's an open-redirect/header-injection sink, not SSRF (#1315).
    -return redirect($request->url());
    -// TaintedSSRF + TaintedHeader (SSRF was mislabelled)
    +return redirect($request->url());
    +// TaintedHeader only

    Full Changelog: v4.15.2...v4.15.3

    Open source →
    Release notes

    v4.15.3

    Compare

    Choose a tag to compare

    Open source →
  5. v4.15.2 23 Jul 2026
    Release notes

    Taint-analysis precision for the query where() family. Resolves the TaintedSql inconsistency from #1300 (the same safe query reported or stayed silent depending on how it was written), and closes two SQL source/sink gaps.

    Features 🛡️

    • Source Request::__get() as user input, so $request->term carries taint like $request->input('term') (#1305).
     DB::table('t')->whereRaw((string) $request->input('term')); // TaintedSql
    -DB::table('t')->whereRaw((string) $request->term);          // silent
    +DB::table('t')->whereRaw((string) $request->term);          // TaintedSql
    • Add SQL taint sinks to whereColumn() / orWhereColumn() on all three identifier positions, which the grammar emits raw (#1308).
    -$builder->whereColumn((string) $request->input('c'), '=', 'other'); // silent
    +$builder->whereColumn((string) $request->input('c'), '=', 'other'); // TaintedSql
    • Gate the whole-argument where() sql-taint strip on a Laravel builder receiver, so a non-builder where(array $parts) that interpolates raw SQL keeps its report (#1311).

    Fixes

    • Fix where() array forms raising a false TaintedSql on PDO-bound value positions; the strip now walks the array literal element-wise and keeps the sink only on raw-identifier positions (#1302, fixes #1300).
     $term = (string) $request->input('term');
    -Model::where([['name', 'LIKE', "%{$term}%"]]); // TaintedSql (false positive)
    +Model::where([['name', 'LIKE', "%{$term}%"]]); // clean — value is PDO-bound
    • Widen the whereLike-family $value param to mixed, matching where() and the PDO-bound runtime, so idiomatic calls stop reporting false positives (#1312).
    -Model::whereLike('name', $request->query('q')); // PossiblyInvalidArgument
    +Model::whereLike('name', $request->query('q')); // clean

    Full Changelog: v4.15.1...v4.15.2

    Open source →
    Release notes

    v4.15.2

    Compare

    Choose a tag to compare

    Open source →
  6. v4.15.1 17 Jul 2026

    Nothing published for this version

  7. v4.15.0 16 Jul 2026

    Nothing published for this version

  8. v4.14.12 12 Jul 2026

    Nothing published for this version

  9. v4.14.11 08 Jul 2026

    Nothing published for this version

  10. v4.14.10 08 Jul 2026

    Nothing published for this version

  11. v4.14.9 02 Jul 2026

    Nothing published for this version

  12. v4.14.8 01 Jul 2026

    Nothing published for this version

  13. v4.14.7 30 Jun 2026

    Nothing published for this version

  14. v4.14.6 29 Jun 2026

    Nothing published for this version

  15. v4.14.5 25 Jun 2026

    Nothing published for this version

  16. v4.14.4 23 Jun 2026

    Nothing published for this version

  17. v4.14.2 21 Jun 2026

    Nothing published for this version

  18. v4.14.1 21 Jun 2026

    Nothing published for this version

  19. v4.14.0 18 Jun 2026

    Nothing published for this version

  20. v4.13.2 15 Jun 2026

    Nothing published for this version

  21. v4.13.1 12 Jun 2026

    Nothing published for this version

  22. v4.13.0 12 Jun 2026

    Nothing published for this version

  23. v4.12.4 08 Jun 2026

    Nothing published for this version

  24. v4.12.2 31 May 2026

    Nothing published for this version

  25. v4.12.1 26 May 2026

    Nothing published for this version

  26. v4.12.0 23 May 2026

    Nothing published for this version

  27. v4.11.0 23 May 2026

    Nothing published for this version

  28. v4.10.2 15 May 2026

    Nothing published for this version

  29. v4.10.1 11 May 2026

    Nothing published for this version

  30. v4.10.0 10 May 2026

    Nothing published for this version

  31. v4.9.3 07 May 2026

    Nothing published for this version

  32. v4.9.2 04 May 2026

    Nothing published for this version

  33. v4.9.1 27 Apr 2026

    Nothing published for this version

  34. v4.9.0 26 Apr 2026

    Nothing published for this version

  35. v4.8.4 18 Apr 2026

    Nothing published for this version

  36. v4.8.3 17 Apr 2026

    Nothing published for this version

  37. v4.8.2 17 Apr 2026

    Nothing published for this version

  38. v4.8.1 16 Apr 2026

    Nothing published for this version

  39. v4.8.0 15 Apr 2026

    Nothing published for this version

  40. v4.7.0 12 Apr 2026

    Nothing published for this version

  41. v4.6.2 07 Apr 2026

    Nothing published for this version

  42. v4.6.1 05 Apr 2026

    Nothing published for this version

  43. v4.6.0 05 Apr 2026

    Nothing published for this version

  44. v4.5.0 30 Mar 2026

    Nothing published for this version

  45. v4.4.0 27 Mar 2026

    Nothing published for this version

  46. v4.3.2 25 Mar 2026

    Nothing published for this version

  47. v4.3.1 25 Mar 2026

    Nothing published for this version

  48. v4.3.0 24 Mar 2026

    Nothing published for this version

  49. v4.2.0 23 Mar 2026

    Nothing published for this version

  50. v4.1.0 22 Mar 2026

    Nothing published for this version

  51. v4.0.1 22 Mar 2026

    Nothing published for this version

  52. v4.0.0 18 Mar 2026

    Nothing published for this version

  53. v4.0.0-rc.2 18 Mar 2026 pre-release

    Nothing published for this version

  54. v4.0.0-rc.1 17 Mar 2026 pre-release

    Nothing published for this version

  55. v4.0.0-beta.2 17 Mar 2026 pre-release

    Nothing published for this version

  56. v4.0.0-beta.1 16 Mar 2026 pre-release

    Nothing published for this version

  57. v3.15.6 21 Aug 2026
    Release notes

    Two new inference sources for Eloquent-backed code, one new opt-in queue-safety rule, and three false-positive fixes across migrations, model attribute helpers, and pipelines.

    Features

    • Add opt-in SerializedQueuedModel rule for ShouldQueue classes that hold an Eloquent model without reaching Illuminate\Queue\SerializesModels, where the whole model is written into the queue payload instead of a ModelIdentifier (#1385). Enable with <findSerializedQueuedModels value="true" />. Detection resolves the flattened __serialize() / __sleep() rather than matching the trait name, so framework bases that already pull the trait in (Illuminate\Foundation\Queue\Queueable, Illuminate\Notifications\Notification) and classes that hand-write their own serialization stay silent.
     final class ReconcileLedger implements ShouldQueue
     {
         public function __construct(private Customer $customer) {}
    -    // silent: the entire Customer row is written into the queue payload
    +    // SerializedQueuedModel: $customer will be serialized whole into the queue payload
     }
    • Infer Arr::pluck() value and key types from the element model's @property annotations, matching what Collection::pluck() and Builder::pluck() already do (#1383).
     /** @param list<Customer> $rows */   // Customer has @property string $id
    -Arr::pluck($rows, 'id');        // array<array-key, mixed>
    +Arr::pluck($rows, 'id');        // list<string>
    -Arr::pluck($rows, 'id', 'id');  // array<array-key, mixed>
    +Arr::pluck($rows, 'id', 'id');  // array<string, string>

    Fixes

    • Support $schema = Schema::connection(...) in migrations, so columns declared through a variable-held builder (#1382).
    • Narrow Model::getAppends() and Model::getMutatedAttributes() to list<string>, so callers no longer have to re-assert the element type (#1381).
    • Type Pipeline::then() from its destination closure instead of leaving it mixed (#1376).

    Full Changelog: v3.15.5...v3.15.6

    Open source →
    Release notes

    v3.15.6

    Compare

    Choose a tag to compare

    Open source →
  58. v3.15.5 18 Aug 2026
    Release notes

    Taint precision and Eloquent type narrowing. Two taint fixes remove a duplicate finding and close a missed SQL injection, and four type fixes sharpen inference on facades, collections, and the query builder.

    Fixes

    • Restore plugin facade stub precedence over Laravel's generated @method tags, so templated facade methods resolve their real return type (#1370)
     $value = Cache::remember('key', 60, fn (): User => User::first());
    -// mixed — the generated @method tag outranked the plugin's stub
    +// User
    • Narrow groupBy() and keyBy() keys for model columns instead of widening to array-key (#1369)
     $byId = User::all()->keyBy('id');
    -// Collection<array-key, User>
    +// Collection<int, User>
    • Type the chunkById() callback like chunk(), so the chunk is a templated collection rather than mixed (#1373)
     Article::query()->chunkById(100, function ($chunk) { ... });
    -// $chunk: mixed
    +// $chunk: Collection<int, Article>
    • Accept Laravel 13's fourth $fetchUsing argument on DB::select(), DB::selectResultSets(), DB::cursor(), and their Connection counterparts, while keeping the three-argument signature an error on Laravel 12 (#1374)
     DB::cursor('select * from users', [], true, [PDO::FETCH_ASSOC]);
    -// TooManyArguments — the stub declared three parameters
    +// accepted on Laravel 13; still TooManyArguments on Laravel 12

    Row types follow the fetch mode, so a custom $fetchUsing no longer claims stdClass:

     $rows = DB::cursor('select 1');                              // Generator<int, stdClass>
     $assoc = DB::cursor('select 1', [], true, [PDO::FETCH_ASSOC]);
    -// Generator<int, stdClass>
    +// Generator<int, mixed>
    • 🛡️ Report a tainted view name as TaintedInclude only, not also TaintedFile (#1358). A view name selects which template executes; it cannot reach an arbitrary path, because the view finder rewrites . to / and appends a fixed extension
    • 🛡️ Key WhereColumnTaintHandler's removal bridge on the AST node itself rather than spl_object_id, closing a missed SQL injection (#1366). Psalm frees foreign ASTs mid-file, so a reissued object handle could hit a stale record and strip sql taint from an unrelated expression
    • Ship the Psalm cache in the generated CI template, pass both thread flags, and install igbinary (#1347)

    Full Changelog: v3.15.4...v3.15.5

    Open source →
    Release notes

    v3.15.5

    Compare

    Choose a tag to compare

    Open source →
  59. v3.15.4 16 Aug 2026
    Release notes

    Extends taint reporting to the call forms Laravel applications actually write — facade statics, response() on its contract, and view() names — and clears the false positives that surfaced alongside it.

    Features

    • 🛡️ Report taint sinks on facade static calls, response() contract methods, and view() names (#1318). A facade's surface is @method pseudo-methods resolved through __callStatic, whose parameters have no docblock to carry a sink, so the static form was silent while the chained form fired.
     Redirect::to($request->input('next'));
    -// silent: the facade pseudo-parameter carried no sink
    +// TaintedHeader: Detected tainted header
    • Detect undefined relations in model eager-load defaults $with and $withCount (#1321). Supports dotted paths, column selectors, and the $withCount alias grammar; defers when an intermediate related model cannot be resolved.
     class Post extends Model
     {
         protected $with = ['auther'];
    -    // silent: eager-load defaults were never validated
    +    // UndefinedModelRelation: relation 'auther' is not defined on Post
     }

    Fixes

    • Honour @psalm-taint-escape on closure validation rules (#1352), in inline validate() arrays, in FormRequest rules(), and when the closure is the field's whole rule. Previously the only way to assert a rule made a value safe was to extract it into a dedicated Rule class.
     $request->validate([
         'path' => ['required', /** @psalm-taint-escape file */ static fn ($attr, $value, $fail) => /* ... */],
     ]);
    -// TaintedFile: a closure body is opaque, so no rule could assert safety
    +// clean
    • Fix TaintedSql false positives on where() array values for nullable, template-bounded, and intersection builder receivers (#1338, #1350). Values in the map form are PDO-bound, but a receiver typed Builder|null, @template T of Builder, or T&Builder declined the strip.
     /** @param Builder|null $query */
     $query->where(['status' => $request->input('status')]);
    -// TaintedSql: Detected tainted SQL
    +// clean
    • Stop reporting TaintedFile on uploaded-file extensions (#1324, #1325). getClientOriginalExtension() is the tail after the final dot of a normalized basename and cannot introduce a path segment, and clientExtension() returns a value from Symfony's MIME registry rather than raw client input. All other taint kinds, including include, are unchanged.
     Storage::putFileAs('uploads', $file, Str::ulid() . '.' . $file->getClientOriginalExtension());
    -// TaintedFile: Detected tainted file handling
    +// clean
    • Suppress cross-class taint flow through the Dispatchable traits (#1334). Psalm conflated taint nodes from the shared trait bodies, so an argument dispatched to one job appeared to reach an unrelated job's constructor sink. Genuine Bus and Event taint is still reported.

    • Fingerprint the migration schema cache on file contents instead of modification times (#1346). A git clone stamps every file with the checkout time, so the fingerprint changed on every CI run and the cache never hit even when the restored schema was still valid.

    Full Changelog: v3.15.3...v3.15.4

    Open source →
    Release notes

    v3.15.4

    Compare

    Choose a tag to compare

    Open source →
  60. v3.15.3 25 Jul 2026
    Release notes

    Taint-analysis precision for static where()/whereNot() calls and redirect responses (Psalm 6 line), ported from the paired v4.15.3 release.

    Fixes

    1. Fix TaintedSql false positives and a false negative on where()/whereNot() static receivers and nested-condition array values (#1300).
     class ConcreteOverrideModel extends Model {
         /** @psalm-taint-sink sql $column */
         public static function where(mixed $column): void {}
     }
    
     $term = (string) $request->input('term');
    -ConcreteOverrideModel::where([['name', '=', $term]]); // silent (false negative, own sink stripped)
    +ConcreteOverrideModel::where([['name', '=', $term]]); // TaintedSql
    
     Article::where([['name', '=', $request->keyword]]);
    -// TaintedSql (false positive) -- mixed value failed the outer position's scalar gate
    +// clean -- inner nested-condition value strips unconditionally on a real Model receiver
    1. Fix TaintedSSRF false positive on redirect() and Redirect/ResponseFactory redirect responses. A redirect is a header/open-redirect sink, not a server-side request, so it now reports the correct sink kind (#1313).
    -return redirect($request->input('next')); // TaintedSSRF (wrong sink kind)
    +return redirect($request->input('next')); // TaintedHeader

    Full Changelog: v3.15.2...v3.15.3

    Open source →
    Release notes

    v3.15.3

    Compare

    Choose a tag to compare

    Open source →

Every package, every release, already written down.

The archive is open and free. Watching your own project is what we are building next.

Browse the archive