PHPackages                             langsys/laravel-request-query-cache - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. langsys/laravel-request-query-cache

ActiveLibrary[Database &amp; ORM](/categories/database)

langsys/laravel-request-query-cache
===================================

Per-request in-memory deduplication for Eloquent queries via firstCached() and getCached() macros.

1.0.0(today)00MITPHPPHP ^8.2

Since Jun 9Pushed todayCompare

[ Source](https://github.com/langsys/laravel-request-query-cache)[ Packagist](https://packagist.org/packages/langsys/laravel-request-query-cache)[ RSS](/packages/langsys-laravel-request-query-cache/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (4)Versions (2)Used By (0)

Laravel Request Query Cache
===========================

[](#laravel-request-query-cache)

Per-request, in-memory deduplication for Eloquent queries. Adds two query-builder macros — `firstCached()` and `getCached()` — that run a given query against the database **once per request** and serve every subsequent identical query from an in-memory store. The store is flushed automatically when the request ends, so results never leak across requests.

This is **not** a persistent cache (no Redis/file). It only dedupes identical queries (same SQL + same bindings) within a single request lifecycle.

Why would I want this?
----------------------

[](#why-would-i-want-this)

The single best use case is **a query you run to validate input that you then need again downstream.**

Validation rules and controllers naturally re-express the same query. A rule fetches a row to check it exists / is in the right state; then the controller (or service) fetches that same row to actually do the work. That's two identical round trips to the database for one logical lookup.

The usual workarounds are awkward: smuggle the already-fetched model out of the rule into the controller, or skip the rule and re-validate inline in the controller. With `firstCached()`/`getCached()` you don't have to. Both layers just write the natural query — identical SQL + bindings hit the database once, and the controller gets the row the rule already loaded.

The goal: **zero validation in the controller/service layer** — validation stays in the rule where it belongs, and the controller reuses the query for free.

### Example: a custom rule and a controller sharing one query

[](#example-a-custom-rule-and-a-controller-sharing-one-query)

A vanilla Laravel validation rule that runs a query:

```
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;

class PendingInvitation implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $invitation = UserInvitation::where('activation_token', $value)
            ->whereNull('redeemed_at')
            ->firstCached();

        if (! $invitation) {
            $fail('This invitation is invalid or has already been used.');
        }
    }
}
```

The controller validates, then reuses the **exact same query** — no second DB hit, no model smuggled out of the rule, no inline re-validation:

```
public function store(Request $request)
{
    $request->validate([
        'token' => ['required', new PendingInvitation],
    ]);

    // Identical SQL + bindings → served from the per-request cache.
    $invitation = UserInvitation::where('activation_token', $request->token)
        ->whereNull('redeemed_at')
        ->firstCached();

    $invitation->redeem($request->user());

    return response()->json($invitation);
}
```

The rule has already done the DB work; the controller's query resolves from the in-memory store. The only requirement is that both queries are identical — same `where`/`whereNull` clauses in the same order, so they produce the same SQL and bindings.

Installation
------------

[](#installation)

```
composer require langsys/laravel-request-query-cache
```

The service provider is auto-discovered. No configuration required.

Usage
-----

[](#usage)

```
// Eloquent collection — caches ->get()
$locales = Locale::query()->getCached();

// Single model — caches ->first()
$invitation = UserInvitation::where('activation_token', $token)
    ->where('user_id', null)
    ->firstCached();
```

If the same query (identical SQL and bindings) runs again during the same request, it returns the stored result without touching the database.

```
$a = User::where('id', 1)->firstCached(); // hits the DB
$b = User::where('id', 1)->firstCached(); // served from cache, no DB hit
// $a === $b
```

Different queries are cached independently — bindings are part of the cache key, so `where('id', 1)` and `where('id', 2)` never collide.

How it works
------------

[](#how-it-works)

- A `RequestQueryCache` singleton holds an in-memory `array` keyed by `md5(sql + serialized bindings)`.
- `getCached()` wraps `->get()`; `firstCached()` wraps `->first()` (and prefixes its key with `first:` so the two never collide on the same query).
- The store is flushed on `app.terminating` (covers PHP-FPM) **and** on Octane's `RequestReceived` event when running under [Laravel Octane](https://laravel.com/docs/octane), guaranteeing every request starts with an empty store.

Caveat: writes within the same request
--------------------------------------

[](#caveat-writes-within-the-same-request)

Because results are memoized on SQL + bindings, if you write to a row and then re-query it with `firstCached()`/`getCached()` in the **same request**, you get the pre-write cached value. Use the uncached `first()`/`get()` after a write you need to read back in-request.

Testing
-------

[](#testing)

```
composer install
vendor/bin/phpunit
```

License
-------

[](#license)

MIT

###  Health Score

39

—

LowBetter than 84% of packages

Maintenance100

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity45

Maturing project, gaining track record

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

0d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/7b51636a402c8fc758f251e1a92b468a3515a5038a3c278a4499782688b83586?d=identicon)[hcuadra](/maintainers/hcuadra)

---

Tags

laraveleloquentcachequerymemoizeper-request

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/langsys-laravel-request-query-cache/health.svg)

```
[![Health](https://phpackages.com/badges/langsys-laravel-request-query-cache/health.svg)](https://phpackages.com/packages/langsys-laravel-request-query-cache)
```

###  Alternatives

[kirschbaum-development/eloquent-power-joins

The Laravel magic applied to joins.

1.6k29.9M42](/packages/kirschbaum-development-eloquent-power-joins)[spatie/laravel-sluggable

Generate slugs when saving Eloquent models

1.5k12.4M291](/packages/spatie-laravel-sluggable)[tucker-eric/eloquentfilter

An Eloquent way to filter Eloquent Models

1.8k5.0M31](/packages/tucker-eric-eloquentfilter)[spiritix/lada-cache

A Redis based, automated and scalable database caching layer for Laravel

592452.8k2](/packages/spiritix-lada-cache)[psalm/plugin-laravel

Psalm plugin for Laravel

3325.1M337](/packages/psalm-plugin-laravel)[watson/validating

Eloquent model validating trait.

9743.4M53](/packages/watson-validating)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
