user8555937
user8555937

Reputation: 2395

Laravel Eloquent Relationship: misunderstanding them?

Good day! I think I am doing something wrong or I'm misunderstanding Eloquent practices.

I have three tables.

  1. orders { city, county }
  2. cities { id, name }
  3. counties { id, name }

I am performing queries upon these tables using Laravel Eloquent One-To-Many-Inverse Relationship. orders.city holds one id of cities and orders.county holds one id of counties. Below is the code of my models.

App\Orders

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Orders extends Model
{
    /*
    *   Table name
    */
    protected $table = 'orders';

    /*
    *   Get county
    */
    public function county()
    {
        return $this -> hasOne('App\Counties', 'id', 'county');
    }

    /*
    *   Get city
    */
    public function city()
    {
        return $this -> hasOne('App\Cities', 'id', 'city');
    }

}

App\Counties

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Counties extends Model
{
    protected $table = 'account_county';
    public $timestamps = false;

    /*
    *   Counties relationship
    */
    public function county()
    {
        return $this -> belongsTo('App\Orders', 'county', 'id');
    }
}

App\Cities

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Cities extends Model
{
    protected $table = 'account_city';
    public $timestamps = false;

    /*
    *   City relationship
    */
    public function city()
    {
        return $this -> belongsTo('App\Orders', 'city', 'id');
    }
}

When I execute the following in Tinker: $order = App\Orders::with(['city', 'county']) -> first();

....I get the following response:

App\Orders {#3042
     county: App\Counties {#3041
       id: 23,
       name: "Argeș",
     },
     city: App\Cities {#3046
       id: 6924,
       name: "Argeșelu",
     },
   }

Now when I run the following code: $order -> city

I get the following response:

"6924"

When I execute the following code however: $order -> City

I get the whole collection:

App\Cities {#3046
       id: 6924,
       name: "Argeșelu",
     },

Why do I need to uppercase the first letter of property to get the collection instance? In documentation it states I should use the dynamic property name directly: $order -> city however in my case this returns the raw string ID not the collection instance. Am I missing something? I am concerned about performance and good practice reasons. I apologize if it's a bad question or if it has been explained somewhere else already.

Upvotes: 0

Views: 111

Answers (1)

Jason
Jason

Reputation: 3040

You're experiencing this problem because of the naming convention you are using for your database fields and relationships.

When loading data from a database table, will assign each field that you pull from your database to an attribute on your model. These are retrieved via PHP's __get and __set magic methods, based on the name of the database field. So $order->city actually fetches the value of the database field on the row of that table.

One thing to note here is that city as the attribute name is case sensitive, which brings us to the next issue.

When you call $order->City on the model (note the case), it first looks for a database field attribute called City. Because of case sensitivity, it cannot find a database field of that name.

The next thing Eloquent does is it checks to see if there is a relationship function called city. Note the case. PHP class functions are not case sensitive, so you can defined a function called city and call it as City(). So when you ask for $order->City, it calls the relationship function of that name and returns the DB results.

This is the reason you get the correct results when you call City instead of city.

To fix the issue, I recommend renaming your relationship fields city and county to the more standard (for Eloquent) values of city_id and county_id. Not only will this reduce your confusions, but you'll benefit from some of the built in Eloquent functionality to make your life easier.


As an aside to this, the reason your $order->city was coming back as a string was due to the way Eloquent treats database fields by convention. It does not generally read the schema to look at the datatypes, but generally treats everything as strings unless you explicitly cast it to a PHP type, or it meets a specific naming convention. For instance, Eloquent will assuming any field ending in _id is a number, and treat it as a integer by default. So renaming city to city_id will fix this for you automagically.

Also, check your relationships as a whole. It seems to be you would be better with BelongsTo relationships both on City and County.

Upvotes: 1

Related Questions