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

Http Precondition Bundle Laravel Package

guhemama/http-precondition-bundle

Symfony bundle for handling HTTP precondition headers such as If-Match, If-None-Match, If-Modified-Since, and If-Unmodified-Since. Helps controllers return correct 304 or 412 responses and enforce optimistic concurrency without repetitive header logic.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check first: this is a Symfony HttpKernel bundle, not a Laravel package. As a Laravel developer the practical value is split two ways: (1) if you maintain a Symfony/standalone-HttpKernel service, use it directly; (2) if you're evaluating it for a Laravel app, treat it as a reference implementation of RFC 7232 preconditions and port the ~60 lines into middleware. The good news is that port is nearly free, because Illuminate\Http\Request and Illuminate\Http\Response both extend their Symfony counterparts — setEtag(), setLastModified(), and $request->isNotModified($response) already exist in your app today.

Install and register (Symfony side). Note the 1.0.1 rename: the bundle class now follows Symfony's vendor-prefixed convention:

composer require guhemama/http-precondition-bundle
// config/bundles.php
return [
    // ...
    Guhemama\HttpPreconditionBundle\GuhemamaHttpPreconditionBundle::class => ['all' => true],
];

If you were on 1.0.0, the class was registered without the vendor prefix (Guhemama\HttpPreconditionBundle\HttpPreconditionBundle). 1.0.1 also reworked the configuration tree, so any config/packages/*.yaml using the old root key will fail at container compile — re-read Configuration.php and adjust the key before upgrading.

Where to look first, in this order:

  1. README.md — the only real spec for a 2-star package; assume it's incomplete, and assume it lags the 1.0.1 config changes.
  2. src/DependencyInjection/Configuration.php — the config tree is the honest API surface, and it is exactly what 1.0.1 changed. Whatever you can't find in the README is defined here.
  3. The event subscriber/listener directory — this is where the precondition is actually evaluated (almost certainly on kernel.request or kernel.controller), and it tells you the exact HTTP semantics implemented.
  4. composer.json — check the symfony/http-kernel constraint before anything else.

First use case: a conditional GET on an expensive read endpoint. Your controller returns a resource plus a validator (ETag or Last-Modified); the bundle compares it against If-None-Match / If-Modified-Since and short-circuits to 304 Not Modified before your controller body runs. The mirror case is a conditional write: If-Match on a PUT/PATCH/DELETE, answering 412 Precondition Failed when the client's ETag is stale — optimistic concurrency for free.

The Laravel equivalent you can ship today, without the bundle:

// app/Http/Middleware/HandlePreconditions.php
public function handle(Request $request, Closure $next): Response
{
    $response = $next($request);

    if (! $response->getEtag() && ! $response->getLastModified()) {
        return $response; // nothing to validate against
    }

    if ($request->isMethodCacheable() && $request->isNotModified($response)) {
        return $response->setNotModified();
    }

    return $response;
}
// routes/api.php
Route::get('/reports/{report}', ReportController::class)
    ->middleware('precondition');

Implementation Patterns

Validators belong on the resource, not the response body. The dominant mistake with precondition packages is hashing the rendered body to produce an ETag. That's a full render plus a hash, and it defeats the point of short-circuiting. Derive the validator from cheap, monotonic state:

$response->setEtag(md5($report->updated_at->timestamp . ':' . $report->version));
$response->setLastModified($report->updated_at);
$response->setPublic();
$response->setMaxAge(0);
$response->headers->addCacheControlDirective('must-revalidate');

Split safe vs. unsafe semantics deliberately. These are two different features wearing one name:

Method Request header Failure response Purpose
GET/HEAD If-None-Match, If-Modified-Since 304 Bandwidth / render savings
PUT/PATCH/DELETE If-Match, If-Unmodified-Since 412 Lost-update prevention

Most implementations of this pattern drift toward only handling the 304 side. Verify in the listener whether strong comparison (If-Match) is implemented; if not, treat concurrent writes as out of scope and use a DB-level version column or updated_at guard instead.

Register the check as a route-level opt-in, not globally. Preconditions on an endpoint that returns Cache-Control: no-store (dashboards, per-user feeds) are pure overhead and risk leaking existence of resources. In Symfony, attach via route defaults/options; in Laravel, use a named middleware group so only read-heavy, cacheable, and idempotent-write routes get it.

Order against security. Preconditions must run after authentication/authorization, otherwise a 412/304 tells an unauthenticated caller that a resource exists and roughly what its version is. On kernel.controller you're safely after the firewall; on kernel.request you are not, unless the listener's priority places it there.

Pair with a shared cache only if you set the right headers. The bundle makes revalidation cheap; it does not make caching happen. Without Cache-Control: public, must-revalidate (or a no-cache directive), no intermediary will ever send If-None-Match and your 304 branch is dead code.

Testing pattern — the highest-signal tests for this feature are two-liners, and you should write them even if you don't write anything else:

$response = $this->get('/reports/1');
$etag = $response->headers->get('ETag');

$this->get('/reports/1', ['If-None-Match' => $etag])
     ->assertStatus(304);

$this->put('/reports/1', [], ['If-Match' => '"stale"'])
     ->assertStatus(412);

Integration with a full-page cache: if you run Symfony HttpCache or a reverse proxy in front, the precondition listener is largely redundant for GET — HttpCache already evaluates If-None-Match against stored responses. Use the bundle at the application layer only for resources you deliberately don't cache (personalized but still revalidatable) or for the write-side If-Match guards, which no cache handles for you.

Gotchas and Tips

  • 1.0.1 is a rename/config release, and the rename is breaking in practice. A patch bump that changes the bundle class FQCN means every config/bundles.php breaks, and it's easy to misread as harmless. Pin to 1.0.1 explicitly and diff your bundles.php and config/packages/ against the release before upgrading. The config tree rework means stale YAML silently becomes an unknown-key error at container compile, not at runtime — run bin/console cache:clear immediately after upgrading so you fail fast.
  • Maintenance risk is still the headline. 1.0.1 is housekeeping (naming convention + configuration), not a feature release; the underlying implementation is unchanged from the 2023 line. 2 stars, 0 dependents. Before building on it, check the symfony/http-kernel constraint in composer.json — if you're on Symfony 7 and the constraint stops at ^6.x, composer require will fail or force a downgrade. Budget for a fork.
  • No dependents means no security review. Precondition handling touches request headers that are fully attacker-controlled (If-Match, If-None-Match, If-Modified-Since are free-text on the wire). Read the comparison logic yourself; don't assume it validates header parsing.
  • Weak ETags and If-Match don't mix. W/"abc" is valid for If-None-Match but must fail strong comparison for If-Match. If the implementation uses loose == instead of a strong-comparison check, clients with proxies that downgrade ETags will get spurious 412s — and worse, a stale write can slip through.
  • If-None-Match: * means "return 304 if the resource exists at all." It's easy to miss and a common source of a 304 on a resource the client has never seen.
  • Date parsing. If-Modified-Since must be compared at second granularity, and any Last-Modified later than Date is invalid. Sub-second updated_at values cause flapping 304s.
  • 304 responses must have no body. Symfony strips it, but if you manually build the response you can break clients. Always use setNotModified().
  • Debugging with curl, not the browser. Browsers silently add If-None-Match and hide the 304; you'll chase ghosts. Use curl -v -H 'If-None-Match: "x"' https://… to see exactly which branch fires. If nothing fires, the cause is usually priority ordering or the listener not being registered for that route.
  • Extension points worth knowing: the validator/ETag resolution (if it's behind an interface, that's your seam for deriving ETags from domain objects rather than controllers) and the config tree (that's where you enable/disable per-route or set global defaults — the 1.0.1 rework makes this the fastest-moving surface). Both are small enough to override in your own subscriber rather than fork the bundle.
  • Porting to Laravel is usually the right call. The whole feature is one middleware plus setEtag()/setLastModified() on the response; you keep Laravel's middleware ordering, testing helpers, and route groups, and you drop a dormant third-party dependency whose last two releases were a 2023 fix and a naming cleanup. Port it, cite the README as the spec, and move on.
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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle