Cache Management: Hook-Driven Cache System for Developers

Last updated on May 21, 2026 05:06

PolyCMS ships with a centralized Cache Management system that provides administrators with a UI to inspect and clear caches — and gives developers a powerful hook-driven API to integrate their own caching logic without conflicting with core or other modules.

This guide covers the architecture, available hooks, and practical examples for module and theme developers who want to extend, replace, or react to cache operations.

Architecture Overview

The CacheService (app/Services/CacheService.php) is the single entry point for all cache operations. It maintains a registry of cache types grouped into four categories:

Group Description Built-in Types
laravel Laravel framework caches Application, View, Config, Route, Event
polycms PolyCMS internal service caches Theme, Module, Settings, Template Resolver
server Server-level caches PHP OPcache
module Module-registered caches (Your module adds here)

The system exposes 7 hooks and filters that modules can use to participate in cache management.

Hook & Filter Reference

Filters (Value Transformers)

These hooks allow modules to modify data before it is used.

cache.types — Register Custom Cache Types

Add your own cache types to the system. Your type will appear in the admin Cache Management page alongside core types.

Signature: array $types

use App\Facades\Hook;

Hook::addFilter('cache.types', function (array $types) {
    $types[] = [
        'key'         => 'cdn_purge',
        'label'       => 'CDN Cache',
        'description' => 'Purge Cloudflare or Fastly edge cache',
        'group'       => 'module',   // Use 'module' for custom types
        'clearable'   => true,
    ];
    return $types;
});

Required fields per type:

Field Type Description
key string Unique slug (used in API calls)
label string Human-readable name
description string Short explanation shown in admin UI
group string laravel, polycms, server, or module
clearable bool Whether the "Clear" button should appear

cache.type.handler — Claim Ownership of a Cache Type

This is the conflict prevention mechanism. When a module claims a cache type, the core will skip its own clearing logic for that type and delegate entirely to the module's cache.clear.{type} action hook.

Signature: ?string $handler, string $type

Use case: You are building a dedicated Redis management module and want to handle the application cache differently than the default php artisan cache:clear.

Hook::addFilter('cache.type.handler', function (?string $handler, string $type) {
    if ($type === 'application') {
        return 'RedisManager'; // Your module name — core skips its clear logic
    }
    return $handler; // Return null for types you don't handle
}, 10, 2);

When a handler is set, the admin UI displays a badge showing which module manages that cache type.

cache.status — Enrich Status Data

Add custom metrics or diagnostics to the cache status response.

Signature: array $status

Hook::addFilter('cache.status', function (array $status) {
    // Add Redis memory info to the status payload
    $status['redis_info'] = [
        'used_memory_mb' => Redis::info()['used_memory_human'] ?? 'N/A',
        'connected_clients' => Redis::info()['connected_clients'] ?? 0,
    ];
    return $status;
});

Actions (Event Callbacks)

These hooks fire at specific lifecycle points. They do not return values.

cache.clearing — Before a Type is Cleared

Fires immediately before any cache type is cleared. Use for logging or pre-clear snapshots.

Signature: string $type

Hook::addAction('cache.clearing', function (string $type) {
    Log::info("Cache clear starting: {$type}");
});

cache.clear.{type} — Handle Clearing for a Specific Type

This is the primary hook for implementing your own clearing logic. If your module has claimed a type via cache.type.handler, this is where the actual work happens.

Signature: void

// Handle clearing for your custom CDN cache type
Hook::addAction('cache.clear.cdn_purge', function () {
    CloudflareApi::purgeEverything(config('services.cloudflare.zone_id'));
});

// Or override the core's application cache clearing
Hook::addAction('cache.clear.application', function () {
    Redis::connection('cache')->flushdb();
});

cache.cleared — After a Type is Cleared

Fires after a cache type has been cleared (or attempted), with a success flag.

Signature: string $type, bool $success

Hook::addAction('cache.cleared', function (string $type, bool $success) {
    if ($success) {
        Log::info("Cache cleared successfully: {$type}");
    } else {
        Log::warning("Cache clear failed: {$type}");
    }
}, 10, 2);

cache.clear_all.before / cache.clear_all.after

Fires before and after a bulk clear operation (when the admin clicks "Clear All Caches").

Hook::addAction('cache.clear_all.before', function (array $types) {
    // $types = ['application', 'view', 'config', 'route', 'event', ...]
    Log::info('Bulk cache clear starting for: ' . implode(', ', $types));
});

Hook::addAction('cache.clear_all.after', function (array $results) {
    // $results = ['application' => 'success', 'view' => 'success', ...]
    $failed = array_filter($results, fn($r) => $r !== 'success');
    if (empty($failed)) {
        Log::info('All caches cleared successfully');
    }
});

REST API Endpoints

The Cache Management system exposes two authenticated API endpoints:

GET /api/v1/system/cache/status

Returns the current status of all registered cache types.

Response:

{
  "data": {
    "driver": "file",
    "store": "file",
    "types": [
      {
        "key": "application",
        "label": "Application Cache",
        "description": "General key-value cache store",
        "group": "laravel",
        "clearable": true,
        "handler": null,
        "info": { "driver": "file" }
      },
      {
        "key": "view",
        "label": "View Cache",
        "group": "laravel",
        "clearable": true,
        "handler": null,
        "info": { "compiled_count": 75 }
      }
    ]
  }
}

POST /api/v1/system/cache/clear

Clear one or more cache types.

Request body:

{
  "types": ["view", "config", "theme"]
}

Or clear everything:

{
  "types": ["all"]
}

Response:

{
  "success": true,
  "results": {
    "view": "success",
    "config": "success",
    "theme": "success"
  },
  "message": "All selected caches cleared successfully."
}

Practical Examples

Example 1: CDN Purge Module

A complete module that registers a CDN cache type and handles its clearing:

// In your module's ServiceProvider boot() method

use App\Facades\Hook;

public function boot(): void
{
    // Register the cache type
    Hook::addFilter('cache.types', function (array $types) {
        $types[] = [
            'key'         => 'cdn',
            'label'       => 'CDN Edge Cache',
            'description' => 'Purge Cloudflare edge cache for all zones',
            'group'       => 'module',
            'clearable'   => true,
        ];
        return $types;
    });

    // Handle clearing
    Hook::addAction('cache.clear.cdn', function () {
        $zoneId = config('services.cloudflare.zone_id');
        $apiToken = config('services.cloudflare.api_token');

        Http::withToken($apiToken)
            ->post("https://api.cloudflare.com/client/v4/zones/{$zoneId}/purge_cache", [
                'purge_everything' => true,
            ]);
    });
}

Example 2: Full-Page Cache Module (Taking Over Core)

A module that replaces the default application cache handler with a Varnish-based solution:

public function boot(): void
{
    // Claim the 'application' cache type
    Hook::addFilter('cache.type.handler', function (?string $handler, string $type) {
        if ($type === 'application') {
            return 'VarnishCache';
        }
        return $handler;
    }, 10, 2);

    // Provide custom clearing logic
    Hook::addAction('cache.clear.application', function () {
        // Ban all objects from Varnish
        Http::withHeaders(['X-Purge' => '.*'])
            ->request('BAN', config('services.varnish.host'));

        // Also clear Laravel's cache store
        Artisan::call('cache:clear');
    });
}

Example 3: Theme Functions — Auto-Clear on Setting Save

A theme that clears its own compiled assets when Theme Options are saved:

// In themes/my-theme/functions.php

Hook::addAction('cache.cleared', function (string $type, bool $success) {
    if ($type === 'theme' && $success) {
        // Regenerate compiled theme CSS
        $css = MyTheme::compileStyles();
        file_put_contents(public_path('themes/my-theme/compiled.css'), $css);
    }
}, 10, 2);

Auto-Clear on Theme Upload

When a theme is uploaded or updated via the admin panel, PolyCMS automatically clears the following caches:

  • View Cache — Removes stale compiled Blade templates
  • Config Cache — Ensures new functions.php settings definitions are loaded
  • OPcache — Flushes PHP bytecode cache so the new PHP files are compiled fresh
  • Theme Cache — Clears the ThemeManager's internal registry
  • Settings Cache — Reloads autoloaded settings (including new Theme Options)
  • Template Cache — Refreshes the TemplateResolver's view path resolution

This eliminates the common issue where new Theme Options or views do not appear after a theme update.

Admin UI

The Cache Management page is accessible at Settings Hub → System → Cache (or directly at /admin/settings/cache). It provides:

  • Cache driver info at the top (file, database, redis, etc.)
  • Grouped cache type cards organized by Laravel, PolyCMS, Server, and Module
  • Status badges showing compiled view count, cached/not-cached states, OPcache hit rate
  • Individual clear buttons per cache type with loading states
  • "Clear All Caches" master button
  • Handler badges showing which module manages a specific type
  • Full dark mode support

Best Practices

  1. Use cache.type.handler for conflict prevention. If your module takes over a cache type, always register a handler so core and other modules know not to interfere.

  2. Keep cache.clear.{type} handlers fast. The admin expects cache clearing to be near-instant. For slow operations (like CDN purges), consider queuing the job and returning immediately.

  3. Always return $handler in the filter chain. If you are checking cache.type.handler for a specific type, always return the original $handler for types you do not manage.

  4. Group as module. When registering custom cache types, use 'group' => 'module' so they appear in the "Module Extensions" section of the admin UI.

  5. Log cache events. Use cache.clearing and cache.cleared for audit trails, especially in production environments.

Related Resources