PHP code example of moonlight-poland / laravel-context-aware-thumbnails

1. Go to this page and download the library: Download moonlight-poland/laravel-context-aware-thumbnails library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

moonlight-poland / laravel-context-aware-thumbnails example snippets


// routes/console.php (Laravel 11+)
Schedule::command('thumbnails:license-refresh')->daily();

// app/Console/Kernel.php (Laravel 10), inside schedule()
$schedule->command('thumbnails:license-refresh')->daily();

'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',
],

// 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
]);

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 --}}

// 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;
}

// config/thumbnails.php
'method' => 'resize',

// config/thumbnails.php
'method' => 'crop',

// config/thumbnails.php
'method' => 'fit',

use Moonlight\Thumbnails\Facades\Thumbnail;

$url = Thumbnail::thumbnail('photos/image.jpg', 'medium');

// 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

use Moonlight\Thumbnails\Services\ThumbnailService;

class ImageController
{
    public function show(ThumbnailService $thumbnails)
    {
        $url = $thumbnails->thumbnail('photos/image.jpg', 'medium');
    }
}

// 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],
],

'driver' => 'gd', // 'gd' (default), 'imagick', or 'intervention'

'driver' => 'gd',

'driver' => 'imagick',

'driver' => 'intervention',

'quality' => 85,              // JPEG quality (1-100)
'fallback_on_error' => true,  // Return original on error
'cache_control' => 'public, max-age=31536000', // 1 year cache

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', ...]

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();

// config/thumbnails.php
'smart_crop' => [
    'enabled' => true,
    'algorithm' => 'energy', // 'energy', 'faces', 'saliency'
    'rule_of_thirds' => true, // Align focal point to rule of thirds
],

thumbnail_extension('photos/photo.jpg'); // "webp" with THUMBNAILS_FORMAT=webp, "jpg" by default



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',
        ]);
    }
}

use App\Http\Controllers\StorageController;

Route::get('/storage/{path}', [StorageController::class, 'serve'])
    ->where('path', '.*')
    ->middleware('signed')  // Laravel's native signed middleware
    ->name('storage.serve');

'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
],

  '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,
    ],
],

// 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'],
    ],
],

// 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

// 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
        ],
    ],
],

// 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
],

// config/thumbnails.php
'error_mode' => 'silent', // 'silent', 'strict', 'fallback'
'placeholder_image' => 'images/placeholder.jpg', // For 'fallback' mode

// config/thumbnails.php
'statistics' => [
    'enabled' => true,
    'send_to_moonlight' => true, // Send to Moonlight dashboard
],

❌ 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
bash
php artisan thumbnails:license-refresh
bash
php artisan vendor:publish --tag=thumbnails-config
bash
php artisan storage:link
bash
php artisan thumbnails:sync-js
blade
{{-- 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 --}}
bash
php artisan thumbnails:sync-js
jsx
// 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>
    );
}
blade
{{-- 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>
--}}