Laravel API store and retrieve base64 images in Storage - php

I am working with Angular and Laravel on a project where I have destinations table
And I need to store destinations, and for every destination there is multiple images I need to store
So there is destination_images table, I made one-to-many relationship between the tables
So I have two models: Destination - DestinationImage
The store Laravel function
public function store(Request $request) {
$validator = Validator::make($request->all(), [
'name' => 'required|string|max:100',
'description' => 'required'
]);
if ($validator->fails()) {
$errors = $validator->errors();
return response()->json($errors);
}
$destination = Destination::create([
'name' => $request->name,
'description' => $request->description
]);
foreach ($request->fileSource as $img_code) {
$ext = explode('/', mime_content_type($img_code))[1];
$img_name = uniqid() . ".$ext";
$decoded_img = base64_decode($img_code);
$path = Storage::put('uploads/destinations' . $img_name, $decoded_img);
DestinationImage::create([
'destination_id' => $destination->id,
'img' => $img_name
]);
}
return response()->json('Destination Added Successfully');
}
and it stores the file successfully but now I need to retrieve the images from Laravel storage and show it in Angular so I made this function
public function view($id) {
$destination = Destination::findOrFail($id);
$destination_images = $destination->destination_images;
foreach ($destination_images as $destination_image) {
$url = Storage::url($destination_image->img);
return response()->json($url);
}
}
but the response is not completed url it's just "/storage/62a7056a5d8c6.png"
Please anyone can help me how to maintain the view function to show the images in Angular?

You are using storage path mean while client can't access to it.
First you need to enable storage link php artisan storage:link and it should able to access http://yourdomain.com/storage/62a7056a5d8c6.png
$image = App\Models\DestinationImage::find(1);
echo url("/destination_images/{$image->id}");
use Illuminate\Support\Facades\Storage;
public function view($id) {
$destination = Destination::findOrFail($id);
$destination_images = $destination->destination_images;
$imageList = [];
foreach ($destination_images as $destination_image) {
$imageList[] = Storage::url($destination_image->img);
return response()->json($imageList);
}
}

Related

Laravel API project how retrieve images from storage and send it to frontend

I am working on Laravel API project
I have destinations table and destination_images table with one-to-many relationship
When storing destination I am also receiving the images and store each image in Storage::disk('public') and generate random name for it and store the image name in the destination_images table
the store function
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required|string|max:100',
'description' => 'nullable|string',
'fileSource' => 'required'
]);
if ($validator->fails()) {
$errors = $validator->errors();
return response()->json($errors);
}
$destination = Destination::create([
'name' => $request->name,
'description' => $request->description
]);
foreach ($request->fileSource as $img) {
$extension = explode('/', explode(':', substr($img, 0, strpos($img, ';')))[1])[1];
$replace = substr($img, 0, strpos($img, ',')+1);
$image = str_replace($replace, '', $img);
$image = str_replace(' ', '+', $image);
$imageName = 'destination-' . Str::random(10).'.'.$extension;
Storage::disk('public')->put($imageName, base64_decode($image));
DestinationImage::create([
'destination_id' => $destination->id,
'img' => $imageName
]);
}
return response()->json('Destination Created Successfully');
}
My question is how to handle the show function? Should I use the image name I am getting from the database with a link in the frontend? What is the best practice for this process?
Laravel can automatically include your relationships. So when you show the Destination just include it. Notice i'm using model binding for the Destination.
public function show(Destianation $destination) {
$destination->load('destinationImage'); // load the relationship.
return $destination;
}
class DestinationImage {
protected $appends = [
'path',
];
public function getPathAttribute()
{
return Storage::disk('public')->path($this->img);
}
}
Now your response should look like this.
{
... // fields
destinationImages: [{
img: "somename.jpg",
}];
}
This is not enough to show the image, Laravel storage has a method called path, to get the full path of the image. Now you need to make an Eloquent Getter and append it to the DestinationImage model. This will automatically add it to your response.
class DestinationImage {
protected $appends = [
'path',
];
public function getPathAttribute()
{
return Storage::disk('public')->url($this->img);
}
}

Validated and uploaded image incorrect name saved in MySQL in Laravel 6

Validation of text and photo takes place in StorePost FormRequest.
public function rules()
{
return [
'name' => 'required',
'exerpt => 'required',
'photo' => 'required|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
];
}
Then the controller part:
public function store( StorePost $request )
{
$imageName = time().'.'.$request->photo->extension();
$request->photo->move(public_path('post-images'), $imageName);
// may modify image name here but it's not elegant
//$data = $request->all();
//$data['photo'] = $imageName;
Post::create( $request->all() );
}
Image saves in MySQL as /private/var/folders/zr/y1drl_rs0sl75rxvgkx8ntzm0000gn/T/phpUJKeEG.
How can I set its name before the request gets to the controller?
I wouldn't like to do this such as here (commented lines).
You may use
$imageName = time().'_'.$request->photo->extension();
$request->photo->storeAs('public/post-images',$imageName);
$post = new Post;
//...
$post->photo = $imageName;
//...
$post->save();

Image update and remove old image on Laravel

Trying to implement update article in my update controller it seems works, but the problem is when I only want to update the post without uploading an image the old always getting remove which is it shouldn't.
here's my store function
public function store(Post $post)
{
$post->update($this->validateRequest());
$this->storeImage($post);
return redirect('post/'.$post->id)->with('success', 'New ariticle has been posted');
}
}
here's my validation
private function validateRequest()
{
return request()->validate([
'title'=> 'required',
'content' => 'required',
'image' => 'sometimes|image|max:5000',
]);
}
here's my update function
public function update(Post $post)
{
File::delete(public_path('storage/'.$post->image));
$post->update($this->validateRequest());
$this->storeImage($post);
return redirect('post/'.$post->id)->with('success', 'This post has
been Edited');
}
}
I've tried to add File::delete to my storeImage function and delete it from my update function, it fix the problem but the old image is not removed from directory
private function storeImage($post)
{
if (request()->has('image')){
File::delete(public_path('storage/'.$post->image))
$post->update([
'image' => request()->image->store('uploads', 'public'),
]);
$image = Image::make(public_path('storage/'.$post->image))->fit(750, 300);
$image->save();
}
}
Ok since I use model binding in my controller I don't have to find the id right?
so I change my update function which is basically Akhtar munir suggested, and turn out to be something like this. The image update work, it also remove the old image when I update it. But I have found another issue, the problem is when I edit article and title it didn't change like when I update it, I hope you can take look at this is this correct?
public function update(Post $post){
$this->validateRequest();
if(request()->hasFile('image') && request('image') != ''){
$imagePath = public_path('storage/'.$post->image);
if(File::exists($imagePath)){
unlink($imagePath);
}
$image = request()->file('image')->store('uploads', 'public');
$post->update([
'title' => request()->title,
'content' => request()->content,
'image' => $image,
]);
}
}
This is what I have done in one of my method. It may help you.
public function update(Request $request, $id)
{
if (UserDocument::where('id',$id)->exists()) {
$this->validateUserDocument($request);
if ($request->hasFile('doc_file') && $request->doc_file != '') {
$doc = UserDocument::where('id',$id)->first();
// dd($doc);
$file_path = storage_path().'/app/'.$doc['doc_file'];
//You can also check existance of the file in storage.
if(Storage::exists($file_path)) {
unlink($file_path); //delete from storage
// Storage::delete($file_path); //Or you can do it as well
}
$file = $request->file('doc_file')->store('documents'); //new file path
$doc->update([
'title' => $request->title,
'doc_file' => $file //new file path updated
]);
session()->flash('success','Document updated successfully!');
return redirect()->route('userdocs');
}
session()->flash('error','Empty file can not be updated!');
return redirect()->back();
}
session()->flash('error','Record not found!');
return redirect()->back();
}
In this code, I just simply want to clearify to you that I have stored image path in database, first I have retrieved that path and with that path I have found image in my local storage, delete it first and then update it with the new one. But make sure to store image path in database in both cases ofcourse with insert and update.
So finally you can also optimize your code like this, it will do the same thing as you expect, whether image and all data or only title and content.
public function update(Post $post){
$this->validateRequest();
$data = [
'title' => request()->title,
'content' => request()->content
];
if (request()->hasFile('image') && request('image') != '') {
$imagePath = public_path('storage/'.$post->image);
if(File::exists($imagePath)){
unlink($imagePath);
}
$image = request()->file('image')->store('uploads', 'public');
$data['image'] = $image;
//$post->update($data);
}
$post->update($data);
}
Try this one
private function storeImage($post)
{
if (request()->hasFile('image')){
$image_path = "/storage/".'prev_img_name'; // prev image path
if(File::exists($image_path)) {
File::delete($image_path);
}
$post->update([
'image' => request()->image->store('uploads', 'public'),
]);
$image = Image::make(public_path('storage/'.$post->image))->fit(750, 300);
$image->save();
}
}

How to make Laravel controller store file upload with a relationship

I am trying to store an uploaded file with a relationship to an Employee model. I am unable to retrieve the employee id after uploading the file to save it to the DB table as a foreign key.
Routes:
Route::resource('employees', 'EmployeesController');
Route::post('documents', 'DocumentsController#createdocument')
So I am on a URL that says http://localhost/public/employees/8 when I hit upload it redirects to http://localhost/public/documents and the file does upload but shows error when writing to DB.
Here is my code. How can I do it?
public function createdocument(Request $request, Employee $id)
{
$file = $request->file('file');
$allowedFileTypes = config('app.allowedFileTypes');
$maxFileSize = config('app.maxFileSize');
$rules = [
'file' => 'required|mimes:'.$allowedFileTypes.'|max:'.$maxFileSize
];
$this->validate($request, $rules);
$time = time(); // Generates a random string of 20 characters
$filename = ($time.'_'.($file->getClientOriginalName())); // Prepend the filename with
$destinationPath = config('app.fileDestinationPath').'/'.$filename;
$uploaded = Storage::put($destinationPath, file_get_contents($file->getRealPath()));
if($uploaded){
$employee = Employee::find($id);
$empdoc = new EmpDocuments();
$empdoc->name = $filename;
$empdoc->employee_id = $employee->id;
$empdoc->save();
}
return redirect('employees');
}
These are my models.
Employee.php
public function EmpDocuments()
{
return $this->hasMany('App\EmpDocuments');
}
public function createdocument(){
return $this->EmpDocuments()->create([
'name' => $filename,
'employee_id' => $id,
]);
}
EmpDocuments.php
public function Employee()
{
return $this->belongsTo('App\Employee');
}
With the above models and controller I am now getting error
General error: 1364 Field 'employee_id' doesn't have a default value (SQL: insert into empdocuments.
How do I capture the employee_id?
Managed to fix it, in case someone has similar problem. Ensure you pass the id with the route action for it to be capture in the next request.
Here is the final controller.
public function update(Request $request, $id)
{
$file = $request->file('file');
$allowedFileTypes = config('app.allowedFileTypes');
$maxFileSize = config('app.maxFileSize');
$rules = [
'file' => 'required|mimes:'.$allowedFileTypes.'|max:'.$maxFileSize
];
$this->validate($request, $rules);
$time = time(); // Generates a random string of 20 characters
$filename = ($time.'_'.($file->getClientOriginalName())); // Prepend the filename with
$destinationPath = config('app.fileDestinationPath').'/'.$filename;
$uploaded = Storage::put($destinationPath, file_get_contents($file->getRealPath()));
if($uploaded){
$employee = Employee::find($id);
$empdoc = new EmpDocuments();
$empdoc->name = $filename;
$employee->empdocuments()->save($empdoc);
return redirect('employees/' . $id . '#documents')->with('message','Document has been uploaded');
}
}
Do you have a relationship between Employee and EmpDocuments ??
If I am understanding well EmpDocuments belongsTO Employees right??
I'm trying to help but I need to understand, one employee can have many documents right?? but each document belongs to just one employee right??
If is like that you should make a relationship in your employee model,
` public function employeeDocuments(){
return $this->hasMany(EmpDocuments::class);
}`
Then in the same model
`public function createEmployeeDocuments(){
return $this->employeeDocuments()->create([
'your_db_fields' =>your file,
'your_db_fields' => your other some data,
]);
}`
The id will be inserted automatically
I hope I helped you!!
https://laravel.com/docs/5.3/eloquent-relationships
Are your fillable empty???
To use the Eloquent create method you need to set you fillable array to mass assignment. Try this, if is still not working tell me and I will try to do my best.
protected $fillable = [ 'employee_id', 'Your_db_field', 'Your_db_field', 'per_page', 'Your_db_field', 'Your_db_field' ];

cakePHP 3.0 uploading images

I want to upload images in my cakephp 3.0 app. But I get the error message:
Notice (8): Undefined index: Images [APP/Controller/ImagesController.php, line 55]
Are there already some examples for uploading files (multiple files at once) in cakePHP 3.0? Because I can only find examples for cakePHP 2.x !
I think I need to add a custom validation method in my ImagesTable.php? But I can't get it to work.
ImagesTable
public function initialize(array $config) {
$validator
->requirePresence('image_path', 'create')
->notEmpty('image_path')
->add('processImageUpload', 'custom', [
'rule' => 'processImageUpload'
])
}
public function processImageUpload($check = array()) {
if(!is_uploaded_file($check['image_path']['tmp_name'])){
return FALSE;
}
if (!move_uploaded_file($check['image_path']['tmp_name'], WWW_ROOT . 'img' . DS . 'images' . DS . $check['image_path']['name'])){
return FALSE;
}
$this->data[$this->alias]['image_path'] = 'images' . DS . $check['image_path']['name'];
return TRUE;
}
ImagesController
public function add()
{
$image = $this->Images->newEntity();
if ($this->request->is('post')) {
$image = $this->Images->patchEntity($image, $this->request->data);
$data = $this->request->data['Images'];
//var_dump($this->request->data);
if(!$data['image_path']['name']){
unset($data['image_path']);
}
// var_dump($this->request->data);
if ($this->Images->save($image)) {
$this->Flash->success('The image has been saved.');
return $this->redirect(['action' => 'index']);
} else {
$this->Flash->error('The image could not be saved. Please, try again.');
}
}
$images = $this->Images->Images->find('list', ['limit' => 200]);
$projects = $this->Images->Projects->find('list', ['limit' => 200]);
$this->set(compact('image', 'images', 'projects'));
$this->set('_serialize', ['image']);
}
Image add.ctp
<?php
echo $this->Form->input('image_path', [
'label' => 'Image',
'type' => 'file'
]
);
?>
Image Entity
protected $_accessible = [
'image_path' => true,
];
In your view file, add like this, in my case Users/dashboard.ctp
<div class="ChImg">
<?php
echo $this->Form->create($particularRecord, ['enctype' => 'multipart/form-data']);
echo $this->Form->input('upload', ['type' => 'file']);
echo $this->Form->button('Update Details', ['class' => 'btn btn-lg btn-success1 btn-block padding-t-b-15']);
echo $this->Form->end();
?>
</div>
In your controller add like this, In my case UsersController
if (!empty($this->request->data)) {
if (!empty($this->request->data['upload']['name'])) {
$file = $this->request->data['upload']; //put the data into a var for easy use
$ext = substr(strtolower(strrchr($file['name'], '.')), 1); //get the extension
$arr_ext = array('jpg', 'jpeg', 'gif'); //set allowed extensions
$setNewFileName = time() . "_" . rand(000000, 999999);
//only process if the extension is valid
if (in_array($ext, $arr_ext)) {
//do the actual uploading of the file. First arg is the tmp name, second arg is
//where we are putting it
move_uploaded_file($file['tmp_name'], WWW_ROOT . '/upload/avatar/' . $setNewFileName . '.' . $ext);
//prepare the filename for database entry
$imageFileName = $setNewFileName . '.' . $ext;
}
}
$getFormvalue = $this->Users->patchEntity($particularRecord, $this->request->data);
if (!empty($this->request->data['upload']['name'])) {
$getFormvalue->avatar = $imageFileName;
}
if ($this->Users->save($getFormvalue)) {
$this->Flash->success('Your profile has been sucessfully updated.');
return $this->redirect(['controller' => 'Users', 'action' => 'dashboard']);
} else {
$this->Flash->error('Records not be saved. Please, try again.');
}
}
Before using this, create a folder in webroot named upload/avatar.
Note: The input('Name Here'), is used in
$this->request->data['upload']['name']
you can print it if you want to see the array result.
Its works like a charm in CakePHP 3.x
Now that everyone makes advertisement for his plugins here, let me do this as well. I've checked the Uploadable behavior linked in the other question, it's pretty simple and half done it seems.
If you want a complete solution that is made to scale on enterprise level check FileStorage out. It has some features I haven't seen in any other implementations yet like taking care of ensuring your won't run into file system limitations in the case you get really many files. It works together with Imagine to process the images. You can use each alone or in combination, this follows SoC.
It is completely event based, you can change everything by implementing your own event listeners. It will require some intermediate level of experience with CakePHP.
There is a quick start guide to see how easy it is to implement it. The following code is taken from it, it's a complete example, please see the quick start guide, it's more detailed.
class Products extends Table {
public function initialize() {
parent::initialize();
$this->hasMany('Images', [
'className' => 'ProductImages',
'foreignKey' => 'foreign_key',
'conditions' => [
'Documents.model' => 'ProductImage'
]
]);
$this->hasMany('Documents', [
'className' => 'FileStorage.FileStorage',
'foreignKey' => 'foreign_key',
'conditions' => [
'Documents.model' => 'ProductDocument'
]
]);
}
}
class ProductsController extends ApController {
// Upload an image
public function upload($productId = null) {
if (!$this->request->is('get')) {
if ($this->Products->Images->upload($productId, $this->request->data)) {
$this->Session->set(__('Upload successful!');
}
}
}
}
class ProductImagesTable extends ImageStorageTable {
public function uploadImage($productId, $data) {
$data['adapter'] = 'Local';
$data['model'] = 'ProductImage',
$data['foreign_key'] = $productId;
$entity = $this->newEntity($data);
return $this->save($data);
}
public function uploadDocument($productId, $data) {
$data['adapter'] = 'Local';
$data['model'] = 'ProductDocument',
$data['foreign_key'] = $productId;
$entity = $this->newEntity($data);
return $this->save($data);
}
}
Maybe the following would help. It's a behavior who helps you to upload files very easy!
http://cakemanager.org/docs/utils/1.0/behaviors/uploadable/
Let me know if you struggle.
Greetz
/*Path to Images folder*/
$dir = WWW_ROOT . 'img' .DS. 'thumbnail';
/*Explode the name and ext*/
$f = explode('.',$data['image']['name']);
$ext = '.'.end($f);
/*Generate a Name in my case i use ID & slug*/
$filename = strtolower($id."-".$slug);
/*Associate the name to the extension */
$image = $filename.$ext;
/*Initialize you object and update you table in my case videos*/
$Videos->image = $image;
if ($this->Videos->save($Videos)) {
/*Save image in the thumbnail folders and replace if exist */
move_uploaded_file($data['image']['tmp_name'],$dir.DS.$filename.'_o'.$ext);
unlink($dir.DS.$filename.'_o'.$ext);
}
<?php
namespace App\Controller\Component;
use Cake\Controller\Component;
use Cake\Controller\ComponentRegistry;
use Cake\Network\Exception\InternalErrorException;
use Cake\Utility\Text;
/**
* Upload component
*/
class UploadRegCompanyComponent extends Component
{
public $max_files = 1;
public function send( $data )
{
if ( !empty( $data ) )
{
if ( count( $data ) > $this->max_files )
{
throw new InternalErrorException("Error Processing Request. Max number files accepted is {$this->max_files}", 1);
}
foreach ($data as $file)
{
$filename = $file['name'];
$file_tmp_name = $file['tmp_name'];
$dir = WWW_ROOT.'img'.DS.'uploads/reg_companies';
$allowed = array('png', 'jpg', 'jpeg');
if ( !in_array( substr( strrchr( $filename , '.') , 1 ) , $allowed) )
{
throw new InternalErrorException("Error Processing Request.", 1);
}
elseif( is_uploaded_file( $file_tmp_name ) )
{
move_uploaded_file($file_tmp_name, $dir.DS.Text::uuid().'-'.$filename);
}
}
}
}
}
We're using https://github.com/josegonzalez/cakephp-upload with great success in our production app, and has done so for quite some time.
Has awesome support for using "Flysystem" (https://flysystem.thephpleague.com/) as well - which is abstractions from specific file system(s) - so moving from normal local file system to S3 is a no-brainer, or Dropbox or whatever place you want :-)
You can find related (high quality) plugins on file uploading right here: https://github.com/FriendsOfCake/awesome-cakephp#files - I've used "Proffer" with success as well, and it's by no means "almost done" or anything alike - both has all my recommendations and is in my eyes production ready!

Categories