Laravel - Updating OneToOne relationship - php

I have two models 'User' and 'Profile'.
'email' field is in User model whereas 'name' field is in Profile model.
'profiles' table has a foreign key of 'user_id'.
I searched a lot but couldn't find a proper solution on how I can update both of these entities in one go.
In my ProfileController, I am doing this but I am sure there is a better way. Please help.
public function update($id)
{
$profile = Profile::where('id', $id);
$profile->name = 'Jon';
$profile->save();
$user = User::where('id', $profile->user_id);
$user->email = 'newjon#example.com';
$user->save();
}
My Profile model has
public function user()
{
return $this->belongsTo('User');
}
And my User model has
public function profile()
{
return $this->hasOne('Profile');
}

You can't do it in one go.
However you could simplify it a bit, by leveraging Laravel features, like this (and do it in one-go-like way):
1 Controller edit
$profile = Profile::with('user')->find($id);
// make sure you eager load the user for below to work
2 View
{{ Form::model($profile) }}
{{ Form::text('name') }}
{{ Form::text('user[email]') }}
{{ Form::close() }}
this will autopopulate your profile data (and user data too)
3 Controller update
$profile = Profile::find($id);
$profile->fill(Input::only(.. fields you want to update ..));
$profile->user->fill(Input::get('user')); // array of user data form the form
$profile->push(); // save both models in one go BUT separate queries
Also make sure you have fillable on your models, so fill will does its job.
Another way would be using model events, but I wouldn't do it that way.

Related

Why does Livewire run a new query on each render and why are relationships lost

Scenario
What i try to do
I am creating a multicolumn user index page, where the right column shows details from the user selected in the left column.
When selected, the user is not pulled out of the collection but freshly out of the database, so the data is up to date.
I defer the loading of the user list using the described method in the livewire documentation.
The user has a 'roles' relationship, which is displayed in the list column.
What I'd expect
I would expect that once the $this→users is set as a collection of the users and a user is selected, only the query will fire for getting the data for this user.
What actually happens
When a user is selected, a query for getting all users from the database is run (again), and because of the fact that the roles from the user are displayed in the list view, for each user, a new query is executed.
After that, a query for getting the selected user is executed. Afterwards another query for getting the roles of the user is fired to.
So my questions
Why does Livewire lose the relations that were eager loaded in the first declaration of public $users?
Why is it that Livewire reruns the query for getting all users, while the public $users is already defined as a collection of users?
Files:
UserListDetail.php
<?php
namespace App\Http\Livewire;
use App\Models\User;
use Livewire\Component;
class UsersListDetail extends Component {
public string $search = '';
public $users;
public $selectedUser;
public int $timesRun = 0;
public bool $readyToLoadUserList = false;
protected $queryString = [
'search' => [ 'except' => '' ],
];
// Defer loading users
public function readyToLoadUserList()
{
// Get all users with roles relationship
$this->users = User::with('roles')->get();
$this->readyToLoadUserList = true;
}
public function selectUser(int $userId)
{
$this->selectedUser = User::with('roles')->find($userId);
}
public function render()
{
return view('livewire.users-list-detail', [
'selectedUser' => $this->selectedUser,
]
);
}
}
simplified version of user-list-detail.blade.php
<div>
<div wire:init="readyToLoadUserList">
#if($readyToLoadUserList)
<ul>
#foreach($users as $user)
<li wire:click="selectUser({{ $user->id }})">
{{ $user→name_first }} {{ $user→name_last }},
#foreach($user→roles as $role)
{{ $role→label }},
#endforeach
</li>
#endforeach
</ul>
#endif
</div>
<div>
#isset($selectedUser)
{{ $name_first
#endisset
</div>
</div>
When selectUser() method is triggered, the livewire will re-render the blade and since wire:init="readyToLoadUserList" is there, it will load every user (again).
Replce readyToLoadUserList() with mount() and simply keep wire:init="" empty.
Also, condition with #if($users->count() > 0)

Creating an Eloquent Object with relation included

I'm pretty much new to opps and laravel both
So, to insert the values into my users and profiles table which hav OneToOne relationship, Here is how my store() method looks like
public function store(Requests\StoreNewUser $request)
{
// crate an objct of user model
$user = new \App\User;
// now request and assign validated input to array of column names in user table
$user->first_name = $request->input('first_name');
$user->last_name = $request->input('last_name');
$user->email = $request->input('email');
$user->password = $request->input('password');
/* want to assign request input to profile table's columns in one go
*/
$user->profile()->user_id = $user->id; // foreign key in profiles table
$user->profile()->mobile_no = $request->input('mobile');
dd($user); // nothing related to profile is returned
}
I'm creating the new record, hence dd() never returns anything related to profile table.
Is this Because the $user object is not including relationship by default?
If yes Can i create the $user object which includes the associated relations in User Model ?
Or do i have to create two separate objects of each table and save() the data But then what is the significance of push() method ?
EDIT 1
P.S. yes, the relationships are already defined in User & Profile model
You may try something like the following. At first save the parent model like this:
$user = new \App\User;
$user->first_name = $request->input('first_name');
// ...
$user->save();
Then create and save the related model using something like this:
$profile = new \App\Profile(['mobile_no' => $request->input('mobile')]);
$user->profile()->save($profile);
Also make sure you have created the profile method in User model:
public function profile()
{
return $this->hasOne('App\Profile');
}
I thought i'd update this answer and make it applicable to Laravel 5 onwards. I'll use #The Alpha answer as a basis.
$profile = new \App\Profile(['mobile_no' => $request->input('mobile')]);
$user->profile()->associate($profile); // You can no longer call 'save' here
$user->profile()->save();
The reason for this is you can no longer call save on the belongsTo relation (or any other), this now returns an instance of Illuminate\Database\Query\Builder.
The clean way to do it now would be having on your User Class file:
public function profile()
{
return $this->hasOne(App\Profile::class);
}
and in your User Controller, the following store method:
public function store(Requests\StoreNewUser $request)
{
$user = App\User::create(
$request->only(
[
'first_name',
'last_name',
'email'
]
)
);
$user->password = Illuminate\Support\Facades\Hash::make($request->password);
//or $user->password = bcrypt($request->password);
$user->profile()->create(
[
'mobile_no' => $request->mobile;
]
);
dd($user);
}
I didn know if u were saving plain text password to you database or using a mutator on password attribute, anyhow the suggested above is a good practice I think
Is this Because the $user object is not including relationship by default? If yes Can i create the $user object which includes the associated relations in User Model ?
Yes you should create the relationship, they're not included by default.
In your User model you'd want to do something like this:
public function profile()
{
return $this->hasOne('App\Profile'); // or whatever your namespace is
}
This would also require you to have a Profile model created.
This would definitely answer your questions regarding inserting related models: http://laravel.com/docs/5.1/eloquent-relationships#inserting-related-models
As The Alpha mentioned, and you also eluded to, I think you need to save your user model first then you can add via relationship.

Laravel form binding with one to one relationships

I have an Account model that has a polymorphic relation to an Address model. This is set up as a one-to-one releationship set up like so:
Account:
public function address()
{
return $this->morphOne('Address', 'hasAddress', 'add_hasaddress_type', 'add_hasaddress_id', 'act_id');
}
Address:
public function hasAddress()
{
return $this->morphTo('hasAddress', 'add_hasaddress_type', 'add_hasaddress_id');
}
On my form to edit the account, I also have the address fields. I can bind my account object simply enough by doing:
{{ Form::model($account, array('route' => array('accounts/edit', $account->act_id), 'method' => 'put')) }}
{{ Form::label('act_name', 'Account Name:') }}
{{ Form::text('act_name', Input::old('act_name')) }}
and that fills in the fields properly. But, how do I populate the address fields? From what I researched, I need to do:
{{ Form::text('address.add_city', Input::old('address.add_city')) }}
To access the relation's values, but this doesn't work.
I also tried
{{ Form::text('address[add_city]', Input::old('address[add_city]')) }}
as suggested by a SO with a similar title. Both of these I tried with and without the old input. Does this just not work with poymorphic relations or am I doing something wrong?
Also, how do you handle these forms in the controller?
Nothing about relations is in the form model binding documentation, and doing a search mainly brings up people asking for one-to-many binding.
It works with any *-to-one relation (for many-to-many, ie. a collection of models it won't work):
// prepare model with related data - eager loading
$account = Account::with('address')->find($someId);
// or lazy loading
$account = Account::find($someId);
$account->load('address');
// view template
{{ Form::model($account, ...) }}
Account: {{ Form::text('acc_name') }}
City: {{ Form::text('address[add_city]') }}
{{ Form::close() }}
No need for Input::old or whatsoever, null is enough as default value. Laravel will fill the data in this order (Docs are wrong here!):
1. old input
2. bound data
3. value passed to the helper
Mind that you must load the relation (dynamic call won't work in this case).
Another thing is processing the input later - Laravel will not automatically hydrate the related model, so you need something like:
$accountData = Input::only(['acc_name', ... other account fields]);
// or
$accountData = Input::except(['address']);
// validate etc, then:
$account->fill($accountData);
$addressData = Input::get('address');
// validate ofc, then:
$account->address->fill($addressData);

Laravel 4: Eloquent relationship get all data

I have 2 relationship data table; users table and memberdetails table.
Users.php
class Users extends Eloquent{
public function memberdetails()
{
return $this->hasOne('Memberdetails','user_id');
}
}
Memberdetails.php
class Memberdetails extends Eloquent{
public function user()
{
return $this->belongsTo('Users','user_id');
}
}
When I try to retrieve data, with $data = User::find($id); I only get data from users table.
Example of my blade form:
{{-- User's Name, stored on user table --}}
{{ Form::text('name',null, array('id'=>'name','class'=>'form-control','required')) }}
{{-- User's address, stored on member table --}}
{{ Form::text('address',null, array('id'=>'address','class'=>'form-control','required')) }}
When I visit, localhost/user/2/edit/, the name field is populated, but address field is empty. How can I retrieve data from both tables and put into a form for editing?
Thank you.
You could use eager loading.
$user = User::with('memberdetails')->find($id);
Using this, you will automatically get the memberdetails when retrieving the user. Then you can use $user->memberdetails
Using eager loading, you do only one query to the DB so it should be the preferred way. If you dont use the with('memberdetails'), you will perform a second query when accessing the memberdetails.
After getting the user instance, access the relationship, then you can access the other class properties
$user = User::find($id);
$userData = $user->memberdetails;

Many-to-Many Eloquent relationship update with Laravel Form Model Binding & Checkboxes

I have 3 tables:
doors
id
name
image
colors
id
name
image
door_colors
id
door_id
color_id
and 2 models with a many-to-many relationship (each door comes in a variety colors, and many colors overlap door-to-door):
Door Model
class Door extends Eloquent {
public function colors()
{
return $this->belongsToMany('Color', 'door_colors');
}
}
Color Model
class Color extends Eloquent {
public function doors()
{
return $this->belongsToMany('Door', 'door_colors');
}
}
I want to create a form where I can edit the door, and update the available colors via checkboxes.
This is my Admin Doors Controller
class AdminDoorsController extends AdminController {
public function edit($id)
{
$data['door'] = Door::find($id);
$data['colors'] = Color::all();
return View::make('admin/doors/form', $data);
}
}
and the Admin Doors Form View
{{ Form::model($door) }}
Colors:
#foreach ($colors as $color)
{{ Form::checkbox('colors[]', $color->id) }} {{ $color->name }}
#endforeach
{{ Form::close() }}
Question 1: How do I make it so that as the checkboxes are outputted, the ones with an existing relationship with the current door are checked and the ones without are unchecked.
Question 2: Once I check the boxes and hit submit, how would I update the relationships? $door->colors()->detach(); to clear all existing ones for this door, then $door->colors()->attach($color_id_array); to create new ones based on an array of color ids?
Any input is appreciated!
Question 1: You should pass this into the view that contains your form, though it can also go right in the view, though that's not really best practice. Do something similar to this...
$checkeds = Door::find(1)->colors()->lists('id');
...where the door you are finding is the door that's being updated. Then before you output the checkbox in the loop, add
$checked = in_array($color->id, $checkeds) ? true : false;
Then you would change
{{ Form::checkbox('colors[]', $color->id) }}
{{ $color->name }}`
to
{{ Form::checkbox('colors[]', $color->id, $checked) }}
{{ $color->name }}
Question 2: There is actually a perfect method given to you for this. Use
$door->colors()->sync(Input::get('colors'));
It will both delete the old ones and add all the new ones in one shot.
Suppose you are modeling user and role and want to edit user with roles.
In your controller edit,
$user = User::find($id);
$roles = Role::lists('name', 'id'); // to populate all roles
In your template if you use select,
{{ Form::select('roles[]', $roles, array_pluck($user->roles, 'id'), ['multiple']) }}
In your controller update,
$inputs = Input::all();
$roles = $inputs['roles'];
$user->roles()->sync($roles);
// $user->fill($inputs);
// $user->save();

Categories