laravel-model-caching

repository·master·Indexed 25 days ago

https://github.com/mike-bronner/laravel-model-caching

A Laravel package providing automatic, self-invalidating caching for Eloquent models and eager-loaded relationships. It supports Redis, Memcached, APC, and DynamoDB drivers to reduce database load on read-heavy applications. Caching is enabled via the Cachable trait or by extending the CachedModel class. It supports model queries, aggregations, and eager-loaded relationships, while providing tools for manual invalidation, cache cool-down periods, and multi-tenancy prefixes.

Tokens
4.7K
Snippets
11
Records
31
Agent score
81%

What's inside laravel-model-caching

  1. How laravel-model-caching works

    master

    The package provides automatic caching for Eloquent model queries and eager-loaded relationships. By adding a specific trait or extending a base class, you enable a layer that caches query results and automatically flushes relevant cache entries when models are created, updated, or deleted. This eliminates the need for manual cache key management and manual invalidation logic.

    What is cached:

    • Model queries: get, first, find, all, paginate, pluck, value, exists.
    • Aggregations: count, sum, avg, min, max.
    • Eager-loaded relationships (using with()).

    What is NOT cached:

    • Lazy-loaded relationships: Only relationships loaded via with() are cached. You must use with() to benefit from caching.
    • Queries with select() clauses: Custom column selections bypass the cache.
    • Queries inside transactions: Cache is not automatically flushed when a transaction commits. You must call flushCache() manually if needed.
    • inRandomOrder() queries: Caching is automatically disabled for these queries to ensure varied results.
  2. Enable caching on your models

    master

    You can enable caching using one of two methods: adding the Cachable trait to your models or extending the CachedModel class.

    The recommended approach is to add the Cachable trait to an abstract base model that all your other models extend.

    Option 2: Extend CachedModel

    Alternatively, you can have your models extend GeneaLabs\LaravelModelCaching\CachedModel directly.

    Note on User models: You can safely use the Cachable trait on the User model without conflicting with Laravel's authentication. However, ensure user updates are performed via Eloquent (not raw DB::table() queries) to ensure cache invalidation triggers correctly.

    // Option 1: Using the Cachable trait in a base model
    namespace App\Models;
    
    use GeneaLabs\LaravelModelCaching\Traits\Cachable;
    use Illuminate\Database\Eloquent\Model;
    
    abstract class BaseModel extends Model
    {
        use Cachable;
    }
    
    // Option 2: Extending CachedModel directly
    namespace App\Models;
    
    use GeneaLabs\LaravelModelCaching\CachedModel;
    
    class Post extends CachedModel
    {
        // ...
    }
  3. Configure a custom DynamoDB cache store

    master

    To use DynamoDB as a dedicated cache store, first define the store in config/cache.php using the dynamodb driver. Then, set MODEL_CACHE_STORE to your new store name.

    Requirements:

    • Install the AWS SDK: composer require aws/aws-sdk-php.
    • Enable DynamoDB TTL on the expires_at attribute in your table.

    Note on Invalidation: Invalidation on DynamoDB uses logical namespace versioning. modelCache:clear rotates a package-wide namespace key, making old rows unreachable. Rows are not physically deleted immediately; they are removed later via TTL.

    When to use DynamoDB vs Redis:

    • DynamoDB: Best for AWS-native/serverless environments or when you want a managed store without running Redis.
    • Redis: Best for lower latency, higher write churn, native tag support, or faster physical cleanup.
    MODEL_CACHE_STORE=dynamodb-model
    AWS_ACCESS_KEY_ID=your-access-key
    AWS_SECRET_ACCESS_KEY=your-secret-key
    AWS_DEFAULT_REGION=us-east-1
    AWS_DYNAMODB_CACHE_ENDPOINT=
    AWS_DYNAMODB_CACHE_TABLE=cache
    'stores' => [
        'dynamodb-model' => [
            'driver' => 'dynamodb',
            'key' => env('AWS_ACCESS_KEY_ID'),
            'secret' => env('AWS_SECRET_ACCESS_KEY'),
            'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
            'table' => env('AWS_DYNAMODB_CACHE_TABLE', 'cache'),
            'endpoint' => env('AWS_DYNAMODB_CACHE_ENDPOINT'),
            'attributes' => [
                'key' => 'key',
                'value' => 'value',
                'expiration' => 'expires_at',
            ],
        ],
    ],
  4. Configure the model caching package

    master

    Publish the configuration file to config/laravel-model-caching.php using the Artisan command. This file allows you to manage cache prefixes, global enablement, database keying, and the cache store used by the package.

    Available configuration keys:

    • cache-prefix: A global prefix for all cache keys. Note: This is set directly in the config file, not via environment variables.
    • enabled: Boolean to enable or disable caching globally.
    • use-database-keying: Boolean to include database connection and name in cache keys (useful for multi-tenant apps).
    • store: The name of the cache store defined in config/cache.php.
    • fallback-to-database: Boolean to allow falling back to direct database queries if the cache backend is unavailable.
    php artisan modelCache:publish --config
  5. Implement a cache cool-down period

    master

    To prevent frequent writes from immediately flushing the cache in high-traffic scenarios, you can implement a cool-down period. This requires two steps:

    1. Declare the default duration on the model using the $cacheCooldownSeconds property.
    2. Activate the cool-down in your query using withCacheCooldownSeconds().

    Once activated, writes during the cool-down window will not flush the cache. After the window expires, the next write triggers a flush and re-warms the cache.

    <?php
    
    namespace App\Models;
    
    use GeneaLabs\LaravelModelCaching\Traits\Cachable;
    use Illuminate\Database\Eloquent\Model;
    
    class Comment extends Model
    {
        use Cachable;
    
        protected $cacheCooldownSeconds = 300; // 5 minutes
    }
    
    // To activate using the model's default:
    Comment::withCacheCooldownSeconds()->get();
    
    // To override with a specific duration:
    Comment::withCacheCooldownSeconds(30)->get();
  6. Configure static analysis for Larastan / PHPStan

    master

    Because the Cachable trait wraps Eloquent's builder, PHPStan may report "undefined method" errors for methods like cache() or flushCache(). To resolve this, add a @mixin annotation to your model.

    If you use a custom Eloquent builder, you must also add a @return override annotation on your model's newEloquentBuilder() method, or add @mixin YourCustomBuilder to the model class.

    use GeneaLabs\LaravelModelCaching\Traits\Cachable;
    use Illuminate\Database\Eloquent\Model;
    
    /**
     * @mixin \GeneaLabs\LaravelModelCaching\CachedBuilder<\Illuminate\Database\Eloquent\Model>
     */
    class Post extends Model
    {
        use Cachable;
    }
  7. Set cache prefixes for multi-tenancy

    master

    You can isolate cache entries per tenant using prefixes. You can set this globally in the config file or per-model using the $cachePrefix property.

    Global prefix: Set 'cache-prefix' => 'tenant-123' in config/laravel-model-caching.php.

    Per-model prefix: Define protected $cachePrefix = 'tenant-123'; on your model class.

    <?php
    
    namespace App\Models;
    
    use GeneaLabs\LaravelModelCaching\Traits\Cachable;
    use Illuminate\
    Database\Eloquent\Model;
    
    class Post extends Model
    {
        use Cachable;
    
        protected $cachePrefix = 'tenant-123';
    }
  8. Disable caching for queries or code blocks

    master

    There are three ways to bypass caching:

    1. Per-query: Use disableCache() on the query builder. This only affects that specific query chain.
    2. Globally: Set MODEL_CACHE_ENABLED=false in your environment.
    3. Code blocks: Wrap your code in runDisabled() using the app('model-cache') instance or the ModelCache Facade.
    // 1. Per-query
    $results = MyModel::disableCache()->where('active', true)->get();
    
    // 2. For a block of code (using app helper)
    $result = app('model-cache')->runDisabled(function () {
        return MyModel::get();
    });
    
    // 3. For a block of code (using Facade)
    use GeneaLabs\LaravelModelCaching\Facades\ModelCache;
    
    ModelCache::runDisabled(function () {
        return MyModel::get();
    });
  9. Manually flush the cache

    master

    You can manually invalidate the cache using Artisan commands or the ModelCache Facade.

    Artisan Commands:

    • Clear a specific model: php artisan modelCache:clear --model='App\Models\Post'
    • Clear all models: php artisan modelCache:clear (Note: The scope of a full clear depends on your driver; see documentation for Redis vs DynamoDB).

    Programmatic Invalidation (Facade): Use ModelCache::invalidate() to clear one or multiple models.

    # Single model
    php artisan modelCache:clear --model='App\Models\Post'
    
    # All models
    php artisan modelCache:clear
    use GeneaLabs\LaravelModelCaching\Facades\ModelCache;
    
    // Single model
    ModelCache::invalidate(App\Models\Post::class);
    
    // Multiple models
    ModelCache::invalidate([
        App\Models\Post::class,
        App\Models\Comment::class,
    ]);