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

Laravel Local Class Scope Laravel Package

mpyw/laravel-local-class-scope

A tiny Laravel macro that lets you reuse global scope classes as local query scopes. Call User::scoped(ActiveScope::class) or pass constructor arguments, chain scopes, and optionally wrap them in model methods or traits. Includes a PHPStan extension for static analysis.

View on GitHub
Deep Wiki
Context7

Product Decisions This Supports

  • Adopt vs. build vs. defer (a micro-adoption, not a platform bet). The entire capability is one MIT-licensed macro that registers Model::scoped(...) on Eloquent builders. There is no infrastructure, no schema change, no vendor contract, and no data migration. That means the PM's real decision is narrow and reversible: do we want this idiom in our codebase? An internal equivalent is a few dozen lines, so "build" is viable; "adopt" wins only if the team wants the pattern without owning it. The exit ramp (remove the package, inline or wrap call sites) is cheap, which is exactly the profile that justifies a low-ceremony trial rather than a formal architecture review.

  • Turning business rules into reviewable, named objects. The product value is not new query capability — the same SQL is expressible today with closures, scopeX() methods, or global scopes. The value is that recurring predicates (active/published visibility, tenant or ownership constraints, plan entitlements, date windows, lifecycle states) become named, parameterized, injectable classes. For a PM this translates into concrete lever movement:

    • Defect reduction — one definition of "active" instead of five drifting copies in controllers, API list endpoints, admin screens, exports, and test fixtures.
    • Faster, safer change — a pricing or eligibility rule changes in one place, not in a grep-and-pray sweep.
    • Cheaper review — a reviewer sees ->scoped(ActiveScope::class) and knows the intent, instead of re-deriving ->where('active', true)->whereNull('deleted_at').
    • Testability — scope output and chaining become directly unit-testable artifacts.
  • Consistency across surfaces, which is a user-facing concern. Divergence in filtering logic shows up as user-visible bugs: a record visible in the app but missing from the export, an admin count that disagrees with a list view, a tenant boundary enforced on one endpoint and not another. Standardizing predicates as shared scope classes is a correctness and trust play as much as a code-style play — the sort of thing that shows up as support tickets long before it shows up as a P1.

  • Forcing useful decisions rather than deferring them. Adoption makes the team answer questions it has been silently answering by accident: where scope classes live (app/Scopes/), whether scopes take constructor arguments, whether to expose friendly scopeActive() wrappers or share via traits, and how chained-scope precedence is documented. Those are decisions worth making explicitly, and the package is a low-cost forcing function for them.

  • Roadmap risk management via standards alignment. The pattern is directly inspired by Laravel's own open proposal for class-based local query scopes (laravel/ideas#636). If the framework ships a native equivalent, migration is largely mechanical: swap the macro call for the native API. That asymmetric upside — cheap to adopt now, cheap to migrate later — is the strongest strategic argument in the package's favor, and it should be stated as reduced future rework, not as a differentiator.

  • Pilot go/no-go as the actual decision on the table. The recommendation this analysis supports is a time-boxed pilot in one non-critical domain, not a repo-wide mandate. Concrete success criteria a PM can hold people to: measurable duplication removed, zero new static-analysis suppressions, zero test regressions, and engineers preferring the class form over closures after a sprint. A reasonable kill criterion: if fewer than ~5 predicates are genuinely shared across two or more models, treat this as a style preference and decline — it is not buying you anything.

When to Consider This Package

Adopt (via pilot) when:

  • The product runs Laravel 11–14 on PHP 8.2+. This is a hard filter — check it first, because the older releases carry outdated dependency constraints and are not the thing you want to bet on.
  • The same predicate is actually duplicated in three or more places — models, controllers/services, API list endpoints, admin filters, exports, or test fixtures. The pilot only has meaning where duplication is real and observable, not hypothesized.
  • You already maintain global Scope classes you'd like to reuse locally, rather than authoring new classes specifically to feed the macro.
  • Engineers value class-based scopes over anonymous closures for readability, parameterization, IDE navigation, and static analysis — including the bundled PHPStan extension for the scoped method, which strengthens an existing analysis gate rather than weakening it.
  • The organization can pin a version, review a small macro, and vendor or fork under MIT if the single maintainer goes quiet. That is a realistic hedge for a package this small.

Look elsewhere, defer, or explicitly decline when:

  • The stack is Laravel ≤10 or PHP ≤8.1 with no funded upgrade path. Blocker, not a trade-off.
  • Scopes are mostly one-off or built dynamically from request payloads. The abstraction cost then exceeds the benefit, and you get a class per endpoint.
  • Procurement or security policy requires adoption evidence or a supported maintainer. 23 stars, zero dependents, and a single maintainer are genuine risk signals that must be documented, not waved through.
  • Closure-based local scopes or global scopes already meet the need and the team does not want a second idiom in the codebase. Two ways to filter is worse than one imperfect way.
  • Candidate scopes have side effects or global-application assumptions — remove() calls against other scopes, an assumption of single invocation, dependence on model-boot timing. These are written to be applied globally and can behave surprisingly when applied locally and repeatedly. This is the highest-risk technical category and deserves explicit review before exposure.
  • A global scoped macro collision is plausible — another package today, or a future framework method of the same name. That is an ambiguous-call upgrade hazard worth naming up front.
  • The team expects Laravel to ship native local class scopes imminently and prefers to wait. Since migration would be mechanical either way, waiting is cheap and defensible.

Verify before committing to a plan. Confirm the future-dated 2026-06-13 release timestamp against the live repository and read the intervening changelogs. The visible v2.2.0 entry is a version bump with no functional content — that could mean deliberate stability or low investment, and those imply very different risk postures. Know which one you are relying on before you write it into a roadmap.

How to Pitch It (Stakeholders)

For executives. This is a small developer-productivity utility, not a platform investment. It lets engineers package recurring database filters — "active users," "tenants on this plan," "age over 18," "published content" — as reusable, named classes instead of copy-pasted query code. The business payoff is consistency: fewer cases where the app, an admin screen, and an export disagree about what "active" means, and one place to change when a rule changes. It is MIT-licensed, installs with a single command, adds no infrastructure, requires no data migration, and can be removed cheaply. Adoption is currently unproven (23 stars, no known dependents, a single maintainer), so the ask is deliberately modest: a time-boxed pilot in one product area with explicit success measures — duplication removed, engineer feedback, no test or static-analysis regressions — followed by a clear standardize-or-stop decision. It also aligns with a direction Laravel has publicly discussed, which caps the downside by making future migration mechanical.

For engineering. Install with composer require mpyw/laravel-local-class-scope, pin the version, and pilot on a single model that already has duplicated filtering. Extract the predicate into App\Scopes\ActiveScope implementing Laravel's Scope contract, qualify columns with $model->qualifyColumn(...) so the scope survives joins, then replace inline clauses with Model::scoped(ActiveScope::class) or parameterized forms such as Model::scoped(AgeScope::class, '>', 18). Use the class-string form for scalar parameters and the new Scope(...) form when a scope needs a dependency that a spread can't express — a clock, a config object, a value object. Chain scopes freely, and expose familiar scopeActive() wrappers or traits where the team wants a friendly API. Before exposing any scope, review it for local-use safety: no remove() calls, no assumption of single invocation, no dependence on model-boot timing. Enable the bundled PHPStan extension before Larastan's in phpstan.neon so analysis covers scoped without suppressions. Cover scope output and chaining with tests, document precedence, and backfill only where a predicate appears three or more times — resist a repo-wide sweep, because a sweep maximizes the surface you'd later have to unwind. If the native Laravel proposal lands, migrate; the change should be mechanical.

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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings