PHPackages                             webo3/laravel-db-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. webo3/laravel-db-cache

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

webo3/laravel-db-cache
======================

Transparent database query caching for Laravel — zero code changes, smart invalidation, multi-driver.

v1.1.3(1mo ago)048MITPHPPHP ^8.1

Since Feb 7Pushed 1mo agoCompare

[ Source](https://github.com/webo3/laravel-db-cache)[ Packagist](https://packagist.org/packages/webo3/laravel-db-cache)[ RSS](/packages/webo3-laravel-db-cache/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (12)Versions (8)Used By (0)

Laravel DB Cache
================

[](#laravel-db-cache)

Transparent database query caching for Laravel — zero code changes, smart invalidation, multi-driver.

This package intercepts SQL queries at the connection level, automatically caching `SELECT` results and invalidating them when mutations (`INSERT`, `UPDATE`, `DELETE`, etc.) affect related tables. No changes to your application code required.

Features
--------

[](#features)

- **Zero-config caching** - Works transparently at the database connection level
- **Smart invalidation** - Automatically invalidates cached queries when related tables are mutated
- **Three cache drivers** - Array (per-request), Redis (persistent with two-tier L1/L2), Null (no-op)
- **Query normalization** - Case-insensitive, whitespace-normalized keys for better hit rates
- **Redis driver highlights**:
    - Two-tier architecture (L1 in-memory + L2 Redis)
    - Redis Hash structures with pipelining for batch operations
    - O(1) table-based invalidation via inverted indexes
    - AWS ElastiCache / Valkey compatible (uses Sets instead of KEYS/SCAN)
    - Automatic igbinary serialization and gzip compression
- **LRU eviction** for the array driver when max size is reached
- **Multi-tenant support** - `setTenantContext()` namespaces cache keys per tenant, preventing cross-tenant data leakage
- **Cursor queries bypassed** - `cursor()` queries are never cached (preserving memory-efficient streaming)
- **Monitoring middleware** included for logging cache statistics

Requirements
------------

[](#requirements)

- PHP 8.1+
- Laravel 9, 10, 11, or 12
- MySQL, PostgreSQL, or SQLite database connection
- Redis (optional, for the `redis` driver)

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

[](#installation)

```
composer require webo3/laravel-db-cache
```

The service provider is auto-discovered. No manual registration needed.

### Publish the configuration

[](#publish-the-configuration)

```
php artisan vendor:publish --tag=db-cache-config
```

This creates `config/db-cache.php` in your application.

Configuration
-------------

[](#configuration)

### Environment Variables

[](#environment-variables)

VariableDefaultDescription`DB_QUERY_CACHE_ENABLED``false`Enable/disable query caching`DB_QUERY_CACHE_DRIVER``array`Cache driver: `array`, `redis`, or `null``DB_QUERY_CACHE_TTL``180`Cache time-to-live in seconds`DB_QUERY_CACHE_MAX_SIZE``1000`Max cached queries (array driver only)`DB_QUERY_CACHE_LOG_ENABLED``false`Enable cache hit/miss/invalidation logging`DB_QUERY_CACHE_CONNECTION``mysql`Database connection name(s) to cache`DB_QUERY_CACHE_REDIS_CONNECTION``db_cache`Redis connection name (redis driver only)### Quick Start

[](#quick-start)

Add to your `.env`:

```
DB_QUERY_CACHE_ENABLED=true
DB_QUERY_CACHE_DRIVER=array
```

Drivers
-------

[](#drivers)

### Array Driver

[](#array-driver)

In-memory cache that lives for the duration of a single HTTP request. No external dependencies.

- Best for: development, testing, detecting duplicate queries within a request
- Cache is **not shared** between requests or workers
- Includes LRU eviction when `max_size` is reached (evicts the oldest 10%)

```
DB_QUERY_CACHE_DRIVER=array
DB_QUERY_CACHE_MAX_SIZE=1000
```

### Redis Driver

[](#redis-driver)

Persistent cache shared across all workers and requests. Uses a two-tier architecture:

- **L1 (in-memory)**: Per-request cache to avoid repeated Redis calls for the same query
- **L2 (Redis)**: Persistent shared cache using Redis Hash structures

```
DB_QUERY_CACHE_DRIVER=redis
DB_QUERY_CACHE_TTL=180
DB_QUERY_CACHE_REDIS_CONNECTION=db_cache
```

#### Redis Connection Setup

[](#redis-connection-setup)

Add a dedicated Redis connection in your `config/database.php`:

```
'redis' => [
    'client' => env('REDIS_CLIENT', 'predis'),

    // ... your other connections ...

    'db_cache' => [
        'url' => env('REDIS_URL'),
        'host' => env('REDIS_HOST', '127.0.0.1'),
        'port' => env('REDIS_PORT', '6379'),
        'database' => env('REDIS_QUERY_CACHE_DB', '2'),
        'timeout' => 2.0,
        'read_timeout' => 2.0,
    ],
],
```

Using a dedicated database (e.g. `2`) keeps query cache data isolated from your application cache.

#### TLS/SSL (AWS ElastiCache, Valkey)

[](#tlsssl-aws-elasticache-valkey)

For remote Redis connections that require TLS (e.g. AWS ElastiCache, Valkey), add `scheme` and `context` options to your `db_cache` connection:

```
'db_cache' => [
    'scheme' => env('REDIS_SCHEME', 'tcp'), // Use 'tls' for SSL connections
    'host' => env('REDIS_HOST', '127.0.0.1'),
    'port' => env('REDIS_PORT', '6379'),
    'database' => env('REDIS_QUERY_CACHE_DB', '2'),
    'timeout' => 2.0,
    'read_timeout' => 2.0,
    ...((env('REDIS_SCHEME') === 'tls') ? [
        'context' => [
            'stream' => [
                'verify_peer' => env('REDIS_SSL_VERIFY_PEER', true),
                'verify_peer_name' => env('REDIS_SSL_VERIFY_PEER_NAME', true),
            ],
        ],
    ] : []),
],
```

Then in your `.env`:

```
REDIS_SCHEME=tls
REDIS_HOST=your-cluster.xxxxx.cache.amazonaws.com
REDIS_PORT=6380
```

#### Redis Client

[](#redis-client)

The package works with both `predis` and `phpredis`:

```
# Predis (pure PHP)
composer require predis/predis

# Or use phpredis (C extension, faster)
# Install via pecl: pecl install redis
```

#### Optional: igbinary for faster serialization

[](#optional-igbinary-for-faster-serialization)

```
pecl install igbinary
```

When available, the Redis driver automatically uses igbinary for serialization and applies gzip compression for results larger than 10KB.

### Null Driver

[](#null-driver)

Disables caching entirely. Useful for debugging or disabling caching in specific environments without removing the package.

```
DB_QUERY_CACHE_DRIVER=null
```

Monitoring Middleware
---------------------

[](#monitoring-middleware)

The package includes a middleware that logs cache statistics at the end of each request.

### Register the middleware

[](#register-the-middleware)

In Laravel 11+ (`bootstrap/app.php`):

```
->withMiddleware(function (Middleware $middleware) {
    $middleware->append(\webO3\LaravelDbCache\Middleware\QueryCacheStatsMiddleware::class);
})
```

In Laravel 9/10 (`app/Http/Kernel.php`):

```
protected $middleware = [
    // ...
    \webO3\LaravelDbCache\Middleware\QueryCacheStatsMiddleware::class,
];
```

The middleware only logs when `DB_QUERY_CACHE_LOG_ENABLED=true`. Log entries include the driver, URL, HTTP method, cached query count, total hits, and hit rate.

Multi-Connection Support
------------------------

[](#multi-connection-support)

You can enable query caching on multiple database connections simultaneously. Use a comma-separated string in your `.env`:

```
DB_QUERY_CACHE_CONNECTION=main,org
```

Or use an array in `config/db-cache.php`:

```
'connection' => ['main', 'org'],
```

Each connection will use the same cache driver and TTL settings. The factory automatically creates the appropriate cached connection class based on the driver (`mysql`, `pgsql`, or `sqlite`).

Multi-Tenant Support
--------------------

[](#multi-tenant-support)

For multi-tenant applications where multiple tenants share the same database connection, the package provides tenant-aware cache isolation via `setTenantContext()`. This namespaces all cache keys by tenant ID, preventing cross-tenant data leakage.

### Usage

[](#usage)

Call `setTenantContext()` on the connection after resolving the tenant — typically in your tenant database resolver or middleware:

```
$connection = DB::connection('org');

if (method_exists($connection, 'setTenantContext')) {
    $connection->setTenantContext((string) $org->id);
}
```

Once set, all cache operations on that connection are scoped to the tenant:

- **Cache keys** are prefixed with the tenant ID (e.g. `app_database_cache:t:42:abc123`)
- **Tracking sets** are tenant-scoped (e.g. `db_cache:t:42:keys`)
- **Table indexes** are tenant-scoped (e.g. `db_cache:t:42:table:users`)
- **Cache invalidation** only affects the current tenant's cached queries

### How it works per driver

[](#how-it-works-per-driver)

DriverBehavior**Redis**Keys, tracking sets, and table indexes are namespaced by tenant. L1 (in-memory) cache is flushed on tenant switch. Each tenant's data is fully isolated in Redis.**Array**Cache is flushed when switching between tenants (since the static array is shared). Within a single request serving one tenant, caching works normally.**Null**No-op (accepts the call, does nothing).### Connections without tenant context

[](#connections-without-tenant-context)

Connections that don't call `setTenantContext()` (e.g. a shared `main` connection) work exactly as before — no tenant prefix is applied. This allows you to cache both shared and tenant-specific connections simultaneously:

```
DB_QUERY_CACHE_CONNECTION=main,org
```

The `main` connection caches globally, while the `org` connection caches per-tenant after `setTenantContext()` is called.

Artisan Command
---------------

[](#artisan-command)

Clear the query cache from the command line:

```
# Clear all cached connections
php artisan db-cache:clear

# Clear a specific connection
php artisan db-cache:clear --connection=org

# Clear a specific tenant's cache
php artisan db-cache:clear --connection=org --tenant=42

# Clear multiple connections (comma-separated)
php artisan db-cache:clear --connection=main,org
```

OptionDescription`--connection`Connection name(s) to clear. Defaults to all connections listed in `DB_QUERY_CACHE_CONNECTION`.`--tenant`Tenant ID to scope the clear to. Sets the tenant context before flushing, so only that tenant's cache keys are removed (redis driver).Programmatic API
----------------

[](#programmatic-api)

Any cached connection (MySQL, PostgreSQL, or SQLite) exposes these methods via the `DB` facade:

```
use Illuminate\Support\Facades\DB;

// Clear all cached queries
DB::connection('mysql')->clearQueryCache();

// Get cache statistics
$stats = DB::connection('pgsql')->getCacheStats();
// Returns: [
//     'driver' => 'redis',
//     'cached_queries_count' => 42,
//     'total_cache_hits' => 128,
//     'queries' => [...],
// ]

// Temporarily disable caching
DB::connection('mysql')->disableQueryCache();

// Re-enable caching
DB::connection('mysql')->enableQueryCache();
```

You can also use the `CachedConnection` interface for type checking:

```
use webO3\LaravelDbCache\Contracts\CachedConnection;

$connection = DB::connection();
if ($connection instanceof CachedConnection) {
    $stats = $connection->getCacheStats();
}
```

How It Works
------------

[](#how-it-works)

1. **SELECT queries** are intercepted at the connection level. The query + bindings are normalized and hashed to produce a cache key. If a cached result exists, it's returned immediately without hitting the database.
2. **Mutation queries** (`INSERT`, `UPDATE`, `DELETE`, `TRUNCATE`, `ALTER`, `DROP`, `CREATE`, `REPLACE`) trigger automatic invalidation. The package extracts table names from the SQL and invalidates all cached queries that reference those tables.
3. **Table extraction** uses regex-based SQL parsing to identify which tables a query reads from or writes to. This supports `FROM`, `JOIN`, `INTO`, `UPDATE`, `DELETE FROM`, subqueries, and more.
4. **Query normalization** ensures that queries with different casing or whitespace produce the same cache key (e.g. `SELECT * FROM users` and `select *  from  users` hit the same cache entry).
5. **Cursor queries** (`DB::cursor()`) are never cached, as they are designed for memory-efficient streaming of large result sets.

Custom Cache Drivers
--------------------

[](#custom-cache-drivers)

You can create your own cache driver by implementing the `QueryCacheDriver` interface:

```
use webO3\LaravelDbCache\Contracts\QueryCacheDriver;

class MyCustomDriver implements QueryCacheDriver
{
    public function get(string $key): ?array { /* ... */ }
    public function put(string $key, mixed $result, string $query, float $executedAt): void { /* ... */ }
    public function has(string $key): bool { /* ... */ }
    public function forget(string $key): void { /* ... */ }
    public function invalidateTables(array $tables, string $query): int { /* ... */ }
    public function flush(): void { /* ... */ }
    public function getStats(): array { /* ... */ }
    public function recordHit(string $key): void { /* ... */ }
    public function getAllKeys(): array { /* ... */ }
    public function setTenantContext(string $tenantId): void { /* ... */ }
}
```

Testing
-------

[](#testing)

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

Tests require a MySQL database connection. Copy `.env.example` to `.env` and configure your database credentials. Redis tests are automatically skipped if Redis is unavailable. Unit tests for `SqlTableExtractor` run without any database.

License
-------

[](#license)

MIT

###  Health Score

43

—

FairBetter than 90% of packages

Maintenance97

Actively maintained with recent releases

Popularity11

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity47

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

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

Every ~7 days

Total

7

Last Release

45d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/0a175e082beae5946d428cc9e5675c903fe5df3691351b6db3ec22eb6b2ea1ab?d=identicon)[moihuguesjoyal](/maintainers/moihuguesjoyal)

---

Top Contributors

[![huguesjoyal](https://avatars.githubusercontent.com/u/16695693?v=4)](https://github.com/huguesjoyal "huguesjoyal (10 commits)")

---

Tags

laraveldatabaseperformancemysqlsqlitepostgresqlcacheQuery Cachedb-cache

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/webo3-laravel-db-cache/health.svg)

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

###  Alternatives

[spiritix/lada-cache

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

591444.8k2](/packages/spiritix-lada-cache)[vcian/laravel-db-auditor

Database DB Auditor provide leverage to audit your MySql,sqlite, PostgreSQL database standards and also provide options to add constraints in table.

28535.1k1](/packages/vcian-laravel-db-auditor)[moharrum/laravel-adminer

Adminer database management tool for your Laravel application.

451.0k](/packages/moharrum-laravel-adminer)[ramadan/easy-model

A Laravel package for enjoyably managing database queries.

101.6k](/packages/ramadan-easy-model)

PHPackages © 2026

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