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

Plugin Laravel Laravel Package

psalm/plugin-laravel

Laravel static analysis with built-in taint-based security scanning: traces user input from request to sink to catch SQL injection, XSS, shell injection, file traversal, SSRF, open redirects and timing-unsafe secret comparisons. Runs locally and in CI, no account or cloud upload.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture Fit

  • Laravel-Specific Static Analysis: The core approach is unchanged — deep stubs for Eloquent, the query builder, facades, and the container — but v3.17.0 is the release where the plugin starts reading runtime application metadata, not just static stubs. The two new rules resolve against the booted app's route table and filesystems.disks config. That is an architectural shift worth naming explicitly: analysis now depends on a bootable application and its configuration under CI, not only on source code.
  • Stance on New Rules: Both new rules are opt-in and off by default. Default behavior on upgrade is materially quieter than v3.16.6 — no repeat of the forced "new findings on unchanged code" dynamic for the security surface.
  • Eloquent Fidelity: Continued, meaningful improvement. Relation generics now resolve through traits and relation-delegating methods ($member->paymentSlips() → MorphMany<PaymentSlip, Member>), {relation}_count/{relation}_exists are correctly nullable until a load is proven, withMin/Max/Sum/Avg are typed from the related column, and Model is marked @psalm-consistent-constructor. This closes a long-standing class of false positives on fluent relation code.
  • Taint-Based Security: No change in this release. The v3.16.6 vendor-stub fix (restoring taint flow through Events\Dispatcher, Http\Client\PendingRequest, FormRequest, Pipeline, facades) stands and must be verified as still effective, not assumed. Treat that verification as a regression check on this upgrade.
  • Rule Design Quality: The two new rules are typos-in-identifiers checks — a high signal-to-noise category. UnconfiguredFilesystemDisk even ships a closest-name suggestion. This is the cheapest class of bug to catch and the least controversial to gate on, which makes these rules easier to adopt than a strictness bump.

Integration Feasibility

  • Setup: Unchanged — composer require plus psalm-laravel init.
  • New Configuration Surface (new): Three ways to enable the rules:
    • <findUnregisteredRouteNames value="true" />
    • <findUnconfiguredFilesystemDisks value="true" />
    • <experimental value="true" /> (umbrella; enables both, and any future experimental rule) Prefer the explicit flags. The umbrella flag is a forward-compatibility liability — it silently opts the project into rules shipped in later patch releases.
  • New Analysis Precondition (important): UnregisteredRouteName requires the booted app's route table; UnconfiguredFilesystemDisk requires filesystems.disks to be loaded. In CI this means the analysis step must have enough environment/config to boot the app and resolve config — route caching, APP_KEY, DB connections during boot, and env-specific config all become analysis-time concerns.
  • Behavioral Change Without Config Change (reduced but nonzero): Several fixes change inference with no opt-in: when()/unless() callback parameters are now type-checked, Collection::filter(callable) narrows like where(), {relation}_count becomes nullable, and Model's consistent-constructor marker is applied. Expect a mixed delta — new errors in some places, disappearing errors in others.
  • Psalm 7.x Dependency: Unchanged — still the dominant integration risk; still pin a known-good 7.x.

Technical Risk

  • Config-Dependent False Positives (new, primary risk): Route names and disk names are resolved from configuration, not source. Any of the following will produce findings that are wrong in CI but right in production:
    • Routes registered by packages, tenancy middleware, or route files conditionally loaded per environment.
    • Disks assembled from env vars or dynamic config (config(['filesystems.disks.x' => ...])).
    • Route caching (route:cache) or a boot path that skips service providers in the analysis environment. This is the classic static-analysis failure mode: the tool is correct about the configuration it saw, and the configuration it saw is not the one that ships.
  • New Findings From Non-Opt-In Inference (moderate):
    • when($limit, function (Builder $q, string $limit) {...}) → InvalidArgument where the callback parameter was previously unchecked. Common in real codebases; expect a cluster here.
    • {relation}_count now nullable → PossiblyNullOperand / null-safety findings on code that assumed int without a proven loadCount().
    • Model consistent-constructor → models overriding __construct with an incompatible signature are now reported.
    • Collection::filter(callable) narrowing → downstream MoreSpecificReturnType / LessSpecificReturnStatement in typed helper signatures.
  • Findings That Disappear (baseline bloat, inverse direction): UnsafeInstantiation on new $class() for class-string<Model>, InvalidArgument/ImplicitToStringCast on sync($collection->pluck(...)), RedundantCondition on {relation}_count, and MoreSpecificReturnType on return $this->rel()->where(...) in non-final models all become unnecessary. Un-pruned baseline entries are now silent dead weight masking future regressions.
  • Stability Fixes (risk-reducing): Two crash classes are fixed — analysis-time type checks no longer autoload analyzed classes (which previously crashed the run or silently disabled the plugin when a vendor file raised a load-time deprecation), and first-class callables ($request->input(...), Artisan::command(...), Job::dispatch(...)) no longer crash with zend.assertions=1. The silent-disable bug is the significant one: any project that saw mysteriously empty results on a prior run may have been running with the plugin effectively off. Re-verify prior "clean" runs on those projects.
  • Version Constraint: Still unresolved from the prior assessment (^4.8 referenced vs. 3.x tags). Verify before bumping; this release does not change that.

Key Questions

  1. Configuration Fidelity in CI
    • Can the CI analysis job boot the app with a config that matches production for routes and filesystems? If routes are tenanted, package-registered, or env-conditional, UnregisteredRouteName will generate false positives that erode trust in the whole run.
    • Is route:cache present in the pipeline before analysis, and does that change the resolved route table?
  2. Which Flag, and Scope of <experimental>
    • Adopt the two explicit flags per-rule, or the umbrella? Recommend explicit; document the decision so a future patch cannot silently add rules.
  3. Triage Follow-Through From v3.16.6
    • Was the v3.16.6 delta actually triaged, or was the baseline regenerated? If the latter, the security surface is still blind and this release's noise reduction will make that harder to notice.
    • Has the v3.16.6 taint behavior been re-confirmed on this version (regression check, not assumption)?
  4. Delta Composition
    • How much of the upgrade-only delta is new errors (when/unless, nullable counts, consistent-constructor) versus removed errors (sync, UnsafeInstantiation)? Both numbers matter; only the first is work.
  5. Prior Silent-Disable Exposure
    • Did any prior run produce suspiciously few findings? The autoload crash fix means a previously "clean" baseline for that run may be invalid.
  6. Runtime Cost of App Boot
    • What is the CI wall-clock delta from booting the application for two opt-in rules, on top of the v3.16.6 data-flow increase? Measure; do not assume.

Integration Approach

Stack Fit

  • Laravel Monoliths with Rich Route Surfaces: Best fit for UnregisteredRouteName — dozens to hundreds of named routes, heavy redirect()->route() and to_route() usage, frequent renames. This is where typos actually reach production.
  • Multi-Disk / Media-Heavy Applications: Best fit for UnconfiguredFilesystemDisk; the closest-name suggestion turns a silent runtime exception into a lint error.
  • Multi-Tenant / Env-Conditional Config: Weakest fit. Dynamic disks and per-tenant route registration are the primary false-positive source. Enabling these rules here requires an analysis config that deliberately enumerates all valid names.
  • API-Only Services: Modest benefit. Few named routes, often one disk. The Eloquent inference fixes still apply; the new rules may not be worth the config-fidelity work.
  • Hybrid Tooling: Unchanged — complements Larastan, PHPStan, and runtime scanning. Runtime scanning remains the backstop wherever config-dependent analysis is unreliable.

Migration Path

  1. Verify the version constraint against published 3.x tags before bumping (carried forward, still open).
  2. Upgrade with both new rules disabled and the current baseline intact. Capture the inference-only delta:
    ./vendor/bin/psalm --no-cache --output-format=github
    
    This isolates the changes that are not opt-in — the when()/unless() checks, nullable relation counts, consistent-constructor, filter() narrowing.
  3. Prune obsolete baseline entries for the now-fixed shapes (UnsafeInstantiation, sync() argument errors, RedundantCondition on _count, non-final-model relation return types). This is the inverse of the v3.16.6 step and should be done deliberately, not by regeneration.
  4. Re-confirm the v3.16.6 taint surface still reports on a known-positive canary (a deliberately tainted Http:: call or FormRequest flow). This catches a silent regression or a re-run of the plugin-disabled bug.
  5. Enable findUnconfiguredFilesystemDisk first, on a branch, with the app bootable and config loaded. Lower false-positive risk than routes; validates that the boot path works.
  6. Enable findUnregisteredRouteNames second, after confirming route-table completeness (no tenancy, no env-conditional route files, or an analysis config that enumerates them).
  7. Triage the rule findings: real typo → fix immediately (highest value-per-minute of anything in this release); config-dependent false positive → narrow suppression or fix the analysis config; neither → documented suppression with a reason.
  8. Baseline the residual, then re-enable CI gating. Keep security-path gating from the prior assessment in place.

Compatibility

  • psalm.xml: Additive only. Two new optional flags; existing configuration, plugin registration, and taint source/sink config are untouched. No breaking change.
  • Newly Accurate Types to Expect:
    • \Storage::disk() → narrows to FilesystemAdapter (now matches the facade)
    • Relation methods via traits / delegated calls → concrete relation generics, e.g. MorphMany<PaymentSlip, Member>
    • Collection::filter(callable) → narrowed type, e.g. Collection<int, Countable&Stringable>, Collection<int, class-string<Job>>
    • {relation}_count / {relation}_exists → int|null / bool|null unless a load is proven
    • withMin/Max/Sum/Avg attributes → typed from the related column
    • when() callbacks → truthy value type; unless() → falsy value type; Closure condition → its return type
  • Baseline Entries Now Redundant: audit and remove — UnsafeInstantiation on new $class() for models, InvalidArgument/ImplicitToStringCast on BelongsToMany::sync() family, RedundantCondition on {relation}_count, MoreSpecificReturnType/LessSpecificReturnStatement on non-final relation delegation, and any suppression around Request::input() on a FormRequest (the @psalm-mutation-free marker was dropped).
  • Local Stubs / Suppressions: Audit around the changed surfaces. A suppression that was correct in v3.16.6 may now hide the newly narrowed type — particularly on relation methods and Storage::disk().

Sequencing

  1. Verify constraint → bump plugin with new rules off → record delta.
  2. Prune obsolete baseline entries for the fixed shapes; do not regenerate wholesale.
  3. Canary-check the v3.16.6 taint reporting still fires.
  4. Validate the app boots under CI analysis config; enable the filesystem-disk rule.
  5. Enable the route-name rule once route-table completeness is confirmed.
  6. Triage rule findings: typo → fix; config artifact → fix config or suppress narrowly.
  7. Baseline residual; restore gating; revisit --level 3 only after both deltas are absorbed.

Operational Impact

Maintenance

  • Configuration Ownership: The two new flags are now reviewed configuration, not defaults. Own them explicitly in psalm.xml, prefer named flags over <experimental>, and add a comment explaining why each is on or off.
  • Analysis-Environment Parity: A new ongoing obligation — the CI analysis environment's config must stay a faithful proxy for production's route table and disk list. This drifts silently when someone adds a tenant-aware route file or an env-derived disk. Add it to the same review checklist as the baseline.
  • Cadence: No major bump; v3.16.6 → v3.17.0 is a minor with opt-in features, which is consistent with the standing guidance that the next major is 4.x, not an imminent v3.x break.
  • Baseline Hygiene, Both Directions: v3.16.6 was about adding previously suppressed real findings. v3.17.0 requires removing entries that are now obsolete. A baseline that only grows is a baseline that hides regressions.
  • Suppression Debt: Continue auditing around Dispatcher, PendingRequest, Response, FormRequest, Pipeline, and facades — plus the new route/disk surfaces once enabled.

Support

  • New Failure Explanations to Document:
    • "The route name exists — we register it in a tenancy service provider." → analysis config gap, not a code bug.
    • "The disk is configured via an env var." → same class; enumerate disks for analysis or disable the rule on that path.
    • "This error went away." → expected for sync(), UnsafeInstantiation, and count nullability; confirm it was fixed, not newly hidden.
  • Stability Improvement: The autoload-deprecation fix means prior "the plugin produced nothing" incidents may have been the plugin silently disabled rather than a clean codebase. When a team reports a suspiciously empty run, that history is the first hypothesis.
  • Debugging: --no-cache and psalm.log remain first-line. For the new rules specifically, the diagnostic is what did the analysis-time app boot see — print the resolved route table / disk list in CI when chasing a disputed finding.
  • Community Resources: Unchanged — GitHub Discussions, sponsor support. Anchor issues for reproducing shapes: #1573 (route names), #1571 (disks), #1645 (when/unless), #1646 (trait relations), #1628 (counts), #1661 (autoload crash).

Scaling

  • Performance: Two effects. The v3.16.6 data-flow increase (facade/pipeline/HTTP-client-heavy code) persists; add a one-time app-boot cost for the opt-in rules. The boot cost is fixed overhead, likely small relative to analysis, but it must be measured — and it also means the CI job now needs a bootable environment (env vars, APP_KEY, possibly DB) that it may not have had.
  • Mitigations (unchanged): path filters to exclude tests/, --threads, GitHub Actions caching, igbinary, and rm -rf ~/.cache/psalm for memory on large codebases (>50K LOC).
  • Scale Ceiling Shift: Config-dependent rules on very large multi-tenant apps are where the rule will strain — the valid-name set becomes large and environment-specific, and false positives scale with the number of dynamic registrations.

Failure Modes

Failure Scenario Impact Mitigation
Route rule enabled against an analysis config missing package/tenant routes Flood of false positives; team disables the rule permanently and loses a genuinely useful check Validate route-table completeness first; enable on a branch; suppress narrowly per dynamic route family with a documented reason
Disk rule enabled where disks come from env vars Same — noise, then abandonment Enumerate disks for analysis, or scope the rule; don't blanket-disable
<experimental value="true" /> used as shorthand Silently opts into rules shipped in later patch releases Use the two explicit flags; treat <experimental> as a governance decision, not convenience
Baseline regenerated instead of pruned Obsolete entries mask future regressions; the "clean" state is fiction Prune the specific fixed shapes explicitly; require a rationale in the baseline commit
when()/unless() callback typing produces an unexpected cluster CI red on unchanged code Isolate the inference-only delta (step 2) before enabling any new rule; batch the fix
Prior run was silently plugin-disabled (autoload deprecation) Historical baselines for that run are invalid; blind spots assumed closed Canary-check taint reporting and a known-bad sample after upgrade
{relation}_count nullability breaks downstream arithmetic New null-safety findings Add explicit loadCount() where the count is actually required, or guard; this is usually a real latent bug
Wrong composer constraint (^4.8 vs 3.x) Unreproducible resolution Verify against published tags before bumping
App boot adds CI time or fails on missing env Slow or failing pipeline Measure the delta; provide a minimal analysis-specific env
Residual taint edge cases Security gaps remain Combine with runtime scanning (e.g., Snyk)

Ramp-Up

  • Onboarding Steps:
    1. Set Expectations, Revised: Unlike v3.16.6, this upgrade is not a step change in reported findings — the headline rules are off by default. Brief developers that the deltas come from two places: inference fixes (some errors appear, some disappear) and the two flags you choose to enable.
    2. Lead With the Easy Win: Demonstrate the typo rules with the changelog examples — redirect()->route('users.shwo', $user) and Storage::disk('publik'). These are the most intuitive findings the tool produces and the fastest way to build trust in it as a preventive tool rather than a compliance tax.
    3. Teach the Triage Split: Real typo → fix now; config-dependent artifact → fix the analysis config or suppress narrowly with a reason; obsolete baseline entry → remove it. Forbid blanket --set-baseline as a first response, and now also forbid blanket baseline regeneration.
    4. Explain the Disappearing Errors: A short note that sync($collection), new $class() on models, and {relation}_count no longer report — so nobody "fixes" a codebase that was already correct or removes a suppression that is still load-bearing elsewhere.
    5. Walk the Changed Surfaces: Use changelog examples as material — when() callback type checking, Collection::filter() narrowing (Countable&Stringable, class-string<Job>), trait-declared relation generics, Storage::disk() narrowing to FilesystemAdapter.
    6. CI Rollout: Upgrade with flags off → prune obsolete baseline entries → canary-check taint → validate app boot → enable disk rule → enable route rule → triage → baseline residual → gate.
    7. Confirm Prior Work Still Holds: The whereDate()/DateTimeInterface resolution and the v3.16.6 mixed/taint fix should both remain in force; noise returning in either area indicates a regression worth reporting upstream.
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.
andydefer/laravel-notification
adachsoft/cli-process-tester
jhaskell13/laraflow
sebastienheyd/boilerplate-media-manager
ccast/tagixo-primix
pretendtrue/laravel-builder
sawmainek/laravel-scout-tntsearch-driver
tomshaw/laravel-calendar-table
dibakar/laravel-ownership
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc