0

I have a new Laravel 5.8 application. I started playing with the Eloquent ORM and its relationships.

There is a problem right away that I encountered.

I have the following tables. (this is just an example, for testing reasons, not going to be an actual application)

Login table:
--------------------------
| id | user    | data_id |
--------------------------
| 1  | admin   | 1       |
| 2  | admin   | 2       |
| 3  | admin   | 3       |
--------------------------

Data table:
--------------
| id | ip_id |
--------------
| 1  | 1     |
| 2  | 2     |
| 3  | 3     |
--------------

IP table:
----------------------
| id | ip            |
----------------------
| 1  | 192.168.1.1   |
| 2  | 192.168.1.2   |
| 3  | 192.168.1.3   |
----------------------

What I wanted is to get the IP belonging to the actual login.

So I added a hasOne relationship to the Login table that has a foreign key for the Data table:

public function data()
{
    return $this->hasOne('App\Models\Data');
}

Then I added a hasOne relationship to the Data table that has a foreign key for the IP table:

public function ip()
{
    return $this->hasOne('App\Models\Ip');
}

Once I was done, I wanted to retrieve the IP address for the first record of the Login table:

Login::find(1)->data()->ip()->get();

But I get this error:

Call to undefined method Illuminate\Database\Eloquent\Relations\HasOne::ip()

What am I missing here and how can I get the IP of that login in the correct way? Do I need a belongsTo somewhere?

Radical_Activity
  • 2,618
  • 10
  • 38
  • 70

3 Answers3

5

1st error: Wrong relationship definition

Laravel relationships are bi-directional. On one-to-one relationships, you can define the direct relationship (HasOne) and the inverse relationship (BelongsTo)

The direct relationship should be:

             HasOne                HasOne
[ Login ] <----------- [ Data ] <----------- [ IP ]

And the inverse relationship should be:

           BelongsTo             BelongsTo
[ Login ] -----------> [ Data ] -----------> [ IP ]

See Eloquent: Relationships - One-to-One docs for details on how defining it.

Note that you don't need to define both directions for a relationship unless you need it. In your case, I think you just need to define the belongsTo direction.

2nd error: You are calling the relationship method, not the relationship itself

When you do:

Login::find(1)->data()->ip()->get();

You are calling the method data that defines your relationship, not the related model. This is useful in some cases, but not on your case.

The correct is call the relationship magic property instead:

Login::find(1)->data->ip;

Note that we don't use the () and we do not need the get() here. Laravel takes care of loading it for us.

Use Eager Loading

Laravel Eloquent have a Eager Loading for relationships that's very useful in some cases because it pre-loads your relationships, and reduce the quantity of queries you do.

In the situation that you described (loading a single Login model) it doesn't make any performance improvement, but also it doesn't slow down.

It's useful when you load many models, so it reduces your database query count from N+1 to 2.

Imagine that you are loading 100 Login models, without eager loading, you will do 1 query to get your Login models, 100 queries to get your Data models, and more 100 queries to get your Ip models.

With eager loading, it will do only 3 queries, causing a big performance increase.

Elias Soares
  • 9,884
  • 4
  • 29
  • 59
  • 2
    Amazing, thanks for the descriptive explanation, I understand it now. I think my problem in thinking was that I thought that the `Login` table is the 'main' table and the rest of it are parts of the Login table. Which is true, but need to think to do it inverse this case. Anyway, thank you so much for this, you saved my day! :) – Radical_Activity Jul 09 '19 at 13:53
2

With your database structure:

Login belongsTo Data

Data hasOne Login

Data belongsTo IP

IP hasOne Data

After fixed your methods you can use of your relations like this

$login = Login::with(['data.ip'])->find(1);

Immeyti
  • 545
  • 3
  • 15
1

You can try like this:

Login

public function data()
{
    return $this->belongsTo('App\Models\Data', 'data_id');
}

Data

public function ip()
{
    return $this->belongsTo('App\Models\Ip', 'ip_id');
}
$login = Login::with(['data.ip'])->find(1);

And inside data you will have ip like $login->data->ip.

mare96
  • 3,749
  • 1
  • 16
  • 28