laravel picture gallery optimisation - laravel

I have photo gallery page, which shows all files from given directory.
I have following logic of handling pictures:
original pictures I keep inside storage/app/img
using console command and Intervention\Image library I create thumbs - in public/thumbs_md I keep small thumbs about 300x300 and in public/thumbs_lg I keep the same pictures, but 1024x768.
My idea about gallery page is that I find all files from given directory inside thumbs_md and show them as thumbs with the links to the files with the same names inside thumbs_lg.
And each picture should have description, which I take from $image->exif('ImageDescription') using Intervention\Image library.
And paginate results with 20 pictures on the page.
It works well with less than 200 pictures in the directory, but when it is more nginx gives me 504 Gateway Time-out.
I found that the reason in getting description from exif.
EXIF info stored only in original source pictures inside storage/app/img. During thumbs generating EXIF is deleted. I could't find a way to keep it. Maybe this is main reason why I get Gateway Time-out. I guess that I have 2 problems:
It takes longer time to get EXIF from big source file rather than from thumb, but I don't know how to copy exif data from source image to thumb.
I retrieve exif data from all files in directory at once, but I don't know how to split it into chunks, because I must to give all elements to paginator at once.
At the moment I just comment code where I read EXIF from all files and there is no problems, but I want to get those descriptions and I don't really want to keep image description in database. I like the fact that it is part of image.
Is there a way to optimise my code?
controller
public function item($slug)
{
$files = Storage::disk('storage')->allFiles('thumbs_md/'.$slug);
$links = Storage::disk('storage')->allFiles('thumbs_lg/'.$slug);
if ( empty($files) ){
abort(404);
}
// generate title of the page
try {
$image = Image::make( '/var/www/storage/app/img/categories/'. $slug.'.jpg');
}
catch(NotReadableException $e)
{
// If error
$title = 'Picture not found';
}
// If no error ...
$title = $image->exif('ImageDescription');
// generate description for picture
$ImageDescription = function($value) {
try { $image = Image::make(str_replace('thumbs_md', '/var/www/storage/app/img', $value)); }
catch(NotReadableException $e)
{
// If error
return 'Picture not found';
}
// If no error ...
return $image->exif('ImageDescription');
};
//$imgDesc = array_map($ImageDescription, $files);
for ($i=0; $i < count($files); $i++) {
$items[$i]['thumb'] = $files[$i];
$items[$i]['link'] = $links[$i];
//$items[$i]['description'] = $imgDesc[$i];
}
$items = Arr::arrayToObject($items);
$items = collect($items)->mypaginate($perPage = 20);
$items->title = $title;
$items->slug = $slug;
return view('pages.gallery.item', compact('items'));
}
view
<div class="gallery">
#foreach ($items as $item)
<a href="/storage/{{ $item->link }}" class="lightGallery">
<img {{-- alt="{{ $item->description }}" --}} src="/storage/{{ $item->thumb }}">
{{-- <p>{{ $item->description }}</p> --}}
</a>
#endforeach
</div>

I don't see why you would not put this stuff in a database, when you've clearly shown that retrieving EXIF data from a big file is slow. You don't want to be doing heavy work on each page load.
You are also limited to filtering data easily as long as it is not in a database table.
The code you have currently is badly written and not performant (you are retrieving the page title from an image???) so the way to go is to design a few database tables to handle this :). Once it is done, I bet you can shrink this controller to under 7 lines.

Intensive or time consuming processing should be avoided during request as far as possible for better user experience.
One way to optimise your workflow would be:
Create database table for categories
Create image_categories table with columns
name(string)
slug(string)(unique)
title(string): exif('ImageDescription') or any value
exif(text): whole exif data for the category image if required
Create database table for Images
Create the images table with columns
image_category(string): slug for the image category
src(string): path to the original image
src_thumb_md(string): path to the thumb_md (300 x 300) image
src_thumb_lg(string): path to the thumb_lg (1024 x 768) image
description(string): exif('ImageDescription')
exif(text): whole exif data for the image if required
Store the data when creating thumbnails for image(s)
Extend the console command which you are using to generate the thumbnails, to
store the paths and exif data to the images table for all images
store the title and exif data to the image_categories table when storing the image for category
Define Image and ImageCategory models with relations (optional)
class ImageCategory extends Model
{
public function images()
{
return $this->hasMany(ImageCategory::class, 'image_category');
}
}
class Image extends Model
{
public function imageCategory()
{
return $this->belongsTo(ImageCategory::class, 'image_category', 'slug');
}
}
Controller & View
If Models are defined
public function item($slug)
{
$category = ImageCategory::where('slug', $slug)->first();
$images = $category->images()->paginate(20);
return view('pages.gallery.item', compact('category', 'images'));
}
Or when models are not defined
public function item($slug)
{
$category = DB::tables('image_categories')->where('slug', $slug)->first();
$images = DB::table('images')->where('image_category', $slug)->paginate(20);
return view('pages.gallery.item', compact('category', 'images'));
}
<div class="gallery">
<h1>{{ $category->title }}</h1>
#foreach ($images as $image)
<a href="/storage/{{ $image->src_thumb_lg }}" class="lightGallery">
<img {{-- alt="{{ $image->description }}" --}} src="/storage/{{ $image->src_thumb_md }}">
{{-- <p>{{ $image->description }}</p> --}}
</a>
#endforeach
<!-- Pagination links -->
{{ $images->links() }}
</div>

Related

How to show multiple images in blade in Laravel from Amazon S3 storage?

I have Book model and there is a field called cover, where I save image link.
I want to get images' links from S3 storage in Collection of Book.
How can I do it?
As you did not provide details on your front-end, I assume you're using blade for it.
To get the images of all the books inside the collection and show them in the front-end, you should do it something like this:
(YourController.php)
public function collection(User $user){
$collection = Collection::where('user_id', $user->id)->first();
$books = $collection->books;
return view('collection', compact('collection', 'books'));
}
In your view, you'd like to foreach each book.
#foreach($books as $book)
<img src="{{Storage::disk('s3')->temporaryUrl('directoryWhereYouStoredYourFiles/' . $book->featured_image, now()->addMinutes(30))}}" alt="cover">
#endforeach

How to get Unique value in blade file laravel

I want to fetch unique value for filters of all products.
My db structure as follows
id Product category_id format attribute
1 demo1 5 HD test1
2 demmo2 4 SD test3
3 demmo3 4 HD test2
4 demmo4 3 HD test3
I want add filters of format and attribute in product page. But in that HD comming 3 times. I want to display that one only.
I am not getting how to display only single time.
Below is my controller code:
$item = Item::where('active_status', 1)->where('status', "1");
$data['item_count'] = $item;
$data['item'] = $item->paginate(20);
return view('frontend.pages.explore', compact('data'));
Below is blade file
<div class="filter-btn">
#foreach($data['item'] as $resolution)
<a class="btn btn-white-outline display-4" href="">{{array_unique($resolution->format)}}</a>
#endforeach
</div>
I am not getting how to display unique value only. Anyone have idea then let me know
since you are paginating your data, your "first page" might not have all the formats, so you have to do another query to your database:
$formats = DB::table('items')->select('format')->distinct()->get()
...
view(..., compact('data', 'formats'))
in the blade table:
#foreach($formats as $resolution)
<a class="btn btn-white-outline display-4" href="">{{$resolution->format}}</a>
#endforeach
If I am correct about your query, then you need to use groupby to list the items in your controller.
$items = Item::groupBy('format')->get();
The solution would be to create a sperate model for Formatand relationship between it and Product or whatever model that needs format , then fetch formats from its table and apply eager load .
this may look longer than your solution , but this is standards shoiuld be taken for vrious reasons :
less sql queries
more flexibility and options regarding our new table
better and less code to crud format
single source for change
less database data and faster load
...
Product.php
public function format(){
return $this->belongsTo(Format::class);
}
Format.php
public function products(){
return $this->hasMany(Product::class);
}
Usage
in controller
// get all formts eager loaded with products
$formats = Format::with('products')->get();
//get all products
$product = Format::latest()->paginate(20);
return view('frontend.pages.explore', compact( 'products' ,'formats'));
in your view
// all prodcuts
#foreach($formats as $format)
<a class="btn btn-white-outline display-4" href="">{{$format->name}}</a>
#endforeach
// in filter ( at clicking ) no additional query
#foreach($format->products as $product)
...
#endforeach

Get only first image in blade template from foreach loop

We have two tables room and room images. I am using relation in both tables and want to get the first image from room images table (For thumbnail). Below foreach loop is working fine on single detail page. Now i want to get the thumbnail image for main page.
#foreach ($room->tbl_roomimages as $single_img)
<img src="/storage/cover_images/{{$single_img->room_image}}" alt="">
#endforeach
I am using hasMany relation for Room images in room model.
You can create a function inside your model to get the first image.
Example:
public function getFirstImageAttribute()
{
return $this->hasOne('RoomImageModel');
}
And use that function to get the first image for your room.
<img src="/storage/cover_images/{{ $room->firstImage }}" alt="">
Thanks!
Although i get the first image like this.
#foreach($room->tbl_roomimages as $single_img)
#if($room->tbl_roomimages->first()==$single_img)
{{$single_img->room_image}}
#endif
#endforeach

Laravel SnappyPDF image attachment

Im currently using barryvdh/laravel-snappy to convert my data to pdf format.
The code i'm using is :
public function generate_pdf($id){
$rfq = RFQ::find($id);
$data = array(
'rfq' => $rfq,
);
//return view('rfq.pdf')->with('rfq', $rfq);
$pdf = PDF::loadView('rfq.pdf',$data)->setPaper('a4')->setOption('margin-bottom', 0);
return $pdf->download($rfq->rfq_no.'-'.date_timestamp_get(date_create()).'.pdf');
}
And then, I have to add an image to the pdf file. So i have to add this line to the pdf.blade.php file.
<img src="{{ asset('img/logo.png') }}">
But then, the image isn't showing properly.
It's visible, but it's OPACITY is very low, around 0.2.
But when viewing the actual image file, and the one in the actual html template, everything's good.
I tried <img src="data:image/png;base64,{{ base64_encode(#file_get_contents(asset('img/logo.png'))) }}">
but still, it's the same.

How to grab content from section within blade template

I have written a training application with each page/slide of the training workbook as a seperate blade template file named as "page1.blade.php", "page2.blade.php" and so on. Each of these files has content of the kind:
#extends('en/frontend/layouts/training_modulename')
{{-- Page title --}}
#section('title')
Page Title
#parent
#stop
{{-- Page content --}}
#section('pageContent')
<div class="pageContentContainer">
<h2>Page Title</h2>
...
</div>
#stop
This works really well when being viewed page by page within the browser. However I also wish to automatically compile all pages into a PDF document. This is being done via dompdf which works amazingly well when I pass each pages html to it manually. However I wish to condense the #section('pageContent') section of each page into one large section which extends a different layout for passing to dompdf.
Given the above context my question is this:
Is there a method in Laravel's blade parser which would allow me to pass it a blade file and just get the rendered html from a particular section? The below pseudo-code demonstrates what I would like to be able to do.
$pages = array(...); // content of the directory
foreach ($pages as $page)
{
$renderedPage = Blade::render($page);
$title = $renderedPage->title;
$pageContent = $renderedPage->pageContent;
}
Instead of doing the normal return of view
return View::make('page');
You can instead store the view in a string
$view = View::make('page');
So then you can do your code something like this (not tested - but you get the idea):
$pages = array(...); // content of the directory
foreach ($pages as $page)
{
$renderedPage[] = view::make($page);
}

Resources