I have 2 tables that are in one to one relationship:
tours :
id|title|content
featured_image:
id|tour_id|name|path
My models FeaturedImage.php
:
class FeaturedImage extends Model
{
public function tour()
{
return $this->hasOne('App\Tour');
}
}
Tour.php
class Tour extends Model
{
public function featuredimage()
{
return $this->belongsTo('App\FeaturedImage');
}
}
I want to save tour_id
in featured_image
table when tour is created. I'm using same form to fill tours
table and to upload featured_image
.
This is my store method looks like:
public function store(Request $request)
{
//validate the date
$this->validate($request, [
'title' => 'required|max:255',
'content' => 'required'
]);
//store the date
$tour = new Tour;
$tour->title = $request->title;
$tour->content = $request->trip_code;
$tour->save();
$featured_image= new FeaturedImage;
// save featured_image
if($request->hasFile('featured_image')){
$image = $request->file('featured_image');
$filename = $image->getClientOriginalName();
$location = public_path('images/featured_image/'.$filename);
Image::make($image)->resize(800, 600)->save($location);
$featured_image->path= $location;
$featured_image->tour()->associate($tour);
$featured_image->save();
}
//redirect to
Session::flash('success','Tour is successfully created !');
return redirect()->route('tours.show',$tour->id);
}
I'm successful to save data into tours
table but unable to save in featured_image
table. I 'm getting this error:
Call to undefined method Illuminate\Database\Query\Builder::associate()
I would be thankful if anyone can help me out.
You can user Mass Assignment
to create your entries into DB like this:
$this->validate(request()->all(), [
'title' => 'required|max:255',
'content' => 'required'
]);
$tour_inputs = array_only(
$tour_inputs.
[
'title',
'content',
]
);
$tour = Tour::create($tour_inputs);
if($request->hasFile('featured_image')) {
$image = $request->file('featured_image');
$filename = $image->getClientOriginalName();
$location = public_path('images/featured_image/'.$filename);
Image::make($image)->resize(800, 600)->save($location);
$featuredImage = $tour->featuredImage()->save(new FeaturedImage([
'name' => $filename,
'path' => $location,
]));
}
Remember to define the
$fillables
inside your models, your models should look like this,do check your relations, that you've made in the models, according to me they aren't correct:
class Tour extends Model
{
protected $fillables = [
'title',
'content',
];
public function featuredImage()
{
return $this->hasOne('App\FeaturedImage');
}
}
class FeaturedImage extends Model
{
protected $fillables = [
'name',
'path',
'tour_id',
];
public function tour()
{
return $this->belongsTo('App\Tour');
}
}
Hope this helps!
From your code the relationships that you have defined are in reverse order.
I mean logically, a Tour
has one FeaturedImage
and a FeaturedImage
belongs to a Tour
.
class Tour extends Model
{
//Mass Assignable fields for the model.
$fillable = ['title', 'content'];
public function featuredimage()
{
return $this->hasOne('App\FeaturedImage');
}
}
and
class FeaturedImage extends Model
{
//Mass Assignable fields for the model
$fillable = ['tour_id', 'name', 'path'];
public function tour()
{
return $this->belongsTo('App\Tour');
}
}
Then in your controller
public function store(Request $request)
{
//validate the data
$this->validate($request, [
'title' => 'required|max:255',
'content' => 'required'
]);
//store the data
$tour = Tour::firstOrCreate([ //protection against duplicate entry
'title' => $request->get('title'),
'content' => $request->get('trip_code')
]);
if($tour) //if the Tour exists then continue
{
// save featured_image
if($request->hasFile('featured_image')){
$image = $request->file('featured_image');
$filename = $image->getClientOriginalName();
$location = public_path('images/featured_image/'.$filename);
Image::make($image)->resize(800, 600)->save($location);
$featured_image = $tour->featuredimage()->create([
'path' => $location,
'name' => $filename //if you have this field on your FeaturedImage
}
//you could also have an else block to redirect back if the input doesn't have a file
//redirect to
Session::flash('success','Tour is successfully created !');
return redirect()->route('tours.show',$tour->id);
}
else
{
//if there occurs any error display the error message and redirect back - probably with validation errors or exception errors
Session::flash('error','Error message');
return redirect()->back()->withInput()->withErrors();
}
}
And don't forget to add the mass assignable fields to the $fillable
array on your models.
UPDATE
For cases where a single form submission includes database transactions in multiple tables, you should use a try{}catch{}
to ensure that either all related transactions run without any issue or neither of the transactions go through - to avoid data discrepancy.
You can rewrite your controller code as
public function store(Request $request)
{
//validate the data
$this->validate($request, [
'title' => 'required|max:255',
'content' => 'required'
]);
//store the data
//use the DB::beginTransaction() to manually control the transaction
//You would ideally want to persist the data to the database only if the input provided by the user
//has valid inputs for Tour as well as FeaturedImage, in case if any one invalid input you do not
//want to persist the data
DB::beginTransaction();
try
{
//firstOrCreate gives protection against duplicate entry for tour with same title and content
$tour = Tour::firstOrCreate([
'title' => $request->get('title'),
'content' => $request->get('trip_code')
]);
//proceed further only if $tour exists
if($tour)
{
// get featured_image
if($request->hasFile('featured_image')){
$image = $request->file('featured_image');
$filename = $image->getClientOriginalName();
$location = public_path('images/featured_image/'.$filename);
Image::make($image)->resize(800, 600)->save($location);
//save the featured_image
$featured_image = $tour->featuredimage()->create([
'path' => $location,
'name' => $filename //if you have this field on your FeaturedImage
}
}
}
catch(\ValidationException $e)
{
//In case of validation error, rollback the database transactions to avoid data discrepancy.
DB::rollBack();
$errors = $e->getMessage();
Session::flash('error', 'Whoops.. Please check the provided inputs');
return redirect()->back()->withInput()->withErrors['errors', $errors];
}
catch(\Exception $e)
{
//In case of any other error, rollback the database transactions to avoid data discrepancy.
DB::rollBack();
$errors = $e->getMessage();
Session::flash('error', 'Whoops.. Something went wrong. Please try again');
return redirect()->back()->withInput()->withErrors['errors', $errors];
}
//If both the transactions to the database i.e. saving the Tour as well as FeaturedImage ran without problem
//Commit to the database
DB::commit();
//redirect to
Session::flash('success','Tour is successfully created !');
return redirect()->route('tours.show',$tour->id);
}
Hope this helps.