Yii 1.1: Relations: BELONGS_TO versus HAS_ONE

35 followers

It's very common to see new Yii users confusing the relations HAS_ONE and BELONGS_TO, and getting it wrong means you won't get proper values back. And though we'll talk about HAS_MANY as well, we're specifically omitting the MANY_MANY relation because it's a whole different animal.

Both BELONGS_TO and HAS_ONE are about linking two models together, and sound like the same thing, but they link in essentially opposite directions. Let's illustrate with three simple tables, each of which has a primary key (id), and a number of linking fields (user_id) that reference the User table.

USER table
- id 
- name

POST table
 - id
 - user_id         REFERENCES User.id
 - content

PROFILE table
 - id
 - user_id         REFERENCES User.id
 - profile_info

KEY POINT: A BELONGS_TO relation says that a field in this model points to the primary key in another model; in this case, the current model owns the linking field.

KEY POINT: A HAS_ONE relation says that some other model has a linking field pointing to this model's primary key; in this case, the related model owns the linking field.

We can thik that a PARENT table will be the one that doesn't have a foreign key, and a CHILD table as the one who "depends" on the parent table, that is, it has a foreign key.

Given that, a CHILD BELONGS_TO a PARENT and a PARENT HAS_ONE CHILD.

Let's put these in context (numbered for reference)

// Post model relations
1.   'user'    => array(self::BELONGS_TO, 'User',    'user_id'),
// Post belongs_to a user, because it is a child table.
 
// Profile model relations
2.   'user'    => array(self::BELONGS_TO, 'User',    'user_id'),
// Profile belongs_to a user, because it is a child table.
 
// User model relations
3.   'posts'   => array(self::HAS_MANY,   'Post',    'user_id'),
// User has_many posts, because User is a parent table.
4.   'profile' => array(self::HAS_ONE,    'Profile', 'user_id'),
// User has_one profile, because User is a parent table.

Relations 1 has the linking field user_id in this model, pointing to the primary key of the related model User. Likewise with relation #2.

Relations 3 and 4 are essentially the same thing as each other: the linking field user_id is not in this model, but in the related model, and the primary key involved is in this model (User). The difference is that HAS_MANY returns an array of possibly multiple objects, while HAS_ONE returns a single object.

HAS_ONE is just a special case of HAS_MANY, and the circumstances where HAS_ONE makes sense are far more limited than HAS_MANY and BELONGS_TO.

Weird example

USER table
- id 
- name
- status_id         REFERENCES status.id

STATUS table
 - id
 - name

Here one would think in human terms that a USER HAS_ONE STATUS. But that doesn't work. As you see, the USER is in fact a "child" table of STATUS, because it is referencing it, it depends on it. So the relations would be:

// User model relations
1.   'status'    => array(self::BELONGS_TO, 'Status',    'status_id'),
// User belongs_to a status, because it is a child table.
 
// Status model relations
2.   'users'    => array(self::HAS_MANY, 'User',    'status_id'),
// Status has_many users, because it is a parent table.

Some points to remember:

  • When defining one of these relations, you don't ever name the primary key; Yii figures it out from the DB schema
  • BELONGS_TO is where this model owns the linking field
  • BELONGS_TO references the related model's primary key
  • HAS_ONE/HAS_MANY is where the related model owns the linking field
  • HAS_ONE/HAS_MANY reference this model's primary key
  • If you define a BELONGS_TO relation in one model, you probably want an associated HAS_MANY relation in its related mode
  • You probably don't really want HAS_ONE ever

Total 9 comments

#15104 report it
leo4all at 2013/10/08 01:22am
Weird example

About the Weird example.

I had seen this way before, but how it possible to Create a user row later. let say, get the status_id, that wwill create the record on Status Table then retrieve on user row added?

Another example of this approach.

#13935 report it
Nisanth thulasi at 2013/07/08 04:58am
Has Many with limit
return array(
     'posts' => array(self::HAS_MANY, 'Post', 'P_ID','limit'=>'5'),
 
    );
#13724 report it
Johnny Gan at 2013/06/19 04:52pm
Key Point is highlight

I like the key point summary part, which is very helpful for Yii beginners. Thanks a lot.

#12521 report it
tamert at 2013/03/25 04:07pm
LIMIT

sorry my english is bad :(

now.. a few Relations, given how by difference Relations to limit

exp;

parent = HAS_MANY = limit 5

children = BELONGS_TO = limit 4

notes = HAS_MANY = limit 10

etc...

#9120 report it
Boaz at 2012/07/22 05:29am
Very useful

Short, concise, clear and to the point. I like it! :) The mentioned points should definitely go into the official guide. AFAIK its not there and it should (the difference between belongs_to and has_one)

#8799 report it
jhonka at 2012/06/27 01:32pm
Very helpful

Thanks, this is exactly what I needed to read to solve my problems.

#5483 report it
fsb at 2011/10/15 10:17am
Confused about schema for profile table

Although it's not explicit in the article, I would think that the relation from profile to user would be an identifying relation. Yet the schema has the relation as non-identifying (a profile can exist without a user). And it allows more than one profile per user. The Yii logic overlaid upon the schema prevents this and appears to make the profile table's id column superflous.

I think ManInTheBox was saying the same in #5030.

#5030 report it
ManInTheBox at 2011/09/07 08:15pm
HAS_ONE = PK, FK

As my point of view, HAS_ONE relation stands for column which is PK, FK in referenced table. Example:

tbl_profile.user_id (PK, FK) references -> tbl_user.id

user HAS_ONE profile and profile BELONGS_TO user

Please tell me if I'm wrong. Great article :-)

#3640 report it
Bauer418 at 2011/04/26 01:03am
Very Helpful

This post was especially helpful in debugging issues with my application's model structure. SJFriedl, very good work.

Leave a comment

Please to leave your comment.

Write new article