I try to use dropzoneJS in order to upload multiple image for my products and so far I can save images in database, also in images folder but I have problem with getting product id to relate each image to products.
Here is what I have:
Databases
Products (where my products including info will save)
Images (where my images including product id will save screenshot provided )
Models
Product:
public function images()
{
return $this->morphMany(Image::class, 'imageable');
}
Image:
class Image extends Model
{
protected $fillable = ['name'];
public function imageable()
{
return $this->morphTo();
}
public function product()
{
return $this->belongsTo(Product::class);
}
}
Image Schema
public function up()
{
Schema::create('images', function (Blueprint $table) {
$table->increments('id');
$table->integer('imageable_id')->nullable();
$table->string('imageable_type')->nullable();
$table->string('name');
$table->timestamps();
});
}
ImageController
class ImageController extends Controller
{
public function dropzone()
{
return view('dropzone-view');
}
public function dropzoneStore(Request $request)
{
// works
$file = $request->file('file');
$filename = 'product' . '-' . time() . '.' . $file->getClientOriginalExtension();
$filePath = public_path('images/');
$request->file('file')->move($filePath, $filename);
return Image::create([
'name' => $filename,
'imageable_id' => $request->input('imageable_id'),
])->id;
}
}
Product Create (Blade)
// Form
{!! Form::open([ 'route' => [ 'dropzone.store' ], 'files' => true, 'enctype' => 'multipart/form-data', 'class' => 'dropzone mt-20', 'id' => 'my-awesome-dropzone' ]) !!}
<div class="fallback">
<input name="file" type="file" multiple />
</div>
<input type="hidden" name="imageIds[]" value="">
{{Form::close()}}
// Javascript
<script type="text/javascript">
Dropzone.autoDiscover = false;
var myDropzone = new Dropzone("form#my-awesome-dropzone", {
headers: {
"X-CSRF-TOKEN": $("meta[name='csrf-token']").attr("content")
},
acceptedFiles: ".jpeg,.jpg,.png,.gif",
dictDefaultMessage: "Drag an image here to upload, or click to select one",
maxFiles: 15, // Maximum Number of Files
maxFilesize: 8, // MB
addRemoveLinks: true,
});
myDropzone.on("success", function (response) {console.log(response.xhr.response); });
</script>
Any idea?
Code for controller:
class ImageController extends Controller
{
public function dropzone($id)
{
$product = Product::find($id);
return view('dropzone-view')
->withProduct($porduct);
}
public function dropzoneStore(Request $request)
{
// works
$file = $request->file('file');
$filename = 'product' . '-' . time() . '.' . $file->getClientOriginalExtension();
$filePath = public_path('images/');
$request->file('file')->move($filePath, $filename);
return Image::create([
'name' => $filename,
'imageable_id' => $request->input('imageable_id'),
])->id;
}
}
Code on blade view:
{!! Form::open([ 'route' => [ 'dropzone.store' ], 'files' => true, 'enctype' => 'multipart/form-data', 'class' => 'dropzone mt-20', 'id' => 'my-awesome-dropzone' ]) !!}
<div class="fallback">
<input name="imageable_id" type="hidden" value="{{$product->id}}" />
<input name="file" type="file" multiple />
</div>
{{Form::close()}}
Try this hope this should get you going. This is one of many way to make this thing work.
this is a morpic relation and this is how you get morphic relation
$post = App\Post::find(1);
foreach ($post->comments as $comment) {
//
}
read about it here polymorphic-relations
What i normally do is,
After you upload a picture, return the ID of the newly created image (you already do that in ImageController)
On your product page, in the dropzone 'success' callback you can read the the image ID and add it to an hidden array input field
In your controller you have to create the new product, and then after saving it , you can attach the images to the correct product, because now you have the id's of the images + the product instance has been made.
Related
I'm using Dropzone.js to upload multiple files with a dropzone and an image preview, which works well, and this file field is part of a form with other fields from Symfony formBuilder.
What I would like now is to be able to send those file data to my Symfony server side.
I followed this tutorial, but the part where he gets the data returns null for me: $request->files->get('file') (of course by changing 'file' with 'product_form[distilleryPhotos][]' for me).
Moreover, my distilleryPhotos field from the builder isn't replaced by the Dropzone one. They seem to be two different fields on front side.
Here is my Controller function:
#[Route('/products/{id}', name: 'product_details', methods: ['GET', 'POST'])]
public function show(int $id, Request $request, TranslatorInterface $translator, SluggerInterface $slugger): Response
{
$repository = $this->getDoctrine()->getRepository(Product::class);
$product = $repository->findOneBy(['id' => $id]);
$form = $this->createForm(ProductFormType::class, $product, ['attr' =>
[
'class' => 'dropzone',
'id' => 'distillery-photos-dropzone'
]
]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid())
{
$product->setName($form->get('name')->getData());
$product->setAge($form->get('age')->getData());
$distilleryPhotos = $form->get('distilleryPhotos')->getData();
if($distilleryPhotos)
{
$photos = [];
foreach($distilleryPhotos as $distilleryPhoto)
{
$originalFilename = pathinfo($distilleryPhoto->getClientOriginalName(), PATHINFO_FILENAME);
// this is needed to safely include the file name as part of the URL
$safeFilename = $slugger->slug($originalFilename);
$newFilename = $safeFilename.'-'.uniqid().'.'.$distilleryPhoto->guessExtension();
// Move the file to the directory where brochures are stored
try
{
$distilleryPhoto->move(
$this->getParameter('distillery_photos_directory'),
$newFilename
);
}
catch (FileException $e)
{
throwException($e);
}
$photos[] = $newFilename;
// updates the 'brochureFilename' property to store the PDF file name
// instead of its contents
}
$product->setDistilleryPhotos($photos);
}
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($product);
$entityManager->flush();
$this->addFlash('success', $translator->trans('Your product was updated successfully.'));
return $this->redirectToRoute('product_details', [
'id' => $id,
]);
}
My twig:
<form name="product_form" method="post" class="dropzone" id="distillery-photos-dropzone" enctype="multipart/form-data" action="{{ path('product_details', {'id': product.id}) }}">
{{ form_end(productForm) }}
And my JS:
let fileUploadActionName = document.querySelector('#distillery-photos-dropzone').action;
let distilleryPhotosDropzone = new Dropzone(
"#distillery-photos-dropzone",
{
paramName: "product_form[distilleryPhotos][]",
maxFilesize: 10,
maxFile: 10,
addRemoveLinks: true,
uploadMultiple: true,
init: function () {
this.on("maxfilesexceeded", function(file) {
this.removeFile(file);
});
this.on("sending", function(file, xhr, formData) {
// send additional data with the file as POST data if needed.
// formData.append("key", "value");
});
this.on("success", function(file, response) {
if (response.uploaded)
alert('File Uploaded: ' + response.fileName);
});
}
}
);
distilleryPhotosDropzone.on("addedfile", file => {
console.log(`File added: ${file.name}`);
});
Might be useful for new visitors. You can use a library that extends Symfony Form and adds a new type DropzneType.
Example
public function buildForm(FormBuilderInterface
$builder, array $options){
// userFiles is OneToMany
$builder->add('userFiles', DropzoneType::class, [
'class' => File::class,
'maxFiles' => 6,
'uploadHandler'=>'uploadhandler', // route name
'removeHandler'=> 'removeHandler'// route name
]);
}
This is my banners structure in home page :
As you can see I have 4 section for banners - small banners | medium banners | news banners | large banners
And I have one model called Banner , 4 controllers to manage this banners and 4 tables to save data.
This is Banner model :
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Banner extends Model
{
protected $fillable = [
'title', 'image', 'url', 'image_title', 'image_alt'
];
}
And Controllers :
SmallController :
class SmallController extends Controller
{
public function small_list()
{
$smallBanners = DB::table('small_banner')->get();
return view('admin.banners.small.list', compact('smallBanners'));
}
public function small_create()
{
return view('admin.banners.small.add');
}
public function small_store(Request $request)
{
$data = $request->validate([
'title' => 'required',
'url' => 'required',
'image' => 'required',
'image_title' => 'max:255',
'image_alt' => 'max:255'
]);
DB::table('small_banner')->insert($data);
return redirect(route('admin.banners.small.index'));
}
public function small_edit($id)
{
$small = DB::table('small_banner')->where('id', $id)->first();
return view('admin.banners.small.edit', compact('small'));
}
public function small_update(Request $request, $id)
{
$small = DB::table('small_banner')->where('id', $id)->first();
if ($request->has('image')) {
if (file_exists($small->image)) {
unlink($small->image);
}
DB::table('small_banner')->where('id', $id)->update([
'image' => $request['image']
]);
}
DB::table('small_banner')->where('id', $id)->update([
'title' => $request['title'],
'url' => $request['url'],
'image_title' => $request['image_title'],
'image_alt' => $request['image_alt']
]);
return redirect(route('admin.banners.small.index'));
}
public function small_delete($id)
{
$small = DB::table('small_banner')->where('id', $id)->first();
DB::table('small_banner')->where('id', $id)->delete();
if (file_exists($small->image)) {
unlink($small->image);
}
return redirect(route('admin.banners.small.index'));
}
}
Other Controllers are like SmallController
And this is how I show this banners :
#foreach($smallBanners as $small)
<div class="col-6 col-lg-3">
<div class="widget-banner card">
<a href="{{ $small->url }}" target="_blank" rel="noopener">
<img class="img-fluid w-100" loading="lazy"
src="{{ $small->image }}" title="{{ $small->title }}"
alt="{{ $small->image_alt }}" width="350" height="200">
</a>
</div>
</div>
#endforeach
Other views like small banner.
But in this case, for example in small banners, if we upload 5 images instead 4 images, the structure will be messed up.
What is the best way to manage this banners and optimize codes ?
let's back to the concept, starting from reducing table usage, or you can stay with your concept
lets's change the structure into below
table : banners
columns :
$table->increments('id');
$table->string('title');
$table->string('image');
$table->string('url');
$table->string('image_title')->nullable(); //guessing from validator that it can be null
$table->string('image_alt')->nullable();
//extra columns
$table->enums('banner_type', ['small', 'medium', 'large', 'news']);
//or
$table->string('banner_type');
$table->boolean('isActive')->default(0);
you have model, but not using it
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Banner extends Model
{
protected $table = 'banners'; //add this line to define table name, make sure you have set the database config in .env
protected $fillable = [
'title', 'image', 'url', 'image_title', 'image_alt', 'banner_type', 'isActive'
];
}
now reducing the controller used to manage banners into just 1 Controller
use Banner;
class BannerController extends Controller
{
public function index()
{
$banners = Banner::get();
return view('admin.banners.index', compact('banners'));
}
public function create()
{
return view('admin.banners.create');
}
public function store_count($request, $type)
{
//using array limit
return Banner::where('banner_type', $type)
->where('isActive', 1)->count() < $this->limits[$type] && $request->isActive == 1;
}
public function update_count($banner, $type)
{
return Banner::whereNotIn('id', [$banner->id])
->where('isActive', 1)
->where('type', $banner->banner_type)->count() < $this->limits[$type] && $banner->isActive == 1;
}
public function store(Request $request)
{
//validating form data
$data = $request->validate([
'title' => "required",
'url' => "required",
'image' => "required",
'image_title' => "max:255",
'image_alt' => "max:255",
'banner_type' => "required|in:small,medium,large,news",
'isActive' => "nullable|in:0,1" //active or not
]);
//validating images active count
if (!$this->store_count($request, $request->banner_type)) {
return redirect()->back()->withInput($request->all())
->withErrors(['isActive' => ' نمیتوان بیشتر از ' . $this->limits[$request['banner_type']] . ' عکس برای این بنر آپلود کرد! ']);
}
Banner::create($data);
return redirect(route('admin.banners.index'));
}
public function show($id)
{
$banner = Banner::findOrFail($id);
return view('admin.banners.edit', compact('banner'));
}
public function update(Request $request, $id)
{
$banner = Banner::findOrFail($id);
//validate update form data here
//your validation
//validating images active count
if(!$this->update_count($banner, $request->banner_type)){
return redirect()->back()
->withInput($request->all())
->withErrors(['isActive' => 'There cant be more than '.$this->limits[$request['banner_type']].' images active');
}
$banner = $banner->fill([
'title' => $request['title'],
'url' => $request['url'],
'image_title' => $request['image_title'],
'image_alt' => $request['image_alt'],
'banner_type' => $request['banner_type'],
'isActive' => $request['isActive'] ?? 0
]);
if ($request->has('image')) {
if (file_exists($banner->image)) {
unlink($banner->image);
}
$banner->image = $request['image'];
}
$banner->update();
return redirect(route('admin.banners.index'));
}
public function delete($id)
{
$banner = Banner::findOrFail($id);
if (file_exists($banner->image)) {
unlink($banner->image);
}
$banner->delete();
return redirect(route('admin.banners.index'));
}
}
now we setup code to choose which images are active, you can use ajax method or use controller above
public function set_active($id)
{
$banner = Banner::findOrFail($id);
$this->validate_count((new Request([])), $banner->banner_type);
$banner->update(['isActive' => 1]);
return redirect(route('admin.banners.index'));
}
//you can use array if want to set different limit of banner type, put it as public variable inside controller class
public $limits = [
'small' => 4,
'medium' => 4,
'large' => 4,
'news' => 4
];
load the data resource into view
public class home()
{
$small = Banner::where('banner_type', 'small')
->where('isActive', 1)->get();
$medium = Banner::where('banner_type', 'medium')
->where('isActive', 1)->get();
$large = Banner::where('banner_type', 'large')
->where('isActive', 1)->get();
$news = Banner::where('banner_type', 'news')
->where('isActive', 1)->get();
return view('home', compact('small', 'medium', 'large', 'news'));
}
I am creating commenting system, now, I want to show user profile images in comment area.
I want to catch and show multiple images from laravel 5.8 public folder. Here is what I tried.
If user has a profile image, I want to show it. If not, I want to show
an avatar using vue avatar component. My images are stored in
public/uploads/profile.
Now I don't have any errors in my console. Also I can show user name and user comments.
Image name is stored in mysql.
comment.vue(first comment)
<div class="user" v-if="comment.user.image && comment.user.image.length > 0">
<span :v-for="(item, index) in comment.user.image">
<img :src="'uploads/' + 'profile' + '/' + item.image">
</span>
</div>
<div else class="user" >
<avatar :username="comment.user.name" :size="45"></avatar>
</div>
commentController.php
public function index(Post $post)
{
return $post->comments()->paginate(10);
}
public function show(Comment $comment)
{
return $comment->replies()->paginate(10);
}
public function store(Request $request, Post $post)
{
return auth()->user()->comments()->create([
'body' => $request->body,
'post_id' => $post->id,
'comment_id' => $request->comment_id
])->fresh();
}
profiles table
Schema::create('profiles', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('user_id');
$table->string('image');
$table->string('gender');
$table->string('country');
$table->string('bod');
$table->string('instagram');
$table->string('description');
$table->timestamps();
});
In my VS code, uploads folder started turning red.
My solution is create a api route to show image. For example.
api.php
Route::get('profile/{fileName}', 'ProfileController#showImage');
ProfileController.php
class ProfileController {
...
public function showImage($fileName)
{
$path = public_path("/images/{$fileName}");
if (!\File::exists($path)) {
return response()->json(['message' => 'Image not found.'], 404);
}
$file = \File::get($path);
$type = \File::mimeType($path);
$response = \Response::make($file, 200);
$response->header("Content-Type", $type);
return $response;
}
}
And you image src will be /api/profile/{imageName}. This is my solution.
i have been trying to update photos in an album but i cant get it right. i know that i need to get the album id of the photos that i want to update but i cannot get the right logic for the request.
this is the error am getting whenever i request for an update
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'album_id' cannot be null (SQL: update `photos` set `album_id` = , `photo` = IMG-20190527-WA0001_1570703623.jpg, `size` = 275807, `updated_at` = 2019-10-10 10:33:44 where `id` = 197)
the photos model for the application is as shown below
class Photos extends Model
{
protected $fillable = array('album_id', 'description', 'photo', 'title', 'size');
public function album(){
return $this->belongsTo('App\Album');
}
}
this is what i have tried for the update logic in the photosController. i have tried to request for the album id but it does not seem to work
public function edit($id){
$photo = Photos::find($id);
return view('photos/edit')->with('photo', $photo);
}
public function update(Request $request, $id){
$this->validate($request, [
'photo' => 'required | max:15000'
]);
$path = [];
//get filename with extension
$filenameWithExt = $request->file('photo')->getClientOriginalName();
//get just filename
$filename = pathinfo($filenameWithExt, PATHINFO_FILENAME);
//get extension
$extension = $request->file('photo')->getClientOriginalExtension();
//create new file name
$filenameToStore = $filename.'_'.time().'.'.$extension;
//get file size
$filesize = $request->file('photo')->getClientSize();
$path = $request->file('photo')->storeAs('public/photos'.$request->input('album_id'), $filenameToStore);
$photo = Photos::find($id);
$photo->album_id = $request->input('album_id');
$photo->size = $filesize;
$photo->photo = $filenameToStore;
$photo->save();
return $path;
}
each time i return the path for the photo am updating there is no id for the photo. only happens when i omit the save method
public/photos//IMG-20190527-WA0001_1570706571.jpg
photo edit php view code
{!!Form::open(['action' => ['PhotosController#update', $photo->id], 'method' => 'POST', 'enctype' => 'multipart/form-data'])!!}
{{Form::file('photo')}}
{{Form::hidden('_method','PUT')}}
{{Form::submit('submit')}}
{!! Form::close() !!}
database model for photos table
public function up()
{
Schema::create('photos', function (Blueprint $table) {
$table->increments('id');
$table->integer('album_id');
$table->string('photo');
$table->string('size');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('photos');
}
help would be appreciated
The error is at this line
$photo->album_id = $request->input('album_id');
You don't have an input in your form with the name album_id
You can either create a hidden one and assign it the value
{!!Form::open(['action' => ['PhotosController#update', $photo->id], 'method' => 'POST', 'enctype' => 'multipart/form-data'])!!}
{{Form::file('photo')}}
{{Form::hidden('_method','PUT')}}
{{-- Here --}}
{{Form::hidden('album_id', $photo->album->id)}}
{{Form::submit('submit')}}
{!! Form::close() !!}
or get it from the relationship like so
$photo->album_id = $photo->album->id;
Note that you're also using that null|empty value here
$path = $request->file('photo')
->storeAs('public/photos'.$request->input('album_id'), $filenameToStore);
So make sure to update that too
Hope this helps
I am using Laravel for my web app and I want to associate files to my posts in indepent way with his own form, but I have some problems
My routes (I am using a auth control package, but actually I am admin):
Route::post('file', 'fileController#store')->name('file.store')
->middleware('permission:file.create');
Route::get('file', 'fileController#index')->name('file.index')
->middleware('permission:file.index');
Route::get('file/create/', 'fileController#create')->name('file.create')
->middleware('permission:file.create');
Route::put('file/{id}', 'fileController#update')->name('file.update')
->middleware('permission:file.edit');
Route::get('file/{id}', 'fileController#show')->name('file.show')
->middleware('permission:file.show');
Route::delete('file/{id}', 'fileController#destroy')->name('file.destroy')
->middleware('permission:file.destroy');
Route::get('file/{id}/edit', 'fileController#edit')->name('file.edit')
->middleware('permission:file.edit');
Route::get('download/{filename}', 'fileController#download')->name('file.download')
->middleware('permission:file.download');
My migration:
Schema::create('files', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->integer('files_id')->unsigned();
$table->string('filenames');
$table->integer('fileable_id')->unsigned();
$table->string('fileable_type');
$table->timestamps();
});
My File Model:
class File extends Model
{
protected $fillable = [
'filenames', 'project_id'
];
public function user()
{
return $this->belongsTo(User::class);
}
My Project Model:
public function files()
{
return $this->morphMany(File::class, 'fileable')->whereNull('files_id');
}
My Controller to store:
class FileController extends Controller
{
public function store(Request $request)
{
$this->validate($request, [
'filenames' => 'required',
'project_id' => 'required',
// 'filenames.*' => 'mimes:doc,pdf,docx,zip'
]);
if($request->hasfile('filenames'))
{
foreach($request->file('filenames') as $file)
{
$name=$file->getClientOriginalName();
$file->move(public_path().'/files/', $name);
$data[] = $name;
}
}
$file= new File();
$file->filenames = $request->get('filenames');
$file->filenames= $name;
$file->user()->associate($request->user());
$project = Project::findOrFail($request->get('project_id'));
$project->files()->save($file);
$file->save();
return back();
}
public function download( $filename = '' ) {
// Check if file exists in storage directory
$file_path = public_path() . '/files/' . $filename;
if ( file_exists( $file_path ) ) {
// Send Download
return \Response::download( $file_path, $filename );
} else {
return back()->with('info', 'Archivo no existe en el servidor');
}
}
The Form in blade:
<form method="post" action="{{ route('file.store') }}" enctype="multipart/form-data">
<div class="input-group hdtuto control-group lst increment" >
<input type="file" name="filenames[]" class="myfrm form-control">
<input type="hidden" name="project_id" value="{{ $project->id }}" />
<div class="input-group-btn">
<button class="btn btn-success" type="button"><i class="fldemo glyphicon glyphicon-plus"></i>Add</button>
</div>
</div>
<button type="submit" class="btn btn-success" style="margin-top:10px">Submit</button>
</form>
Foreach to download files:
#foreach($project->files as $file)
<li>{{ $file->user->name }}: <a href="{{ url('/download/')}}/{{$file->filenames}}" download> {{$file->filenames}}</a></li>
#endforeach
I send files from Project Controll
The reason you are getting the first error message is because the Project with the id you get from Request is not found in the Database and returns null instead of an object. That would mean you are indeed calling files() method on null. To resolve this there are multiple steps.
1.) Make sure project_id is inside the Request at all times:
$this->validate($request, [
'filenames' => 'required',
'project_id' => 'required',
// 'filenames.*' => 'mimes:doc,pdf,docx,zip'
]);
2.) Make sure to check for project if it exists after retrieving it from database, this can be done in two ways.
a) You can either find the project or throw an Exception if it's not found:
$project = Project::findOrFail($request->get('project_id');`
b) You can check with a simple if statement if it does not exist and do something
$project = Project::find($request->get('project_id');
if (!$project) {
// Project not found in database
// Handle it
}