# 🧠 Laravel Class-Based Cache System

This document outlines a scalable, developer-friendly cache system using class-based logic in Laravel. It supports clean `get`, `refresh`, and `clear` operations, and works with both **tagged** and **non-tagged** cache drivers.

---

## 📂 Directory Structure

```
app/
└── Cache/
    ├── Contracts/
    │   └── Cacheable.php
    ├── BaseCacheable.php
    ├── CategoryTreeCache.php
    └── ...
```

---

## ✅ Purpose

This system gives you:

* Cleaner, testable cache logic per data domain.
* Class-based control of cache lifecycle.
* Optional tag support (with driver fallback).
* Seamless compatibility with Redis, Memcached, or File cache.

---

## 🧩 Components

### 1. `Cacheable` Interface

```php
namespace App\Cache\Contracts;

interface Cacheable
{
    public function key(): string;
    public function ttl(): ?int;
    public function forever(): bool;
    public function resolve(): mixed;
    public function tags(): array;

    public function get(): mixed;
    public function refresh(): mixed;
    public function clear(): bool;
}
```

---

### 2. `BaseCacheable` Abstract Class

```php
namespace App\Cache;

use Illuminate\Support\Facades\Cache;
use App\Cache\Contracts\Cacheable;

abstract class BaseCacheable implements Cacheable
{
    abstract public function key(): string;
    abstract public function ttl(): ?int;
    abstract public function forever(): bool;
    abstract public function resolve(): mixed;

    public function tags(): array
    {
        return [];
    }

    protected function cacheStore()
    {
        $store = Cache::store();
        $taggableDrivers = ['redis', 'memcached'];
        $currentDriver = config('cache.default');

        // Use tags only if supported by the cache driver
        if (!empty($this->tags()) && in_array($currentDriver, $taggableDrivers)) {
            return Cache::tags($this->tags());
        }

        return $store;
    }

    public function get(): mixed
    {
        return $this->forever()
            ? $this->cacheStore()->rememberForever($this->key(), fn () => $this->resolve())
            : $this->cacheStore()->remember($this->key(), $this->ttl(), fn () => $this->resolve());
    }

    public function refresh(): mixed
    {
        $data = $this->resolve();

        if ($this->forever()) {
            $this->cacheStore()->forever($this->key(), $data);
        } else {
            $this->cacheStore()->put($this->key(), $data, $this->ttl());
        }

        return $data;
    }

    public function clear(): bool
    {
        return $this->cacheStore()->forget($this->key());
    }
}
```

---

### 3. Example: `CategoryTreeCache`

```php
namespace App\Cache;

use App\Models\Category;
use Illuminate\Support\Collection;

class CategoryTreeCache extends BaseCacheable
{
    public function key(): string
    {
        return 'category_tree';
    }

    public function ttl(): ?int
    {
        return null;
    }

    public function forever(): bool
    {
        return true;
    }

    public function tags(): array
    {
        return ['categories'];
    }

    public function resolve(): Collection
    {
        return Category::with('children')->whereNull('parent_id')->get();
    }
}
```

---

### 4. Global Helper Function: `cacheable()`

```php
use App\Cache\Contracts\Cacheable;

if (!function_exists('cacheable')) {
    function cacheable(string|Cacheable $classOrInstance): Cacheable
    {
        $cacheable = is_string($classOrInstance)
            ? app($classOrInstance)
            : $classOrInstance;

        if (! $cacheable instanceof Cacheable) {
            throw new InvalidArgumentException("Must implement Cacheable interface.");
        }

        return $cacheable;
    }
}
```

---

## 🛠️ Usage Guide

### ✅ Get data from cache

```php
$categories = cacheable(CategoryTreeCache::class)->get();
```

### 🔄 Refresh the cache

```php
cacheable(CategoryTreeCache::class)->refresh();
```

### 🧹 Clear the cache

```php
cacheable(CategoryTreeCache::class)->clear();
```

---

## ⚠️ Driver Compatibility

| Cache Driver | Supports Tags? |
| ------------ | -------------- |
| Redis        | ✅ Yes          |
| Memcached    | ✅ Yes          |
| File         | ❌ No           |
| Database     | ❌ No           |
| Array        | ❌ No           |

This system automatically disables tags if they’re unsupported. No crash, just fallback.

---

## 📌 Best Practices

* Use `forever()` for data that rarely changes.
* Use `tags()` when working with Redis or Memcached for grouped clearing.
* Use `refresh()` after admin updates or data mutations.
* Avoid `forever()` for dynamic or fast-changing data.

---

## ✅ Real-World Benefits

| Benefit              | Description                                          |
| -------------------- | ---------------------------------------------------- |
| Encapsulation        | Each cache logic lives in its own class              |
| Safety               | No more hardcoded cache keys scattered in code       |
| Flexibility          | Swap or reuse cache logic like services              |
| Compatibility        | Works with and without tagging                       |
| Developer Experience | Self-explaining API: `get()`, `refresh()`, `clear()` |


