Resize and replace image in Laravel request - php

I need to be able to resize an image and put the resized version back into the $request, does anyone know if thats possible?
Basically I have inherited some code that contains potentially 100+ separate file upload sections, and it is now my task to resize all images on the site if they are above a certain size.
So I now need to intercept ALL image uploads on the application, detect if they are above a set size and if they are, resize them.
All code i've found online only shows how to resize the image then save the resized version straight away, but I need to be able to resize the image then put it BACK into the $request to be processed by the controller.
The images come in the form of arrays of images from separate sections, so i need to be able to loop the entire request, check if any of the inputs contain/are files, then if they are check the sizes of them. If they're above a set size, then resize them and replace them in the $request so that when the request continues, the controller can process the image as normal but it will be processing the new resized version.
I have tried resizing images and then using laravels $request->merge() method but I cannot get it to work.
At the moment I am resizing all images in a middleware, like this
public function handle($request, Closure $next)
{
foreach($request->files as $fileKey => $file){
//Create a new array to add the newly sized images in
$newFileArray = [];
//Get each of the files that are being uploaded in the request, if there are no files this will just be ignored.
foreach ($file as $key => $f) {
if(!is_null($f)){
$image = Image::make($f);
if($image->height() > 500 || $image->width() > 500){
$image->resize(500, null, function ($constraint) {
$constraint->aspectRatio();
});
}
$newFileArray[$key] = $image;
} else {
$newFileArray[$key] = null;
}
}
$request->merge([
$fileKey => $newFileArray
]);
};
return $next($request);
}
I just can't get it to work!
Is this possible?
EDIT
After a great suggestion in the comments of one of the answers below, I've achieved this by editing the temp image file directly so I don't have to mess with the request, this is how i've done it.
public function handle($request, Closure $next)
{
foreach($request->files as $fileKey => $file){
//Get each of the files that are being uploaded in the request, if there are no files this will just be ignored.
foreach ($file as $key => $f) {
if(!is_null($f)){
$image = Image::make($f->getPathName());
if($image->height() > 500 || $image->width() > 500){
$image->resize(500, null, function ($constraint) {
$constraint->aspectRatio();
});
$image->save($f->getPathName());
}
}
}
};
return $next($request);
}

I just read that Laravel uses PSR-7 requests.
https://laravel.com/docs/5.7/requests#psr7-requests
These are immutable. In other words, you can't change the data once set. What you can do however, is get it to create a new request with your new parameters.
Looking at the PSR-7 interface, we see there is a method which looks like exactly what you need:
https://github.com/php-fig/http-message/blob/master/src/ServerRequestInterface.php#L150
/**
* Create a new instance with the specified uploaded files.
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that has the
* updated body parameters.
*
* #param array $uploadedFiles An array tree of UploadedFileInterface instances.
* #return static
* #throws \InvalidArgumentException if an invalid structure is provided.
*/
public function withUploadedFiles(array $uploadedFiles);
So, do your thing, create your array, and once it's ready, replace your request like this:
$request = $request->withUploadedFiles($yourNewArray);

Related

Laravel Azure Blob Storage Cache Images

this is my scenario:
I have a Laravel 6.x application and I'm using the FlySystemAzureBlobStorage package to store images on Azure Blob Storage.
Then I want to use the InterventionImageCache package to get and cache the images for faster client's downloads in different sizes.
I've already done it in this way:
public static function getImageResponseForApi($storageDiskName, $imagePath, $width = null, $height = null)
{
//check if the image exists on disk
$exists = empty($storageDiskName) ?
Storage::exists($imagePath) :
Storage::disk($storageDiskName)->exists($imagePath);
if ($exists) {
$image = empty($storageDiskName) ?
Storage::get($imagePath) :
Storage::disk($storageDiskName)->get($imagePath);
if (!empty($width) || !empty($height)) {
//use the image cache function to get the cached image if exists
$image = \Image::cache(function ($ima) use ($image, $width, $height) {
//check if height and with are higher than original or not
$ima->make($image)->resize($width, $height, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
});
});
}
$sizes = getimagesizefromstring($image);
$headers = [
'Content-Length' => strlen($image),
'Content-Type' => $sizes["mime"]
];
return \Response::make($image, 200, $headers);
} else {
return null;
}
}
There is a problem working in this way: I must download the image from azure blob storage before the system can check if a resized cached version of it exists.
The Azure package i'm using doesn't provide the possibility to get the image paths, so I can't find another solution to my problem.
So, is there a way to achieve the image caching without I have to download the file every time?
I use a separate route for showing/caching the image in my blade.
I store the image as BLOB in MYSQL PRODUCT table identified by product_id
BLADE:
<img src="/dbimage/{{$product->id}}.png" >
ROUTE:
Route::get('/dbimage/{id}',[ProductImageController::class, 'getImage']);
CONTROLLER:
class ProductImageController extends Controller
{
public function getImage($prodcutImageID){
$productID=explode(".",$prodcutImageID);
$rendered_buffer= Product::all()->find($productID[0])->image;
$response = Response::make($rendered_buffer);
$response->header('Content-Type', 'image/png');
$response->header('Cache-Control','max-age=2592000');
return $response;
}
}
This makes the browsers cache the images

Laravel 5.5: add url to json object to save multiple pictures

I need to save multiple pictures with Laravel. The pictures are stored separately on disk and the links to the places are stored in a JSON file in the database. So each users has an column in the database with:
{"images": ["/user/57/house-11-1.png", "/use/57/house-12-2.png"]}
So when the user uploads a file and click on save this code happens:
$path = $this->processImage($request, $user->id, $house->id);
/* uploads the image to the server and pass path of the image*/
if ($path) {
$jsonstring = $house->images;
dd($jsonstring);
$arr = json_decode($jsonstring);
$arr['images'] = [$path];
$json = json_encode($arr);
dd($json);
$house->images = $json;
$house->save();
}
The laravel model that is used is called userHouse and saves images as:
class userHouse extends Model implements Changeable
{
protected $casts = [
'images' => 'array',
];
...
}
processImage function:
private function processImage($request, $userID, $houseId)
{
$path = null;
$number = rand(1, 99);
if ($request->hasFile('image')) {
$image = Image::make($request->file('image'))
->resize(750, null, function ($constraint) {
$constraint->aspectRatio();
})
->encode('png');
$path = "/vendors/{$userID}/horse-{$houseId}-{$number}.png";
Storage::disk('fileadmin')->put($path, $image->encoded);
}
return $path;
}
the error I get is:
[2019-05-27 08:01:15] local.ERROR: Serialization of 'Illuminate\Http\UploadedFile' is not allowed {"userId":57,"email":"info#test.com","exception":"[object] (Exception(code: 0): Serialization of 'Illuminate\\Http\\UploadedFile' is not allowed at /Users/dsfsdf/ah-website/user/laravel/framework/src/Illuminate/Session/Store.php:128)
[stacktrace]
How can I add the url to the JSON string in the database? Uploading single images works.
What I could think of is that the processImage method cannot handle multiple uploaded files in the request.
Also, IMO, this method should actually receive the UploadedFile object instead of the entire request.
So, the code would be similar to:
$images = $house->images;
foreach ($request->images as $uploadedFile) {
$images[] = $this->processImage($uploadedFile, $user->id, $house->id);
}
$house->images = $images;
$house->save();
Also, if you defined the images attribute as array in the model, there is no need to decode/encode it as laravel already does it.

Laravel middleware cancel request and keep page in same state

Here's my current issue.
At the moment, I have a page with elements that can be added in and appended via AJAX. These elements contain forms, image uploads etc.
I have a middleware on my entire application that checks the size of any image being uploaded at any given time and makes sure its under 5MB (image validation for each image upload on the application is not an option, it has to be 1 controller that maintains all image upload validation).
If the request detects an image thats over 5MB, it will run this code
return redirect()->back()->withInput($request->all())->withErrors(array('Image' => 'Sorry, ' . $file->getClientOriginalName() . ' is too large, maximum file size is 5MB. Please reduce the size of your image!'));
This code is very temperamental, and heres why.
I need the page to be in the EXACT same state i left it in, when its returned. That means all AJAX loaded elements, all images, everything needs to be in the same state, so redirect()->back()->withInput($request->all()) doesn't work, because it still refreshes the page and removes everything loaded appended and added in that instance.
I need to be able to cancel the request if it fails.
In plain english, When this middleware is ran, detect all images. If there is an image that is over 5MB, do not refresh the page or anything. Just error
I know this seems silly, because the request cannot pass something back without refreshing, but I thought id ask / open to suggestions.
Here's my middleware
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\UploadedFile;
use Symfony\Component\HttpFoundation\Response;
class ImageInterceptor
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
foreach (array_filter(array_flatten($request->files->all())) as $file) {
//Check if the file being uploaded is not a csv
if($file->getClientOriginalExtension() != 'csv'){
$filename = $file->getClientOriginalName();
$size = $file->getClientSize(); // size in bytes!
$onemb = pow(1024, 2);
if ($size > $onemb * 5) {
//Return back, image is too big!
return redirect()->back()->withInput($request->all())->withErrors(array('Image' => 'Sorry, ' . $file->getClientOriginalName() . ' is too large, maximum file size is 5MB. Please reduce the size of your image!'));
}
}
}
return $next($request);
}
}
If you plan on having the page on the same state, then you can't tell it to redirect backwards with errors, you would have to return an array, or string, or whatever is your needs. By saying redirect backwards, it's telling the browser where to navigate.
About maintaining the inputs, you can try something among these lines:
<input type="text" name="firstname" id="firstname" class="form-control" value="{{ $user->firstname or old('firstname') }}">
Why don't you create a form request? I really doubt you need that validation for every page you require. Middleware, in my opinion, should handle authentication and authorization .
A formrequest would be something like:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class Example extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'photo' => 'required|mimes:jpeg,bmp,png|size:5000'
];
}
}
And on your controller, you just place a argument on the function (instead of Request $request, you place Example $request). This way, you can access every request information that Illuminate has plus your own validation.
https://laravel.com/docs/5.2/validation#rule-mimetypes

How to upload multiple files with simple form in symfony 3

I want to create a form with multiple files (images) upload in Symfony 3 and and simple form (i'm not using symfony form builder), but i get only one file (the first file). i'm using POSTMAN for send files via post method.
public function testAction(Request $request)
{
$file = $request->files->get('images');
$ext = $file->guessExtension();
$file_name = time() . '.' . $ext;
$path_of_file = 'uploads/test';
$file->move($path_of_file, $file_name);
var_dump($file);
die();
}
You didn't provide enough information, but maybe the problem is that you didn't set key property as array in Postman like this 'images[]' - than your Symfony endpoint will get an array of UploadedFile objects with all the needed data about your files and you also need to put foreach in your code here:
public function testAction(Request $request)
{
$file = $request->files->get('images');
foreach ($file as $item) {
do some operations
}

Return a default image, if no image is found Laravel Intervention

For a travel application, the mobile application needs to get a default image for each city from their city code.
For example: example.com/imageCache/thumbnail/JFK.png
Where thumbnail is custom filter defined as:
/**
* Sample filter for image manipulation
* via image cache
*/
namespace App\ImageFilters;
use Intervention\Image\Filters\FilterInterface;
use Intervention\Image\Image;
use Intervention\Image\ImageManagerStatic;
class Thumbnail implements FilterInterface
{
/**
* Applies filter to given image
*
* #param Image $image
* #return Image
*/
public function applyFilter(Image $image)
{
//TODO: Do something to check if the image doesn't exist.
$gradient = ImageManagerStatic::make(public_path('images/gradient.png'));
return $image->fit(200, 200)->insert($gradient,'center')->blur();
}
}
The application however throws 404 even before this function is called.
I would like to show a default image, if the image is not found.
Thanks in Advance.
The URL manipulation as it is may not work in this case.
Write a route getCityImage/{cityCode} as:
public function getCityImage($cityCode){
if(file_exists('path_to_city_images/'.$cityCode.'.png'){
$image = Intervention\Image\Image::make('path_to_city_images/'.$cityCode.'.png');
return $image->filter(new Thumbnail());
}
else {
return $your_default_image;
}
}

Categories