moonlight-poland/laravel-context-aware-thumbnails
Composer 安装命令:
composer require moonlight-poland/laravel-context-aware-thumbnails
包简介
Context-Aware on-demand thumbnail generation for Laravel with intelligent path organization, Smart Crop, AVIF/WebP support, Laravel Native Signed URLs, and variants system. Automatically organize thumbnails by user/post/album. Zero config, blazing fast! Laravel 12+ ready.
关键字:
README 文档
README
Intelligent On-Demand Image Thumbnails with Smart Crop & Modern Formats
Copyright © 2024-2026 Moonlight Poland. All rights reserved.
Contact: kontakt@howtodraw.pl
License: Commercial License - Free for personal use, paid for commercial use
Repository: https://github.com/Moonlight4000/laravel-thumbnails
Generate image thumbnails on-the-fly in Laravel with Context-Aware Thumbnails™ - the only package that organizes thumbnails exactly where your content lives!
No pre-generation needed. No Redis required. Smart organization included.™
🌟 What Makes Us Unique?
- 🎯 Context-Aware Organization™ - Thumbnails organized by user/post/album (no other package does this!)
- ⚛️ React/Vue/JavaScript Support - The ONLY Laravel thumbnail package with
sync-jscommand for frontend frameworks - 🔐 Signed URLs (Facebook-style) - Time-limited, cryptographically signed URLs to prevent hotlinking
- 🤖 Smart Crop with AI Energy Detection - Automatically focuses on important image areas
- 🚀 AVIF/WebP Support - Modern formats for 50%+ smaller file sizes
- 🔒 Commercial Licensing - Professional support & tamper detection included
📋 Table of Contents
- Why Choose This Over Other Packages?
- What Makes Context-Aware Thumbnails™ Special?
- License Notice
- Features
- Installation
- Quick Start
- Configuration
- Usage
- Context-Aware Thumbnails™
- Advanced Features
- Artisan Commands
- Testing
- Contributing
- License
- Credits
🏆 Why Choose This Over Other Packages?
📊 Complete Feature Comparison
| Feature | Laravel Smart Thumbnails™ (moonlight-poland) |
askancy/ laravel-smart-thumbnails |
lee-to/ laravel-thumbnails |
spatie/ laravel-medialibrary |
|---|---|---|---|---|
| 🎯 UNIQUE FEATURES | ||||
| Context-Aware Organization™ | ✅ ONLY US! | ❌ | ❌ | ❌ |
| Custom path templates | ✅ {user_id}/{post_id} |
❌ | ❌ | ⚠️ Limited |
| Per-user/post isolation | ✅ Built-in | ❌ Manual | ❌ Manual | ⚠️ Via DB |
| Commercial licensing | ✅ $500-$15k | ❌ MIT (free) | ❌ MIT | ✅ Spatie |
| 🖼️ IMAGE PROCESSING | ||||
| AVIF format support | ✅ v2.0+ | ✅ | ❌ | ✅ |
| WebP format support | ✅ v2.0+ | ✅ | ❌ | ✅ |
| Smart Crop (AI energy) | ✅ v2.0+ | ✅ | ❌ | ✅ |
| Crop/Fit/Resize methods | ✅ All 3 | ✅ SmartCrop | ✅ All 3 | ✅ Yes |
| Multiple drivers | ✅ GD/Imagick/Intervention | ✅ GD/Imagick | ⚠️ Intervention only | ✅ Yes |
| Quality control | ✅ Per size | ✅ Per variant | ✅ Global | ✅ Yes |
| 🛡️ ERROR HANDLING | ||||
| Silent/Strict modes | ✅ v2.0+ | ✅ | ❌ | ⚠️ Limited |
| Bulletproof fallbacks | ✅ | ✅ | ⚠️ Basic | ✅ |
| Never breaks app | ✅ | ✅ | ⚠️ Can throw | ✅ |
| ⚡ GENERATION | ||||
| On-demand (lazy) | ✅ Automatic | ✅ Automatic | ✅ Manual | ✅ Manual |
| Middleware fallback | ✅ Auto 404→generate | ❌ | ❌ | ❌ |
| Zero config | ✅ Works out-of-box | ⚠️ Requires setup | ⚠️ Setup needed | ❌ Complex |
| 📁 ORGANIZATION | ||||
| Subdirectory strategies | ✅ Context-aware | ✅ 5 strategies | ❌ Flat | ⚠️ Via DB |
| Hash-based distribution | ⚠️ Manual | ✅ Automatic | ❌ | ❌ |
| Date-based folders | ⚠️ Manual | ✅ Automatic | ❌ | ❌ |
| Handles millions of files | ✅ Yes | ✅ Yes | ⚠️ Slow | ✅ Yes |
| 🎨 VARIANTS & PRESETS | ||||
| Multiple sizes per preset | ✅ | ✅ Variants | ✅ | ✅ |
| Responsive images | ✅ | ✅ | ✅ | ✅ |
| Named presets | ✅ 'small', 'large' |
✅ | ✅ | ✅ |
| 🔧 DEVELOPER EXPERIENCE | ||||
| Blade directive | ✅ @thumbnail() |
❌ | ❌ | ❌ |
| Helper function | ✅ thumbnail() |
❌ | ✅ | ❌ |
| Eloquent trait | ✅ HasThumbnails |
❌ | ✅ | ✅ |
| React/Vue/JS | ✅ ONLY US! | ❌ | ❌ | ❌ |
| Auto-sync JS helper | ✅ sync-js |
❌ | ❌ | ❌ |
| Artisan commands | ✅ generate, clear, sync-js | ✅ purge, optimize | ❌ | ✅ Many |
| 📊 MONITORING | ||||
| Statistics & analytics | ✅ v2.0+ | ✅ Full | ❌ | ✅ |
| Performance metrics | ✅ v2.0+ | ✅ | ❌ | ⚠️ |
| Disk usage tracking | ✅ v2.0+ | ✅ | ❌ | ✅ |
| 🔒 SECURITY | ||||
| File validation | ✅ v2.0+ | ✅ | ⚠️ Basic | ✅ |
| Size limits | ✅ v2.0+ | ✅ | ❌ | ✅ |
| Extension whitelist | ✅ v2.0+ | ✅ | ❌ | ✅ |
| Signed URLs (Facebook-style) | ✅ v2.0.16+ | ❌ | ❌ | ⚠️ Via S3 |
| Time-limited links | ✅ v2.0.16+ | ❌ | ❌ | ❌ |
| Hotlinking prevention | ✅ v2.0.16+ | ❌ | ❌ | ⚠️ Via S3 |
| Tamper detection | ✅ Commercial only | ❌ | ❌ | ❌ |
| 💾 STORAGE | ||||
| Filesystem cache | ✅ | ✅ | ✅ | ✅ |
| Redis/Memcached tags | ❌ | ✅ | ❌ | ⚠️ |
| Multi-disk support | ✅ | ✅ | ✅ | ✅ |
| S3/Cloud storage | ✅ | ✅ | ✅ | ✅ |
| Database storage | ❌ | ❌ | ❌ | ✅ |
| 📦 INSTALLATION | ||||
| Installs | 🆕 New | 17 | ~500 | 50,000+ |
| Stars | ⭐ New | 1 | ~50 | 5,000+ |
| Maturity | 🆕 v2.0.1 | 🆕 v2.0 | ⚠️ v1.x | ✅ v11.x |
🎯 Which Package Should You Choose?
Choose Laravel Context-Aware Thumbnails™ (moonlight-poland) if you need:
- ✅ Context-Aware organization (unique feature!)
- ✅ Thumbnails organized by user/post/album automatically
- ✅ React/Vue/JavaScript support (ONLY package with sync-js!)
- ✅ Signed URLs (Facebook-style) - Time-limited, cryptographically signed protection
- ✅ Auto-strategy: Context-Aware for models, Hash for paths
- ✅ Smart Crop with energy detection (v2.0)
- ✅ AVIF/WebP modern formats (v2.0)
- ✅ Variants system for multiple sizes (v2.0)
- ✅ Daily usage statistics sent to Moonlight (v2.0)
- ✅ Blade directives and helpers for easy use
- ✅ Automatic middleware fallback
- ✅ Commercial support with licensing
- ✅ Simple filesystem-based solution
🔥 What Makes Context-Aware Thumbnails™ Special?
Other packages dump all thumbnails in one folder. We organize them exactly where your content lives:
❌ OTHER PACKAGES:
storage/thumbnails/
├── user1_avatar_thumb_small.jpg
├── post42_image_thumb_small.jpg
├── gallery_photo_thumb_small.jpg
└── ... 10,000+ files in one folder!
✅ CONTEXT-AWARE THUMBNAILS™:
storage/
├── user-posts/1/12/thumbnails/image_thumb_small.jpg
├── galleries/5/3/thumbnails/photo_thumb_medium.jpg
├── avatars/8/thumbnails/avatar_thumb_small.jpg
└── fanpages/42/photos/thumbnails/banner_thumb_large.jpg
Benefits:
- ✅ Delete post → thumbnails automatically deleted with folder
- ✅ Per-user backups → backup specific user folders
- ✅ CDN routing → route different contexts to different CDNs
- ✅ Filesystem performance → fewer files per directory = faster I/O
- ✅ Security → isolate user content with directory permissions
- ✅ Organization → find thumbnails instantly, no database queries
⚠️ License Notice
This is a COMMERCIAL package with a dual-licensing model:
- 🆓 FREE for personal/non-commercial use
- 💼 PAID for commercial use ($500-$15,000/year)
See LICENSE.md for details.
Contact: kontakt@howtodraw.pl
GitHub: https://github.com/Moonlight4000/laravel-thumbnails
✨ Features
- 🔥 Context-Aware Thumbnails™ - Organize thumbnails by user/post/album/any structure (UNIQUE!)
- 🚀 On-Demand Generation - Thumbnails generated only when requested (lazy loading)
- 🔐 Signed URLs (Facebook-style) - Time-limited, cryptographically signed URLs to prevent hotlinking
- 💾 Filesystem Cache - Fast subsequent loads, no Redis/Memcached needed
- 🔌 Zero Configuration - Sensible defaults, works out of the box
- 🎨 Multiple Drivers - GD (default), Imagick, or Intervention Image
- 📐 3 Resize Methods - Resize (proportional), Crop (exact size), Fit (with padding)
- 🔧 Fully Configurable - Custom sizes, quality, drivers, paths, and more
- 🎯 Blade Directive -
@thumbnail('path/image.jpg', 'small', 'post', ['user_id' => 1]) - ⚛️ React/Vue/JavaScript Helper - Full feature parity with PHP (sync-js command)
- 📦 Facade & Helpers - Multiple ways to use
- 🗑️ Auto Cleanup - Delete folder = thumbnails gone
- 🛠️ Artisan Commands - Generate or clear thumbnails via CLI
- ✅ Laravel 10 & 11 - Full support for modern Laravel
📦 Installation
composer require moonlight-poland/laravel-smart-thumbnails
Optional Dependencies (Recommended)
For best performance and advanced features, install these optional packages:
# Intervention Image - Required for Smart Crop and better performance composer require intervention/image # Imagick Extension - Required for AVIF format support # (Install via your system's package manager, e.g., apt install php-imagick)
What you get with optional dependencies:
- ✅ Smart Crop - AI-powered energy detection (requires Intervention Image)
- ✅ AVIF format - Modern image format with 50% smaller files (requires ext-imagick)
- ✅ Better performance - Intervention Image is faster than GD for large images
- ⚠️ Without them - Package falls back to GD (works, but limited features)
License Activation
For Personal (Free) use:
php artisan thumbnails:license --type=personal
For Commercial use:
# Enter your license key (from purchase email)
php artisan thumbnails:license YOUR-LICENSE-KEY
Contact for licensing: kontakt@howtodraw.pl
Optional: Publish Config
php artisan vendor:publish --tag=thumbnails-config
Make Sure Storage is Linked
php artisan storage:link
For React/Vue Apps: Generate JS Helper
REQUIRED if using React, Vue, or any JavaScript framework:
php artisan thumbnails:sync-js
This generates resources/js/utils/thumbnails.js with your config contexts.
When to run:
- ✅ After installation
- ✅ After changing
config/thumbnails.php - ✅ After adding new contexts
See React/Vue Usage section below for details.
🚀 Quick Start
Basic Usage (Blade)
{{-- Original image --}} <img src="{{ asset('storage/photos/cat.jpg') }}"> {{-- Thumbnail (auto-generated on first request!) --}} <img src="@thumbnail('photos/cat.jpg', 'small')">
That's it! 🎉
- First request: Generates thumbnail (~50-200ms)
- Next requests: Cached file served by Nginx (~1-5ms)
🔥 Context-Aware Thumbnails™ (UNIQUE FEATURE!)
The only Laravel package that organizes thumbnails exactly where your content lives!
Why Context Matters
Traditional packages dump all thumbnails into one folder. This causes:
- ❌ Messy filesystem (thousands of files in one directory)
- ❌ Difficult cleanup (delete post, but thumbnails remain)
- ❌ No per-user isolation
- ❌ CDN routing nightmare
- ❌ Slow backups (can't backup specific content types)
Context-Aware Thumbnails™ solves this:
{{-- USER POST CONTEXT --}} <img src="@thumbnail('image.jpg', 'small', 'post', ['user_id' => 1, 'post_id' => 12])"> {{-- Result: /storage/user-posts/1/12/thumbnails/image_thumb_small.jpg --}} {{-- GALLERY CONTEXT --}} <img src="@thumbnail('photo.jpg', 'medium', 'gallery', ['user_id' => 5, 'album_id' => 3])"> {{-- Result: /storage/galleries/5/3/thumbnails/photo_thumb_medium.jpg --}} {{-- AVATAR CONTEXT --}} <img src="@thumbnail('avatar.jpg', 'small', 'avatar', ['user_id' => 8])"> {{-- Result: /storage/avatars/8/thumbnails/avatar_thumb_small.jpg --}} {{-- NO CONTEXT (default) --}} <img src="@thumbnail('cat.jpg', 'small')"> {{-- Result: /storage/thumbnails/cat_thumb_small.jpg --}}
Configuration
Define custom contexts in config/thumbnails.php:
'contexts' => [ // User posts - separate per user and post 'post' => 'user-posts/{user_id}/{post_id}', // Gallery - separate per user and album 'gallery' => 'galleries/{user_id}/{album_id}', // Avatars - per user only 'avatar' => 'avatars/{user_id}', // Fanpage content 'fanpage' => 'fanpages/{fanpage_id}/{type}', // Your custom contexts 'product' => 'products/{category_id}/{product_id}', 'team' => 'companies/{company_id}/team', ],
PHP Usage
// In controllers $url = thumbnail('image.jpg', 'small', true, 'post', [ 'user_id' => auth()->id(), 'post_id' => $post->id ]); // Helper functions $url = thumbnail_url('photo.jpg', 'medium', 'gallery', [ 'user_id' => $user->id, 'album_id' => $album->id ]); // Facade use Thumbnail; $url = Thumbnail::generate('avatar.jpg', 'small', true, 'avatar', [ 'user_id' => $user->id ]);
Model Integration
use Moonlight\Thumbnails\Traits\HasThumbnails; class UserPost extends Model { use HasThumbnails; // Define default context for this model protected $thumbnailContext = 'post'; // Provide context data automatically public function getThumbnailContextData(): array { return [ 'user_id' => $this->user_id, 'post_id' => $this->id, ]; } } // In Blade - context applied automatically! <img src="{{ $post->thumbnail('image.jpg', 'small') }}"> {{-- Auto-uses 'post' context with user_id and post_id --}}
Benefits
✅ Perfect organization - thumbnails live with their content
✅ Easy cleanup - delete post folder, thumbnails gone
✅ Per-user isolation - great for multi-tenant apps
✅ CDN-friendly - route /user-posts/1/* to User 1's CDN
✅ Faster backups - backup specific content types
✅ Better performance - fewer files per directory
🎨 React / Vue / JavaScript Usage
🌟 UNIQUE FEATURE: We are the ONLY Laravel thumbnail package that provides seamless React/Vue/JavaScript integration with automatic context synchronization! Other packages only work with Blade.
IMPORTANT: For React/Vue apps, you need to generate a JavaScript helper that mirrors your PHP config.
Step 1: Generate JS Helper
php artisan thumbnails:sync-js
This creates resources/js/utils/thumbnails.js with your contexts from config/thumbnails.php.
Run this command whenever you:
- Change
config/thumbnails.php - Add new contexts
- Change filename patterns
Step 2: Import in React/Vue
✅ YES, the import is REQUIRED! Without it, your React/Vue components won't have thumbnail URLs.
// React Component import { getThumbnailUrl } from '@/utils/thumbnails'; function PostMedia({ post }) { const mediaFiles = post.media_files || []; return ( <div> {mediaFiles.map((media, index) => ( <img key={index} src={getThumbnailUrl(media.path, 'small')} alt={media.alt} /> ))} </div> ); }
Available Functions
import { getThumbnailUrl, // Basic usage getThumbnailUrlWithContext, // With Context-Aware buildContextPath, // Build context path only THUMBNAIL_CONTEXTS, // Available contexts THUMBNAIL_SIZES // Available sizes } from '@/utils/thumbnails'; // Basic usage (path already includes context) const url = getThumbnailUrl('user-posts/1/12/img.jpg', 'small'); // → /storage/user-posts/1/12/thumbnails/img_thumb_small.jpg // With crop method + WebP format const url = getThumbnailUrl('user-posts/1/12/img.jpg', 'small', { method: 'crop', // crop, fit, or resize format: 'webp', // webp, avif, jpg, png quality: 85, // 1-100 smart_crop: true // AI energy detection (v2.0+) }); // → /storage/user-posts/1/12/thumbnails/img_thumb_small_crop.webp?quality=85&smart_crop=1 // Context-Aware (filename + context + data) const url = getThumbnailUrlWithContext( 'img.jpg', // Just filename 'small', // Size 'post', // Context { user_id: 1, post_id: 12 }, // Context data { method: 'crop', format: 'webp' } // Options (optional) ); // → /storage/user-posts/1/12/thumbnails/img_thumb_small_crop.webp // Build context path only const path = buildContextPath('post', { user_id: 1, post_id: 12 }); // → user-posts/1/12
✅ Full Feature Parity with PHP
JavaScript helper supports ALL PHP features:
- ✅ Context-Aware paths - Organized by user/post/album
- ✅ Resize methods -
crop,fit,resize - ✅ Modern formats -
webp,avif,jpg,png - ✅ Quality control - 1-100
- ✅ Smart Crop - AI energy detection (v2.0+)
- ✅ On-demand generation - Middleware handles 404
Example with all options:
// React Component with Smart Crop + WebP function PostMedia({ post }) { return ( <div> {post.media_files.map((media, idx) => ( <img key={idx} src={getThumbnailUrl(media.path, 'medium', { method: 'crop', format: 'webp', quality: 90, smart_crop: true // AI focuses on important areas! })} alt={media.alt} /> ))} </div> ); }
PHP Backend Setup for React
In your PHP accessor (e.g., UserPost.php):
// Return ONLY path, React will build thumbnail URL public function getMediaFilesAttribute(): array { $mediaFiles = []; foreach ($this->attachments as $attachment) { if ($attachment['type'] === 'image') { $mediaFiles[] = [ 'type' => $attachment['type'], 'path' => $attachment['path'], // e.g., 'user-posts/1/12/img.jpg' 'url' => asset('storage/' . $attachment['path']), 'alt' => $attachment['original_name'], ]; } } return $mediaFiles; }
React will:
- Call
getThumbnailUrl(media.path, 'small') - Build URL:
/storage/user-posts/1/12/thumbnails/img_thumb_small.jpg - Browser requests thumbnail
- 404 on first request → middleware generates thumbnail
- 200 on next requests → cached file served by Nginx
Workflow
┌─────────────────────────────────────────────────────────┐
│ 1. Change config/thumbnails.php │
│ (add new context, change pattern, etc.) │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 2. Run: php artisan thumbnails:sync-js │
│ Generates: resources/js/utils/thumbnails.js │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 3. Commit thumbnails.js to git │
│ (single source of truth in PHP, synced to JS) │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 4. React uses getThumbnailUrl() automatically │
│ (always in sync with PHP config) │
└─────────────────────────────────────────────────────────┘
Vue Example
<template> <div v-for="media in post.media_files" :key="media.path"> <img :src="getThumbnailUrl(media.path, 'small')" :alt="media.alt" /> </div> </template> <script setup> import { getThumbnailUrl } from '@/utils/thumbnails'; const props = defineProps({ post: Object }); </script>
📦 Benefits
- ✅ Automatic Cleanup - Delete post folder = all thumbnails gone
- ✅ Per-User Isolation - Easy permissions & backups per user
- ✅ CDN Routing - Route different contexts to different CDNs
- ✅ Performance - Fewer files per directory = faster filesystem
- ✅ Organization - Find any thumbnail instantly
- ✅ Scalability - No "one folder with million files" problem
📐 Resize Methods
Choose how thumbnails should be generated:
1. Resize (Default - Proportional)
// config/thumbnails.php 'method' => 'resize',
- ✅ Preserves aspect ratio
- ✅ No cropping
- ⚠️ Final size may differ slightly from target
Use for: Product images, photos where full content must be visible
2. Crop (Exact Size - Center Crop)
// config/thumbnails.php 'method' => 'crop',
- ✅ Exact dimensions guaranteed
- ✅ Fills entire thumbnail
- ⚠️ May cut edges (center-focused)
Use for: Avatars, thumbnails in grids, cards
3. Fit (Preserve All - Add Padding)
// config/thumbnails.php 'method' => 'fit',
- ✅ Entire image visible
- ✅ Exact dimensions
- ⚠️ May have padding/borders
Use for: Logos, icons, images where nothing can be cut
Visual comparison:
Original: 800x600 → Target: 200x200
RESIZE: 200x150 (proportional, smaller)
CROP: 200x200 (center cropped)
FIT: 200x200 (padded top/bottom)
📚 Usage Methods
1. Blade Directive
<img src="@thumbnail('photos/image.jpg', 'small')"> <img src="@thumbnail('photos/image.jpg', 'medium')"> <img src="@thumbnail('photos/image.jpg', 'large')">
2. Facade
use Moonlight\Thumbnails\Facades\Thumbnail; $url = Thumbnail::thumbnail('photos/image.jpg', 'medium');
3. Helper Functions
// Get URL $url = thumbnail('photos/image.jpg', 'small'); // Aliases $url = thumbnail_url('photos/image.jpg', 'small'); $path = thumbnail_path('photos/image.jpg', 'small'); // Returns relative path
4. Service Injection
use Moonlight\Thumbnails\Services\ThumbnailService; class ImageController { public function show(ThumbnailService $thumbnails) { $url = $thumbnails->thumbnail('photos/image.jpg', 'medium'); } }
5. JavaScript (Frontend)
import { getThumbnailUrl } from 'moonlight-thumbnails'; const thumbUrl = getThumbnailUrl('photos/cat.jpg', 'small'); // Returns: /storage/photos/thumbnails/cat_thumb_small.jpg
⚙️ Configuration
Default Sizes
// config/thumbnails.php 'sizes' => [ 'small' => ['width' => 150, 'height' => 150], 'medium' => ['width' => 300, 'height' => 300], 'large' => ['width' => 600, 'height' => 600], // Add your custom sizes: 'avatar' => ['width' => 200, 'height' => 200], 'banner' => ['width' => 1200, 'height' => 400], ],
Drivers
'driver' => 'gd', // 'gd' (default), 'imagick', or 'intervention'
GD (built-in, no extra dependencies)
'driver' => 'gd',
Imagick (better quality, requires ext-imagick)
'driver' => 'imagick',
Intervention Image (most features, requires package)
composer require intervention/image
'driver' => 'intervention',
Quality & Performance
'quality' => 85, // JPEG quality (1-100) 'fallback_on_error' => true, // Return original on error 'cache_control' => 'public, max-age=31536000', // 1 year cache
🎯 Advanced Features
HasThumbnails Trait
Automatically delete thumbnails when model is deleted:
use Moonlight\Thumbnails\Traits\HasThumbnails; class UserPost extends Model { use HasThumbnails; // Define which fields contain images protected $thumbnailFields = ['cover_image', 'gallery_image']; } // Usage in model $post->thumbnail('cover_image', 'small'); // Get thumbnail URL $post->thumbnails('cover_image'); // Get all sizes: ['small' => 'url', ...]
Artisan Commands
# Generate thumbnails for specific image php artisan thumbnails:generate photos/image.jpg # Generate specific size php artisan thumbnails:generate photos/image.jpg --size=small # Force regenerate (overwrite existing) php artisan thumbnails:generate photos/image.jpg --force # Clear all thumbnails php artisan thumbnails:clear # Clear specific directory php artisan thumbnails:clear photos # Clear specific image thumbnails php artisan thumbnails:clear photos/image.jpg
Manual Management
use Moonlight\Thumbnails\Facades\Thumbnail; // Delete all thumbnails for an image Thumbnail::deleteThumbnails('photos/image.jpg'); // Clear all thumbnails in directory Thumbnail::clearAllThumbnails('photos'); // Clear ALL thumbnails in app Thumbnail::clearAllThumbnails();
🆕 V2.0 New Features
Smart Crop (AI Energy Detection)
Automatically detect the most important part of the image for intelligent cropping:
// config/thumbnails.php 'smart_crop' => [ 'enabled' => true, 'algorithm' => 'energy', // 'energy', 'faces', 'saliency' 'rule_of_thirds' => true, // Align focal point to rule of thirds ],
Usage:
{{-- Smart crop will detect focal point automatically --}} <img src="@thumbnail('photos/portrait.jpg', 'square', 'post', ['user_id' => 1], 'smart-crop')">
When to use:
- Portrait photos (focuses on face/eyes)
- Product photos (focuses on the product)
- Landscape photos (focuses on horizon/main subject)
Modern Image Formats (AVIF/WebP)
Automatically convert thumbnails to modern formats for 50% smaller file sizes:
// config/thumbnails.php 'formats' => [ 'auto_convert' => true, 'priority' => ['avif', 'webp', 'jpg'], // Try in order 'quality' => [ 'avif' => 85, 'webp' => 90, 'jpg' => 85, ], ],
How it works:
- Package checks available image libraries (GD, Imagick, Intervention)
- Selects best available format from priority list
- Generates thumbnail in optimal format
- Falls back to JPEG if modern formats unavailable
Performance:
- AVIF: ~50% smaller than JPEG (requires Imagick)
- WebP: ~30% smaller than JPEG (GD/Imagick)
- Automatic fallback ensures compatibility
🔐 Laravel Native Signed URLs Integration
Version 2.0.18+ uses Laravel's native URL::temporarySignedRoute() for signed URLs instead of custom Facebook-style implementation.
⚙️ Required Setup (4 steps)
1️⃣ Create StorageController
Create app/Http/Controllers/StorageController.php:
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Storage; class StorageController extends Controller { /** * Serve files from storage with signed URL validation * * Automatically routes audio files to AudioStreamController for * HTTP Range Request support (seeking in audio players). */ public function serve(Request $request, string $path) { $path = urldecode($path); // Optional: Delegate audio files to AudioStreamController // if (preg_match('/\.(mp3|wav|ogg|m4a|flac)$/i', $path)) { // return app(AudioStreamController::class)->stream($request, $path); // } $disk = config('thumbnails.disk', 'public'); $fullPath = Storage::disk($disk)->path($path); if (!file_exists($fullPath)) { abort(404, 'File not found'); } // Set cache headers based on signed URLs config $cacheControl = config('thumbnails.signed_urls.enabled') ? 'private, no-cache, must-revalidate' : 'public, max-age=31536000'; return response()->file($fullPath, [ 'Cache-Control' => $cacheControl, 'Content-Type' => mime_content_type($fullPath) ?: 'application/octet-stream', ]); } }
2️⃣ Add Route with Signed Middleware
Add to routes/web.php:
use App\Http\Controllers\StorageController; Route::get('/storage/{path}', [StorageController::class, 'serve']) ->where('path', '.*') ->middleware('signed') // Laravel's native signed middleware ->name('storage.serve');
⚠️ IMPORTANT: Place this route BEFORE any catch-all routes!
3️⃣ Disable Laravel's Auto-Routes
In config/filesystems.php, set serve => false:
'public' => [ 'driver' => 'local', 'root' => storage_path('app/public'), 'url' => env('APP_URL').'/storage', 'visibility' => 'public', 'serve' => false, // ✅ Disable auto-registration ], 'local' => [ 'driver' => 'local', 'root' => storage_path('app/private'), 'serve' => false, // ✅ Also disable for local ],
4️⃣ Remove public/storage Symlink
Laravel should route ALL /storage/* requests through the controller:
# Linux/Mac rm public/storage # Windows PowerShell Remove-Item public/storage # Or manually delete the folder/symlink
Why remove it?
- Symlink causes Apache/Nginx to serve files statically (bypassing Laravel)
- Static serving = NO middleware = NO signed URL validation
- Your images would load even with expired URLs! ❌
🎯 Enable Signed URLs
In .env:
THUMBNAILS_SIGNED_URLS=true # Enable for thumbnails THUMBNAILS_SIGNED_ORIGINALS=true # Also sign original images THUMBNAILS_URL_EXPIRATION=7200 # 2 hours (or 604800 for 7 days)
Generated URLs:
{{-- Before (no signed URLs) --}} <img src="/storage/user-posts/1/14/img.jpg"> {{-- After (with signed URLs) --}} <img src="/storage/user-posts/1/14/img.jpg?expires=1767553796&signature=abc123...">
✨ How It Works
original()/thumbnail()helpers generate signed URL usingURL::temporarySignedRoute()- Browser requests the signed URL
signedmiddleware validatesexpiresandsignatureparameters- If valid:
StorageControllerserves the file - If invalid/expired: Laravel returns
403 Invalid signature
ThumbnailFallback Middleware:
- When thumbnail doesn't exist (404), middleware generates it on-demand
- Returns
302 redirectto signed URL (if enabled) - Browser makes new request with signed URL → validated by
signedmiddleware - Works perfectly with React/Vue/JavaScript!
📊 URL Expiration Times
# 5 minutes (for one-time shares) THUMBNAILS_URL_EXPIRATION=300 # 1 hour (for temporary content) THUMBNAILS_URL_EXPIRATION=3600 # 2 hours (recommended for SPA apps) THUMBNAILS_URL_EXPIRATION=7200 # 7 days (Facebook-style) THUMBNAILS_URL_EXPIRATION=604800 # 30 days (for long-term content) THUMBNAILS_URL_EXPIRATION=2592000
🔒 Security Benefits
- ✅ Prevents hotlinking - Other sites can't steal your bandwidth
- ✅ Time-limited access - Links expire after set time
- ✅ No database required - Stateless validation
- ✅ Laravel native - Uses built-in
URL::temporarySignedRoute() - ✅ Cryptographically secure - HMAC-SHA256 signatures
🐛 Troubleshooting
Images still load after expiration?
- ✅ Check if
public/storagesymlink exists (delete it!) - ✅ Verify
serve => falseinconfig/filesystems.php - ✅ Clear cache:
php artisan optimize:clear
403 Invalid signature on valid URLs?
- ✅ Check if route is named
storage.serve - ✅ Verify
signedmiddleware is applied - ✅ Ensure route is placed BEFORE catch-all routes
React/Vue images not loading?
- ✅ Backend must return
thumbnailURL (not justpath) - ✅ Example:
'thumbnail' => thumbnail($path, 'large', true), // ✅ Returns signed URL 'thumbnail_small' => thumbnail($path, 'small', true),
// config/thumbnails.php 'formats' => [ 'auto_convert' => true, 'priority' => ['avif', 'webp', 'jpg'], // Try AVIF first, fallback to WebP, then JPG 'quality' => [ 'avif' => 75, 'webp' => 80, 'jpg' => 85, ], ],
Usage with Blade directive:
{{-- Automatically generates AVIF, WebP, and JPG variants --}} @thumbnail_picture('photos/sunset.jpg', 'large', 'post', ['user_id' => 5]) {{-- Output: <picture> <source srcset="/storage/.../sunset_thumb_large.avif" type="image/avif"> <source srcset="/storage/.../sunset_thumb_large.webp" type="image/webp"> <img src="/storage/.../sunset_thumb_large.jpg" alt="..."> </picture> --}}
File size comparison:
- AVIF: ~50% smaller than JPEG (best quality per byte)
- WebP: ~30% smaller than JPEG
- JPG: Original compression
Variants System (Generate Multiple Sizes)
Generate multiple thumbnail sizes at once with preset collections:
// config/thumbnails.php 'variants' => [ 'avatar' => [ ['width' => 50, 'height' => 50, 'method' => 'crop'], ['width' => 150, 'height' => 150, 'method' => 'crop'], ['width' => 300, 'height' => 300, 'method' => 'crop'], ], 'gallery' => [ ['width' => 300, 'height' => 200, 'method' => 'crop'], ['width' => 800, 'height' => 600, 'method' => 'resize'], ['width' => 1200, 'height' => 800, 'method' => 'resize'], ], ],
Usage:
// Generate all avatar sizes at once $variants = thumbnail_variant($user, 'avatar.jpg', 'avatar'); // Returns: ['50x50' => 'url', '150x150' => 'url', '300x300' => 'url'] // In Blade @foreach(thumbnail_variant($user, 'photo.jpg', 'gallery') as $size => $url) <img src="{{ $url }}" alt="Gallery {{ $size }}"> @endforeach
When to use:
- User avatars (small, medium, large)
- Gallery thumbnails (grid, lightbox, full-screen)
- Responsive images (different screen sizes)
Subdirectory Strategies (Performance at Scale)
Choose how thumbnails are organized on the filesystem:
// config/thumbnails.php 'subdirectory' => [ 'auto_strategy' => true, // Automatically select best strategy 'default' => 'hash-prefix', 'strategies' => [ 'context-aware' => [ 'priority' => 100, // Highest - used for models // Result: user-posts/1/12/thumbnails/image_thumb_small.jpg ], 'hash-prefix' => [ 'priority' => 1, // Lowest - fallback for string paths 'config' => [ 'levels' => 2, // e.g., a/b/ 'length' => 2, ], // Result: thumbnails/a/b/image_thumb_small.jpg ], 'date-based' => [ 'config' => [ 'format' => 'Y/m/d', // e.g., 2026/01/03/ ], // Result: thumbnails/2026/01/03/image_thumb_small.jpg ], ], ],
Performance Benefits:
| Files | Without Subdirs | With Hash Prefix |
|---|---|---|
| 1,000 | ⚠️ Slow | ✅ Fast |
| 10,000 | ❌ Very Slow | ✅ Fast |
| 100,000 | ❌ Unusable | ✅ Fast |
| 1,000,000 | ❌ Impossible | ✅ Fast |
Why: Operating systems slow down with >1000 files per directory.
Security Validation
Protect against malicious file uploads:
// config/thumbnails.php 'security' => [ 'max_file_size' => 10, // MB 'allowed_extensions' => ['jpg', 'jpeg', 'png', 'gif', 'webp', 'avif'], 'allowed_mime_types' => [ 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/avif', ], 'max_dimensions' => [ 'width' => 10000, 'height' => 10000, ], 'block_svg' => true, // Prevent XXE attacks ],
Automatic validation: Package validates all images before processing.
Error Handling Modes
Control how the package behaves when errors occur:
// config/thumbnails.php 'error_mode' => 'silent', // 'silent', 'strict', 'fallback' 'placeholder_image' => 'images/placeholder.jpg', // For 'fallback' mode
Modes:
- silent (recommended for production): Log error, return original image
- strict (recommended for development): Throw exception
- fallback: Return placeholder image
Example:
{{-- If thumbnail generation fails, original image is returned (silent mode) --}} <img src="@thumbnail('photos/corrupted.jpg', 'small')"> {{-- Instead of crashing, shows original image --}}
Daily Usage Statistics (Privacy-Friendly)
Track thumbnail usage for analytics (commercial license holders only):
// config/thumbnails.php 'statistics' => [ 'enabled' => true, 'send_to_moonlight' => true, // Send to Moonlight dashboard ],
What's tracked:
- ✅ Daily usage count (how many thumbnails generated today)
- ✅ Methods used (resize, crop, fit)
- ✅ Popular sizes (which sizes are most used)
- ✅ PHP/Laravel versions
- ✅ Domain (where package is installed)
What's NOT tracked:
- ❌ Individual images (no filenames)
- ❌ User data (no emails, IPs, personal info)
- ❌ Image content (we never see your images)
View statistics: Commercial license holders can view stats at https://howtodraw.pl/developer/licenses
🏗️ How It Works
Architecture
User Request → /storage/photos/thumbnails/image_thumb_small.jpg
↓
[Nginx tries to serve]
↓ (404/403 - file doesn't exist)
[ThumbnailFallback Middleware]
↓
Parses URL:
- Original: photos/image.jpg
- Size: small
↓
ThumbnailService::thumbnail()
↓
Generates thumbnail (150x150)
Saves to: photos/thumbnails/image_thumb_small.jpg
↓
Returns thumbnail (200 OK)
Header: X-Thumbnail-Generated: on-demand
↓
[Next request → Nginx serves cached file directly]
File Structure
Before first request:
storage/app/public/photos/
└── cat.jpg (original, 2.5 MB)
After thumbnail request:
storage/app/public/photos/
├── cat.jpg (original, 2.5 MB)
└── thumbnails/
├── cat_thumb_small.jpg (150x150, 15 KB)
├── cat_thumb_medium.jpg (300x300, 45 KB)
└── cat_thumb_large.jpg (600x600, 120 KB)
💼 Licensing
Choose Your License
| License | Price | Best For | Limits |
|---|---|---|---|
| Personal | FREE | Hobby projects, open-source | Non-commercial only |
| Small Business | $500/year | Startups, freelancers | 1-10 devs, <$500k revenue |
| Medium Business | $1,500/year | Growing companies | 11-50 devs, $500k-$10M revenue |
| Enterprise | $15,000/year | Large corporations | 50+ devs, unlimited |
Full details: LICENSE.md
Contact for commercial licensing: kontakt@howtodraw.pl
Why Commercial License?
- 🛠️ Ongoing Development - New features, bug fixes, updates
- 💬 Priority Support - Fast response times
- 📖 Comprehensive Docs - Tutorials, examples, best practices
- 🔒 Security Updates - Critical patches within 24h
- 💼 Business Continuity - SLA for Enterprise customers
🆚 Comparison
| Feature | This Package | Traditional Solutions |
|---|---|---|
| Generation | On-demand (lazy) | Pre-generate all sizes |
| Performance | Fast (only needed) | Slow (generates unused) |
| Storage | Efficient | Wastes space |
| Setup | Zero config | Complex setup |
| Cache | Filesystem | Often needs Redis |
| Dependencies | ext-gd (built-in) | Various |
📖 Examples
Gallery with Thumbnails
@foreach($images as $image) <a href="{{ asset('storage/' . $image->path) }}"> <img src="@thumbnail($image->path, 'small')" alt="{{ $image->title }}" loading="lazy"> </a> @endforeach
Responsive Images
<img src="@thumbnail('photos/image.jpg', 'small')" srcset=" @thumbnail('photos/image.jpg', 'small') 150w, @thumbnail('photos/image.jpg', 'medium') 300w, @thumbnail('photos/image.jpg', 'large') 600w " sizes="(max-width: 600px) 150px, (max-width: 1200px) 300px, 600px" alt="Responsive image">
React Component
import { getThumbnailUrl } from 'moonlight-thumbnails'; function ImageGallery({ images }) { return ( <div className="grid grid-cols-3 gap-4"> {images.map(img => ( <img key={img.id} src={getThumbnailUrl(img.path, 'medium')} alt={img.title} loading="lazy" /> ))} </div> ); }
🤝 Contributing
This is a commercial package. We welcome:
- 🐛 Bug reports (GitHub Issues)
- 💡 Feature suggestions (GitHub Issues)
- 📖 Documentation improvements (PRs welcome)
Contact: kontakt@howtodraw.pl
📄 License
Commercial License with free personal tier.
See LICENSE.md for full terms.
💝 Credits
Inspired by:
Built with ❤️ by Moonlight Poland Team
📞 Support
GitHub Issues: https://github.com/Moonlight4000/laravel-thumbnails/issues
Email: kontakt@howtodraw.pl
⭐ If this package helped you, please star it on GitHub!
moonlight-poland/laravel-context-aware-thumbnails 适用场景与选型建议
moonlight-poland/laravel-context-aware-thumbnails 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 14 次下载、GitHub Stars 达 0, 最近一次更新时间为 2026 年 01 月 03 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「symfony」 「images」 「gd」 「imagick」 「laravel」 「lazy」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 moonlight-poland/laravel-context-aware-thumbnails 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 moonlight-poland/laravel-context-aware-thumbnails 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 moonlight-poland/laravel-context-aware-thumbnails 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
A Laravel Nova field for distribute your images as array of object.
Pexels API Client for www.pexels.com
The bundle for easy using json-rpc api on your project
Stand-alone zoomify tile generator (format Zoomify) for use with OpenSeadragon, OpenLayers and various viewers.
TYPO3 CMS extension to create gallery content element with preset crop ratios and pagination
Image manipulation library for Laravel 8 based on Imagine and inspired by Croppa for easy url based manipulation
统计信息
- 总下载量: 14
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 33
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-01-03