fomvasss/laravel-imagepresets
Composer 安装命令:
composer require fomvasss/laravel-imagepresets
包简介
Image presets (on-the-fly resize/convert) for Laravel via League Glide
README 文档
README
On-the-fly image resizing, converting and caching for Laravel, powered by League/Glide.
Support
If this package is useful to you, consider supporting its development:
USDT TRC20 address:
THLgp6DxiAtbNHvgnKV56vk1L38UuUagKf
Features
- On-the-fly resize, crop and format conversion (WebP, AVIF, JPG, PNG, GIF)
- Automatic caching of processed images to any Laravel filesystem disk (local, S3, GCS, FTP, etc.)
- Remote disk support (S3 / GCS / FTP) — Glide processes locally, result is uploaded automatically
- SVG passthrough with optional XSS sanitization
- Remote image support with SSRF and image-bomb protection
- Race condition protection via Cache lock (Redis/Memcached)
- Auto-registered route — no manual setup required
- Facade + global helper + Blade directive
- Artisan command
imagepresets:clear - Fully configurable via
config/imagepresets.php
Requirements
| Dependency | Version |
|---|---|
| PHP | ^8.1 |
| Laravel | 10 / 11 / 12 / 13 |
| league/glide | ^2.0 | ^3.0 |
Optional:
imagickPHP extension — required for AVIF output and SVG rasterizationenshrined/svg-sanitize— full SVG sanitization (recommended)
Installation
composer require fomvasss/laravel-imagepresets
The service provider is auto-discovered via Laravel's package discovery.
Publish configuration
php artisan vendor:publish --tag=imagepresets-config
This creates config/imagepresets.php in your application.
Configuration
Key options in config/imagepresets.php:
// Route 'route' => [ 'prefix' => env('IMAGEPRESET_ROUTE_PREFIX', 'imagepreset'), 'name' => env('IMAGEPRESET_ROUTE_NAME', 'imagepreset'), 'middleware' => ['throttle:240,1'], ], // Storage disk and subdirectory for cached presets 'disk' => env('IMAGEPRESET_DISK', 'public'), // or 's3', 'gcs', etc. 'path' => env('IMAGEPRESET_PATH', ''), // Processing driver: 'gd' or 'imagick' 'driver' => env('IMAGEPRESET_DRIVER', 'gd'), // Default output quality and format 'quality' => 80, 'format' => 'webp', // HTTP cache lifetime (seconds) 'cache_max_age' => 31536000, // Allowed dimensions, qualities, fit methods, formats // Use ['*'] as a wildcard to allow any value (no restriction) 'allowed_widths' => [100, 200, 300, 400, 600, 800, 1000, 1200, 1600], 'allowed_heights' => [100, 200, 300, 400, 600, 800], 'allowed_sizes' => [[300, 200], [600, 400], [1200, 800]], 'allowed_qualities' => [50, 60, 70, 80, 90, 100], 'allowed_fits' => ['contain', 'crop', 'fill', 'fill-max', 'max', 'stretch'], 'allowed_formats' => ['webp', 'jpg', 'png', 'gif'], // SVG optionslaravel-imagepresets 'svg' => [ 'sanitize' => true, 'remove_remote_references' => true, 'rasterize' => false, // requires driver=imagick ], // Remote image protection 'max_download_bytes' => 20 * 1024 * 1024, 'max_image_pixels' => 150_000_000, // ~150 Mpx image-bomb protection 'allowed_hosts' => [], // e.g. ['cdn.example.com'] // Local Glide working dir when using a remote disk (S3/GCS/FTP) 'local_cache_dir' => storage_path('app/imagepreset_glide_cache'),
Cache Lock: For correct multi-server behaviour set
CACHE_DRIVER=redisin.env. Thefiledriver only locks within a single PHP process.
Remote disk (S3 / GCS / FTP)
Set the disk in .env — the package detects it automatically:
IMAGEPRESET_DISK=s3 IMAGEPRESET_PATH=imagepresets
Processing flow for remote disks:
- Glide processes the image into
local_cache_dir(local) - The result is uploaded to the remote disk via Flysystem
- The local file is deleted
- The response is streamed directly from the remote disk
# Clear S3 preset cache
php artisan imagepresets:clear --disk=s3
Usage
Endpoint
GET /imagepreset?src=...&w=...&h=...&q=...&fm=...&fit=...
Query parameters
| Parameter | Type | Description |
|---|---|---|
src |
string | Required. Relative path or remote URL of the source image |
preset |
string | Named preset defined in config/imagepresets.php (presets section) |
w |
int | Output width in pixels (must be in allowed_widths, or any if ['*']) |
h |
int | Output height in pixels (must be in allowed_heights, or any if ['*']) |
q |
int | Quality 1–100 (must be in allowed_qualities, or any if ['*']) |
fm |
string | Output format: webp, jpg, png, gif, avif |
fit |
string | Fit method: contain, crop, fill, fill-max, max, stretch |
blur |
int | Blur radius 0–100 |
sharp |
int | Sharpen amount 0–100 |
or |
string | Orientation: auto (EXIF), 0, 90, 180, 270 |
crop |
string | Coordinate crop: w,h,x,y — e.g. 200,200,10,10 |
bg |
string | Background fill colour (hex without #): fff, ff5733 |
When both w and h are passed, the pair must be listed in allowed_sizes (unless allowed_sizes = ['*']).
Fit methods
| Value | Description |
|---|---|
contain |
Scales the image to fit within w×h, preserving aspect ratio. No cropping. Transparent/empty space is not filled. |
max |
Same as contain but never upscales beyond the original dimensions. |
fill |
Scales the image to fill the entire w×h canvas. Empty space is filled with the bg colour. May upscale small images. |
fill-max |
Same as fill but never upscales — if the image is smaller than the canvas it is centred and the remaining space is filled with bg. Equivalent to Spatie MediaLibrary's Fit::FillMax. |
crop |
Scales and crops the image to exactly w×h. No empty space, but edges may be trimmed. |
stretch |
Stretches the image to exactly w×h ignoring the aspect ratio. |
fill-maxvscrop: usefill-maxwhen the full image must remain visible (e.g. og:image banners, product feeds); usecropwhen exact pixel dimensions are required and trimming is acceptable.
// The full image is visible; white padding fills the remaining canvas area $url = imagepreset_url('photo.jpg', ['w' => 1300, 'h' => 650, 'fit' => 'fill-max', 'bg' => 'ffffff', 'fm' => 'jpg']);
Wildcard mode
Set any of the allowed_* config keys to ['*'] to disable the corresponding restriction entirely:
'allowed_widths' => ['*'], // any width is accepted 'allowed_heights' => ['*'], // any height is accepted 'allowed_sizes' => ['*'], // any w+h pair is accepted 'allowed_qualities' => ['*'], // any quality value 1–100 is accepted
Note: Base HTTP validation limits still apply —
wandhare capped at20000,qmust be an integer.
Helper function
$url = imagepreset_url('storage/images/photo.jpg', ['w' => 800, 'fm' => 'webp']); // → https://example.com/imagepreset?fm=webp&src=storage%2Fimages%2Fphoto.jpg&w=800
$url = imagepreset_url('https://example.com/storage/images/photo.jpg', ['w' => 800, 'fm' => 'webp']); // → https://example.com/imagepreset?fm=webp&src=https://example.com/storage%2Fimages%2Fphoto.jpg&w=800
Named Presets
Define reusable named presets in config/imagepresets.php:
'presets' => [ 'thumb' => ['w' => 300, 'h' => 200, 'fm' => 'webp', 'q' => 80, 'fit' => 'crop'], 'hero' => ['w' => 1200, 'fm' => 'webp', 'q' => 85], 'avatar' => ['w' => 96, 'h' => 96, 'fm' => 'webp', 'fit' => 'crop'], // og:image social banner — fill-max keeps the full image, fills gaps with bg colour 'og_banner' => ['w' => 1300, 'h' => 650, 'fit' => 'fill-max', 'fm' => 'jpg', 'q' => 85, 'bg' => 'ffffff'], ],
Use a preset by name:
// Helper — shorthand string $url = imagepreset_url('photo.jpg', 'thumb'); // Helper — array key $url = imagepreset_url('photo.jpg', ['preset' => 'hero']); // Facade Imagepreset::url('photo.jpg', 'avatar'); // Blade directive <img src="@imagepreset('photo.jpg', 'thumb')" alt="Thumbnail"> // HTML endpoint <img src="/imagepreset?src=photo.jpg&preset=thumb" alt="Thumbnail">
Explicit params passed alongside a preset override the preset defaults:
// Uses thumb preset but overrides format to jpg $url = imagepreset_url('photo.jpg', ['preset' => 'thumb', 'fm' => 'jpg']);
Security: preset params come from trusted config and bypass
allowed_widths/allowed_heights/allowed_sizes/allowed_qualitieschecks. Explicit override params are still validated against the allowlists.
Facade
use Fomvasss\Imagepresets\Facades\Imagepreset; $url = Imagepreset::url('storage/images/photo.jpg', ['w' => 400, 'h' => 300]);
Blade directive
<img src="@imagepreset('storage/images/photo.jpg', ['w' => 600, 'fm' => 'webp'])" alt="Photo">
HTML example
<img src="/imagepreset?src=storage/images/photo.jpg&w=800&fm=webp" alt="Photo">
SVG Support
SVG files are passed through without dimension transformations. The cache key is based solely on src to avoid duplicates.
// SVG is cached and served as-is (sanitized by default) $url = imagepreset_url('storage/icons/logo.svg');
Enable sanitization (recommended) in config:
'svg' => [ 'sanitize' => true, ],
For full sanitization install the optional dependency:
composer require enshrined/svg-sanitize
Without it, a basic regex sanitizer removes <script> tags, on* event attributes, and javascript: URIs.
SVG rasterization
To convert SVG to raster when w, h or fm is passed:
'svg' => [ 'rasterize' => true, // requires driver=imagick ],
Remote Images
Pass any allowed external URL as src:
$url = imagepreset_url('https://cdn.example.com/photo.jpg', ['w' => 400]);
Allowed hosts must be declared in config:
'allowed_hosts' => [ 'cdn.example.com', ],
Security measures:
- HTTP redirects are blocked (SSRF protection)
- Private and reserved IP ranges are rejected
localhostis rejected- Maximum download size:
max_download_bytes - Maximum pixel area:
max_image_pixels(image-bomb protection)
Artisan Commands
Clear preset cache
php artisan imagepresets:clear
Options:
| Option | Description |
|---|---|
--disk= |
Override disk (default: imagepresets.disk config) |
--path= |
Override path (default: imagepresets.path config) |
--temp |
Also clear source_dir and temp_dir |
# Clear cache + temp directories php artisan imagepresets:clear --temp # Clear a custom disk/path php artisan imagepresets:clear --disk=s3 --path=presets
Audit Log (discovering required sizes)
Use audit logging in local / staging environments to discover which image params
the frontend actually requests — then promote them to explicit allowlists for production.
Workflow
- Enable wildcard mode + audit log in
.env(non-production only):
IMAGEPRESET_AUDIT_LOG=true # IMAGEPRESET_AUDIT_LOG_ONLY_NEW=true # log only cache misses (first generation)
Entries are written to the application default log channel (
LOG_CHANNELin.env).
- Let the frontend work freely — every new param combination is logged:
{"message":"imagepreset_request","context":{"params":{"src":"products/photo.jpg","w":640,"fm":"webp"},"ip":"127.0.0.1","url":"http://app.test/imagepreset?src=..."}}
- Analyse the log to collect unique combinations:
# All unique w values requested grep -oh '"w":[0-9]*' storage/logs/*.log | sort -u # All unique [w, h] pairs grep -oh '"w":[0-9]*,"h":[0-9]*' storage/logs/*.log | sort -u # All unique quality values grep -oh '"q":[0-9]*' storage/logs/*.log | sort -u
- Promote findings to explicit allowlists in
config/imagepresets.phpand disable wildcard + audit log before deploying to production:
'allowed_widths' => [320, 640, 960, 1280], 'allowed_heights' => [200, 400], 'allowed_sizes' => [[640, 400], [1280, 800]], 'allowed_qualities' => [80, 90],
# .env (production) IMAGEPRESET_AUDIT_LOG=false
Config reference
| Key | Default | Description |
|---|---|---|
audit_log.enabled |
false |
Enable/disable via IMAGEPRESET_AUDIT_LOG |
audit_log.only_new |
true |
Log only cache misses — skip already-cached combinations |
Excluding Preset Cache from Backups
The /imagepreset cache directory contains auto-generated files that can always be
recreated on demand. Including it in backups wastes storage and increases backup time.
Recommended: separate disk outside the backup scope
Define a dedicated filesystem disk that lives outside your regular backup directory:
// config/filesystems.php 'imagepresets' => [ 'driver' => 'local', 'root' => storage_path('app/imagepresets'), // not inside app/public // 'url' => env('APP_URL').'/imagepresets', // no need for a URL since this is only a temporary working dir for Glide 'visibility' => 'public', 'throw' => false, ],
# .env IMAGEPRESET_DISK=imagepresets
Now the cache folder is completely outside storage/app/public and will never
appear in backups that include only storage_path('app/public').
Note: The cache directory is recreated automatically on the first request for each preset — no manual intervention is needed after a restore.
HTTP Caching & CDN / Reverse Proxy
Every response from the /imagepreset endpoint includes headers optimised for aggressive edge caching:
Cache-Control: public, max-age=31536000, s-maxage=31536000, immutable
ETag: "<hash>"
Last-Modified: <date>
Nginx
Cache processed presets directly on the server — Laravel is bypassed on subsequent requests:
proxy_cache_path /var/cache/nginx/imagepresets levels=1:2 keys_zone=imagepresets:20m max_size=2g inactive=365d use_temp_path=off; server { # ... location /imagepreset { proxy_cache imagepresets; proxy_cache_valid 200 365d; proxy_cache_use_stale error timeout updating http_500 http_502 http_503; proxy_cache_lock on; # prevents cache stampede proxy_cache_key "$scheme$host$request_uri"; add_header X-Cache-Status $upstream_cache_status; proxy_pass http://127.0.0.1:9000; # your PHP-FPM / app } }
Verifying the cache
Make the same request twice using GET — the first returns MISS, the second must return HIT.
This command works for both Nginx (X-Cache-Status) and Cloudflare (cf-cache-status):
# Sends GET, discards body, prints response headers curl -s -o /dev/null -D - "https://example.com/imagepreset?src=photo.jpg&w=800&fm=webp"
Expected response headers:
X-Cache-Status: HIT # Nginx proxy cache
cf-cache-status: HIT # Cloudflare edge cache
Note:
curl -Isends a HEAD request — Cloudflare does not cache HEAD and always returnscf-cache-status: DYNAMIC. Always use a GET request to verify caching.
X-Cache-Status |
Meaning |
|---|---|
MISS |
No cache entry — request went to PHP |
HIT |
Served from Nginx cache, PHP was not invoked |
EXPIRED |
Cache entry exists but expired — being refreshed |
BYPASS |
Caching was skipped |
cf-cache-status |
Meaning |
|---|---|
MISS |
Cloudflare has no cache — request forwarded to origin |
HIT |
Served from Cloudflare edge cache |
DYNAMIC |
Not cached — HEAD request or no Cache Rule configured |
EXPIRED |
Cache expired — being refreshed from origin |
Compare response times — a HIT is typically 10–100× faster:
time curl -s "https://example.com/imagepreset?src=photo.jpg&w=800" -o /dev/null
Cloudflare
Add a Cache Rule in the Cloudflare dashboard:
- If → URI Path starts with
/imagepresets - Then → Cache Level: Cache Everything, Edge Cache TTL: 1 year
Or via Terraform / API:
{
"description": "Cache imagepresets",
"expression": "(http.request.uri.path starts_with \"/imagepreset\")",
"action": "set_cache_settings",
"action_parameters": {
"cache": true,
"edge_ttl": { "mode": "override_origin", "default": 31536000 },
"browser_ttl": { "mode": "override_origin", "default": 31536000 }
}
}
Cache invalidation
When you change a preset definition or replace a source image, the cached files must be purged:
# Clear the Laravel-level disk cache (always required) php artisan imagepresets:clear # Nginx — reload or flush proxy cache directory find /var/cache/nginx/imagepresets -type f -delete # Cloudflare — purge by prefix via API curl -X POST "https://api.cloudflare.com/client/v4/zones/{ZONE_ID}/purge_cache" \ -H "Authorization: Bearer {TOKEN}" \ -H "Content-Type: application/json" \ --data '{"prefixes":["https://example.com/imagepreset"]}'
Tip: Use versioned
srcpaths (e.g.photo_v2.jpg) or append a query param (?v=2) to bust the cache without a full purge.
Response Headers
Every response includes:
| Header | Value |
|---|---|
Content-Type |
Correct MIME type |
Cache-Control |
See HTTP Caching section below |
ETag |
Based on file mtime + size |
Last-Modified |
File modification time |
Content-Disposition |
inline |
X-Content-Type-Options |
nosniff |
Content-Security-Policy |
SVG only: default-src 'none'; style-src 'unsafe-inline'; sandbox |
HTTP Caching
The package implements intelligent cache headers based on file generation state:
Newly generated files (first request):
Cache-Control: no-store
- File was just created — may have issues
- Prevents aggressive browser/CDN caching
- Next request will re-validate
Cached files (subsequent requests):
Cache-Control: public, max-age=31536000, s-maxage=31536000, immutable
- File exists and is stable
- URL contains MD5 hash of all parameters — content never changes for same URL
- Safe to cache for 1 year in browser and CDN (Cloudflare, Fastly, Akamai, etc.)
immutablesignals the URL is content-addressed — file will never change
Configuration:
// config/imagepresets.php 'cache_max_age' => env('IMAGEPRESET_CACHE_MAX_AGE', 31536000), // 1 year in seconds
This approach minimizes bandwidth and server load while maintaining reliability during the generation phase.
Security
| Threat | Protection |
|---|---|
| Path traversal | .. and null bytes rejected in src |
| SSRF via remote URL | Private/reserved IPs + localhost blocked; redirects disabled |
| Image bomb | Pixel area check (max_image_pixels) for both local and remote files |
| SVG XSS | Sanitization via enshrined/svg-sanitize or regex fallback; CSP header |
| Cache stampede | Cache::lock() with double-check after acquiring |
| Content sniffing | X-Content-Type-Options: nosniff |
| Arbitrary URL generation | Optional Signed URL (see below) |
Signed URL (optional)
By default the endpoint is open — any request that passes whitelist validation is processed. Enable Signed URL to restrict access only to URLs generated by the server.
Enable
# .env IMAGEPRESET_SIGNED_URL=true
Or in config/imagepresets.php:
'route' => [ // ... 'signed' => true, ],
How it works
imagepreset_url(),Imagepreset::url()and@imagepreset()automatically generate a permanent signed URL viaURL::signedRoute().- The
signedmiddleware validates the HMAC signature on every request. - Requests without a valid signature return 403 Forbidden.
- Tampering with any parameter (e.g. changing
w=300tow=9999) invalidates the signature.
Example
// Generates: https://example.com/imagepreset?fm=webp&signature=...&src=photo.jpg&w=800 $url = imagepreset_url('photo.jpg', ['w' => 800, 'fm' => 'webp']);
<img src="@imagepreset('photo.jpg', ['w' => 600, 'fm' => 'webp'])" alt="Photo">
Notes
- Signed URLs are permanent (no expiry). This makes them fully compatible with CDN / reverse-proxy caching — no extra CDN configuration required.
- When
signed = false(default) behaviour is identical to previous versions — fully backwards-compatible. - The signature is based on
APP_KEY. Rotating the key invalidates all previously generated URLs.
Testing
composer test # or vendor/bin/phpunit
License
MIT — see LICENSE.
fomvasss/laravel-imagepresets 适用场景与选型建议
fomvasss/laravel-imagepresets 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 128 次下载、GitHub Stars 达 9, 最近一次更新时间为 2026 年 05 月 02 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「image」 「resize」 「laravel」 「glide」 「preset」 「Webp」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 fomvasss/laravel-imagepresets 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 fomvasss/laravel-imagepresets 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 fomvasss/laravel-imagepresets 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
Yii2 LightBox image galary widget uses Lightbox v2.10.0 by Lokesh Dhakar
The Yii2 extension uses jQuery jquery.carousel-1.1.min.js and makes image carousel from php array of structure defined.
Image cache
The lireincore/imgcache integration for the Yii2 framework
The Yii2 extension uses jQuery PrettyPhoto and OwlCarousel js and makes image galary from php array of structure defined.
Yii2 prettyPhoto image galary widget uses Lightbox view control jquery.prettyphoto.js
统计信息
- 总下载量: 128
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 9
- 点击次数: 41
- 依赖项目数: 0
- 推荐数: 1
其他信息
- 授权协议: MIT
- 更新时间: 2026-05-02