Get relationships after insert - php

In my code, I insert a new row into the database:
$post = new Post;
$post->user_id = Auth::user()->id;
// more inserts
$post->save();
In my Post.php, I have:
protected $with = [
'user', 'answers', 'questions'
];
public function users()
{
return $this->belongsTo('App\User');
}
// etc
But when I return $post after I insert, there are no relationships (users, answers, questions) attached to it.
How can I get all of the default relationships to load after an insert?

The save() method persists the data to the database, but it doesn't do anything about refreshing the data on the Model or reloading relationships.
The easiest solution would be to refresh your object after calling save(). This will automatically eager load the relationships you've defined in your $with property on the model:
// ...
$post->save();
// refresh the post from the database
$post = $post->fresh();
Another option is to just manually reload the relationships yourself, using the load() method.
// ...
$post->save();
// reload the desired relationships
$post->load(['user', 'answers', 'questions']);
However, this duplicates the code that defines the relationships you'd like to be auto loaded (defined once in the Model, and then once in this code). You can mitigate that by creating a new function on your Model.
// in Post model
public function reloadRelations() {
$this->load($this->with);
}
// code usage
// ...
$post->save();
// call your new function to reload the relations
$post->reloadRelations();
However, the only real benefit of going this route over just calling the built in fresh() method is that this won't re-run the query to get the original Post data.
If you're handling 1000s of requests a second, maybe the one query might make a difference, but other than that, I wouldn't worry about it, and just use the fresh() method. But, the options are here for you to choose.

Instead of manually setting the attribute user_id, you should use the associate method from \Illuminate\Database\Eloquent\Relations\BelongsTo class.
$post->user()->associate(Auth::user());
// now you have the user inside your post.
dd($post->user);

May be, in model Post.php:
protected $primaryKey = 'id';
public function users()
{
return $this->hasOne('App\User', 'id', 'user_id');
}
before:
migration "posts"
Schema::create('articles', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->foreign('user_id')->references('id')->on('users');
// .....
});
Hopefully this will solve your problem

Related

How to exclude/include specific database columns when eager-loading a nested model in Laravel 8?

To keep things simple, I'll give an example in code to what I'm trying to Achieve.
Let's assume that we have a model called User with the following code:
class User
{
public function posts()
{
return $this->hasOne(Post::class);
}
}
And the Post model look something like this.
class Post
{
public function comments()
{
return $this->hasMany(Comment::class);
}
}
And now what I want to be able to do is for example:
$user = Auth::user();
$comments = $user->post->comments; //here, I only want the "comment_id" and "comment_content" fields to be included, not all the Comment Model fields !
you can simply use hasManyThrough in user model to connect comments table.
read about hasManyThrough here: docs
and after having a relation between user and comments (hasManyThrough)
you can use load (Lazy eager loading) method
there is an piece of code you can use here
$user->load(["comments" => function($q){
$q->select("comment_id","comment_content");
}]);
read more about Lazy eager loading: docs

I'm getting an error when trying to save a new model with 2 fields referencing different ids in the same table

I'm new to laravel, and I've picked up the basic workflow of creating, updating and deleting database entries using migrations, models and controllers. But now I'm trying to do the same with a subscriptions table that has a subscriberId and a followeeId in it. Both of these fields reference different ids of the same table (users). This kind of task seem to require some finetuning. And I'm stuck.
Here's my code with some comments.
Subscriptions Table
Schema::create('subscriptions', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('subscriberId');
$table->unsignedBigInteger('followeeId');
$table->foreign('subscriberId')->references('id')->on('users');
$table->foreign('followeeId')->references('id')->on('users');
});
Previously, I've used another approach to foreign ids, namely the one with the $table->foreignId('user_id')->constrained() pattern, but in this particular case I need to make sure that the two foreign ids reference different users, so I went for a more verbose option.
User Model
public function subscriptions()
{
return $this->hasMany(Subscription::class, 'subscriberId');
}
Here I've added the second parameter. This seems to work.
Subscription Model
class Subscription extends Model
{
use HasFactory;
protected $fillable = [
'subscriberId',
'followeeId'
];
public function subscriberId()
{
return $this->belongsTo(User::class, 'id', 'subscriberId');
}
public function followeeId()
{
return $this->belongsTo(User::class, 'id', 'followeeId');
}
}
Here I pass additional parameters, too, although in this case I'm not so sure if these are the correct ones. But this is my best guess. If I'm not mistaken, the second parameter of the belongsTo relation is inferred from the model that is being passed in, not the model of the parent class as is the case with the hasMany relation. So in this case that would be 'id' of the users table, which would be the default here anyway, but I need the third parameter, so I explicitly state the second parameter as well. Again, I'm not sure about this combination, but that's what I was able to make of the docs. I've also used other combinations of additional parameters, and even tried getting rid of these two public functions altogether, but that won't work either.
Now, here's the controller. If I do this:
$user->subscriptions()->get();
I do get the subscriptions I want. But if I do this instead:
$user->subscriptions()->create([
'subscriberId' => 1,
'followeeId' => 2
]);
I get the 500 error. I've also tried another approach:
$newSub = new Subscription;
$newSub->subscriberId = 1;
$newSub->followeeId = 2;
$newSub->save();
return $newSub;
But still no success. I still get the 500 error when I try to save()
Please help me out.
Solution
I should have used
public $timestamps = false
in the Subscription model, and I also misunderstood the docs. The correct combo is
User Model
public function subscriptions()
{
return $this->hasMany(Subscription::class, 'subscriberId');
}
and
Subscription Model
public function subscriberId()
{
return $this->belongsTo(User::class, 'subscriberId');
}
public function followeeId()
{
return $this->belongsTo(User::class, 'followeeId');
}

Laravel: matches array (Tinder clone) to my user returns empty

I'm making a Tinder clone to practice, I want to retrieve a user matches (when both users like eachother) but when I do:
$user = User::with('matches')->findOrFail(1);
The matches array returns empty, despite all users liking eachothers,
I created a Users_Users_liked pivot table:
Schema::create('users_users_liked', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('user_id')->index();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade')->onUpdate('cascade');
$table->unsignedInteger('user_liked_id')->nullable()->index();
$table->foreign('user_liked_id')->references('id')->on('users')->onDelete('cascade')->onUpdate('cascade');
$table->timestamps();
});
I set up the User model with relationship as follows (notice matches realtionship, which always returns an empty array)
public function likesToUsers()
{
return $this->belongsToMany(self::class, 'Users_Users_liked', 'user_id', 'user_liked_id');
}
public function likesFromUsers()
{
return $this->belongsToMany(self::class, 'Users_Users_liked', 'user_liked_id', 'user_id');
}
public function matches()
{
return $this->likesFromUsers()->whereIn('user_id', $this->likesToUsers->keyBy('id'));
}
In my UsersSeeder I give likes to user 1 from all users
$users = json_decode($json, true);
foreach ($users as $data)
{
$user = new User();
$user->name = $data['name'];
$user->save();
if($user->id > 1)
{
$user->likesToUsers()->attach([1]);
$user->save();
}
}
$user = User::findOrFail(1);
$user->likesToUsers()->attach([2,3,4,5,6,7,8,9,10,11]);
$user->save();
There are a couple of issues in your current matches relationship:
whereIn takes an array of ids (where the values of each item is the id), whereas, using keyBy will mean that the values are the instances of the model. pluck would have been the right method to use i.e. $this->likesToUsers->pluck('id').
The above would only have worked if you already had the model loaded so eager loading wouldn't have worked. There are a couple of reasons for this:
The models wouldn't have yet been loaded so you couldn't then call the likesToUsers relationship to actually retrieve the ids i.e. you can't use the value of one relationship in another relationship in this way.
Even if you were lazily eager loading the relationships, if you were loading multiple models, Laravel would actually only use the relationship values from the first model (I think it's the first) so the rests of the relationships would be wrong.
What you can do though is use the query side of a relationship to retrieve the data. The long and the short of it is don't try and use the value of one relationship in another when trying to eager load.
All that being said, one option would potentially be to join the pivot table on the relationship (again) and compared the opposite columns:
public function matches()
{
return $this->likesFromUsers()
->join('users_users_liked as alt_users_users_liked', function (JoinClause $join) {
$join
->whereColumn('users_users_liked.user_liked_id', 'alt_users_users_liked.user_id')
->whereColumn('users_users_liked.user_id', 'alt_users_users_liked.user_liked_id');
});
}
Some general notes/tips:
You can simplify your users_users_liked migration by using the foreignId() method instead:
$table->increments('id');
$table->foreignId('user_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate();
$table->foreignId('user_liked_id')->constrained('users')->cascadeOnDelete()->cascadeOnUpdate();
$table->timestamps();
It's also usually a good idea to add a unique constraint to your pivot tables (unless you potentially want duplicates):
$table->unique(['user_id', 'user_liked_id']);
Finally, it might be worth taking a look at Model Factories.

Laravel model ID null after save (ID is incrementing)

I have a save method where I do validation, create a model and save it. Later in the save method I try and access the ID of the model and it's NULL.
Schema:
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
$table->string('body');
//and other fields that aren't important right now
$table->timestamps();
});
}
Save method:
public function savePost(Request $request) {
$this->validate($request, [
//some validation happens here
]);
$post = new Post();
$title = $request->input('title');
$body = $request->input('body');
$post->title = $title;
$post->body = $body;
$post->save();
//I later try to access the $post->id here and it's still NULL.
var_dump($post->id);//NULL
}
In MySQL the ID is being set (just an auto incrementing unsigned integer) - it seems like it gets set after the savePost method is complete.
I thought that after $post->save() was complete, the model's ID would be set - but that seems to not be the case. How can I access the model's ID from the same method where I save() it? Or have I made a mistake somewhere?
Laravel Framework version is 5.3.26
Edit: just to clarify: the model (including the auto incrementing PK) is being stored in MySQL, I'm just not able to access the model's id in the same method that I save it.
Figured it out:
I ran into this issue when switching from a string PK to an auto incrementing unsigned int.
I still had public $incrementing = false; in the Post model, which explains this issue. Switching to $incrementing = true solved the problem.
For other people how are using Pivot/Junction table, hence, if your model is extending Pivot instead of Model then this is why,
As #noob mentioned, You've to enable public $incrementing = true; manually or just extend Model instead.

Update a column that exist in the many to many relationship table in laravel 5.2

I have three tables which are User table, Events table, and Events_user table. I have four columns in the Events_user table which are :
Schema::create('events_user', function(Blueprint $table){
$table->increments('id');
$table->integer('user_id')->unsigned()->index();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->integer('events_id')->unsigned()->index();
$table->foreign('events_id')->references('id')->on('events')->onDelete('cascade');
$table->integer('eventstatus')->unsigned()->index();
$table->timestamps();
});
I have a function which add an event base on the user id, show one user can have many of the events. But the event can share to other user. The function is like this :
public function store(EventsRequests $request){
$id = Auth::user()->id;
$events= Events::create($request->all());
$events->user()->attach($id); //Store the User_ID and the Event_ID at the events_user table.
Flash::success('Your event has been created!');
return redirect('/');//Return into home page
// return $events;
}
Then it will help me update my events_user table, but how do I update the column which is eventstatus?
Can anyone show me how could I update it? Cause I want to use the eventstatus help me determine if there is '1' then cant be edit, then the edit button will disappear.
How many way can accomplish to the function?
For the show page:
I have use the function when the user click the event that exist on the calendar view, then the javascript will pass the id back and redirect to the view with the id.
public function show($id){
// $user_id=Auth::user()->name;
$showevents = Events::findOrFail($id);
return view('events.show',compact('showevents','user'));
}
How about when i want to pass the eventstatus into the view? So i can check on my view, if the eventstatus equal to one, the edit button wont show up.
Updated relationship model
User.php
public function events(){
return $this->belongsToMany('App\Events');
}
Events.php
public function user(){
return $this->belongsToMany('App\User')->withPivot('eventstatus');
}
If you want to update the field while making the association, you can pass an array of field=>value pairs as the second parameter to the attach() method. You can read in the documentation here.
$events->users()->attach($id, ['eventstatus' => 1]);
If you want to update the field for an existing association, you can pass an array of field=>value pairs to the save() method:
$user = Auth::user();
$event = $user->events()->first();
$user->events()->save($event, ['eventstatus' => 1]);
If you want to access the field to read the data, you can access it through the pivot property:
$user = Auth::user();
foreach ($user->events as $event) {
echo $event->pivot->eventstatus;
}
In addition to all of this, you need to make sure your relationship definitions include access to the extra property:
// User class:
public function events() {
return $this->belongsTo(Event::class)->withPivot('eventstatus');
}
// Event class:
public function users() {
return $this->belongsTo(User::class)->withPivot('eventstatus');
}
Edit
updated answer for additional question
To get the information you're looking for, you need to be able to get to the pivot record. The pivot record is available on the models in the loaded relationship.
Since you have the Event, you need to go through the related User models, find the user you want (assuming the logged in user), and then get the status off of the pivot attribute on that user.
public function show($id)
{
// get your event
$showevents = Events::findOrFail($id);
// get your user
$user = Auth::user();
// default status to send to view
$status = 0;
// if there is a user...
if ($user) {
// find the user in the "user" relationship
// NB: calling "find" on a Collection, not a query
$eventUser = $showevents->user->find($user->id);
// if the user is associated to this event,
// get the status off the pivot table,
// otherwise just set the default status
$status = $eventUser ? $eventUser->pivot->eventstatus : $status;
}
// add your status to the data sent to the view
return view('events.show', compact('showevents', 'user', 'status'));
}
In addition to all this, make sure both relationship definitions include the ->withPivot('eventstatus'). You want to make sure you can get at it from both sides, if need be.
Can you share your event and user model relationship code?
so , it will help me to get into deep.
OR
you can try this in event model like:
public function user()
{
return $this->belongsToMany('App\Event')
->withPivot('eventstatus')
->withTimestamps();
}
then while updating you can do something like this iam not sure but hope this help.
$events->user()->attach($id,['eventstatus' => 1]);
Thanks
For fetching extra column:
public function show($id){
$showevents = Events::findOrFail($id);
$event_status = $showevents->pivot->eventstatus;
return view('events.show',compact('showevents','event_status'));
}
but for that you have to define this column in models:
// User class:
public function events() {
return $this->belongsTo(Event::class)->withPivot('eventstatus');
}
// Event class:
public function users() {
return $this->belongsTo(User::class)->withPivot('eventstatus');
}

Categories