Laravel 5: one form to update 2 linked tables. How to? - php

I have the 2 simple tables below:
CUSTOMERS
id, email
CLAIMS
id, customer_id(fk), description
I created the related models (Customers.php and Claims.php) and set-up relationships: hasOne() and belongsTo().
I also have my related RESTful controllers ready: CustomersController.php and ClaimsController.php.
What would be the best solution if I need to create/update records in both tables by submitting one form? Create one general controller? Mix models?
I have been searching in Laravel docs and on Google and still have no idea how to achieve this.

Customer model
public function claims(){
return $this->hasMany('App\Claims');
}
Claims model
public function customer(){
return $this->belongsTo('App\Customer');
}
Now in controller u need to send request in store action
Something like this
class CreateCustomerClaim extends Request {
/**
* 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 [
'email' => 'required',
'description'=> 'required'
];
}
}
Now in store action send your request, grab data from request and insert it in db
public function store(CreateCustomerClainsRextends $request)
{
//example
$customer= new Customer($request->all());
Auth::user()->claims()->save($customer);
}
if u need to update use same request in update function, when u grab data from request just use update. Here is example where i update 3 different tables from one request
public function update($id,ArtikalUpdateRequest $request)
{
$article = Artikal::findOrFail($id);
if($article !== null){
$article->update($request->all());
\DB::table('artikal_podkategorija')
->where('artikal_id', $article->id)
->update(array('podkategorija_id' => $request['podkategorija']));
\DB::table('arikalslike')
->where('artikal_id', $article->id)
->update(array('NazivSlike' => $request['NazivSlike']));
$slika = \DB::table('arikalslike')
->where('artikal_id', $article->id)->first();
$image = Request::file('image');
//dd($image);
if($image != null){
$destinationPath = 'uploads/artiklislike/';
$thumb = $slika->SifraSlike;
$fileName = $thumb;
$nazivthumb = $slika->NazivThumb;
$slika->NazivSlike = $request['NazivSlike'];
$slika->NazivThumb = $nazivthumb;
$slika->SifraSlike = $fileName;
$slika->artikal_id = $article->id;
//Snima sliku
$img = Image::make(Input::file('image'));
$destinationPath = $destinationPath.$fileName;
Image::make($img)->save($destinationPath);
// Snima sliku u manjem formatu thumb
$destinationPath = 'uploads/artiklislike/';
$img = Image::make(Input::file('image'));
$destinationPath = $destinationPath.$nazivthumb;
Image::make($img)->resize(300, 200)->save($destinationPath);
}
}
return redirect('artikli')->with(['flash_message' => 'Uspiješno ste obrisali artikal!']);
}

Related

An Update in Laravel Framework is returning A new Record

I am new to PHP and Laravel. I have learned much, but I am having a problem seeing my error in what should be a simple task.
I have a form which is populated from data in a MySQL database. When I submit it, it creates a new record instead of updating the existing record. This is the form action that I am using:
<form action="{{route('updateAlert', $alert->id)}}" method="post" name="saveAlert" id="saveAlert" class="needs-validation"
#csrf
#method("PUT")
///form fields here
</form>
Here are the two related routes. editAlert brings you to the form above. updateAlert is supposed to bring you to the update method on my AlertController.
Route::get('/alerts/edit/{id}', 'AlertController#edit')->name('editAlert');
Route::put('/alerts/edit/{id}', 'AlertController#update')->name('updateAlert');
Here is what my AlertController looks like:
/**
* Update the specified resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function update(Request $request, Alert $alert)
{
$alert->type = $request->type;
$alert->title = $request->title;
$alert->body = $request->body;
$alert->link = $request->link;
$alert->eff_dt = Carbon::parse($request->eff_dt);
$alert->exp_dt = Carbon::parse($request->exp_dt);
$alert->note = $request->note;
$alert->user_id = auth()->user()->id;
$alert->save();
return redirect()->route('viewAlerts')->with('success', 'Your alert has been updated.');
}
What am I missing? I have the same basic code in another section of the app that is working as expected. Thanks in advance.
You Are Not Fetching your row id. You need to use model and pass your id to your model to update any specific row.
Ex.
I the model name just pass your model name.
public function update(Request $request, Alert $alert)
{
$alert = ModelName::find($alert);
$alert->type = $request->type;
$alert->title = $request->title;
$alert->body = $request->body;
$alert->link = $request->link;
$alert->eff_dt = Carbon::parse($request->eff_dt);
$alert->exp_dt = Carbon::parse($request->exp_dt);
$alert->note = $request->note;
$alert->user_id = auth()->user()->id;
$alert->save();
return redirect()->route('viewAlerts')->with('success', 'Your alert has been updated.');
}
To update things in Laravel, you need a query builder object; Alert $alert returns an object of the model, so it can not be used to update things.
Note: find method is a special method whose objects can be used to update records, unlike the "first" method.
So your code must be changed to:
public function update(Request $request, Alert $alert)
{
$alert = Alert::where('id', $alert->id); // or: Alert::find($alert->id);
$alert->type = $request->type;
$alert->title = $request->title;
$alert->body = $request->body;
$alert->link = $request->link;
$alert->eff_dt = Carbon::parse($request->eff_dt);
$alert->exp_dt = Carbon::parse($request->exp_dt);
$alert->note = $request->note;
$alert->user_id = auth()->user()->id;
$alert->save();
}

I want to save data in two tables when i click the button Yii2

For example, I have two tables. One called "PedidoReservaQuarto" and another "PedidoReserva". What I want to do is this, I want to save the data in both tables when I click the button. But first I need to enter data in the table "PedidoReservaQuarto" because of the id.
My action controller
public function actionCreate($id)
{
$modelPedidoReservaQuarto = new PedidoReservaQuarto();
$modelPedidoReserva = new PedidoReserva();
$modelPedidoReservaQuarto->quartoId = $id;
$modelPedidoReservaQuarto->save();
if ($modelPedidoReserva->load(Yii::$app->request->post()) && $modelPedidoReserva->save()) {
return $this->redirect(['create', 'id' => $modelPedidoReserva->id]);
}
return $this->render('../pedido-reserva/create', [
'model' => $modelPedidoReserva,
]);
}
Model PedidoReservaQuarto
public function getPedidoReservas()
{
return $this->hasMany(PedidoReserva::className(), ['reservaQuartoId' => 'id']);
}
/**
* Gets query for [[Quarto]].
*
* #return \yii\db\ActiveQuery
*/
public function getQuarto()
{
return $this->hasOne(Quarto::className(), ['id' => 'quartoId']);
}
Model PedidoReserva
public function getReservaQuarto()
{
return $this->hasOne(PedidoReservaQuarto::className(), ['id' => 'reservaQuartoId']);
}
Assuming in you form you have the data you need for both the model and send these data using method post (click submit button)
and you must obtain the id from modelPedidoReserva for assign to model modelPedidoReservaQuarto you could try
public function actionCreate()
{
$modelPedidoReservaQuarto = new PedidoReservaQuarto();
$modelPedidoReserva = new PedidoReserva();
$modelPedidoReservaQuarto->quartoId = $id;
$modelPedidoReservaQuarto->save();
if ($modelPedidoReserva->load(Yii::$app->request->post()) && $modelPedidoReserva->save()) {
$modelPedidoReservaQuarto->load(Yii::$app->request->post());
$modelPedidoReservaQuarto->quartoId = $modelPedidoReserva->id;
$modelPedidoReservaQuarto-save();
}
return $this->render('../pedido-reserva/create', [
'model' => $modelPedidoReserva,
]);
}
once you have saved the $modelPedidoReserva the related id is already available ..

Laravel Form best way to store polymorphic relationship

I have a notes model. Which has a polymorphic 'noteable' method that ideally anything can use. Probably up to 5 different models such as Customers, Staff, Users etc can use.
I'm looking for the best possible solution for creating the note against these, as dynamically as possible.
At the moment, i'm adding on a query string in the routes. I.e. when viewing a customer there's an "Add Note" button like so:
route('note.create', ['customer_id' => $customer->id])
In my form then i'm checking for any query string's and adding them to the post request (in VueJS) which works.
Then in my controller i'm checking for each possible query string i.e.:
if($request->has('individual_id'))
{
$individual = Individual::findOrFail($request->individual_id_id);
// store against individual
// return note
}elseif($request->has('customer_id'))
{
$customer = Customer::findOrFail($request->customer_id);
// store against the customer
// return note
}
I'm pretty sure this is not the best way to do this. But, i cannot think of another way at the moment.
I'm sure someone else has come across this in the past too!
Thank you
In order to optimize your code, dont add too many if else in your code, say for example if you have tons of polymorphic relationship then will you add tons of if else ? will you ?,it will rapidly increase your code base.
Try instead the follwing tip.
when making a call to backend do a maping e.g
$identifier_map = [1,2,3,4];
// 1 for Customer
// 2 for Staff
// 3 for Users
// 4 for Individual
and so on
then make call to note controller with noteable_id and noteable_identifier
route('note.create', ['noteable_id' => $id, 'noteable_identifier' => $identifier_map[0]])
then on backend in your controller you can do something like
if($request->has('noteable_id') && $request->has('noteable_identifier'))
{
$noteables = [ 'Customers', 'Staff', 'Users','Individual']; // mapper for models,add more models.
$noteable_model = app('App\\'.$noteables[$request->noteable_identifier]);
$noteable_model::findOrFail($request->noteable_id);
}
so with these lines of code your can handle tons of polymorphic relationship.
Not sure about the best way but I have a similar scenario to yours and this is the code that I use.
my form actions looks like this
action="{{ route('notes.store', ['model' => 'Customer', 'id' => $customer->id]) }}"
action="{{ route('notes.store', ['model' => 'User', 'id' => $user->id]) }}"
etc..
And my controller looks this
public function store(Request $request)
{
// Build up the model string
$model = '\App\Models\\'.$request->model;
// Get the requester id
$id = $request->id;
if ($id) {
// get the parent
$parent = $model::find($id);
// validate the data and create the note
$parent->notes()->create($this->validatedData());
// redirect back to the requester
return Redirect::back()->withErrors(['msg', 'message']);
} else {
// validate the data and create the note without parent association
Note::create($this->validatedData());
// Redirect to index view
return redirect()->route('notes.index');
}
}
protected function validatedData()
{
// validate form fields
return request()->validate([
'name' => 'required|string',
'body' => 'required|min:3',
]);
}
The scenario as I understand is:
-You submit noteable_id from the create-form
-You want to remove if statements on the store function.
You could do that by sending another key in the request FROM the create_form "noteable_type". So, your store route will be
route('note.store',['noteableClass'=>'App\User','id'=>$user->id])
And on the Notes Controller:
public function store(Request $request)
{
return Note::storeData($request->noteable_type,$request->id);
}
Your Note model will look like this:
class Note extends Model
{
public function noteable()
{
return $this->morphTo();
}
public static function storeData($noteableClass,$id){
$noteableObject = $noteableClass::find($id);
$noteableObject->notes()->create([
'note' => 'test note'
]);
return $noteableObject->notes;
}
}
This works for get method on store. For post, form submission will work.
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Requests\NoteStoreRequest $request
* #return \Illuminate\Http\Response
*/
public function store(NoteStoreRequest $request) {
// REF: NoteStoreRequest does the validation
// TODO: Customize this suffix on your own
$suffix = '_id';
/**
* Resolve model class name.
*
* #param string $name
* #return string
*/
function modelNameResolver(string $name) {
// TODO: Customize this function on your own
return 'App\\Models\\'.Str::ucfirst($name);
}
foreach ($request->all() as $key => $value) {
if (Str::endsWith($key, $suffix)) {
$class = modelNameResolver(Str::beforeLast($key, $suffix));
$noteable = $class::findOrFail($value);
return $noteable->notes()->create($request->validated());
}
}
// TODO: Customize this exception response
throw new InternalServerException;
}

Laravel doctrine2 many to many relation with extra column

So I'm beginning to struggle with Doctrine2 when it comes to a many-to-many relation for a project where the relation has 1 extra column.
I have the following tables:
Profiles
id
extra data
Skills
id
name
profile_has_skills
profile_id
skill_id
level
Now I added the level column later on, and noticed some problems happening, of course I am missing level now whenever I try to create the relation.
My question is, with the code below, how would I go over to add this in my doctrine?
My controller:
public function store(Request $request)
{
$time = new DateTime();
$this->validate($request, [
'name' => 'required',
'lastname' => 'required',
'gender' => 'required',
'profile_skills' => 'required'
]);
$this->em->getConnection()->beginTransaction();
try {
$profile = new Profile(
$request->input('company_id'),
$request->input('name'),
$request->input('lastname'),
$request->input('gender'),
new DateTime(),
$time,
$time
);
$company = $this->em->getRepository(Company::class)->find($request->input('company_id'));
$profile->addCompany($company);
foreach($request->input('profile_skills') as $skill => $level) {
$skill = $this->em->getRepository(Skill::class)->find($skill);
$skill->level = $level;
$profile->addSkill($skill);
}
$this->em->persist($profile);
$this->em->flush();
$this->em->getConnection()->commit();
} catch (OptimisticLockException $e) {
$this->em->getConnection()->rollBack();
throw $e;
}
return redirect(route('profiles.index'));
}
My ProfileHasSkill entity looks as follow:
/**
* #ORM\Entity
* #ORM\Table(name="profile_has_skill")
*
*/
class ProfileHasSkill
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #Column(type="integer", name="profile_id")
*/
protected $profile_id;
/**
* #Column(type="integer", name="skill_id")
*/
protected $skill_id;
/**
* #Column(type="integer", name="level")
*/
protected $level;
/**
* #param $profile_id
* #param $skill_id
* #param $level
*/
public function __construct($profile_id, $skill_id, $level = 0)
{
$this->profile_id = $profile_id;
$this->skill_id = $skill_id;
$this->level = $level;
}
And my addSkill method inside the profile entity is as follow:
public function addSkill(Skill $skill)
{
if ($this->skills->contains($skill)) {
return;
}
return $this->skills->add($skill);
}
But anytime I try to run this it gives me the following error
An exception occurred while executing
'INSERT INTO profile_has_skill (profile_id, skill_id) VALUES (?, ?)'
with params [3, 2]: SQLSTATE[HY000]: General error: 1364 Field 'level'
doesn't have a default value
Now I know one way to get rid of this error is setting a default value in the database, but I much rather just find out why it's not picking up my skill level that I'm also passing?
As per my solution which has worked, by reading another question passed by #Nicola Havric - Read as follow That doctrine does not support extra columns in a many-to-many relation. Thus you should use the relation as it's own entity. My own solution was to change the way I wanted it to run with flushing.
In my controller I changed my code as follow:
try {
$profile = new Profile(
$request->input('company_id'),
$request->input('name'),
$request->input('lastname'),
$request->input('gender'),
new DateTime(),
$time,
$time
);
$company = $this->em->getRepository(Company::class)->find($request->input('company_id'));
$profile->addCompany($company);
//Flush the user, so I can grab it's profile ID
$this->em->persist($profile);
$this->em->flush();
foreach($request->input('profile_skills') as $skill => $level) {
$skill = $this->em->getRepository(Skill::class)->find($skill);
$skill->level = $level;
$profile->addSkill($skill);
}
$this->em->getConnection()->commit();
Inside my Profile Entity function:
public function addSkill(Skill $skill)
{
//I left this check since it only checks if the relation is set already. If so, it will skip it.
if ($this->skills->contains($skill)) {
return;
}
//Since this function gets called inside a loop, I can call the entity to add a new "relation" to the table.
(new ProfileHasSkill($this->getId(), $skill, $skill->level))->addSkill($this->getId(), $skill, $skill->level);
return true;
}
Inside my ProfileHasSkill entity:
public function addSkill($profileId, $skill)
{
//Creating a new ProfileHasSkill inside the table.
$profileSkill = new ProfileHasSkill(
$profileId,
$skill->getId(),
$skill->level
);
/*Since I do a roll-back inside my controller in case something goes wrong.
I decided to add the flush here.
As far no additional checks where needed in my case
since I require a Profile instance and a Skill instance inside the Profile entity.*/
EntityManager::persist($profileSkill);
EntityManager::flush();
}
The thing with many-to-many relationships is that any additional columns other than two primary keys from both tables are considered pivot columns, when attaching entities to such relationships you want to use the method attach which accepts array of ids as first parameter and an array with pivot columns, take the following into consideration.
public function addSkill(Skill $skill)
{
if ($this->skills->contains($skill)) {
return;
}
//Dunno what this method does
return $this->skills->add($skill);
//But this is the correct way of adding a skill
$this->skills->attach($skill->id, ['level' => $skill->level]);
}
Hope this can clarify few things even though Eloquent was used as an example; here is the manual link for the above code.

Laravel 5 make routing with unique parameter

I am currently using a route with a parameter nameto query the profile of a user in my application.
So for example: /members/johnwill show the profile of John.
You see the problem here, if there are 2 John's then it's gonna be a problem.
I know I could do something like this /members/idsince id is unique but I want the url to look pretty with the user's name and not a random number.
So my question is if there is a way to use the id to make it unique but to display the name in url?
my route:
Route::get('/members/{name}', 'UserController#usersProfile');
My usersProfile method:
/**
* Returns a users profile.
*
* #param string $name
*
* #return \Illuminate\Http\Response
*/
public function usersProfile($name)
{
$profile = $this->userService->getProfile($name);
if ($profile == null) {
return redirect('members')->with('status', 'Whoops, looks like that member does not exist (yet).');
}
return view('members/profile', ['profile' => $profile]);
}
you can use some package for slug your model name :
https://github.com/spatie/laravel-sluggable
https://github.com/cviebrock/eloquent-sluggable
these packages automatically doing that for you.
if you have same name this will happen:
http://example.com/post/my-dinner-with-andre-francois
http://example.com/post/my-dinner-with-andre-francois-1
http://example.com/post/my-dinner-with-andre-francois-2
just in production i have some issue with scopes...
when you apply global scope on model , it may create duplicate slug for that ... you can fix that by add this to your model :
public function scopeFindSimilarSlugs(Builder $query, Model $model, $attribute, $config, $slug)
{
$separator = $config['separator'];
return $query->withoutGlobalScopes()->where(function (Builder $q) use ($attribute, $slug, $separator) {
$q->where($attribute, '=', $slug)
->orWhere($attribute, 'LIKE', $slug . $separator . '%');
});
}
You should add another column to your user table with name 'slug' or 'username' and map that column in your route.
In your users model, make a static function to generate unique slug with a number attached and save it in that new column to provide users with public/private profile URLs.
This below sample will stop returning slug/username after 100 users with the same name.
public static function getUniqueSlug($name) {
$original_slug = getSlug($name); // Default getter to get value in model
$slug = str_slug($original_slug); //Convert a string into url friendly string with hyphens
while(true) {
$count = User::where('slug', $slug)->count();
if($count > 0) {
$slug = $original_slug."-".rand(1,99);
} else {
return $slug;
}
}
}

Categories