lacodix/laravel-global-or-scope
Add global scopes to Eloquent models combined with OR conditions, so multiple scopes are wrapped in a single or group. Disable some or all or-scopes on the fly with withoutGlobalOrScopes for flexible queries.
Architecture fit. Eloquent composes global scopes with AND only; there is no first-class way to express "scope A or scope B" at the model level. This package fills that gap without patching the ORM: it registers a single composite OrScope that implements the native Illuminate\Database\Eloquent\Scope contract and internally applies N scopes inside a grouped orWhere. Integration is through public extension points (booting(), addGlobalScope, builder methods), so the architectural blast radius is genuinely small — one trait per model, no config, no migrations, no service provider assets.
Integration feasibility. composer require, no publish step, no config file. Requires PHP 8.1+ and Laravel 9+; releases track framework majors (v1.1.0 → Laravel 11, v1.2.0 → Laravel 12). Time-to-first-working-query is well under an hour for any Laravel developer. Surface area is four concepts: the GlobalOrScope trait, addGlobalOrScope(s), withGlobalOrScopes / withoutGlobalOrScope(s), and the raw OrScope class for composition.
Technical risk.
booting() or before parent::boot(). Register late and the model silently returns unfiltered data — a silent data-exposure class of bug, not a crash. This is the single biggest concern if any scope is a tenancy or visibility filter.removedOrScopes(), withoutGlobalOrScopes() and the static clearGlobalOrScopes() imply per-query and per-process bookkeeping. Long-lived workers (Octane, Swoole, queue daemons) plus static model state is a known leakage pattern and needs explicit verification.OR group is strictly broader than AND. A mass update()/delete() on a builder carrying an or-scope can affect far more rows than a reviewer expects. Needs a policy, not just a code review.lacodix.de. No downstream canary; no community validation of edge cases.whereHas, soft deletes, nested scopes), not assumed.Key questions for the maintainer / spike.
withoutGlobalOrScope(Scope1::class) correctly re-group the remaining branches, or does it flatten the parentheses?chunkById, paginator count queries, clone() of a modified builder?update(), delete(), restore(), and relation queries, or only read SELECTs?clearGlobalOrScopes() request-scoped or process-scoped? Is it Octane-safe?Stack fit. Adopt only where an access rule is genuinely disjunctive — "created by me or public or shared with my team." If every scope in the model is conjunctive, the package adds a dependency for zero behavior change.
Encapsulation. Apply the trait to a single base model or a dedicated concern, and wrap the policy in one named scope class (e.g. PostVisibilityScope composing OwnedScope and PublicScope). All call sites then go through our own abstraction, not the vendor API. Exit cost becomes "swap one class," which matters given the vendor profile.
Build vs. buy. The equivalent behavior is roughly 50 lines against the public Scope contract. The package buys time and existing tests; the vendor risk is modest but so is the asset. Decision rule: use it and pin the version if the need is read-path and non-critical; implement in-house if the OR group participates in tenancy or authorization isolation, where fail-open and third-party bugs are unacceptable.
Compatibility. Confirm the package constraint against our exact Laravel minor, not just the major. If we're mid-upgrade, run a CI matrix across the Laravel versions we actually support. PHP 8.1+ is almost certainly already satisfied.
Sequencing.
toRawSql() for representative queries and diff against hand-written SQL.composer.json and keep it in the dependency review loop.Maintenance. One maintainer, small release cadence, releases tracking framework majors with some lag. No SLA, no commercial backing, 0 dependents means no ecosystem pressure to fix regressions. Budget for the realistic case where we fork or vendor the code at some point; it is small enough that this is a viable fallback.
Support. GitHub issues, security reports to a personal address. Documentation lives on the maintainer's own site — mirror the usage patterns we depend on into our own ADR/runbook so a docs outage or site change doesn't strand the team.
Scaling. Zero runtime overhead — this is query construction only. The real scaling risk is in the database: OR predicates across different columns routinely defeat single-column indexes, turning a scoped read into a sequential scan on large tables. Require an EXPLAIN review against production-shaped data before rollout on any high-volume table; if the plan degrades, consider a composite/partial index or a denormalized visibility column instead of an OR group.
Failure modes.
update/delete from an OR group on a write path.withoutGlobalOrScope() is keyed by class across multiple models.Ramp-up. Near-zero for a mid-level Laravel developer — the README is sufficient to be productive in an hour. The ramp that actually matters is review discipline: reviewers must recognize an or-scope as a broadening change, demand a SQL assertion test, and demand an index check. That cultural cost is larger than the technical onboarding cost and should be written into the adoption note.
Overall recommendation. A well-scoped, honestly-architected micro-package that solves a real Eloquent limitation. Viable for a narrow, read-path, non-authorization-critical use case on a pinned version with our own wrapper and SQL-level tests. Not appropriate as the sole mechanism for tenancy isolation or access control, where its fail-open characteristics and single-maintainer support profile are disqualifying.
How can I help you explore Laravel packages today?