Cache Purging

Chainsaw caches rendered image variants. Depending on the provider, that cache lives locally (InterventionProvider / ImagineProvider — on a Flysystem filesystem plus an optional PSR-6 existence pool) or at the CDN edge (URL-grammar providers). When a source image is deleted or replaced, stale variants must be evicted. Chainsaw exposes a narrow Provider\PurgeableInterface interface for this.

Automatic alternative

If overwriting source files is common (CMS uploads, pipeline-driven regenerations), consider automatic cache invalidation via SourceVersioner instead of manual purging. The version tag flows through all three cache layers so stale entries age out on their own.

use Timber\Chainsaw\Provider\PurgeableInterface;

$purger = $factory->purger();
$purger?->purge('photos/cat.jpg');

purge() takes the original source path — the same string you passed to $factory->image(...). The provider hashes, transforms, or forwards it as needed.

Wiring

Local providers (Intervention, Imagine)

Both local providers implement Purgeable directly. No extra wiring — $factory->purger() returns the provider when it implements Purgeable. The example below wires InterventionProvider; ImagineProvider behaves identically.

use Timber\Chainsaw\Provider\Intervention\InterventionProvider;
use Timber\Chainsaw\ImageFactory;
use Timber\Chainsaw\Source\FlysystemSourceAdapter;

$provider = new InterventionProvider(
    sourceReader: new FlysystemSourceAdapter($sourceFs),
    cache: $cacheFs,
    cachePublicUrl: '/cache',
    manager: $imageManager,
    existsPool: $psr6Pool, // optional
);

$factory = new ImageFactory($provider);

$factory->purger()?->purge('photos/cat.jpg');
// Deletes every cached variant for that source
// from the cache filesystem and evicts matching PSR-6 entries.

URL providers (CDN)

No CDN purger ships today. Cloudflare and Imgix free/standard-tier APIs can only purge exact URLs (no source-scoped wildcard), which requires a per-source variant-URL index recorded at url() time. That design is tracked as a follow-up.

Stateless-origin providers (Imgproxy, Imagor, Thumbor, wsrv.nl) need no Chainsaw-side purge — they re-derive from the origin on every request. If a CDN sits in front of them, purge that CDN directly.

Framework glue

WordPress — delete_attachment

add_action('delete_attachment', static function (int $attachmentId) use ($factory): void {
    $path = get_attached_file($attachmentId);
    if ($path === false) {
        return;
    }
    $relative = ltrim(str_replace(wp_get_upload_dir()['basedir'], '', $path), '/');
    $factory->purger()?->purge($relative);
});

Pass the same path Chainsaw received when building image URLs for the attachment — typically the path relative to the uploads directory (or whatever your source Flysystem is rooted at).

Symfony — Doctrine postRemove

use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
use Doctrine\ORM\Event\PostRemoveEventArgs;
use Doctrine\ORM\Events;
use Timber\Chainsaw\ImageFactory;

#[AsDoctrineListener(event: Events::postRemove)]
final readonly class PurgeMediaListener
{
    public function __construct(
        private ImageFactory $chainsaw,
    ) {
    }

    public function postRemove(PostRemoveEventArgs $args): void
    {
        $entity = $args->getObject();
        if (! $entity instanceof Media) {
            return;
        }

        $this->chainsaw->purger()?->purge($entity->getPath());
    }
}

Custom purgers

Any object implementing Provider\PurgeableInterface can be injected:

$factory = new ImageFactory(provider: $provider, purger: $myPurger);

An injected purger takes precedence over a Purgeable provider, which is useful for decorating (e.g. a composite purger that evicts both InterventionProvider’s local cache and a CDN sitting in front of it). Chainsaw ships Provider\CompositePurger for exactly this fan-out — see Multiple providers.

Multiple providers

When several factories are managed through a FactoryRegistry, the registry hands you purgers without reaching into each factory:

// Fan-out: purge the source on every factory whose provider can purge.
// Non-purgeable URL/CDN providers are skipped automatically.
$registry->purger()->purge('photos/cat.jpg');

// Targeted: purge a single named factory.
$registry->purger('local')->purge('photos/cat.jpg');

purger() (no argument) returns a CompositePurger spanning the default factory plus every named factory that exposes a purger. It attempts all of them — one backend raising CannotPurge doesn’t stop the rest — then aggregates the failures into a single CannotPurge. Purgers shared across factories are de-duplicated, and a registry with nothing purgeable is a safe no-op.

purger('name') returns that one factory’s purger, throwing FactoryNotPurgeable if its provider can’t purge (or FactoryNotFound if the name isn’t registered).