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

Precondition Laravel Package

onursimsek/precondition

Precondition is a Laravel package for declaring and checking preconditions before your application logic runs. Define requirements once, verify them automatically, and keep controllers and actions clean with reusable guard-style rules.

View on GitHub
Deep Wiki
Context7

Getting Started

Start with the README and then the src/ directory—the package is small, so the contract interface (usually something like Precondition with a passes()/response() pair) is the whole mental model. Everything else is glue.

composer require onursimsek/precondition
php artisan vendor:publish --tag=precondition-config   # if a config is shipped

First use case: guard a route with a single check you currently repeat in three controllers.

// app/Http/Preconditions/TeamIsActive.php
class TeamIsActive implements Precondition
{
    public function __construct(private Team $team) {}

    public function passes(Request $request): bool
    {
        return $this->team->is_active;
    }

    public function response(Request $request): Response
    {
        return response()->json(['message' => 'Team suspended'], 412);
    }
}

Attach it where the route is declared, not inside the controller—the value of the package is that the failure path is declared next to the route and never reaches your handler. Verify it once by hitting the route with a failing fixture and confirming you get the configured status (412 by default) and not a 500 from a downstream null.

Implementation Patterns

One precondition per concern. Resist the temptation to write CanAccessOrder that checks ownership, status, payment, and tenancy. Four small classes stay testable and reorderable; one fat class becomes the new god-controller.

Route-level composition for API endpoints. Preconditions read best as a declarative list on the route group, which makes the failure surface obvious in code review:

Route::get('/invoices/{invoice}', ShowInvoice::class)
    ->preconditions([OwnsInvoice::class, InvoiceIsFinalized::class]);

Optimistic locking for write endpoints. If the package exposes conditional-request support (ETag / If-Match), this is the killer day-to-day use: return an ETag on GET, require If-Match on PUT/PATCH, and let a stale client get a 412 instead of silently clobbering a colleague's edit. Pair it with Last-Modified/If-Modified-Since on read-heavy resources so repeat polls return 304 with no body.

Feature flags and maintenance gates. Preconditions are a clean home for "is this route enabled in this environment" logic that otherwise leaks into middleware stacks. Keep them in app/Http/Preconditions grouped by domain so the set doesn't sprawl.

Consistent error envelopes. Centralize the failing response in a base class or trait (ApiPrecondition) that emits your standard {message, code, errors} shape. Do not return raw strings from individual preconditions—that's how you end up with three JSON shapes across one API.

Test preconditions in isolation, then once through the route. Unit-test passes() with plain model factories—no HTTP. Then write a single feature test per route asserting the 412 and the message; that is enough to catch wiring regressions without duplicating every unit case.

Gotchas and Tips

  • Pick 412, 403, or 404 deliberately. 412 ("precondition failed") is correct for state/version mismatches. For authorization use 403, and for "you shouldn't know this exists" use 404. Leaking a 412 on someone else's resource ID confirms the ID is real.
  • Evaluation order matters and is usually top-to-bottom, first failure wins. Put cheap checks (auth, flags) before expensive ones (DB lookups, external calls). If you need a specific status code from a specific check, verify ordering with a test rather than assuming.
  • Route model binding has already run by the time preconditions evaluate in most setups. A missing model returns 404 before your precondition ever fires—so don't try to use a precondition to produce a custom "not found" message.
  • Constructor injection works, but resolve per-request state from $request, not from singletons. Anything bound as a singleton will serve stale data across Octane workers.
  • Cache the result per request. If several preconditions touch the same aggregate, memoize on the instance; without it you'll see the same query three times in the debugbar.
  • Run php artisan config:cache in CI, not just locally. A published config referencing env() at the top level silently returns null in production—this is the single most common "works on my machine" bug with small packages.
  • ETags must be quoted and strong unless prefixed with W/. ETag: abc123 is invalid; ETag: "abc123" is not. Compare with ===, not ==, and regenerate the ETag whenever the representation changes—not on every request.
  • 304 responses must have no body. Laravel will strip it if you use the framework's response helpers, but a hand-rolled response('', 304) with headers set after the fact can leak content through middleware that appends to the body.
  • Debug a failing precondition by logging passes() inputs, not by dumping the whole request. The usual culprit is a stale relation ($team->is_active on a cached model) rather than logic.
  • Extension points: bind your own implementation of the precondition contract in a service provider if you want a shared base (logging, metrics, standard error shape) without editing vendor files. Never modify vendor/—this package is small enough that a fork is tempting, but a container binding plus a base class covers 90% of customization.
  • Watch the release cadence. The project is young and low-star; pin the version in composer.json and keep a short integration test around the one behavior you depend on so an upgrade that changes default status codes or ordering fails loudly in CI.
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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
cgv/localized-resource