Tuesday, June 21, 2011

cakephp multiple relations (hasmany primarykey)

×You have 1 new comment. See your responses.

Suppose (for lack of better example) I have a model Person and another model Twin (meaning a pair of twins). Twins has two Person foreign keys, say first_born_id and second_born_id referencing the id fields of two different people (in Person). How do I set up the relationships in cake?

I guess Twin would have something like:

$belongsTo = array('FirstBorn' => array('className' => 'Person',
'foreignKey' => 'firstborn_id'),
'SecondBorn' => array('className' => 'Person',
'foreignKey' => 'secondborn_id'));

But how should I set up Person? I could do it like:

$hasOne = array('TwinAsFirstborn' => array('className' => 'Twin',
'foreignKey' => 'firstborn_id'),
'TwinAsSecondborn' => array('className' => 'Twin',
'foreignKey' => 'secondborn_id'));

But then when I have a Person and I want to know about its twin I would need to check both relationships. I guess I am hoping there is a way to get a "Twin" relationship in Person representing any of the two ways a Person can be in a Twin relationship.

Or is there a better way to set this up?

link|edit|flag

add comment

2 Answers

up vote 1 down vote accepted

I agree with the Twin example being a little confusing. Let me assume that you have a Product model (Twin in your example) and it always has 2 Component models attached to it.

components: id - name
products
: id - component1_id - component2_id

I would set up Product as follows:

var $belongsTo = array(
'Component1' => array(
'className' => 'Component',
'foreignKey' => 'component1_id'
),
'Component2' => array(
'className' => 'Component',
'foreignKey' => 'component2_id'
)
);

And Component as:

var $hasMany = array(
'ProductWithComponentAsComponent1' => array(
'className' => 'Product',
'foreignKey' => 'component1_id'
),
'ProductWithComponentAsComponent2' => array(
'className' => 'Product',
'foreignKey' => 'component2_id'
)
);

Basically, you should replace your hasOne with hasMany. Each component hasMany products where it is the first component. At the same time, it hasMany products where it is the second component. Hope that makes things clear.

EDIT 1: (Oh, and "ProductWithComponentAsComponent#" is just for the purpose of explanation. You can keep whatever short, sweet alias you'd like depending on your actual models.)

EDIT 2: Simple rule of thumb for using hasOne relationships - Use it only when you're splitting a single table into many (like User/Profile)

EDIT 3: If you want all Products for a Component, then you can do this in two ways.

(A) Define the foreignKey as false in your hasMany relation.

var $hasMany = array(
'Product' => array(
'className' => 'Product',
'foreignKey' => false,
'conditions' => array(
'or' => array(
"Product.component1_id = Component.id",
"Product.component2_id = Component.id"
)
)
)
);

(B) If the above method does not work (Cake acts weird now and then), you can also use a join to make it obey. Create a function in the Component model like so:

function fetchProducts($id) {
if (!$id) {
return null;
}
return $this->Product->find('all', array(
'fields' => array('Product.*'),
'joins' => array(
array
(
'table' => 'components',
'alias' => 'Component',
'foreignKey' => false,
'type' => 'inner',
'conditions' => array(
'or' => array(
"Product.component1_id = Component.id",
"Product.component2_id = Component.id"
),
'Component.id' => $id
)
)
)
));
}
link|edit|flag



Maybe I misunderstand you, but the Component model represents an instance of a component (not a type of components) and a Component can only be part of one Product (also an instance, not a "class") either as component1 or component2. So shouldn't the relationship be hasOne? Also, with your Component model (basically same as mine in the first example, except for hasOne->hasMany) the problem remains about how to access the Product of a Component without having to check both relationships (I might not care if the Component is a component1 or component2 of the Product).zephyr Jan 5 at 20:58


You can replace Component with ComponentInstance if you like. Refer to Edit 2 regarding hasOne: Use it primarily when splitting tables. To access all Products of a component, refer to Edit 3. :)RabidFire Jan 6 at 2:56


Ah. I didn't know you could set foreignKey to false (is this documented somewhere?). However it seems to break recursiveness. With recursive set to 2 for the Component I get an "Unknown column: Component.id in WHERE clause" error as the Component is not present in the separate queries (from recursiveness) on Product. For regularly declared foreignKey relationships these type of queries get the id as a constant (e.g. "(3)" instead of "'Component'.'id'"). One could do something like your method B, but I had hoped that there was a way that integrates better with the rest of the CakePHP system.zephyr Jan 6 at 11:48


Yea, as I mentioned, Cake acts weird at times! Use the Containable behavior instead of recursive. Apart from that, I guess you're good to go! :)RabidFire Jan 6 at 13:00


Yes, I can avoid the problem with Containable, but I still won't be able to (automagically) get the models related to the Product as if it was a regularly defined foreignKey relationship. Maybe I can do something with afterFind in Component, but it will probably feel a bit hackish (I would have to avoid auto recursion with Containable, and then get it anyway with afterFind.. or something.). Thank you for your help!zephyr Jan 6 at 16:00


add comment

Why would you define a twin like this?

A twin, first or second born is a person. The thing that associates them with a parent is the fact they are a "child" and their "DOB" is the same.

So would you not do something like:

Person -> ID, Name, Age, DOB, Parent_ID

The parent_ID is stored into the childs record, and the twin is determined by comparing all children under a parent for DOB?

Does this make it any easier to set up your cakePHP relationships?

link|edit|flag



Ah, well I guess my example was not too well chosen. In the real application Person is a type of component and Twin is a product made up of two of these components (or possibly only one of them actually). So the two "person"s have no other connection than that they together make up a pair of "twin".zephyr Jan 5 at 13:30


Ahhh I see - and does this "bundle" of components only ever have 2 records? Are you able to post your current Data Structure?diagonalbatman Jan 5 at 13:32


Yes, there can only be two components (and they have different roles in the product, so they are differentiated by their role as well, hence the "firstborn" and "secondborn" in the example). I can't post the actual code but the Models look exactly like in the example (plus some unrelated stuff). The component database table has an "id" field, and the product table has two foreign key fields ("component_a_id" and "component_b_id") both referencing the component "id" field.zephyr Jan 5 at 13:46


add comment

Your Answer
















Not the answer you're looking for? Browse other questions tagged or ask your own question.

1 comment:

  1. Little long but really very helpful thing I have seen here.

    ReplyDelete