Laravel 5 - Handling form data which include multiple file uploads - forms

I am trying to work out the best way to do something. I have a form with a lot of fields, including a file input which allows multiple files to be uploaded.
All of this is linked to the model/form ReportingDoc
{!! Form::model(new App\ReportingDoc, [
'class'=>'form-horizontal',
'route' => ['projects.reportingDoc.store', $project->id],
'files' => true
]) !!}
<div class="form-group">
{!! Form::label('workType', 'Work Type:', array('class' => 'col-sm-5 control-label blue')) !!}
<div class="col-sm-7">
<select class="workType" name="workType">
<option value="recurring">Recurring</option>
<option value="adHoc">Ad-hoc</option>
</select>
</div>
</div>
//Lots of other inputs
<div class="form-group">
{!! Form::label('filePath', 'Supporting Documents:', array('class' => 'col-md-5 control-label green')) !!}
<div class="col-md-7">
{!! Form::file('filePath[]', array('multiple'=>true)) !!}
</div>
</div>
<div class="form-group">
{!! Form::submit('Save Data', ['class' => 'btn btn-primary']) !!}
</div>
{!! Form::close() !!}
The model looks like the following:
class ReportingDoc extends Model
{
protected $table = 'reporting_doc';
protected $guarded = [];
public function project()
{
return $this->belongsTo('App\Project', 'projectId');
}
}
So the model belongs to a Project. At the moment, the migration looks like the following:
public function up()
{
Schema::create('reporting_doc', function(Blueprint $table)
{
$table->increments('id');
$table->String('workType')->default('');
//all my other inputs
$table->String('filePath')->default('');
$table->timestamps();
});
Schema::table('reporting_doc', function (Blueprint $table) {
$table->integer('projectId')->unsigned()->default(0);
$table->foreign('projectId')->references('id')->on('projects')->onDelete('cascade');
});
}
In the controller for this route, I have my store method. At the moment it looks like the following:
public function store(Request $request, Project $project)
{
$workType = Input::get('workType');
//Other inputs
$projectId = $project->id;
$reportingDoc = new ReportingDoc();
$reportingDoc->workType = $workType;
//Other inputs
$reportingDoc->projectId = $projectId;
$dsReportingDoc->save();
return Redirect::route('projects.reportingDoc.create', $project->id)
->with('message', 'Files uploaded.');
/* Old part
$files = Input::file('filePath');
$file_count = count($files);
$uploadcount = 0;
if(isset($files)) {
foreach($files as $file) {
$destinationPath = public_path() .'/uploads/';
$filename = $file->getClientOriginalName();
$upload_success = $file->move($destinationPath, $filename);
$uploadcount ++;
}
}
if($uploadcount == $file_count){
Session::flash('success', 'Upload successfully');
return Redirect::route('projects.reportingDoc.create', $project->id)
->with('message', 'Files uploaded.');
}
else {
return Redirect::route('projects.reportingDoc.create', $project->id)
->with('message', 'Something went wrong - Please try again.');
}
*/
}
As you can see, I have commented out the file part for now. What I was going to do was get the paths for all the uploaded files, and serialize them into
one field in the database - filePath.
However, to me this seems very messy. I would imagine it is best to have something like an uploads table which is linked to this model. I can then create an uploads object for each file that is uploaded.
One thing I am confused about is the form, and it being linked to my ReportingDoc Model. With this new approach, would I need to link it somehow to both models? ReportingDoc and Uploads?
Really, I am just looking for advice and guidance on the best way to achieve what I am after.
Many thanks

You are already almost done. The best way to do what you're trying to achieve is to create a ReportingDocUpload model which belongs to ReportingDoc, then remove the filePath field from Reporting Doc.
Your model should look like: class ReportingDocUpload { ... protected $fillable = ['filepath', 'reportingdoc_id']; ... }
Then to save your files, do this:
if($file_count) {
foreach($files as $file) {
$destinationPath = public_path() .'/uploads/';
$filename = $file->getClientOriginalName();
$upload_success = $file->move($destinationPath, $filename);
$rUpload = ReportingDocUpload::create(['filepath' => $filename, 'reportingdoc_id' => $reportingDoc->id]);
$uploadcount ++;
}
}
Don't forget to delete files from disk when the entry is deleted...
public static function boot()
{
parent::boot();
//delete associated file
static::deleting(function($docUpload){
try{
$filepath = public_path('uploads/').$docUpload->filepath;
if(file_exists($filepath))
{
unlink($filepath);
}
}
catch(\Exception $e)
{
//some error while deleteing
\Log::info('Error deleting: '.$filepath);
\Log::debug($e);
}
});
}
Cheers..

Related

Troubleshooting CakePHP form submission

I recently set up the ability to tag posts on my site. I had everything working fine. Then as I was wrapping up I tested all my admin side forms again. The Add Tag form no longer does anything. It doesn't even flash an error or redirect after submission. The page just reloads at the same URL. The only changes to the site I have made since initial testing was move the forms to the admin side of the dev site. Here is some code to hopefully reveal what the mystery is. Also my edit tag form is doing similar thing. It has no flash message but redirects back to the index, like its supposed to but with no changes made to the tag. Ill include the edit code as well.
Add.ctp in src/Template/Admin/Tags/Add.ctp
<div class="tags form large-9 medium-8 columns content">
<?= $this->Form->create($tag) ?>
<div class="form-group">
<fieldset>
<h1 class="page-header">New Tag</h1>
<?php
echo $this->Form->input('name', ['class' => 'form-control']);
?>
</fieldset>
</div>
<?= $this->Form->button(__('Submit'), ['class' => 'btn btn-primary']) ?>
<?= $this->Form->end() ?>
</div>
Here is my Add funciton in my TagsController:
public function add()
{
$this->viewBuilder()->layout('admin');
$tag = $this->Tags->newEntity();
if ($this->request->is('post')) {
$tag = $this->Tags->patchEntity($tag, $this->request->data);
if ($this->Tags->save($tag)) {
$this->Flash->success(__('The tag has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('The tag could not be saved. Please, try again.'));
}
$this->set(compact('tag'));
$this->set('_serialize', ['tag']);
}
Here is my Edit funciton in my TagsController:
public function edit($id = null)
{
$this->viewBuilder()->layout('admin');
$tag = $this->Tags->get($id, [
'contain' => []
]);
if ($this->request->is(['patch', 'post', 'put'])) {
$tag = $this->Tags->patchEntity($tag, $this->request->data);
if ($this->Tags->save($tag)) {
$this->Flash->success(__('The tag has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('The tag could not be saved. Please, try again.'));
}
$this->set(compact('tag'));
$this->set('_serialize', ['tag']);
}
Edit.ctp in src/Template/Admin/Tags/Edit.ctp
<div class="tags form large-9 medium-8 columns content">
<?= $this->Form->create($tag) ?>
<div class="form-group">
<fieldset>
<h1 class="page-header">Edit Tag</h1>
<?php
echo $this->Form->input('name', array('class' => 'form-control'));
?>
</fieldset>
</div>
<?= $this->Form->button(__('Submit'), ['class' => 'btn btn-primary']) ?>
<?= $this->Form->end() ?>
</div>
Just as a side note. I started getting errors when creating a new post as well.
General error: 1364 Field 'section_id' doesn't have a default value
I did go into my DB and give the field a default value. But then when I fill out the form for a new post again, the error just moves to the next table column. I am assuming they are some how related since they popped up at the same time and because tags and posts are related to each other.
TagsTable:
class TagsTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->table('tags');
$this->displayField('name');
$this->primaryKey('id');
$this->hasMany('PostsTags', [
'foreignKey' => 'tag_id'
]);
}
/**
* Default validation rules.
*
* #param \Cake\Validation\Validator $validator Validator instance.
* #return \Cake\Validation\Validator
*/
public function validationDefault(Validator $validator)
{
$validator
->integer('id')
->allowEmpty('id', 'create');
$validator
->requirePresence('name', 'create')
->notEmpty('name');
return $validator;
}
}
Tags Entity:
class Tag extends Entity
{
/**
* Fields that can be mass assigned using newEntity() or patchEntity().
*
* Note that when '*' is set to true, this allows all unspecified fields to
* be mass assigned. For security purposes, it is advised to set '*' to false
* (or remove it), and explicitly make individual fields accessible as needed.
*
* #var array
*/
protected $_accessible = [
'*' => false,
'id' => false
];
}
When I place <?php debug($tag); ?> into my add.ctp view this is the out put it gives me:
object(App\Model\Entity\Tag) {
'[new]' => true,
'[accessible]' => [],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Tags'
}
Again, in question always post debug pathEntity output, in your case debug($tag), also Tag Entity, your validation code, and how looks your db tags table.
Answer:
General error: 1364 Field 'section_id' doesn't have a default value
This means that you have not passed a value for this field.
You can change that table field to accept null or empty value and/or set default if not passed from application, or make validation in your TagsTable to be sure if submitted data valid before send to db.
After question updated:
protected $_accessible = [
'*' => false, <---- should be true
'id' => false
];
This means that all fields except id are accessible

Category dropdown is fetching more then the title in laravel 5 is not working

My category dropdown is fetching more than "title": "Arts Blog" as the value. I'm getting this in return...and I just need the $category->title only. Looking for A little help.
{"id":1,"title":"Arts Blog","slug":"arts-blog","created_at":null,"updated_at":null}
I just want the "title" as $category->title...
Here's PostsController.php
public function edit(Post $post)
{
$posts = Post::with('author');
$categories = Category::all();
$cats = array();
foreach ($categories as $category){
$cats[$category->id] = $category->title;
}
return view('posts.edit')->withPost($post)->withCategories($cats);
}
edit.blade.php
<div class'form-group'>
{{ Form::label('category_id', 'Category :')}}
{{ Form::select('category_id', $categories, null, ['class' => 'form-control']) }}
</div>
Not sure what exactly you want to achieve. You can simplify things using Collections in Laravel.
Try this:
public function edit(Post $post)
{
$categories = Category::all()->pluck('title', 'id')->toArray();
...
}

Laravel file upload image not working on hosting

Help my code works on xampp and php artisan serve localhost:8000
but in server hosting the validation fail. When I try to upload image it, returns Errors: The image field is required.
public function store(Request $request)
{
$this->validate($request, array(
'image' => 'mimes:jpeg,png,bmp|required|max:3000'
));
Session::flash('success', 'Image been uploaded.');
return redirect()->route('galleries.index');
}
html
{!! Form::open(['route' => 'galleries.store', 'class' => 'form-inline', 'files' => true]) !!}
{!! Form::file('image', ['required' => '']) !!}
{!! Form::submit('Upload', ['class' => 'btn btn-success']) !!}
{!! Form::close() !!}
Flash Data
Sometimes you may wish to store items in the session only for the next request. You may do so using the flash method. Data stored in the session using this method will only be available during the subsequent HTTP request, and then will be deleted. Flash data is primarily useful for short-lived status messages:
In this case you can use this source for upload file
$fileName = $this->getFileName($file);
$pathUpload = $this->createPath(sprintf('%s', $config['path']));
$media = $file->move($pathUpload, $fileName);
protected function createPath($paths)
{
if (!is_array($paths)) {
if (!\File::exists($paths)) {
\File::makeDirectory($paths, $mode = 0777, true, true);
}
} else {
foreach ($paths as $path) {
if (!\File::exists($path)) {
\File::makeDirectory($path, $mode = 0777, true, true);
}
}
}
return $paths;
}
Function createPath for create folder and set permission.
Function move using file_put_content method for upload your file

store data from dropdown menu & relationship - laravel 5.2

How can i store my data inside db using {!! form !!} builder?
controller:
public function create()
{
$categories = \DB::table('categories')->lists('title', 'id');
return view('dash.reports.create')->with('categories', $categories);
}
/**
* Store a newly created resource in storage.
*
* #return void
*/
public function store(Request $request)
{
$this->validate($request, ['title' => 'required', ]);
Report::create($request->all());
Session::flash('flash_message', 'Report added!');
return redirect('dash/reports');
}
my view:
{!! Form::label('category_id', trans('reports.category_id'), ['class' => 'col-sm-3 control-label']) !!}
<div class="col-sm-6">
{!! Form::select('category',
(['0' => 'Select a Category'] + $categories),
null,
['class' => 'form-control'])
!!}
{!! $errors->first('category_id', '<p class="help-block">:message</p>') !!}
when i press button action return me blank page, any idea?
You don't have a form, so you're not submitting data to the correct page. You're sending a POST request to the same URL that you used to access the form, which is probably not what you want.
This guide will show you how to set up a Laravel form that points at a controller method which will allow you to capture input, validate it, and store it in the database.
http://www.easylaravelbook.com/blog/2015/02/09/creating-a-contact-form-in-laravel-5-using-the-form-request-feature/
solved
view:
{!! Form::select('category_id', $category, null, ['class' => 'form-control'] ) !!}
controller:
public function create()
{
$category = Category::lists('title','id');
return view('dash.reports.create')->with('category', $category);
}

Laravel: Multiple File Upload, Input::hasFile(key) always false

i generated a multiple upload form with the former generator tool from https://github.com/Anahkiasen/former.
{{ Former::files('file')->accept('image')->required(); }}
that results in
<input multiple="true" accept="image/*" required="true" id="file[]" type="file" name="file[]">
After I've submit the form Ive figured out that Input::hasFile('file') always returns false whilst Input:has('file') returns true.
What i've tried until now:
Log::info(Input::file('file')); <-- empty
foreach(Input::file('file') as $file) <-- Invalid argument supplied for foreach()
Log::info("test");
if(Input::has('file'))
{
if(is_array(Input::get('file')))
foreach ( Input::get('file') as $file)
{
Log::info($file); <-- returns the filename
$validator = Validator::make( array('file' => $file), array('file' => 'required|image'));
if($validator->fails()) {
...
}
}
}
Of course, the validator always fails cause Input::get('file') does not return a file object. How do I have to modify my code to catch the submited files?
Thanks for the help, the answer from Kestutis made it clear. The common way to define a file form in Laravel is
echo Form::open(array('url' => 'foo/bar', 'files' => true))
This Options sets the proper encryption type with enctype='multipart/form-data'.
With the laravel form builder "Former" you have to open the form with
Former::open_for_files()
After that u can validate the form in the common way.
if(Input::hasFile('files')) {
Log::info(Input::File('files'));
$rules = array(
...
);
if(!array(Input::File('files')))
$rules["files"] = 'required|image';
else
for($i=0;$i<count(Input::File('files'));$i++) {
$rules["files.$i"] = 'required|image';
}
$validation = Validator::make(Input::all(), $rules);
if ($validation->fails())
{
return ...
}
else {
// everything is ok ...
}