packagist psalm/plugin-laravel v4.17.0

latest release: v3.17.0
5 hours ago

Two opt-in rules that catch typos in route names and filesystem disks, plus sharper inference for Eloquent relations, aggregate attributes, when()/unless() callbacks, and Collection::filter(). How to enable them in your psalm.xml:

<pluginClass class="Psalm\LaravelPlugin\Plugin">
    <findMissingViews value="true" />
    <findUnregisteredRouteNames value="true" />
</pluginClass>

or enable experimental features:

<pluginClass class="Psalm\LaravelPlugin\Plugin">
    <experimental value="true" />
</pluginClass>

Features

  • Add opt-in UnregisteredRouteName rule for route names missing from the booted app's route table: route(), to_route(), URL::route()/signedRoute()/temporarySignedRoute(), Redirect::route(), redirect()->route(), url()->route(). Enable with <findUnregisteredRouteNames value="true" /> or <experimental value="true" /> (#1573)
 return redirect()->route('users.shwo', $user);
+// UnregisteredRouteName: Route name 'users.shwo' is not registered
  • Add opt-in UnconfiguredFilesystemDisk rule for Storage::disk() names absent from filesystems.disks, with a closest-name suggestion. Enable with <findUnconfiguredFilesystemDisks value="true" /> or <experimental value="true" />. \Storage::disk() now also narrows to FilesystemAdapter like the facade (#1571)
 Storage::disk('publik')->put($path, $contents);
+// UnconfiguredFilesystemDisk: Disk 'publik' is not configured in filesystems.disks, did you mean 'public'?
  • Type when()/unless() callback parameters from the condition value: when() passes the truthy value, unless() the falsy one, and a Closure condition passes its return type (#1645)
 /** @var ?int $limit */
 $query->when($limit, function (Builder $q, string $limit) { /* ... */ });
-// no issue: the callback parameter was never checked
+// InvalidArgument: when expects callable(Builder<...>, int):mixed|null, but Closure(Builder, string):void provided
  • Infer relation generics for relation methods declared in a trait or delegating to another relation method (return $this->allRevisions()->where(...)) (#1646)
 // HasPaymentSlips trait: public function paymentSlips(): MorphMany { return $this->morphMany(PaymentSlip::class, 'owner'); }
 $member->paymentSlips();
-// MorphMany<Model, Model>
+// MorphMany<PaymentSlip, Member>
  • Narrow Collection::filter(callable) like where(callable), and recognize is_subclass_of(), is_a(), is_scalar(), is_iterable(), is_resource() and null checks (!is_null($x), $x !== null) in both (#1651)
 $items->filter(fn (Countable $x): bool => $x instanceof Stringable);
-// Collection<int, Countable>
+// Collection<int, Countable&Stringable>
 $classes->filter(fn (string $c): bool => is_subclass_of($c, Job::class));
-// Collection<int, string>
+// Collection<int, class-string<Job>>

Fixes

  • Type {relation}_count / {relation}_exists as nullable unless withCount()/withExists()/loadCount()/loadExists() is proven on that model (or the relation is in $withCount), and type withMin/withMax/withSum/withAvg attributes from the related column's type (#1628)
 $post->comments_count ?? $post->loadCount('comments')->comments_count;
-// RedundantCondition: comments_count was always int
+// no issue: int|null until a count is proven loaded
  • Mark Model as @psalm-consistent-constructor, so new $class() on a class-string<Model> is no longer UnsafeInstantiation. A model overriding __construct with an incompatible signature is now reported, since Eloquent's own new static($attributes) already breaks on it (#1626)
 /** @param class-string<Model> $class */
 function make(string $class): Model { return new $class(); }
-// UnsafeInstantiation
+// no issue
  • Accept any Collection in BelongsToMany::sync(), syncWithoutDetaching(), syncWithPivotValues() and their Laravel 13 *OrFail() variants (#1644)
 $user->roles()->sync($roles->pluck('id'));
-// InvalidArgument / ImplicitToStringCast
+// no issue
  • Keep $this as the declaring model for relation calls inside non-final models, so return $this->rel()->where(...) no longer reports MoreSpecificReturnType / LessSpecificReturnStatement (#1629, #1632)
  • Resolve scopes and SoftDeletes methods per receiver model on a generic custom builder shared by a parent and child model, instead of whichever model was scanned last (#1638)
  • Drop @psalm-mutation-free from Request::input(), which lazily decodes JSON: a FormRequest overriding input() is no longer flagged (#1627)
  • Stop analysis-time type checks from autoloading analyzed classes, which crashed the run or silently disabled the plugin when a vendor file raised a load-time deprecation (#1661)
  • Fix a crash on first-class callables ($request->input(...), Artisan::command(...), Job::dispatch(...)) with zend.assertions=1 (#1659)

Full Changelog: v4.16.6...v4.17.0

Don't miss a new plugin-laravel release

NewReleases is sending notifications on new releases.