PHPackages                             tedon/kachet - 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. [Caching](/categories/caching)
4. /
5. tedon/kachet

ActiveLibrary[Caching](/categories/caching)

tedon/kachet
============

Elegant method-level caching for PHP classes

v1.0.0(5mo ago)03MITPHPPHP ^8.2

Since Nov 20Pushed 5mo agoCompare

[ Source](https://github.com/zouravand/kachet)[ Packagist](https://packagist.org/packages/tedon/kachet)[ RSS](/packages/tedon-kachet/feed)WikiDiscussions master Synced 1mo ago

READMEChangelogDependencies (3)Versions (3)Used By (0)

Kachet - Elegant Method-Level Caching for PHP
=============================================

[](#kachet---elegant-method-level-caching-for-php)

A Laravel package that provides elegant, method-level caching through a simple proxy pattern. Cache your class methods with zero boilerplate using either PHP attributes or programmatic configuration.

Features
--------

[](#features)

- **Zero boilerplate caching** - Add caching with a single method call
- **Dual configuration** - Use PHP attributes or programmatic configuration
- **Cache proxy pattern** - `$obj->method()` calls directly, `$obj->cached()->method()` uses cache
- **Flexible cache keys** - Support for dynamic cache keys with sprintf-style placeholders
- **Redis integration** - Built for Redis but works with any Laravel cache driver
- **Laravel integration** - Service provider and facade included
- **PHP 8.2+** - Built with modern PHP features

What is Kachet?
---------------

[](#what-is-kachet)

Kachet provides a clean, intuitive API for caching method results in your PHP classes. Instead of manually wrapping your methods with cache logic, you can:

1. Add the `Kachetable` trait to your class
2. Configure cacheable methods using attributes or a configuration method
3. Call `cached()` when you want to use the cache

```
$user = new UserRepository();

// Direct call - always executes the method
$result = $user->findById(1);

// Cached call - uses cache if available
$result = $user->cached()->findById(1);
```

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

[](#installation)

Install via Composer:

```
composer require tedon/kachet
```

For Laravel applications, the service provider will be automatically registered.

Basic Usage
-----------

[](#basic-usage)

### Using PHP Attributes (Recommended)

[](#using-php-attributes-recommended)

The simplest way to add caching is using PHP attributes:

```
use Tedon\Kachet\Traits\Kachetable;
use Tedon\Kachet\UseKachet;
use Tedon\Kachet\KachetProxy;

/**
 * @method KachetProxy cached()
 */
class UserRepository
{
    use Kachetable;

    #[UseKachet(cacheKey: 'user:%d', ttl: 3600)]
    public function findById(int $id): array
    {
        // Expensive database query
        return DB::table('users')->find($id);
    }

    #[UseKachet(cacheKey: 'users:latest', ttl: 60)]
    public function listLatest(): array
    {
        return DB::table('users')
            ->orderBy('created_at', 'desc')
            ->limit(10)
            ->get();
    }
}

// Usage
$repo = new UserRepository();

// Direct call - always executes the query
$user = $repo->findById(1);

// Cached call - uses cache for 3600 seconds
$user = $repo->cached()->findById(1);
// Cache key: "kachet:user:1"

// List without cache
$users = $repo->listLatest();

// List with cache
$users = $repo->cached()->listLatest();
// Cache key: "kachet:users:latest"
```

### Using Programmatic Configuration

[](#using-programmatic-configuration)

If you prefer to configure cache settings in a method, use the `cachedMethods()` approach:

```
use Tedon\Kachet\Traits\Kachetable;
use Tedon\Kachet\KachetProxy;

/**
 * @method KachetProxy cached()
 */
class ProductRepository
{
    use Kachetable;

    public function findById(int $id): array
    {
        return DB::table('products')->find($id);
    }

    public function listByCategory(string $category): array
    {
        return DB::table('products')
            ->where('category', $category)
            ->get();
    }

    public function cachedMethods(): array
    {
        return [
            [
                'methodName' => 'findById',
                'cacheKey' => 'product:%d',
                'ttl' => 3600,
            ],
            [
                'methodName' => 'listByCategory',
                'cacheKey' => 'products:category:%s',
                'ttl' => 1800,
            ],
        ];
    }
}

// Usage
$repo = new ProductRepository();

// Cache product for 1 hour
$product = $repo->cached()->findById(42);
// Cache key: "kachet:product:42"

// Cache category listing for 30 minutes
$products = $repo->cached()->listByCategory('electronics');
// Cache key: "kachet:products:category:electronics"
```

Advanced Usage
--------------

[](#advanced-usage)

### Dynamic Cache Keys with sprintf

[](#dynamic-cache-keys-with-sprintf)

Cache keys support sprintf-style placeholders that automatically map to method arguments:

```
#[UseKachet(cacheKey: 'post:%d:comments:page:%d', ttl: 600)]
public function getPostComments(int $postId, int $page): array
{
    return DB::table('comments')
        ->where('post_id', $postId)
        ->paginate($page);
}

// Usage
$comments = $repo->cached()->getPostComments(123, 2);
// Cache key: "kachet:post:123:comments:page:2"
```

Supported sprintf formats:

- `%d` - Integer
- `%s` - String
- `%f` - Float
- And all other standard sprintf formats

### Forever Cache (No TTL)

[](#forever-cache-no-ttl)

Omit the TTL to cache indefinitely:

```
#[UseKachet(cacheKey: 'settings')]
public function getSettings(): array
{
    return DB::table('settings')->pluck('value', 'key');
}
```

### Custom Cache Drivers

[](#custom-cache-drivers)

Specify a custom cache driver (configured in `config/cache.php`):

```
public function cachedMethods(): array
{
    return [
        [
            'methodName' => 'heavyComputation',
            'cacheKey' => 'computation:%d',
            'ttl' => 86400,
            'driver' => 'redis',
        ],
    ];
}
```

### Cache Tags

[](#cache-tags)

Use tags for easier cache invalidation:

```
public function cachedMethods(): array
{
    return [
        [
            'methodName' => 'findById',
            'cacheKey' => 'user:%d',
            'ttl' => 3600,
            'tags' => ['users'],
        ],
    ];
}

// Invalidate all user-related caches
Cache::tags(['users'])->flush();
```

### Cache Patterns

[](#cache-patterns)

Kachet supports different serialization patterns for cached data:

```
use Tedon\Kachet\Constants\CachePattern;

public function cachedMethods(): array
{
    return [
        [
            'methodName' => 'getComplexData',
            'cacheKey' => 'complex:data',
            'ttl' => 3600,
            'storePattern' => CachePattern::JSON, // JSON serialization
        ],
        [
            'methodName' => 'getStructuredData',
            'cacheKey' => 'structured:data',
            'ttl' => 3600,
            'storePattern' => CachePattern::TOON, // TOON serialization
        ],
    ];
}
```

Available patterns:

- `CachePattern::BASE` - No serialization (default)
- `CachePattern::JSON` - JSON serialization
- `CachePattern::TOON` - TOON format (requires tedon/tooner)

### Caching Null Values

[](#caching-null-values)

Control whether null results should be cached:

```
public function cachedMethods(): array
{
    return [
        [
            'methodName' => 'findOptional',
            'cacheKey' => 'optional:%d',
            'ttl' => 600,
            'cacheNullValue' => true, // Cache null results
        ],
    ];
}
```

### Custom Cache Prefix

[](#custom-cache-prefix)

Change the default cache key prefix using a class-level attribute:

```
use Tedon\Kachet\Traits\Kachetable;
use Tedon\Kachet\UseKachet;
use Tedon\Kachet\KachetProxy;

/**
 * @method KachetProxy cached()
 */
#[UseKachet(cacheKey: 'myapp:v2:')]
class MyRepository
{
    use Kachetable;

    #[UseKachet(cacheKey: 'user:%d', ttl: 3600)]
    public function findById(int $id): array
    {
        return DB::table('users')->find($id);
    }
}

// Cache key will be: "myapp:v2:user:1"
$user = $repo->cached()->findById(1);
```

Configuration Options
---------------------

[](#configuration-options)

### Attribute Configuration

[](#attribute-configuration)

When using the `#[UseKachet]` attribute:

```
#[UseKachet(
    cacheKey: 'my:cache:key',  // Required: Cache key with optional sprintf placeholders
    ttl: 3600,                  // Optional: Time to live in seconds (null = forever)
)]
```

### Programmatic Configuration

[](#programmatic-configuration)

When using `cachedMethods()`:

```
[
    'methodName' => 'myMethod',          // Required: Method name to cache
    'cacheKey' => 'my:cache:key',        // Required: Cache key with sprintf placeholders
    'ttl' => 3600,                       // Optional: Time to live (null = forever)
    'tags' => ['tag1', 'tag2'],          // Optional: Cache tags
    'driver' => 'redis',                 // Optional: Specific cache driver
    'cacheNullValue' => true,            // Optional: Cache null results (default: false)
    'storePattern' => CachePattern::JSON, // Optional: Serialization pattern
]
```

Complete Example
----------------

[](#complete-example)

Here's a comprehensive example showing multiple caching strategies:

```
use Tedon\Kachet\Traits\Kachetable;
use Tedon\Kachet\UseKachet;
use Tedon\Kachet\KachetProxy;
use Tedon\Kachet\Constants\CachePattern;

/**
 * @method KachetProxy cached()
 */
class BlogRepository
{
    use Kachetable;

    protected string $cachePrefix = 'blog:v1:';

    // Simple attribute-based caching
    #[UseKachet(cacheKey: 'post:%d', ttl: 3600)]
    public function findPost(int $id): array
    {
        return DB::table('posts')->find($id);
    }

    // Multi-parameter cache key
    #[UseKachet(cacheKey: 'posts:%s:page:%d', ttl: 600)]
    public function listByCategory(string $category, int $page = 1): array
    {
        return DB::table('posts')
            ->where('category', $category)
            ->paginate($page);
    }

    // Forever cache
    #[UseKachet(cacheKey: 'categories')]
    public function getAllCategories(): array
    {
        return DB::table('categories')->pluck('name', 'id');
    }

    // Complex programmatic configuration
    public function getStats(int $year): array
    {
        return DB::table('posts')
            ->whereYear('created_at', $year)
            ->selectRaw('COUNT(*) as total, AVG(views) as avg_views')
            ->first();
    }

    public function cachedMethods(): array
    {
        return [
            [
                'methodName' => 'getStats',
                'cacheKey' => 'stats:%d',
                'ttl' => 86400,
                'tags' => ['statistics', 'posts'],
                'storePattern' => CachePattern::JSON,
                'driver' => 'redis',
            ],
        ];
    }
}

// Usage examples
$blog = new BlogRepository();

// Direct calls - no caching
$post = $blog->findPost(1);
$posts = $blog->listByCategory('tech', 2);
$categories = $blog->getAllCategories();
$stats = $blog->getStats(2024);

// Cached calls
$post = $blog->cached()->findPost(1);
// Key: "blog:v1:post:1", TTL: 3600s

$posts = $blog->cached()->listByCategory('tech', 2);
// Key: "blog:v1:posts:tech:page:2", TTL: 600s

$categories = $blog->cached()->getAllCategories();
// Key: "blog:v1:categories", TTL: forever

$stats = $blog->cached()->getStats(2024);
// Key: "blog:v1:stats:2024", TTL: 86400s
// Serialized as JSON, tagged with ['statistics', 'posts']

// Clear specific caches
Cache::tags(['statistics'])->flush();
```

Laravel Facade Usage
--------------------

[](#laravel-facade-usage)

Kachet provides a facade for direct cache operations:

```
use Tedon\Kachet\Facades\Kachet;

// Check if a cache key exists
if (Kachet::has('user:123')) {
    // ...
}

// Manually cache a value
Kachet::put('custom:key', $value, 3600);

// Retrieve a cached value
$value = Kachet::get('custom:key');
```

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

[](#how-it-works)

Kachet uses PHP's magic `__call` method to intercept method calls on the proxy object:

1. When you call `$obj->cached()`, it returns a `KachetProxy` instance
2. The proxy holds a reference to your original object
3. When you call a method on the proxy (e.g., `->findById(1)`), the proxy:
    - Looks up the cache configuration for that method
    - Generates a cache key using the method arguments
    - Checks if the result exists in cache
    - If cached: returns the cached value
    - If not cached: calls the original method, caches the result, and returns it

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

[](#requirements)

- PHP 8.2 or higher
- Laravel 10.x, 11.x, or 12.x
- Redis (recommended) or any Laravel-supported cache driver

Testing
-------

[](#testing)

```
composer test
```

IDE Support &amp; Autocomplete
------------------------------

[](#ide-support--autocomplete)

Kachet fully supports IDE autocomplete for cached methods using PHPDoc annotations. To enable autocomplete in your IDE (PhpStorm, VSCode, etc.), add the following annotation to your class:

```
use Tedon\Kachet\Traits\Kachetable;
use Tedon\Kachet\KachetProxy;

/**
 * @method KachetProxy cached()
 */
class UserRepository
{
    use Kachetable;

    public function findById(int $id): array { /* ... */ }
    public function listLatest(): array { /* ... */ }
}
```

With this annotation:

- Your IDE will autocomplete `$repo->cached()->findById()`
- Type hints and parameter suggestions will work correctly
- You'll get proper code navigation and refactoring support

The `@method KachetProxy cached()` annotation tells the IDE that:

1. The `cached()` method returns a `KachetProxy` instance
2. The proxy is generic over `static` (your class type)
3. Through the `@mixin` annotation in `KachetProxy`, the IDE knows the proxy has all your class methods

**Note:** This annotation is optional - your code will work without it, but adding it significantly improves the development experience.

Best Practices
--------------

[](#best-practices)

1. **Use attributes for simple cases** - They're cleaner and easier to read
2. **Use programmatic config for complex cases** - When you need tags, custom drivers, or patterns
3. **Choose appropriate TTLs** - Shorter for frequently changing data, longer for stable data
4. **Use cache tags** - Makes cache invalidation easier
5. **Set custom prefixes** - Include version numbers for easier cache busting
6. **Cache expensive operations** - Database queries, API calls, complex computations
7. **Don't cache everything** - Simple getters/setters don't need caching
8. **Add IDE autocomplete annotations** - Include `@method KachetProxy cached()` for better IDE support

Cache Invalidation
------------------

[](#cache-invalidation)

```
use Illuminate\Support\Facades\Cache;

// Clear specific key
Cache::forget('kachet:user:123');

// Clear by pattern (Redis only)
Cache::tags(['users'])->flush();

// Clear all cache
Cache::flush();
```

Performance Tips
----------------

[](#performance-tips)

- Use Redis for better performance with high-traffic applications
- Set appropriate TTLs to balance freshness and performance
- Use cache tags for efficient bulk invalidation
- Monitor cache hit rates using Laravel Telescope or similar tools

Contributing
------------

[](#contributing)

Contributions are welcome! Please feel free to submit a Pull Request.

License
-------

[](#license)

MIT License - see the [LICENSE](LICENSE) file for details.

Author
------

[](#author)

**Pouya Zouravand**

- Email:

Credits
-------

[](#credits)

Built with inspiration from Laravel's elegant API design principles and modern PHP best practices.

###  Health Score

34

—

LowBetter than 77% of packages

Maintenance70

Regular maintenance activity

Popularity3

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity48

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

Unknown

Total

1

Last Release

179d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/ae3a667533c45f88ba513ab71d1bf17df37c99c0b918e60160b49ce6a7ae5adc?d=identicon)[pouya.zuravand](/maintainers/pouya.zuravand)

---

Top Contributors

[![zouravand](https://avatars.githubusercontent.com/u/4015493?v=4)](https://github.com/zouravand "zouravand (5 commits)")

---

Tags

laravelproxyrediscachecachingattributesrepositorymethod-cache

### Embed Badge

![Health badge](/badges/tedon-kachet/health.svg)

```
[![Health](https://phpackages.com/badges/tedon-kachet/health.svg)](https://phpackages.com/packages/tedon-kachet)
```

###  Alternatives

[awssat/laravel-visits

Laravel Redis visits counter for Eloquent models

975163.6k2](/packages/awssat-laravel-visits)[tedivm/stash-bundle

Incorporates the Stash caching library into Symfony.

841.4M16](/packages/tedivm-stash-bundle)[byerikas/cache-tags

Allows for Redis/Valkey cache flushing multiple tagged items by a single tag.

1413.9k](/packages/byerikas-cache-tags)

PHPackages © 2026

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