Discover Hidden Gems in Laravel Spatie Media Library - TechvBlogs

Discover Hidden Gems in Laravel Spatie Media Library

Unlock the full potential of Laravel Spatie Media Library with our guide! Explore hidden features and tips to enhance your media management experience. Elevate your Laravel development skills today.


Smit Pipaliya - Author - TechvBlogs
Smit Pipaliya
 

3 months ago

TechvBlogs - Google News

Features We’ll Explore:

  1. mart Cropping: Prevent unintentional cropping or resizing errors for a polished appearance.

  2. Fallback Images: Set default images for cases where the original is missing, ensuring a seamless user experience.

  3. Image Sequencing: Organize your media by establishing a specific order, allowing for a structured presentation.

  4. Custom Enhancements: Integrate personalized properties and fields to tailor your media management to specific requirements.

  5. Universal Assigning: Effortlessly link media to multiple models simultaneously, streamlining your workflow.

  6. Bulk Retrieval: Retrieve media efficiently from various models at once, saving time and enhancing productivity.

  7. Adaptive Display: Showcase images that adapt to different screen sizes, ensuring a responsive and visually appealing presentation.

  8. API Resource Management: Standardize your output when working with APIs, facilitating seamless integration and communication.

Now, let’s delve deeper into each feature.

Ensuring Optimal Image Cropping and Resizing

Occasionally, when utilizing the Spatie Laravel MediaLibrary, you may observe that images don't appear as expected. They might be excessively cropped or resized in a less than ideal manner.

The issue of improperly cropped or resized images in the Spatie Laravel MediaLibrary often stems from using incorrect settings during the image conversion process. To address this, you can employ the fit method. Here's a guide on how to do it correctly:

Model:

public function registerMediaConversions(Media $media = null): void
{
    $this->addMediaConversion('thumbnail')
        ->fit(Manipulations::FIT_MAX, 200, 200)
        ->nonQueued();
}

In the provided example, utilizing the Manipulations::FIT_MAX approach ensures that the image adapts to the specified maximum width and height without undergoing any cropping. This method helps maintain the original aspect ratio while fitting the image within the defined constraints.

Indeed, the Spatie Laravel MediaLibrary offers a variety of manipulation options to cater to different image handling needs:

  • Manipulations::FIT_CONTAIN: Ensures the image stays within the set dimensions without any cropping, preserving the original aspect ratio.

  • Manipulations::FIT_MAX: Resizes the image based on the provided dimensions, avoiding stretching or cropping while maintaining the aspect ratio.

  • Manipulations::FIT_FILL: Resizes the image and fills any remaining space with a specified background color (typically white). No cropping or stretching occurs.

  • Manipulations::FIT_FILL_MAX: Similar to FIT_FILL, but it will enlarge the image if it's smaller than the set dimensions.

  • Manipulations::FIT_STRETCH: Stretches the image to fit the given dimensions, disregarding the original aspect ratio.

  • Manipulations::FIT_CROP: Adjusts the image to the desired dimensions and crops any excess, providing a precise control over the final output.

Each of these manipulation methods serves a distinct purpose, allowing you to choose the one that best suits your requirements and ensuring that your images consistently appear at their best.

Displaying a Placeholder Image When the Main Image is Missing

A proactive approach to address the absence of an image when creating new content is to automatically attach a default image. This ensures that, in cases where no image is provided, your content still retains visual appeal and coherence by featuring a predefined default image.

For instance, if you’re adding a post:

PostController.php:

public function store(StorePostRequest $request)
{
    $post = Post::create($request->validated());

    if ($request->hasFile('images')) {
        foreach ($request->file('images', []) as $image) {
            $post->addMedia($image)->toMediaCollection();
        }
    } else {
        // Attach a default image
        $post->addMedia(storage_path('defaults/defaultPostImage.png'))
             ->preservingOriginal()
             ->toMediaCollection();
    }

    return redirect()->route('posts.index');
}

Another effective method is to implement a placeholder image to be displayed when presenting content, particularly if the main image is not found.

Here's a guide on how to set up this feature:

 Model:

public function registerMediaConversions(Media $media = null): void
{
    $this->addMediaConversion('thumbnail')
         ->fit(Manipulations::FIT_FILL, 200, 200)
         ->nonQueued();
}

public function registerMediaCollections(): void
{
    $this->addMediaCollection('default')
         ->useFallbackUrl(asset('fallback/fallbackPostImage.png'))
         ->useFallbackUrl(asset('fallback/fallbackPostImage.png'), 'thumbnail')
         ->useFallbackPath(public_path('fallback/fallbackPostImage.png'))
         ->useFallbackPath(public_path('fallback/fallbackPostImage.png'), 'thumbnail');
}

Now, in your view, instead of:

<img src="{{ $post->getFirstMedia()?->getUrl('thumbnail') }}" class="object-contain" alt="{{ $post->title }}"/>

Use:

<img src="{{ $post->getFirstMediaUrl('default', 'thumbnail') }}" class="object-contain" alt="{{ $post->title }}"/>

By implementing this approach, if the primary image is unavailable, your users will seamlessly encounter the designated fallback image, contributing to a consistent and smooth user experience.

How to Organize and Sort Images

Fortunate for users of the media library package, arranging and sequencing images is made simple. By listing the IDs of your images in the preferred order, you can effortlessly rearrange and organize your image collection.

Controller:

use Spatie\MediaLibrary\MediaCollections\Models\Media;
//...

public function adjustImageOrder() {
    Media::setNewOrder([3, 2, 1]);
}

This method initiates with the number 1 and incrementally increases for each ID, ensuring a sequential order.

For finer control, you have the flexibility to individually change the order of each image. Here's a straightforward setup with links to move images either up or down:

View (posts/show.blade.php):

<div class="p-4">
    @foreach($post->getMedia() as $media)
    <div class="flex flex-rows space-x-4">
        <div class="flex flex-col justify-center">
            @if($media->order_column !== $post->getMedia()->min('order_column'))
            <a href="{{ route('posts.shiftImage', [$post, $media, 'up']) }}"
               class="text-blue-500 hover:text-blue-600 underline">Move up</a>
            @endif
            @if($media->order_column !== $post->getMedia()->max('order_column'))
            <a href="{{ route('posts.shiftImage', [$post, $media, 'down']) }}"
               class="text-blue-500 hover:text-blue-600 underline">Move down</a>
            @endif
        </div>
        <div>
            <span>Current position: {{ $media->order_column }}</span>
        </div>
        <div>
            <img src="{{ $media->getUrl('thumbnail') }}" alt="{{ $media->name }}">
        </div>
    </div>
    @endforeach
</div>

Routes (web.php):

use App\Http\Controllers\PostController;
//...

Route::resource('posts', PostController::class);
Route::get('posts/{post}/shift/{media}/{direction}', [PostController::class, 'shiftImage'])->name('posts.shiftImage');

Controller (PostController.php):

use Spatie\MediaLibrary\MediaCollections\Models\Media;
//...

public function shiftImage(Post $post, Media $media, string $direction)
{
    $targetMedia = ($direction === 'up') 
        ? $post->media()->where('order_column', '<', $media->order_column)->orderByDesc('order_column')->first() 
        : $post->media()->where('order_column', '>', $media->order_column)->orderBy('order_column')->first();
    list($media->order_column, $targetMedia->order_column) = [$targetMedia->order_column, $media->order_column];
    
    $media->save();
    $targetMedia->save();
    return back();
}

By clicking the "Move up" or "Move down" links, you can easily adjust the position of the image. This functionality proves especially useful, considering that deleting an image could potentially disrupt the established order. This method ensures a convenient and straightforward way to maintain the desired sequence of images.

Enhancing Images with Custom Data

Enhancing images can be achieved through two avenues:

  1. Expanding the Media Table
  2. Tapping into Custom Properties

Let's delve into both approaches with a comprehensive example.

Starting with the first method, let's expand the 'media' table by including a new column named 'caption.'

Migration:

return new class extends Migration {
    public function up() {
        Schema::table('media', function (Blueprint $table) {
            $table->string('caption')->nullable();
        });
    }
};

Now, let’s ensure the added details can be saved.

PostMediaController.php:

class PostMediaController extends Controller {
    public function editDetails(Request $request, Post $post, int $mediaID) {
        $this->validate($request, [
            'caption' => ['nullable', 'string', 'max:200'],
            'alt_text' => ['nullable', 'string', 'max:200'],
        ]);

        $mediaItem = $post->media()->find($mediaID);

        // Saving the caption
        $mediaItem->update(['caption' => $request->input('caption')]);

        // Storing the alt_text as a custom property
        $mediaItem->setCustomProperty('alt_text', $request->input('alt_text'));
        $mediaItem->save();

        return back();
    }
}

routes/web.php:

use App\Http\Controllers\PostController;
use App\Http\Controllers\PostMediaController;

To enhance user interaction, it's essential to incorporate a user interface. Let's implement a form, allowing users to effortlessly update captions and alternative text for an enriched experience.

show.blade.php:

{{-- ... --}}
@foreach($post->getMedia() as $media)
<div class="flex space-x-4">
    <img src="{{ $media->getUrl('thumbnail') }}" alt="{{ $media->name }}">
    <form action="{{ route('posts.media.update', [$post, $media]) }}" method="POST">
        @csrf
    @method('PUT')
        <!-- Caption Input -->
        <label>Caption</label>
        <input type="text" name="caption" value="{{ old('caption', $media->caption) }}">
        <!-- Alt Text Input -->
        <label>Alt Text</label>
        <input type="text" name="alt_text" value="{{ old('alt_text', $media->getCustomProperty('alt_text')) }}">
        <button type="submit">Update</button>
    </form>
</div>
@endforeach
{{-- ... --}}

After successfully completing these steps, users will encounter an intuitive interface where they can input additional details for images.

Finally, when it comes to displaying these images along with their newly added information:

index.blade.php:

@foreach($posts as $post)
<tr>
    <td>
        @foreach($post->getMedia() as $media)
        <img src="{{ $media->getUrl('thumbnail') }}"
             alt="{{ $media->getCustomProperty('alt_text', $post->title) }}">
        <span>{{ $media->caption }}</span>
        @endforeach
    </td>
</tr>
@endforeach

Take note that you can directly access the ‘caption’ field. As for the ‘alt_text,’ utilize the getCustomProperty method. In case the 'alt_text' isn't available, it gracefully defaults to the post's title.

And there you have it a straightforward yet powerful method to augment your images with additional information.

Linking an Image to Multiple Data Entries

To begin, let's create a new table called media_holders to store the images. Additionally, we'll require another table to establish the many-to-many relationship between posts and media holders.

Migration:public function up() {
    Schema::create('media_holders', function (Blueprint $table) {
        $table->id();
        $table->softDeletes();
        $table->timestamps();
    });

    // A table for linking media to posts
    Schema::create('media_holder_post', function (Blueprint $table) {
        $table->foreignId('media_holder_id')->constrained()->cascadeOnDelete();
        $table->foreignId('post_id')->constrained()->cascadeOnDelete();
    });
}

Let’s model our MediaHolder:

MediaHolder.php:

class MediaHolder extends Model implements HasMedia {
    use SoftDeletes, InteractsWithMedia;

    public function registerMediaConversions(Media $media = null) {
        $this->addMediaConversion('thumbnail')->fit(Manipulations::FIT_MAX, 200, 200);
    }

    // Link with posts
    public function posts() {
        return $this->belongsToMany(Post::class);
    }
}

For the Post model, add a relationship to MediaHolder:

class Post extends Model {
    // Link with media holders
    public function media() {
        return $this->belongsToMany(MediaHolder::class);
    }
}

To manage the creation of media and its association with posts, let's set up a controller dedicated to handling these functionalities.

PostMediaController.php:

class PostMediaController extends Controller {
    public function create() {
        $posts = Post::pluck('title', 'id');
        return view('postMedia.create', compact('posts'));
    }

    public function store(Request $request) {
        $this->validate($request, [
            'image' => ['required', 'image'],
            'posts' => ['required', 'array'],
            'posts.*' => ['required', 'exists:posts,id']
        ]);
        $media = MediaHolder::create();
        $media->addMedia($request->file('image'));
        $media->posts()->sync($request->input('posts'));
        return back();
    }
}

In our routes:

Route::resource('media', PostMediaController::class)->only(['create', 'store']);

Provide users with a straightforward form to upload images and select the posts to which they should be linked.

create.blade.php:

<form action="{{ route('media.store') }}" method="post" enctype="multipart/form-data">
    @csrf
    <div>
        <label>Image</label>
        <input type="file" name="image" id="image">
    </div>
    @foreach($posts as $id => $title)
    <div>
        <label for="post-{{ $id }}">
            <input type="checkbox" name="posts[]" id="post-{{ $id }}" value="{{ $id }}">
            {{ $title }}
        </label>
    </div>
    @endforeach
    <button type="submit">Upload</button>
</form>

To display related images:

index.blade.php:

@foreach($posts as $post)
<tr>
    <td>
        @foreach($post->media as $media)
        <img src="{{ $media->getFirstMedia()?->getUrl('thumbnail') }}" alt="{{ $post->title }}">
        @endforeach
    </td>
    <td>{{ $post->title }}</td>
    <td>{{ $post->post_text }}</td>
</tr>
@endforeach

 Lastly, ensure to circumvent the N+1 query issue by employing eager loading:

$posts = Post::with(['media.media'])->paginate(10);

By adopting this approach, you can effortlessly share a single image across multiple data entries. While it may introduce a slight curve, it effectively accomplishes the task at hand.

Displaying All Images from Different Data Entries

To initiate, let's establish a controller designed to manage this specific request.

AllMediaController.php:

use App\Models\Post;
use Spatie\MediaLibrary\MediaCollections\Models\Media;

class AllMediaController extends Controller {
    public function showAllImages() {
        $images = Media::query()
            ->with('model')
            ->where('model_type', Post::class)
            ->orderBy('order_column')
            ->orderBy('name')
            ->get();
        return view('allImages.display', ['images' => $images]);
    }
}

For the controller to function as intended, it is imperative to define a corresponding route.

web.php:

use App\Http\Controllers\AllMediaController;
Route::get('allImages', [AllMediaController::class, 'showAllImages'])->name('allImages');

Now, let's generate a simple view to showcase the images.

display.blade.php:

<div class="grid grid-cols-3 gap-4">
    @foreach($images as $image)
    <div class="text-center">
        <h2 class="text-xl font-semibold">{{ $image->model->title }}</h2>
        <img src="{{ $image->getUrl() }}" alt="{{ $image->name }}" width="200" class="my-2"/>
        <span>{{ $image->name }} (Position: {{ $image->order_column }})</span>
    </div>
    @endforeach
</div>

Utilizing Laravel’s Media Library and the Media model simplifies the retrieval and display of images. The process closely resembles handling any other Eloquent model. The getUrl() method proves invaluable for effortlessly showcasing images. Consequently, fetching and displaying images from diverse data entries becomes straightforward with minimal code.

Showcasing Flexible Images Based on Device Size

When incorporating an image into your model, simply employ the withResponsiveImages() method. This ensures that responsive images are seamlessly integrated into your model, enhancing the overall user experience.

PostController.php:

public function saveImage(StorePostRequest $request) {
    $newPost = Post::create($request->validated());

    foreach ($request->file('images', []) as $img) {
        $newPost->addMedia($img)
            ->withResponsiveImages()  // This magic line creates responsive versions
            ->toMediaCollection();
    }

    return redirect()->route('posts.listing');
}

When it comes to displaying your responsive images, utilize the following snippet:

posts/listing.blade.php:

<img src="{{ $post->getFirstMedia()?->getUrl('thumbnail') }}"
     srcset="{{ implode(', ', $post->getFirstMedia()?->getResponsiveImageUrls()) }}"
     class="object-contain"
     alt="{{ $post->title }}"/>

Understanding the Code:

  • Ensure compatibility with older browsers by employing the src attribute as a fallback when srcset is not recognized.
  • The real magic occurs in srcset, where modern browsers intelligently select the optimal image from the provided list based on the device's resolution.
  • Access all responsive image links effortlessly with getResponsiveImageUrls().
  • Utilize the implode function to convert this array into a string format suitable for the srcset attribute.

For a more concise approach, Laravel Media Library offers a swift method to showcase images:

{{ $post->getFirstMedia() }}

This line automatically generates the required <img .../> tag! Nevertheless, the more extended method offers additional customization options, such as modifying the ALT text or adjusting styles. Select the approach that best aligns with your specific requirements.

A word of advice: When dealing with a large number of images, consider implementing Laravel’s Queue. This ensures that image processing occurs in the background, contributing to a seamless user experience.

Simplifying Media Output with API Resources

Ensuring consistency in responses is crucial when working with APIs. Leveraging Laravel’s API Resources facilitates the establishment of a standardized output for our media. Let’s delve into the setup process:

This resource will serve as a representation for a collection of media items.

MediaCollection.php:

use Illuminate\Http\Resources\Json\ResourceCollection;

class MediaCollection extends ResourceCollection
{
    public $collects = MediaResource::class;

    public function toArray($request)
    {
        return $this->collection->toArray();
    }
}

This resource is designed to represent a singular media item.

MediaResource.php:

use Illuminate\Http\Resources\Json\JsonResource;

class MediaResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'uuid' => $this->uuid,
            'size' => $this->humanReadableSize,
            'name' => $this->name,
            'filename' => $this->file_name,
            'mime' => $this->mime_type,
            'order' => $this->order_column,
            'urls' => [
                'full' => $this->getFullUrl(),
                'conversions' => collect($this->generated_conversions)
                                    ->map(fn($value, $key) => $this->getFullUrl($key)),
                'responsive' => $this->getResponsiveImageUrls()
            ],
            'custom_data' => $this->custom_properties,
        ];
    }
}

Upon retrieving media, it will now be presented in a structured and standardized format:

{
    "media": [
        {
            "uuid": "sample-uuid",
            "size": "500 KB",
            "name": "Sample Image",
            "filename": "sample.png",
            "mime": "image/png",
            "order": 1,
            "urls": {
                "full": "http://sample-url.com/sample.png",
                "conversions": ["..."],
                "responsive": ["..."]
            },
            "custom_data": []
        }
    ]
}

To retrieve posts along with their associated media, seamlessly incorporate the MediaCollection within your PostResource. This integration ensures a comprehensive representation of posts and their corresponding media items.

PostResource.php:

use App\Http\Resources\Media\MediaCollection;

class PostResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'text' => $this->post_text,
            'media' => $this->whenLoaded('media', new MediaCollection($this->media)),
            // ... other fields ...
        ];
    }
}

With this configuration, you can consistently obtain a unified media output, fostering a seamless and predictable integration when working with the API. This approach enhances the overall cohesion and reliability of your API interactions.

Thank you! Your input is always appreciated. If you have any more questions or need further assistance, feel free to ask!

Comments (0)

Comment


Note: All Input Fields are required.