marceli-to / 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
Prerequisites
- PHP 8.1 or higher
- Laravel 10.x or 11.x
- Composer
Basic Installation
You can install the package via composer:
composer require marceli-to/image-cache
Post-Installation Setup
- 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
- Configure your filesystem:
Ensure your Laravel filesystem is properly configured for storing cached images. By default, this package uses the public
disk. Update your .env
file and config/filesystems.php
as needed:
// config/filesystems.php 'disks' => [ // ... 'public' => [ 'driver' => 'local', 'root' => storage_path('app/public'), 'url' => env('APP_URL').'/storage', 'visibility' => 'public', ], // ... ],
Then create the symbolic link to make the storage publicly accessible:
php artisan storage:link
- Service Provider Registration:
The package uses Laravel's auto-discovery feature to register the service provider and facade automatically. If you have disabled auto-discovery, add the following to your config/app.php
:
// config/app.php 'providers' => [ // ... MarceliTo\ImageCache\ImageCacheServiceProvider::class, ], 'aliases' => [ // ... 'ImageCache' => MarceliTo\ImageCache\Facades\ImageCache::class, ],
- Create cache directory:
The package will attempt to create the cache directory automatically, but you may need to ensure proper permissions:
mkdir -p storage/app/public/cache/images chmod -R 775 storage/app/public/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
Features
Modern PHP Type Declarations
The package uses modern PHP 7.4+ type declarations throughout the codebase for improved code quality and IDE support:
- Parameter type hints for all method parameters
- Return type declarations for all methods
- Property type declarations for class properties
Robust Error Handling
The package includes comprehensive error handling:
- Proper exception handling with try/catch blocks
- Detailed logging using Laravel's Log facade
- Appropriate HTTP responses for different error scenarios
- Specific exception types for different error conditions
Input Validation
All user inputs are thoroughly validated:
- Filename validation to prevent directory traversal attacks
- Template validation to ensure templates exist
- Parameter validation for dimensions, coordinates, and ratios
- Sanitization of all inputs before processing
Usage
Basic Usage
use MarceliTo\ImageCache\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\ImageCache\Facades\ImageCache; class ImageController extends Controller { public function show($template, $filename) { try { $path = ImageCache::getCachedImage($template, $filename); if (!$path) { return response()->make('Image not found', 404); } return response()->file($path); } catch (InvalidArgumentException $e) { // Handle validation errors return response()->make('Invalid input: ' . $e->getMessage(), 400); } catch (Exception $e) { // Handle unexpected errors Log::error("Error in image controller: {$e->getMessage()}", [ 'exception' => $e ]); return response()->make('Server error', 500); } } }
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 cropping using maxSize (single dimension) --> <img src="/img/crop/image.jpg/800/100,150,200,300" alt="Image"> <!-- With cropping using width and height --> <img src="/img/crop/image.jpg/800/600/100,150,200,300" alt="Image"> <!-- With aspect ratio --> <img src="/img/crop/image.jpg/800/600/100,150,200,300/16x9" alt="Image">
The URL format for standard templates is:
/img/{template}/{filename}
The URL format for crop with maxSize is:
/img/crop/{filename}/{maxSize?}/{coords?}/{ratio?}
The URL format for crop with dimensions is:
/img/crop/{filename}/{maxWidth?}/{maxHeight?}/{coords?}/{ratio?}
Where:
template
: One of the templates defined in your config (e.g., 'large', 'small', 'thumbnail')filename
: The name of the image file to processmaxSize
: (Optional) Maximum size for the output imagemaxWidth
: (Optional) Maximum width for the output imagemaxHeight
: (Optional) Maximum height for the output imagecoords
: (Optional) Comma-separated string in the formatwidth,height,x,y
for croppingratio
: (Optional) Aspect ratio in the formatwidth x height
(e.g., '16x9')
Important notes about inputs:
- Filenames must contain only alphanumeric characters, underscores, hyphens, slashes, and dots
- File extensions must be one of: jpg, jpeg, png, gif, webp
- Coordinates must be in the format
width,height,x,y
with all values being positive integers - Ratio must be in the format
width x height
with both values being positive integers - Maximum dimensions must be positive integers and within the configured limits
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(Request $request, string $template, string $filename, ?string $maxW = null, ?string $maxH = null, ?string $coords = null): Response { try { // Validate inputs $this->validateTemplate($template); $this->validateFilename($filename); $params = []; if ($maxW) { $this->validateMaxDimension($maxW, 'maxWidth'); $params['maxWidth'] = (int) $maxW; } if ($maxH) { $this->validateMaxDimension($maxH, 'maxHeight'); $params['maxHeight'] = (int) $maxH; } if ($coords) { $this->validateCoords($coords); $params['coords'] = $coords; } $path = ImageCache::getCachedImage($template, $filename, $params); if (!$path) { return response()->make('Image not found', 404); } return response()->file($path); } catch (Exception $e) { // Handle errors return response()->make('Error: ' . $e->getMessage(), 400); } }
Programmatic Usage
You can also use the package programmatically:
use MarceliTo\ImageCache\Facades\ImageCache; use InvalidArgumentException; use RuntimeException; try { // 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' => '200,300,100,150', // Format: width,height,x,y 'ratio' => '16x9' ]); if (!$path) { // Handle image not found } } catch (InvalidArgumentException $e) { // Handle validation errors Log::warning("Invalid input: {$e->getMessage()}"); } catch (RuntimeException $e) { // Handle runtime errors Log::error("Error processing image: {$e->getMessage()}"); } catch (Exception $e) { // Handle unexpected errors Log::error("Unexpected error: {$e->getMessage()}"); }
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\ImageCache\Facades\ImageCache; try { // Clear all cached images $success = ImageCache::clearAllCache(); // Clear cached images for a specific template $success = ImageCache::clearTemplateCache('large'); // Clear cached images for a specific file $success = ImageCache::clearImageCache('image.jpg'); } catch (Exception $e) { Log::error("Error clearing cache: {$e->getMessage()}"); }
Custom Templates
You can create your own templates by:
- Creating a class that implements
Intervention\Image\Interfaces\ModifierInterface
- Adding the template to your config file
Example template class:
<?php namespace App\ImageTemplates; use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\ModifierInterface; class CustomTemplate implements ModifierInterface { /** * Maximum width for the image */ protected int $maxWidth = 1000; /** * Maximum height for the image */ protected int $maxHeight = 800; /** * Apply filter to image */ public function apply(ImageInterface $image): ImageInterface { // Get image orientation $orientation = $image->height() > $image->width() ? 'portrait' : 'landscape'; // Scale down the image based on orientation while maintaining aspect ratio if ($orientation === 'landscape') { return $image->scaleDown(width: $this->maxWidth); } else { return $image->scaleDown(height: $this->maxHeight); } } }
Then add it to your config:
// config/image-cache.php 'templates' => [ 'custom' => \App\ImageTemplates\CustomTemplate::class, // ...other templates ],
Error Handling
The package includes comprehensive error handling:
Exception Types
InvalidArgumentException
: Thrown when input validation failsRuntimeException
: Thrown when file operations or image processing failsException
: Caught for any unexpected errors
Logging
All errors and warnings are logged using Laravel's Log facade:
- Warning level: Input validation failures, image not found
- Error level: File operation failures, image processing errors
HTTP Responses
The package returns appropriate HTTP responses:
- 400 Bad Request: For invalid inputs
- 404 Not Found: When images can't be found
- 500 Server Error: For unexpected errors
Security
The package includes several security features:
- Path Sanitization: Prevents directory traversal attacks
- Input Validation: Validates all user inputs before processing
- File Extension Validation: Only allows specific image file extensions
- Error Message Sanitization: Ensures error messages don't expose sensitive information
License
The MIT License (MIT). Please see License File for more information.