marceli-to / intervention-image-cache
A simple image caching package for Intervention Image v3
Requires
- php: ^8.1
- illuminate/filesystem: ^10.0|^11.0
- illuminate/support: ^10.0|^11.0
- intervention/image: ^3.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0
- phpunit/phpunit: ^10.0
README
A simple image caching package for Laravel using Intervention Image v3.
Installation
You can install the package via composer:
composer require marceli-to/intervention-image-cache
Intervention Image v3 Compatibility
This package is built for Intervention Image v3, which has a significantly different API compared to v2. If you're upgrading from a package that used Intervention Image v2, please note these key differences:
- The v3 API uses interfaces like
ImageInterface
andModifierInterface
- Image manipulation methods return a new image instance rather than modifying the original
- The driver system has changed (GD is used by default in this package)
For more details on Intervention Image v3, please refer to the official documentation.
Configuration
Publish the configuration file:
php artisan vendor:publish --tag=image-cache-config
This will create a config/image-cache.php
file where you can configure:
- Cache path
- Cache lifetime
- Image search paths
- Available templates
- Route configuration
Usage
Basic Usage
use MarceliTo\InterventionImageCache\Facades\ImageCache; // Get a cached image $path = ImageCache::getCachedImage('large', 'image.jpg'); // Display the image in a view <img src="{{ asset('storage/cache/images/' . basename($path)) }}" alt="Image">
In a Controller
use MarceliTo\InterventionImageCache\Facades\ImageCache; class ImageController extends Controller { public function show($template, $filename) { $path = ImageCache::getCachedImage($template, $filename); if (!$path) { abort(404); } return response()->file($path); } }
In Views
The package automatically registers the necessary routes, so you can use it directly in your views:
<img src="/img/thumbnail/image.jpg" alt="Image"> <!-- With custom dimensions --> <img src="/img/large/image.jpg/1200/800" alt="Image"> <!-- With cropping (x,y,width,height) --> <img src="/img/crop/image.jpg/800/600/100,150,500,300" alt="Image">
The URL format is:
/img/{template}/{filename}/{maxWidth?}/{maxHeight?}/{coords?}
Where:
template
: One of the templates defined in your config (e.g., 'large', 'small', 'thumbnail', 'crop')filename
: The name of the image file to processmaxWidth
: (Optional) Maximum width for the output imagemaxHeight
: (Optional) Maximum height for the output imagecoords
: (Optional) Comma-separated string in the formatx,y,width,height
for cropping
Important notes about cropping coordinates:
- All four values must be numeric
- x and y coordinates must be non-negative (values less than 0 will be set to 0)
- Width and height must be positive (values less than or equal to 0 will skip cropping)
- Coordinates that exceed image dimensions will be adjusted automatically
- If all coordinates are 0 (0,0,0,0), cropping will be skipped
Custom Controller
If you prefer to use your own controller, you can disable the automatic route registration in the config file:
// config/image-cache.php 'register_routes' => false,
Then create your own route and controller:
// routes/web.php Route::get('/img/{template}/{filename}/{maxW?}/{maxH?}/{coords?}', [ImageController::class, 'getResponse']); // App\Http\Controllers\ImageController.php public function getResponse($template, $filename, $maxW = null, $maxH = null, $coords = null) { $params = []; if ($maxW) { $params['maxWidth'] = (int) $maxW; } if ($maxH) { $params['maxHeight'] = (int) $maxH; } if ($coords) { $params['coords'] = $coords; } $path = ImageCache::getCachedImage($template, $filename, $params); if (!$path || !file_exists($path)) { abort(404); } $mime = mime_content_type($path); $content = file_get_contents($path); return response($content) ->header('Content-Type', $mime) ->header('Cache-Control', 'public, max-age=31536000'); }
Programmatic Usage
You can also use the package programmatically:
use MarceliTo\InterventionImageCache\Facades\ImageCache; // Get a cached image $path = ImageCache::getCachedImage('large', 'image.jpg', [ 'maxWidth' => 1200, 'maxHeight' => 800 ]); // Get a cached image with cropping $path = ImageCache::getCachedImage('crop', 'image.jpg', [ 'maxWidth' => 800, 'maxHeight' => 600, 'coords' => '100,150,500,300' // Format: x,y,width,height ]);
Clearing the Cache
You can clear the cache using the provided Artisan command:
# Clear all cached images php artisan image:clear-cache # Clear cached images for a specific template php artisan image:clear-cache large
Or programmatically:
use MarceliTo\InterventionImageCache\Facades\ImageCache; // Clear all cached images ImageCache::clearCache(); // Clear cached images for a specific template ImageCache::clearTemplateCache('large');
Custom Templates
You can create your own templates by:
- Creating a class that implements
Intervention\Image\Interfaces\ModifierInterface
- Registering it in the
templates
array in the config file
Example:
<?php namespace App\ImageTemplates; use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\ModifierInterface; class Custom implements ModifierInterface { public function apply(ImageInterface $image): ImageInterface { return $image->resize(500, 500); } }
Then in your config:
'templates' => [ // ... 'custom' => \App\ImageTemplates\Custom::class, ],
Using Cropping in Custom Templates
You can implement cropping in your custom templates by following this pattern:
<?php namespace App\ImageTemplates; use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\ModifierInterface; class CustomCrop implements ModifierInterface { /** * Coordinates for cropping (x,y,width,height) */ protected $coords = null; /** * Maximum width for resizing after crop */ protected $max_width = null; /** * Maximum height for resizing after crop */ protected $max_height = null; /** * Constructor with optional parameters */ public function __construct($max_width = null, $max_height = null, $coords = null) { // Ensure max dimensions are positive $this->max_width = $max_width > 0 ? $max_width : null; $this->max_height = $max_height > 0 ? $max_height : null; $this->coords = $coords; } /** * Apply filter to image */ public function apply(ImageInterface $image): ImageInterface { // First apply cropping if coordinates are provided if ($this->coords && is_string($this->coords)) { $image = $this->applyCropping($image); } // Then resize if needed if ($this->max_width || $this->max_height) { $image = $this->applyResize($image); } return $image; } /** * Apply cropping based on coordinates * * @param ImageInterface $image * @return ImageInterface */ protected function applyCropping(ImageInterface $image): ImageInterface { // Parse coordinates (x,y,width,height) $coordsArray = explode(',', $this->coords); // Ensure we have all 4 coordinates and they're numeric if (count($coordsArray) === 4 && array_reduce($coordsArray, fn($carry, $item) => $carry && is_numeric(trim($item)), true)) { $x = max(0, (int) trim($coordsArray[0])); $y = max(0, (int) trim($coordsArray[1])); $width = (int) trim($coordsArray[2]); $height = (int) trim($coordsArray[3]); // Skip cropping if width or height is 0/negative, or if all coordinates are 0 if ($width <= 0 || $height <= 0 || ($x === 0 && $y === 0 && $width === 0 && $height === 0)) { return $image; } // Ensure coordinates don't exceed image dimensions $imageWidth = $image->width(); $imageHeight = $image->height(); if ($x >= $imageWidth || $y >= $imageHeight) { return $image; } // Adjust width/height if they exceed image boundaries $width = min($width, $imageWidth - $x); $height = min($height, $imageHeight - $y); // Apply crop return $image->crop($width, $height, $x, $y); } return $image; } /** * Apply resize after cropping if needed * * @param ImageInterface $image * @return ImageInterface */ protected function applyResize(ImageInterface $image): ImageInterface { $width = $image->width(); $height = $image->height(); // Calculate aspect ratios if both dimensions are provided if ($this->max_width && $this->max_height) { $width_ratio = $width / $this->max_width; $height_ratio = $height / $this->max_height; if ($width_ratio > 1 || $height_ratio > 1) { $resize_ratio = max($width_ratio, $height_ratio); $new_width = round($width / $resize_ratio); $new_height = round($height / $resize_ratio); return $image->resize($new_width, $new_height); } } // Handle single dimension constraints elseif ($this->max_width && $width > $this->max_width) { $ratio = $width / $this->max_width; return $image->resize( $this->max_width, round($height / $ratio) ); } elseif ($this->max_height && $height > $this->max_height) { $ratio = $height / $this->max_height; return $image->resize( round($width / $ratio), $this->max_height ); } return $image; } }
Testing
The package includes a comprehensive test suite. To run the tests:
composer test
Or you can run PHPUnit directly:
./vendor/bin/phpunit
The test suite includes:
- Unit tests for template classes
- Unit tests for the ImageCache class
- Feature tests for the HTTP endpoints
License
The MIT License (MIT).