Getting Started
Requirements
- PHP 8.4+
- A provider backend (see below)
Installation
composer require timber/chainsaw
Depending on your provider, install the required dependencies:
composer require intervention/image league/flysystem
composer require imagine/imagine league/flysystem
No additional dependencies required.
No additional dependencies required.
Setup
1. Create a provider
use Intervention\Image\Drivers\Gd\Driver as GdDriver;
use Intervention\Image\ImageManager;
use League\Flysystem\Filesystem;
use League\Flysystem\Local\LocalFilesystemAdapter;
use Timber\Chainsaw\Provider\Intervention\InterventionProvider;
use Timber\Chainsaw\Source\FlysystemSourceAdapter;
$sourceFs = new Filesystem(new LocalFilesystemAdapter('/path/to/images'));
$cache = new Filesystem(new LocalFilesystemAdapter('/path/to/cache'));
$provider = new InterventionProvider(
sourceReader: new FlysystemSourceAdapter($sourceFs),
cache: $cache,
cachePublicUrl: '/images/cache',
manager: new ImageManager(new GdDriver()),
);
use Imagine\Gd\Imagine; // or Imagine\Imagick\Imagine
use League\Flysystem\Filesystem;
use League\Flysystem\Local\LocalFilesystemAdapter;
use Timber\Chainsaw\Provider\Imagine\ImagineProvider;
use Timber\Chainsaw\Source\FlysystemSourceAdapter;
$sourceFs = new Filesystem(new LocalFilesystemAdapter('/path/to/images'));
$cache = new Filesystem(new LocalFilesystemAdapter('/path/to/cache'));
$provider = new ImagineProvider(
sourceReader: new FlysystemSourceAdapter($sourceFs),
cache: $cache,
cachePublicUrl: '/images/cache',
imagine: new Imagine(),
);
use Timber\Chainsaw\Provider\Cloudinary\CloudinaryProvider;
$provider = new CloudinaryProvider(
cloudName: 'my-cloud',
deliveryType: 'upload',
);
use Timber\Chainsaw\Provider\Cloudflare\CloudflareProvider;
$provider = new CloudflareProvider(
host: 'https://example.com',
);
2. Create a factory
use Timber\Chainsaw\ImageFactory;
$factory = new ImageFactory($provider);
3. Use it
$image = $factory->image('photo.jpg');
// Resize
echo $image->width(400);
// Chain manipulations
echo $image->crop(800, 600)->greyscale()->quality(80);
Factory configuration
ImageFactory is final readonly — every dependency is passed at construction:
use Timber\Chainsaw\Encoding;
use Timber\Chainsaw\Enum\Format;
use Timber\Chainsaw\ImageFactory;
use Timber\Chainsaw\Manipulator\Crop;
use Timber\Chainsaw\Manipulator\Watermark;
use Timber\Chainsaw\Preset;
use Timber\Chainsaw\Presets;
use Timber\Chainsaw\Watermark\WatermarkFromUrl;
$factory = new ImageFactory(
provider: $provider,
// Default manipulators applied to every image
defaults: [new Watermark(new WatermarkFromUrl('logo.png'), Position::BottomRight)],
// Default encoding
defaultEncoding: new Encoding(quality: 85),
// Named presets
presets: (new Presets())
->add(new Preset('thumbnail', [new Crop(200, 200)]))
->add(new Preset('hero', [new Crop(1200, 600)])),
);
Image metadata
Providing source dimensions enables dimension math without I/O, which in turn lets the renderer emit intrinsic width/height attributes on rendered <img> and <source> for CLS-safe markup.
Explicit metadata (zero I/O)
If you already know the source dimensions (from a CMS, database, or manifest), pass them directly:
use Timber\Chainsaw\Metadata\ImageMetadata;
$meta = new ImageMetadata(1920, 1080);
$image = $factory->image('photo.jpg', $meta);
$dims = $image->width(400)->dimensions();
// → width: 400, height: 225 (calculated from ratio)
Without metadata, dimensions are unknown until the provider processes the image.
Auto-resolved metadata (via MetadataResolverInterface)
For workflows where dimensions aren’t known upfront, wire a MetadataResolverInterface on the factory. The resolver reads the source bytes once and caches the result in a PSR-6 pool, so subsequent calls for the same source are zero-I/O:
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Timber\Chainsaw\ImageFactory;
use Timber\Chainsaw\Metadata\CachedMetadataResolver;
use Timber\Chainsaw\Source\FlysystemSourceAdapter;
$resolver = new CachedMetadataResolver(
upstream: new FlysystemSourceAdapter($source), // Flysystem holding the originals
pool: new FilesystemAdapter(directory: '/var/cache/metadata'),
);
$factory = new ImageFactory(
provider: $provider,
metadataResolver: $resolver,
);
$image = $factory->image('photo.jpg'); // resolver reads + caches on first call
Shipped resolvers:
FlysystemSourceAdapter— reads source bytes, dimensions, and last-modified version via any Flysystem adapter (local, S3, FTP, memory). Same class used for URL-grammar provider source bytes.NativeMetadataResolver— reads local paths withgetimagesize()(optional basePath prefix).
Explicit metadata always wins: $factory->image('photo.jpg', $explicit) never calls the resolver.
See also: Automatic cache invalidation — when your resolver also implements SourceVersioner, the library propagates a version tag through metadata, variant, and CDN caches so overwriting a source bytes auto-invalidates everything.