How do I fetch all parents in a self relationship? - laravel-5

I have a User table with a self relationship.
the table has the fields like it follows:
id
parent_id (FK to User)
name
A User can have a parent, and this parent can have a parent and such on until infinite(although I will probably go like 3-4 levels tops the application sets no limit).
How do I fetch all the genealogical tree from a user that has no children(bottom of tree user) excluding siblings.
For instance:
I have user1, his parent is parent1 which has a grandparent1. If grandparent1 has other children(like a supposedly parent2) I don't want to fetch that. Just user1->parent1->grandparent1. Is there any way I can loop associations until it is not found?
EDIT1: I really want to use QueryBuilder for this. Making a foreach calling a parent() method until it returns null will probably not be the best solution due to optimization

Try with this:
class User extends Model {
public function parent()
{
return $this->belongsTo('App\User', 'parent_id');
}
public function children()
{
return $this->hasMany('App\User', 'parent_id');
}
}

Related

Laravel Multiple Foreign Keys on Intermediate Table

I have the following table structure:
default_tasks
id
...
locations
id
...
users
id
...
default_task_location
id
location_id
default_task_id
default_assignee_id (FK to users.id)
In my blade file I'm looping through the default tasks for the location, and trying to output the default assigned user. I can get the ID by doing $task->pivot->default_assignee_id, but I want to get the actual user model so I can output the user's name in my blade file
I've gathered from searching that the way to accomplish this is to create a pivot table model. So I've done this:
class DefaultTaskLocationPivot extends Pivot
{
public function user()
{
return $this->belongsTo(User::class, 'default_assignee_id');
}
public function defaultTask()
{
return $this->belongsTo(DefaultTask::class);
}
public function location()
{
return $this->belongsTo(Location::class);
}
}
and in my location model I've done this:
public function taskDefaultAssignee()
{
return $this->belongsToMany(User::class)
->using(DefaultTaskLocationPivot::class)->withPivot(['default_task_id', 'frequency_type_override', 'create_on_day_override', 'due_on_day_override', 'default_assignee_id']);
}
However, I'm still unable to access the user associated with the default_assignee_id. I've tried things like $task->location->taskDefaultAssignee, but it doesn't seem like the relationship is even available if I dump the object.

Laravel hasMany relationship detach all

Is there a simple solution to detach (without deleting) all related models through a hasMany Relation on a model?
For example, I have two tables:
College (id, name)
Student (id, name, college_id(nullable) )
In the College model I define this relationship:
class College extends Model
{
public function students()
{
return $this->hasMany('App\Student','college_id','id');
}
}
What is the best way to detach all current College students from the College (i.e, get all the students of the college and set their college_id to null)?
Is there an easy way to detach all students from the College model using eloquent?
Something like
class College extends Model
{
...
public function detachAllStudents()
{
...
}
}
P.S. already have read this question Laravel hasMany detach, but get errors when I try to apply it to my application
Yes, you can detach all the college students as below. Actually, we already have the college_id in your students table and we have to make this college_id null somehow. This is not called detach in Eloquent. The word detach come when you have many to many relationships. So, let's just update the students and see if it works or not.
$college->students()->update(['college_id' => null);
So, you method can be completed as below:
public function detachAllStudents()
{
$college->students()->update(['college_id' => null]);
}
That's it!
Directly from the documentation https://laravel.com/docs/5.8/eloquent-relationships:
Toggling Associations
The many-to-many relationship also provides a toggle method which "toggles" the attachment status of the given IDs. If the given ID is currently attached, it will be detached. Likewise, if it is currently detached, it will be attached:
$user->roles()->toggle([1, 2, 3]);
Despite Imran answer perfectly fits this scenario I would add a more generic approach. Let's say $college respects an interface instead of an implementation, you wouldn't know the foreign key at run time.
<?php
use Illuminate\Database\Eloquent\Relations\HasMany;
interface HasStudents {
public function students(): HasMany;
}
class College extends Model implements HasStudents
{
public function students(): HasMany;
{
return $this->hasMany(Student::class, 'college_id', 'id');
}
}
function detachStudents(HasStudents $model): void {
$studentsRelationship = $model->students();
$studentsRelationship->update([
$studentsRelationship->getQualifiedForeignKeyName() => null
]);
}
detachStudents($college);

How to eager load a relation with a condition in Eloquent?

I have a relation that can be inherited from a parent if not set for the object itself.
For an example setup let's say we have events that have a venue.
class Event extends Model
{
public function venue()
{
return $this->belongsTo('App\Venue');
}
public function activities()
{
return $this->hasMany('App\Activity');
}
}
And there are activities in the events that mostly take place in the same venue, but sometimes could be elsewhere while still belonging to the same event.
class Activity extends Model
{
public function event()
{
return $this->belongsTo('App\Event');
}
public function venue()
{
if ($this->venue_id)
return $this->belongsTo('App\Venue');
return $this->event->venue();
}
}
If I simply request activities for an event and work with them it is fine. But if I try to eager load the venues for activities, I only get the ones that are set directly on the activity, never requesting one from parent.
$activities = $event->activities;
$activities->load('venue'); // Works correctly without this line
foreach ($activities as $activity)
if ($activity->venue) // Doesn't take venue from the parent (event)
echo $activity->venue->name; //Only shows if venue_id is set on activity
Is there any chance to fix the relations so I could load them in bulk?
By their very nature, eager loaded relationships do not have the relationship method run for each parent model. If they did, you would have the N+1 issue, which is exactly what eager loading is meant to solve.
The relationship method is run once on the model that is used to start the query. This gets the base query to run, and then all of the parent model ids are injected into that query.
In order to do what you want, you need to change things up a little bit. First, your Activity can be directly related to venues, so setup that relationship without any conditions. Next, create an accessor method that will return the proper venue for the Activity.
So, your code would look something like:
class Activity extends Model
{
public function event()
{
return $this->belongsTo('App\Event');
}
public function venue()
{
return $this->belongsTo('App\Venue');
}
public function getActivityVenueAttribute()
{
return $this->venue ?? $this->event->venue ?? null;
}
}
The other option would be to always assign the venue_id on the Activity, even if it is the same as the Event venue_id. Then you don't need to worry about the venue id missing on the activity.

creating self referencing entities in laravel 5.5

i have a situation where i have entity, base, which can contain ingredients, drinks and bases itself. I know how to make relations to ingredients and drinks, but do not know how to do it when it comes to bases within base. Any help would be appreciated. I can not use solution with parent_id, because one base can belong to several other bases, and do not want duplicates in the bases table. I need some solution with pivot table.
Assuming you have parent_id in bases table
class Base extends Model
{
public function children()
{
return $this->hasMany(Base::class,'parent_id','id');
}
public function parent()
{
return $this->belongsTo(Base::class,'id','parent_id');
}
}
And then you can easily access bases of a base like this
foreach($base->children() as $childBase){
$childBase->ingredients();
$childBase->drinks();
}
Finally i found solution, self referencing entity can be done with itself with Many To Many relation.
Model relation look like this :
class BaseDrink extends Model {
public function drinkbase(){
return $this
->belongsToMany('App\BaseDrink', 'basedrink_basedrink','basedrink_id', 'parent_basedrink_id')
->withPivot('created_by')
->withPivot('last_modified_by')
->withPivot('id')
->withTimestamps();
}
}

How do i access data using two BelongsTo?

I have three tables - "courses", "lessons" and "tasks". Each lesson belongsTo a course, and each task BelongsTo a lesson. I want to output a task, showing the task name, the lesson name, and the course name. How do I access the course table data? To get the lesson information linked to a course, I have used the following in my Task model:
$lessonName = $this->lessons->lesson_name;
To get the course name associated to that lesson, I have tried the following with no success, but I am really guessing here:
$courseName = $this->lessons->courses->course_name;
My model relationships are as follows:
Course.php
public function lessons()
{
return $this->hasMany('App\Lesson');
}
Lesson.php
public function tasks()
{
return $this->belongsTo('App\Task', 'task_id', 'id');
}
Task.php
public function lessons()
{
return $this->belongsTo('App\Lesson', 'lesson_id', 'id');
}
Where am I going wrong? Thanks
there is another way you can do this by using accessors.
on your Task model do the following:
public function getLessonAttribute(){
return Lesson::where('id', $this->attributes[*foreign_key_field*])->first();
}
Here you receive all the data regarding the lesson that the task belongs to, and can use them as any other attribute (field) of the model.
on your Lesson model get the course that it belongs to.
public function getCourseAttribute(){
return Course::where('id', $this->attributes[*course_foreign_key_field*])->first();
}
and then assuming that $task is your collection, you can access the lesson and the course like the following in blade:
$task->lesson->lesson_name and $task->lesson->course->course_name
In your lesson.php model doesn't exist relationship courses so there are your issue. Use answer what is told you #jeroenF
So you want the inverse of hasManyThrough?
The hasManyThrough feature of Laravel (see their site) facilitates connecting your Courses to Task directly, without having the intermediate connection in a separate relationship.
You are looking for the inverse?

Resources