Recursive model associations

While usually pretty rare there are times when you want to define an association for a model that relates back to itself; parent record, children records, etc. Here's an example of how to get this up and running in your own model.

Here's a model called Person which holds records for, you guessed it, people. The schema consists of id, father_id, mother_id, first_name and last_name. Our goal here is to configure three extra associations for the model, PersonMother, PersonFather and PersonChildren so that CakePHP will automatically handle these associations for us.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

<?php /** * Person * * Application model for `Person` records. * * Schema: id, father_id, mother_id, first_name, last_name * * @author Joe Beeson */ class Person extends AppModel { /** * "Belongs to" associations. * * @var array * @access public */ public $belongsTo = array( 'PersonMother' => array( 'className' => 'Person', 'foreignKey' => 'mother_id' ), 'PersonFather' => array( 'className' => 'Person', 'foreignKey' => 'father_id' ) ); /** * "Has many" associations. * * @var array * @access public */ public $hasMany = array( 'PersonChildren' => array( 'className' => 'Person', /** * Since we have [two] fields that could possibly associate us * to our `PersonChild` records we have to override the query * to make sure we're searching both. */ 'finderQuery' => 'SELECT `PersonChildren`.* FROM `people` AS `PersonChildren` WHERE `PersonChildren`.`father_id` IN ({$__cakeID__$}) OR `PersonChildren`.`mother_id` IN ({$__cakeID__$})', // Note we also need to say false to "foreignKey" 'foreignKey' => false ) ); }

Our PersonFather and PersonMother records are a belongs to association, not a has one and the reason being that we want to be able to see the records from the other "direction" -- we get our parent records when we're queried, not visa-versa.
The secret sauce here is actually the className declaration in the association. This tells CakePHP that despite the association name, which usually tells it which model to load, that we want to use the Person model. This means that all of our other configurations, table, functions, behaviors, etc, will not need to be repeated since we are the model that will be associated.
The PersonChildren record is a bit trickier, reason being because there are two columns that may associate a parent with their children, father_id and mother_id. Luckily CakePHP gives us a configuration option, finderQuery, to help tell it how to get its associated records. If we didn't have such a unique situation we could get away without manually defining our own query.
One of the great things about defining all of your associations instead of relying upon your own custom code for finding records is that you can utilize tools like Containable or Linkable for selecting only the associations you need.

Permalink

| Leave a comment  »