How to preview picture stored in the fake path

In Laravel, here are several approaches to preview images stored with "fake paths" or abstracted storage:

1. Using Laravel Storage Facade with Response

Create a Preview Route

php
// routes/web.php
Route::get('/preview/{filename}', [ImageController::class, 'preview'])
    ->name('image.preview');

Controller Method

php
// app/Http/Controllers/ImageController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\StreamedResponse;

class ImageController extends Controller
{
    public function preview($filename)
    {
        // Map fake filename to real path (you can use database, config, or logic)
        $realPath = $this->resolveFakePath($filename);
        
        // Check if file exists in storage
        if (!Storage::exists($realPath)) {
            abort(404);
        }
        
        // Get file contents and appropriate MIME type
        $file = Storage::get($realPath);
        $type = Storage::mimeType($realPath);
        
        return response($file, 200)
            ->header('Content-Type', $type)
            ->header('Content-Disposition', 'inline; filename="' . $filename . '"');
    }
    
    private function resolveFakePath($fakePath)
    {
        // Example mapping - you can use database, config array, or logic
        $mappings = [
            'user-avatar-123' => 'avatars/actual_avatar_1.jpg',
            'product-img-456' => 'products/images/product_photo_2.png',
        ];
        
        return $mappings[$fakePath] ?? $fakePath;
    }
}

2. Using Database-driven Mapping

Migration for Image Mapping

php
// database/migrations/2024_01_01_create_image_mappings_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateImageMappingsTable extends Migration
{
    public function up()
    {
        Schema::create('image_mappings', function (Blueprint $table) {
            $table->id();
            $table->string('fake_path')->unique();
            $table->string('real_path');
            $table->string('mime_type');
            $table->boolean('is_active')->default(true);
            $table->timestamps();
        });
    }
    
    public function down()
    {
        Schema::dropIfExists('image_mappings');
    }
}

Model

php
// app/Models/ImageMapping.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class ImageMapping extends Model
{
    protected $fillable = ['fake_path', 'real_path', 'mime_type', 'is_active'];
}

Updated Controller

php
// app/Http/Controllers/ImageController.php
public function preview($fakePath)
{
    $mapping = ImageMapping::where('fake_path', $fakePath)
                          ->where('is_active', true)
                          ->first();
    
    if (!$mapping || !Storage::exists($mapping->real_path)) {
        abort(404, 'Image not found');
    }
    
    $file = Storage::get($mapping->real_path);
    
    return response($file, 200)
        ->header('Content-Type', $mapping->mime_type)
        ->header('Content-Disposition', 'inline; filename="' . $fakePath . '"');
}

3. Using Streamed Response (Better for Large Files)

php
public function streamPreview($fakePath)
{
    $realPath = $this->resolveFakePath($fakePath);
    
    if (!Storage::exists($realPath)) {
        abort(404);
    }
    
    $stream = Storage::readStream($realPath);
    $mimeType = Storage::mimeType($realPath);
    
    return response()->stream(function () use ($stream) {
        fpassthru($stream);
        if (is_resource($stream)) {
            fclose($stream);
        }
    }, 200, [
        'Content-Type' => $mimeType,
        'Content-Disposition' => 'inline',
        'Cache-Control' => 'public, max-age=3600'
    ]);
}

4. Using Laravel Filesystem with Security

Secure Preview with Validation

php
public function securePreview(Request $request, $fakePath)
{
    // Add any authentication/authorization logic
    if (!auth()->check()) {
        abort(403, 'Unauthorized');
    }
    
    // Validate the fake path format
    $request->validate([
        'fakePath' => 'required|alpha_dash|max:100'
    ]);
    
    $realPath = $this->resolveFakePath($fakePath);
    
    // Prevent directory traversal
    if (str_contains($realPath, '..') || str_contains($realPath, '/../')) {
        abort(403, 'Invalid path');
    }
    
    if (!Storage::exists($realPath)) {
        abort(404);
    }
    
    // Check if file is actually an image
    $mimeType = Storage::mimeType($realPath);
    if (!str_starts_with($mimeType, 'image/')) {
        abort(403, 'File is not an image');
    }
    
    return response(Storage::get($realPath), 200)
        ->header('Content-Type', $mimeType);
}

5. Using Blade Templates for Preview

In your Blade template:

blade
{{-- Using the route directly --}}
<img src="{{ route('image.preview', ['filename' => 'user-avatar-123']) }}" 
     alt="User Avatar" 
     class="img-preview">

{{-- With error handling --}}
<img src="{{ route('image.preview', ['filename' => $imageFakePath]) }}" 
     alt="Preview" 
     onerror="this.style.display='none'"
     class="preview-image">

{{-- Multiple images from array --}}
@foreach($imagePaths as $fakePath)
<div class="image-container">
    <img src="{{ route('image.preview', ['filename' => $fakePath]) }}" 
         alt="Image {{ $loop->iteration }}"
         loading="lazy">
</div>
@endforeach

6. Using Intervention Image for Processing

First install Intervention Image:

bash
composer require intervention/image

php
use Intervention\Image\Facades\Image;

public function previewWithResize($fakePath, $width = 300, $height = 300)
{
    $realPath = $this->resolveFakePath($fakePath);
    
    if (!Storage::exists($realPath)) {
        abort(404);
    }
    
    $image = Image::make(Storage::get($realPath));
    
    // Resize maintaining aspect ratio
    $image->resize($width, $height, function ($constraint) {
        $constraint->aspectRatio();
        $constraint->upsize();
    });
    
    return $image->response('jpg');
}

7. Route Configuration

php
// routes/web.php

// Basic preview
Route::get('/preview/{filename}', [ImageController::class, 'preview'])
    ->name('image.preview');

// With size parameters
Route::get('/preview/{filename}/{width?}/{height?}', [ImageController::class, 'previewWithResize'])
    ->where(['width' => '[0-9]+', 'height' => '[0-9]+'])
    ->name('image.preview.resized');

// Secure preview requiring auth
Route::get('/secure-preview/{filename}', [ImageController::class, 'securePreview'])
    ->middleware(['auth'])
    ->name('image.preview.secure');

8. Caching for Performance

php
use Illuminate\Support\Facades\Cache;

public function previewWithCache($fakePath)
{
    return Cache::remember("image_preview_{$fakePath}", 3600, function () use ($fakePath) {
        $realPath = $this->resolveFakePath($fakePath);
        
        if (!Storage::exists($realPath)) {
            return response('Not found', 404);
        }
        
        $file = Storage::get($realPath);
        $mimeType = Storage::mimeType($realPath);
        
        return response($file, 200)
            ->header('Content-Type', $mimeType)
            ->header('Content-Disposition', 'inline');
    });
}

Key Points:

  • Use Storage facade instead of direct file paths

  • Always validate fake paths to prevent security issues

  • Consider using database for persistent mappings

  • Add authentication for sensitive images

  • Use caching for better performance

  • Handle errors gracefully with proper HTTP status codes

To Top