What’s the purpose of the $relation parameter in HasRelationships->belongsTo()? - laravel

So I was wandering in the source code of the HasRelationsShips trait and found that there’s this $relation parameter in methods like belongsTo that doesn’t show up in Laravel’s doc examples and by the looks of it serves only to debug purposes. Is this correct?

It is not for debug purposes, it is so you can define the relation name explicitly. Without that argument the BelongsTo will implicitly use the calling method name as the 'relation' name, example:
function user() {
return $this->belongsTo(User::class);
}
This will use a backtrace to know that the belongsTo was called from a method named user. So it will know the relation is user. Since we didn't explicitly tell it any foreign key it will use the relation, user, to build the foreign key: $relation .'_'. $relatedInstance->getKeyName() which would build user_id. This relation name is also used so that other relationship functionality knows what the name of the relationship method is on this model for this relationship, such as the associate and disassociate methods.

Just to elaborate on lagbox's answer, the use of a backtrace happens in Illuminate\Database\Eloquent\Concerns\HasRelations::guessBelongsToRelation() and looks like this:
protected function guessBelongsToRelation()
{
[$one, $two, $caller] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
return $caller['function'];
}
The use of debug_backtrace() is controversial – and I'm not a big fan of micro-optimizations generally – but in this case with modern PHP it's very easy to skip the function altogether.
All my belongs-to relationships are simply defined like this, using a named argument and magic constant:
public function user(): BelongsTo
{
return $this->belongsTo(User::class, relation: __FUNCTION__);
}

Related

"BookInfo::type must return a relationship instance but null was returned"

First of all, this is my first Laravel project, it's just something I'm knocking together to learn, so please go easy on me.
Let's say I'm creating an application representing a library. Each Book has some info (BookInfo) about it, and each BookInfo contains a (reference to) a BookType that says if it's "fiction" or "non-fiction".
So if my BookInfo schema looks somewhat like:
//...
$table->unsignedBigInteger('book_id');
$table->unsignedBigInteger('book_type_id');
//...
$table->foreign('book_id')->references('id)->on('books');
$table->foreign('book_type_id')->references('id)->on('book_type');
And my BookType schema looks like this:
//...
$table->id();
$table->string('name');
What does the method on the BookInfo model look like that returns the BookType?
I've got this, and seem to have tried innumerable alternatives (with slightly different errors):
public function type()
{
return BookType::where('book_type_id', $this->book_type_id)->get()->first();
}
And yes, I'm sure that the id I'm trying to lookup there exists in the book_type table. I actually only really want the ->name of the BookType but obviously I need to get the BookType first...
public function type(): BelongsTo
{
return $this->belongsTo(BookType::class, 'book_type_id');
}
OR if you change the method name to bookType you can omit the second argument to belongsTo because it will automatically resolve the foreign key based on the method name, and you can just do:
public function bookType(): BelongsTo
{
return $this->belongsTo(BookType::class);
}
Here's the eloquent relationship reference from the Laravel docs: https://laravel.com/docs/9.x/eloquent-relationships
in migration for foreign key use: $table->foreignIdFor(BookType::class, 'book_type_id');

Laravel Relationship with OR case

Assume I have a User model, and also I have Couple model which forms of 2 users, father_id and mother_id which are essentially user_ids
On User model, I have
public function kids() {
return $this->hasMany('App\Kid', 'father_id');
}
However, I want to check if user_id is either father_id or mother_id, return the related Kid model.
Is there a way to achieve it with a single relationship? What is the proper way of handling this scenario, so I can use $user->kids that would check for both cases?
There is a way, but you wouldn't typically use it to "check" if there are related models.
If you have a field that determines if the model is representing a father or mother, such as is_father, you could do:
public function kids()
{
return ($this->is_father)
? $this->hasMany(Kid::class, 'father_id')
: $this->hasMany(Kid::class, 'mother_id');
}
Essentially, the relationship method MUST return a relationship instance. But you can do logic before you return this.
NOTE: The relationship is cached, so even if the is_father value changes in the same thread run, it will utilize the same relationship that it did before. This can cause unwanted bugs.

Laravel 5.5 retrieving null by nested relation

I have 3 databases:
Routes:
id
name
Rates:
Id
Route_id
Car_id
Cars:
id
name
My model for routes
public function rates()
{
return $this->hasMany('App\Rate', 'route_id');
}
My model for rates
public function car() {
return $this->belongsTo('App\Car','car_id');
}
Now I need to access the car relation, but when I do
return $this->route->with('from','to','rates.car')->paginate(74);
I get null for the car relation
{"id":1,"from_id":1,"to_id":2,"distance":400,"created_at":null,"updated_at":null,"from":{"id":1,"name":"\u0410\u043a\u043a\u043e","created_at":null,"updated_at":null,"lat":32.93310000000000314912540488876402378082275390625,"long":35.0827000000000026602720026858150959014892578125},"to":{"id":2,"name":"\u0410\u0440\u0430\u0434","created_at":null,"updated_at":null,"lat":31.261399999999998300381776061840355396270751953125,"long":35.21490000000000009094947017729282379150390625},"rates":[{"id":1,"route_id":1,"car_id":1,"rate":1123,"night_rate":1391,"car":null},{"id":5551,"route_id":1,"car_id":2,"rate":1123,"night_rate":1391,"car":null},{"id":11101,"route_id":1,"car_id":3,"rate":1123,"night_rate":1391,"car":null},{"id":16651,"route_id":1,"car_id":4,"rate":1123,"night_rate":1391,"car":null},{"id":22201,"route_id":1,"car_id":5,"rate":1123,"night_rate":1391,"car":null},{"id":27751,"route_id":1,"car_id":6,"rate":1123,"night_rate":1391,"car":null},{"id":33301,"route_id":1,"car_id":7,"rate":1123,"night_rate":1391,"car":null},{"id":38851,"route_id":1,"car_id":8,"rate":1123,"night_rate":1391,"car":null}]},
From my understanding you are trying to access a Car model through a Route model.
A couple of things I noticed that should help you find a solution.
First off I think the inverse relation you are supposed to use the belongToMany() function instead.
public function car() {
return $this->belongsToMany('App\Car','Rates'); // Perhaps call the table something like routes_cars to more clearly define it's a pivot table
}
Next I see you are trying to use model functions within the context of $this(). I assume you are doing this in your model? That logic should be in a controller, that might cause some undesired results but I'm not entirely sure. Also it looks like your parameters are incorrect when using with(). You use the function name that you defined in belongsToMany()
App/Route::with('car')->paginate(74);
With the correct relationships setup you rarely need to worry about the pivot table. If you are going to add extra information in the pivot table there are laravel functions to help you do that in the documentation.

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?

Inspect Model relations

I am trying to inspect my Eloquent models to find out their relations to other Models. The problem is that relations are simply defined as a single method and no central index of relations exists:
public function posts()
{
return $this->hasMany('Post');
}
In order to inspect all relations I need to extract the list of methods, take out the ones inherited from Eloquent, execute each single one and check the return type:
$all = get_class_methods($model);
$inherited = get_class_methods('Eloquent');
$unique = array_diff($all, $inherited);
foreach($unique AS $method)
{
$relation = $model->$method();
if(is_a($relation, 'Illuminate\Database\Eloquent\Relations\Relation'))
{
//... this is a relation, do something with it
}
}
Needless to say, this is very dangerous. Is there a way to do this kind of inspection in a different, more secure way?
You could add PHPDoc comments to your relationship methods and then use the PHP reflection API to extract those from the source.

Resources