Understanding CakePHP Associations

June 6, 2007 - 8:43pm

CakePHP uses the Model-View-Controller (MVC) concept as a framework to encapsulate the three basic layers of an application. Models, the component of the MVC framework that connect the application to the data, are especially good at describing relational databases. One of the questions that seems to come up alot on the CakePHP Group in one form or another, is what the practical difference is between the various relationship types (or associations) that CakePHP models can implement. I'll try to explain what's helped me keep them straight...

NOTE: This is not a tutorial on writing a CakePHP application, but rather a few tips for understanding associations. For more complete information on writing a CakePHP application, see the manual.

I remember studying basic genetics in biology in high school, and our teacher said that genetics is one of those topics that people have an extremely hard time understanding... until it just clicks -- then you can't understand why you didn't get it before. I feel that learning associations in CakePHP can be a similar issue. I've been writing relational database applications for several years, but I remember when I was defining models in my first Cake app... I couldn't for the life of me get a grip on what the difference between "hasOne" and "belongsTo" was, let alone the infamous hasAndBelongsToMany! That was, until I took a closer look at the CakePHP Manual chapter on Models and made an important discovery: the difference between hasOne and belongsTo lies in which table in the relationship does the 'pointing.'

belongsTo:

A -> B
If table A has a field that references table B, then table A is said to "belongTo" table B.
Just imagine that every record in the A table says "I belong to a record in table B!"
In the movie database example below, since for each movie there is one director, and the movie table has a field that points to a director, each movie belongs to a director. With belongsTo, each record can only be associated with one foreign record, but multiple records can be associated with any given foreign record. For instance, all three Matrix movies were directed by the Wachowski brothers, so each movie would belong to the Brothers' entry in the directors table.

hasOne:

A <- B

If only a single record in table B has a field that references table A, then table A is said to "hasOne" table B.

Every record in table A says: "I have one record in table B that points to me!"

hasOne is basically belongsTo, but the pointing goes in the other direction. If table A hasOne table B, then it can also be said that table B belongsTo table A (except only one B record would be associated with any A record).

In all honesty, I don't think I've ever used this association. This is because if every record in table A only has one record associated in table B, the tables may be able to be combined. Where the use for this association comes in is when you have 1-to-1 relations, but the data may be best kept in different tables for logical or technical reasons. For instance, in the CakePHP manual blog example, every user hasOne profile -- in theory, the tables could be combined, but it probably is easier to keep them apart and accessible through association.

hasMany:

A <- B (multiple)

If table B has a field that references table A, and multiple records in table B can point to the same A record, then table A is said to "hasMany" table B.

Every record in table A says: "I have many records in table B that point to me!"

hasMany is the true sibling to belongsTo. If table A has many B, then table B can be said to belongTo A (and the multiple works, since A has MANY). hasMany and belongsTo are very complimentary if you need to get to a relationship from each side of it.

hasAndBelongsToMany:

A <-> C <-> B (multiple)

If every record in table A can link to multiple references in table B, and every record in table B can be linked to by multiple A records, table A is said to "hasAndBelongToMany" table B.

HABTM is a little more complicated, because it uses a link table. A link table will have at least two fields, one to point to the id of the first table, and another to point to the id of the linked record in the second table. In our movie database example below, since a single movie can have multiple genres, and you can have more than one movie in any given genre, the movie table has many and belongs to the genre table. HABTM associations can go both ways since the link table will work in each direction, so they can be (and often are) defined in both models (as it is done below).

Let's consider a sample application where we're developing a small movie database (an extremely small step-brother to IMDb). This application will be a database of movies categorized by genre with directors and movie reviews. Each movie can be in multiple genres (Action/Horror, Sci-Fi/Thriller, etc), can have one director (we'll ignore multiple directors for now), and have multiple reviews.

We'll start by outlining our models:

  1. Movie
    1. belongsTo Director
    2. hasMany Reviews
    3. hasAndBelongsToMany Genres
  2. Director
    1. hasMany Movies
  3. Review
    1. belongsTo Movie
  4. Genre
    1. hasAndBelongsToMany Movies

And now some code:

class Movie extends AppModel {
	var $name = 'Movie' ;
 
	var $belongsTo = array( 'Director' => array( 'className' => 'Director' ) );
 
	var $hasMany = array( 'Reviews' => array( 'className' => 'Review' ) );
 
	// We don't need many settings in this association, because we'll stick to Cake's naming conventions
	var $hasAndBelongsToMany = array( 'Genres' => array( 'className' => 'Genre' ) );
}
 
class Director extends AppModel {
	var $name = 'Director' ;
 
	$hasMany = array( 'Movie' => array( 'className' => 'Movie' ) );
}
 
class Review extends AppModel {
	var $name = 'Review';
 
	$belongsTo = array( 'Movie' => array( 'className' => 'Movie' ) );
}
 
class Genre extends AppModel {
	var $name = 'Genre';
 
	// We specify the join table here because Cake would expect the table to be called genres_movies from this side
	$hasAndBelongsToMany = array( 'Movies' => array( 'className' => 'Movie',
	                                                 'joinTable' => 'movies_genres'
	                                               );
}

And for good measure, here's a basic outline of the table structure:

  1. movies
    1. id
    2. title
    3. release_date
    4. director_id
  2. directors
    1. id
    2. first_name
    3. last_name
  3. reviews
    1. id
    2. movie_id
    3. author_name
    4. body_text
  4. genres
    1. id
    2. name
  5. genres_movies (thanks Beertigger!)
    1. id
    2. movie_id
    3. genre_id

Using these models and tables, we can use this line of code in a controller:

$movie = $this->Movie->read(null, $id); //assuming $id contains a movie id...

Will gives us a data structure like this in the $movie variable:

Array (
[id] =>
[title] =>
[release_date] =>
[director_id] =>
[Director] => Array (
[id] =>
[first_name] =>
[last_name] =>
)
[Reviews] => Array (
[0] => Array (
[id] =>
[movie_id] =>
[author_name] =>
)
[1] => Array (
[id] =>
[movie_id] =>
[author_name] =>
)
)
[Genres] => Array (
[0] => Array (
[id] =>
[name] =>
)
[1] => Array (
[id] =>
[name] =>
)
)
)
  1. Anonymous on June 7, 2007 - 4:51am

    A very helpful article. Especially the tricky hasAndBelongsToMany explanation.

    Just one thing, shouldn't it be Director hasMany Movies?

  2. Josh on June 7, 2007 - 6:43pm

    Yep, you're right. Fixed it. Thanks!

  3. Jelka (not verified) on June 26, 2007 - 4:49pm

    I just googled you up and I will bookmark your blog :)

    I was searching for ways (if there are any) to deploy cake php on my bluehost page.

    I'm new to cakePHP and I was just wondering if there is a way for me to edit my apache config file so that the www root will point to the app/webroot of my cake application (as I tested on my machine this must be done in order for cake to work correctly).

  4. Josh on June 30, 2007 - 10:53am

    Jelka,

    When I use Cake oh bluehost, I usually just make a new subdomain for the site I'm making. If that's not an option, you can use apache's mod_rewrite to have the "base" url point to a subdirectory using the RewriteBase directive. Apache has some great documentation on mod_rewrite available at http://httpd.apache.org/docs/1.3/mod/mod_rewrite.html

  5. Chris (not verified) on July 4, 2007 - 1:06pm

    Joshua... you have just answered all my questions... Thanks, great post.

    One thing... Shouldn't we have:
    $hasMany = array( 'Movies' => array( 'className' => 'Movie' ) );
    instead of
    $hasMany = array( 'Movie' => array( 'className' => 'Movie' ) );
    in model director? (I'm not sure...)

    Thanks again for great post...

    I'll note this on my blog (although it's just starting).

    Cheers

  6. Dickey (not verified) on July 19, 2007 - 2:37pm

    This has been most helpful. Here a sql schema I came up with:

    CREATE TABLE movies (
    id int(11) unsigned NOT NULL auto_increment,
    title varchar(50) collate utf8_unicode_ci default NULL,
    release_date datetime default NULL,
    director_id int(11) unsigned NOT NULL default '0',
    created datetime NOT NULL default '0000-00-00 00:00:00',
    modified datetime NOT NULL default '0000-00-00 00:00:00',
    PRIMARY KEY (id),
    KEY director_id (director_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

    CREATE TABLE directors (
    id int(11) unsigned NOT NULL auto_increment,
    first_name varchar(50) collate utf8_unicode_ci default NULL,
    last_name varchar(50) collate utf8_unicode_ci default NULL,
    created datetime NOT NULL default '0000-00-00 00:00:00',
    modified datetime NOT NULL default '0000-00-00 00:00:00',
    PRIMARY KEY (id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

    CREATE TABLE reviews (
    id int(11) unsigned NOT NULL auto_increment,
    movie_id int(11) unsigned NOT NULL default '0',
    author_name varchar(50) collate utf8_unicode_ci default NULL,
    body_text varchar(50) collate utf8_unicode_ci default NULL,
    created datetime NOT NULL default '0000-00-00 00:00:00',
    modified datetime NOT NULL default '0000-00-00 00:00:00',
    PRIMARY KEY (id),
    KEY movie_id (movie_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

    CREATE TABLE genres (
    id int(11) unsigned NOT NULL auto_increment,
    name varchar(50) collate utf8_unicode_ci default NULL,
    created datetime NOT NULL default '0000-00-00 00:00:00',
    modified datetime NOT NULL default '0000-00-00 00:00:00',
    PRIMARY KEY (id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

    CREATE TABLE movies_genres (
    id int(11) unsigned NOT NULL auto_increment,
    movie_id int(11) unsigned NOT NULL default '0',
    genre_id int(11) unsigned NOT NULL default '0',
    created datetime NOT NULL default '0000-00-00 00:00:00',
    modified datetime NOT NULL default '0000-00-00 00:00:00',
    PRIMARY KEY (id),
    KEY movie_id (movie_id),
    KEY genre_id (genre_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

  7. Beertigger (not verified) on July 27, 2007 - 8:43pm

    Shouldn't the movies_genres table be genres_movies, following the manual's criteria for placing the referenced tables in alphabetical order?

  8. Josh on August 3, 2007 - 7:15pm

    Yep, you're right. I never even noticed that little rule. Thanks!

  9. Josh on August 3, 2007 - 7:17pm

    Glad I could help.

    I'm not sure if there is a rule about whether the array keys for associations need to be plural or singular. I typically use singular as you have said, but plural works just fine. You can actually call it anything you want and it works just fine, which I find very useful when I need to associate two table fields with the same table.

  10. kennethd (not verified) on August 12, 2007 - 9:08pm

    Thanks for the helpful post.

    I too have been having trouble getting my head around HasOne vs. BelongsTo, and your post helped me make a distinction that I think clarifies the usefulness of HasOne (examples based on my record store project):

    belongsTo == many:1 relationship, the current table may have many rows referencing a single foreign key (catalog:label)

    hasMany == 1:many relationship, each row in the current table may reference many foreign keys (distro:catalog)

    hasOne == 1:1 relationship, here I think the manual's User--Profile example is too simplistic

    I have a table called addresses, with a primary key called address_id, which is referenced by several tables: contacts, users, artists, labels, distros, and customer_addresses

    Address doesn't belongTo all of those objects, but rather each hasOne address (Customer hasMany CustomerAddress, and CustomerAddress hasOne Address)

    and of course,

    hasAndBelongsToMany == many:many relationships, which also require an intermediary table, but where the relationship can go either way (catalog:keywords or catalog:tags)

  11. JDS (not verified) on August 14, 2007 - 2:25pm

    Finally, a concise, clear, and specific description of HasOne vs BelongsTo, which, as it turns out, I was constantly getting reversed.

    Thanks!

  12. Andrea (not verified) on August 16, 2007 - 10:45am

    Firstly thanks for this post!
    Secondly I have a question :D If I want to implement a Many to Many relation using a table with other fields like:

    Users
    -id
    -name
    -surname

    Groups
    -id
    -name

    Users_Groups
    -user_id
    -group-id
    -join_date
    -kind

    how can I implement this thing?
    Thanks!

    Ps. sorry for my little English! :D

  13. Lennard (not verified) on August 16, 2007 - 11:51am

    Thank you so much for sharing this! I was also (and still am) struggling with associations, but after several hours of studying manuals, your blog post and playing around with different models and examples I think it is somewhat clear now ;-)

  14. Josh on August 17, 2007 - 12:02am

    I accomplished this by using a custom finder query. I will write an article on this eventually, but for now, try to make use of this custom finder query example that I used in an application. The SQL is T-SQL for Microsoft SQL Server, but the concepts are the same for other database engines. Of special note is the {$__cakeID__$} where the foreign key is inserted.

    	var $hasAndBelongsToMany = array(
    	    'Activity' => array('className'  => 'Activity',
    	                        'joinTable'  => 'facultyWorkload_activity_links',
    	                        'foreignKey' => 'faculty_id',
    	                        'associationForeignKey' => 'activity_id',
    	                        'unique'     => true,
    	                        'finderQuery' => '
    SELECT 
    	[Activity].[id] as [Activity.id],
    	[Activity].[name] as [Activity.name],
    	[Activity].[activity_type_id] as [Activity.activity_type_id],
    	[Activity].[credits] as [Activity.credits],
    	[ActivityLink].[students] as [Activity.students],
    	[ActivityLink].[id] as [Activity.activity_link_id]
    FROM 
    	[facultyWorkload_activities] AS [Activity]
    	JOIN [facultyWorkload_activity_links] AS [ActivityLink] ON [ActivityLink].[activity_id] = [Activity].[id]
    WHERE 
    	[ActivityLink].[faculty_id] = {$__cakeID__$}
    ORDER BY
    	[Activity].[name] ASC'
    	                  ));

  15. JC (not verified) on August 22, 2007 - 10:48pm

    How would you handle 'related movies', movies related to other movies?

    'Movies' Has and Belongs to Many 'Movies'

  16. Josh on August 23, 2007 - 12:46pm
    It's easy enough to do self-join relationships using Cake. When you put the entry in the association array, name the key the way you want it:
    var $hasAndBelongsToMany = array( 'Genres'  => array( 'className' => 'Genre' ),
                                      'Relatedmovies' => array( 'className' => 'Movie' ) );
  17. Adrian Gould (not verified) on September 7, 2007 - 5:39am

    Thanks Joshua for the very well explained blog entry, it is helpful.

    I do admit that the arrows do provide a problem for decifering when it comes to the equivalent relational models... so I've taken the step of creating a few diagrams to show the ER equivalents to the models.

    I know when I teach Normalisation that I use an arrow along with 1:m style notation to make sure that my students get the differences.

    So with this in mind... I've completed the relevant story at http://it.swantafe.wa.edu.au/index.php?option=com_content&task=view&id=1...

    All the best, Ady

  18. Adrian Gould (not verified) on September 9, 2007 - 9:52pm

    Joshua

    thank you for a great article! Helped me get my head around the concepts. Also I have pointed my students to this URL so they can see the concepts.

    I've taken the liberty of creating ER diagrams that represent each of the associations to help my students, as well as myself [it always the way] understand the link between the two concepts. It took me a litle while to make sure I got the arrows the right way round on the two different diagrams.

    Anyhow you can find the article at: http://it.swantafe.wa.edu.au/index.php?option=com_content&task=view&id=1...

    All the best

    Adrian
    Lecturer in IT, Swan TAFE, PERTH, Western Australia

  19. Charlie (not verified) on September 12, 2007 - 4:23am

    Hi,

    I want to use the belongsTo feature of cake, but in both of my used tables I have the field 'name'. How can I make use of 'Country.name AS cname' feature of the queries without having to write my own queries?

  20. Josh on September 17, 2007 - 1:46pm
    Cake will abstract the field naming for you and encapsulate it in the returned array structure, so this isn't something that should pose a problem for you.

    If tables `states` and `countries` both have a `name` field, and states belongsTo countries, then your returned array might look like this (slightly different depending on how and where you execute the query):

    Array (
    	[name] => Pennsylvania,
    	[abbr] => PA,
    	[capitol] => Harrisburg,
    	[Country] => Array(
    		[name] => United States of America
    		[abbr] => USA
    		[capitol] => Washington D.C.
    	)
    )
    
  21. Jeremy (not verified) on September 27, 2007 - 9:24am

    This was exactly the explanation I had been looking for, for days. Thanks for making it so clear! They should take this and put it in the manual.

  22. Kostas (not verified) on October 3, 2007 - 10:03am

    Hey Joshua nice work!

    In your examples After having set all the HABTM associations in the appropriate models,how can you retrieve the 1. id field from the following table?

    genres_moview

    1. id
    2. movie_id
    3. genre_id

    I think that the only solution is by setting a custom SQL (findQuery)

    Any ideas ?

  23. Josh on October 5, 2007 - 8:47am

    I will probably write an article about this technique, but I need to look into some recent changes that have been generating some noise in CakePHP community about HABTM associations first.

    One option, of course, is to create a model for the link table and perform a find* on that model once you have the information to build the conditions for the find. If I understand what you're asking, you'd like to retrieve additional information from the link table with the other data being pulled by the find call on one of the related models.

    What you want to do is perfectly doable, but it's not the most straight-forward thing. You're correct that it requires a custom query, but with some thought, this isn't so difficult to rule it out.

    In my first Cake project, I was developing a little app that interfaced with our MSSQL system to pull data about faculty members and details about their credit load, student count, etc to process overall "workload." There was of course a table (view actually) that had a row for each faculty member, but there was also a table that listed all the possible non-course activities a professor could engage in (coaching, chairing a department, etc). So, there was a HABTM relationship between faculty and activities.

    The catch was that each activity that a professor had also had a student count associated with it -- data that was best stored along with the link itself in the association link table. It took a lot of digging and eventually trudging through the source yielded the most reliable answers. Here's the custom query I used to accomplish this:

    var $hasAndBelongsToMany = array(
    	    'Activity' => array('className'  => 'Activity',
    	                        'joinTable'  => 'facultyWorkload_activity_links',
    	                        'foreignKey' => 'faculty_id',
    	                        'associationForeignKey' => 'activity_id',
    	                        'unique'     => true,
    	                        'finderQuery' => '
    SELECT 
    	[Activity].[id] as [Activity.id],
    	[Activity].[name] as [Activity.name],
    	[Activity].[activity_type_id] as [Activity.activity_type_id],
    	[Activity].[credits] as [Activity.credits],
    	[ActivityLink].[students] as [Activity.students],
    	[ActivityLink].[id] as [Activity.activity_link_id]
    FROM 
    	[facultyWorkload_activities] AS [Activity]
    	JOIN [facultyWorkload_activity_links] AS [ActivityLink] ON [ActivityLink].[activity_id] = [Activity].[id]
    WHERE 
    	[ActivityLink].[faculty_id] = {$__cakeID__$}
    ORDER BY
    	[Activity].[name] ASC'
    	                  )

    Note the {$__cakeID__$} token -- it's an identifier that cake will substitute with the ID of the model being retrieved. I'll be honest and note that I don't have a 100% understanding of how cake handles this -- but that's part of the magic of cake: understanding the inner workings is helpful and powerful, but not always necessary :).

    Hope that helps!

  24. Miko (not verified) on October 5, 2007 - 12:35pm

    Before reading your article, I never really understood the difference between belongsTo and hasOne! Good stuff!

  25. Conficio (not verified) on October 5, 2007 - 4:06pm

    Hi Josh,
    can you extend your article or write another one that explains the consequences of the various relations in the case of deleting an object.

    For example should "hasMany" implicate owner ship, saying if post has many comments than if a particular post is deleted all related comments should be deleted too.

    K<o>

  26. Prakash (not verified) on October 14, 2007 - 9:24pm

    This is an awesome explanation! I was able to build a small movie database with grouping just by understanding what everything meant! I have a somewhat stupid question though:

    Genre > Director > Movie

    Everything works the way I want it to for my movie collection, but how do i display the genre on the movie page using cake? Right now scaffolding automatically links director which is fine, but I'd like to see the genre as well. Sorry if this is going a little beyond what you're trying to do here, any help would be appreciated. Thanks again!

  27. fatigue (not verified) on October 16, 2007 - 7:48am

    Hi Joshua. Thank you for your great post. By the way, there is a little a bug in your code.

    On the class definition of "director", "genre" and "review", there should be "var" before the $belongsTo, $hasMany and $hasAndBelongsToMany (like as var $belongsTo etc.).

    Also, I have a question for you: If a model belongs to different models (more than one), how can we add our model code? For example, we shall add a "Production Firm" table for Movies. Than, every movies shall be belongs to a "Production Firm" and a "Director" etc.

  28. Josh on November 18, 2007 - 12:52am

    To add more than one association of a given nature, it's simply a matter of adding another element to the respective association definition array.

    Thanks for the note about the var... I actually think you can leave 'var' out and PHP is okay with it (though I always use var).

  29. Josh on November 18, 2007 - 12:55am

    If you need to do association lookups in both directions, you should define a given associations from each end of the relationship.

    If model A belongsTo model B, then model B can also hasOne model A, etc.

  30. Josh on November 18, 2007 - 1:00am

    That consideration will be largely dependent on how your application is structured. In the example of a movie database there are no examples where deleting one record would mandate the deletion of one of its related records.

    However, if your application has content items with associated comments, deletion of an item would logically include disposing of the comment records.

    There is no magic rule here, just logic. If you think procedurally and logically enough to be programming web applications, you can determine when a record needs to be deleted, I'd say. If I were to look for a "rule of thumb," I'd say it's to delete information when it loses it's potential to be used again.

  31. Greg (not verified) on November 27, 2007 - 2:03pm

    Fantastic article!

    I've been playing with Cake for a couple weeks and my structures just weren't working. I didn't realize that you needed to put reciprocal association in both models. Your great example really showed me where I was making my mistakes.

    Thanks

  32. Anonymous (not verified) on December 13, 2007 - 5:19am

    I tried to run the same thing but having a strange problem.
    I'm asked to remove the s at the end of the movies_id colunm in the reviews table.

    Cake1.2 naturaly puts need s so why in this case the s is not needed.

    this is my model

    CREATE TABLE categories (
    id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
    name TEXT NULL,
    PRIMARY KEY(id)
    );
    CREATE TABLE categories_places (
    id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
    categories_id INTEGER UNSIGNED NOT NULL,
    places_id INTEGER UNSIGNED NOT NULL,
    PRIMARY KEY(id),
    INDEX categories_places_FKIndex1(categories_id),
    INDEX categories_places_FKIndex2(places_id)
    );
    CREATE TABLE categoryinfos (
    id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
    categories_id INTEGER UNSIGNED NOT NULL,
    name TEXT NULL,
    lang VARCHAR(50) NULL,
    PRIMARY KEY(id),
    INDEX categoryinfos_FKIndex1(categories_id)
    );
    CREATE TABLE placeimageinfos (
    id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
    placeimages_id INTEGER UNSIGNED NOT NULL,
    name TEXT NULL,
    lang VARCHAR(50) NULL,
    PRIMARY KEY(id),
    INDEX placeimageinfos_FKIndex1(placeimages_id)
    );
    CREATE TABLE placeimages (
    id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
    places_id INTEGER UNSIGNED NOT NULL,
    name TEXT NULL,
    PRIMARY KEY(id),
    INDEX placeimages_FKIndex1(places_id)
    );
    CREATE TABLE placeinfos (
    id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
    places_id INTEGER UNSIGNED NOT NULL,
    name TEXT NULL,
    lang VARCHAR(50) NULL,
    PRIMARY KEY(id),
    INDEX placeinfos_FKIndex1(places_id)
    );
    CREATE TABLE places (
    id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
    name TEXT NULL,
    PRIMARY KEY(id)
    );

    What I want to do is I have some places that belong to some categories(a place can belong to many categories).And the categories and the places have language information according to language
    I want to retrieves a category with all the places that belong to it with the language information that is passed.
    How can I write that with cake HABTM

  33. Josh on December 16, 2007 - 1:25pm

    1) You don't put the 's' on 'movie' in the field name, since the field will point to only one movie, it is singular. So, movie_id.

    2) Forming an HABTM relationship between places and categories isn't a problem, and you already have the categories_places table defined (though note that you're using plurals in your field names again!). Just define the HABTM in the direction which you plan to retrieve the data: If you want to get all places when you know the category, you define the HABTM in the category model. If you want the categories for a given place, you define HABTM in the places model. If you want to be able to go both directions, define the relationship in both models.

  34. Anonymous (not verified) on December 18, 2007 - 5:47pm

    Hey Joshua,
    I've read through the cakephp manual and the cakephp tutorial on IBM - but never understood the difference between belongsTo and hasOne. I eventually thought, I could use them interchangeably.
    Really appreciate the time you took in clearly explaining the concepts.
    Awesome job.
    Thanks Again

  35. Lance (not verified) on December 19, 2007 - 10:27am

    Thanks for the tutorial, it's getting clearer for me. I am trying to build a small application and have been hacking to do what associations do naturally, so now I am trying to use associations to achieve it cleaner and more easily.
    Thanks!

  36. rav (not verified) on December 24, 2007 - 1:04am

    Hi, I have 2 questions.

    Firstly I have a database schema and I'm rather confused about how cake handles associations.

    Does cakes implementation of the associations mean that the foreign key relationships don't have to be defined in the database?

    Looking at Anon's database schema above, he/she has created a database schema with no foreign keys. Does Cake require the database design to be ridden of Foreign Key? And if not how does it deal with Foreign Key settings such as "On Update" and "On Delete" when Cake sends calls like save() and delete() to the database.

    This is my one major road blocker at the moment. I would generally prefer to implement my db with all the necessary associations. It makes it functional across different applications since it is itself an individual tier.

    Any idea what I should do?

  37. albert (not verified) on December 29, 2007 - 1:12am

    Hello, nice tutorial but i have a problem because if i do this in a controller :
    $this->Movie->findAll();
    i will have a big table with all attributes of all tables but if i want only some attributes i can't filter the attributes of the associations tables. somebody know how i can do ?

  38. Dave (not verified) on December 31, 2007 - 6:34pm

    Thanks for the good explanation. Can you expand on how one would define child models in an add view? I have a case where there will always be ten child models for every parent. When a new parent has been created, I'd like to have the ten child models fields defined on the view.

    Thanks again!

  39. knoodrake (not verified) on January 8, 2008 - 9:55am

    Hi. a Very good tutorial, and many useful comments below !
    First, please excuse my English since I'm French.

    I've a problem with HABTM ( hasAndBelongsToMany ) relations.

    like in the cake's manual examples, i have 3 things: Posts, Tags, and a relation table for them.

    Assuming that a post haveAndBelongsToMany tags, i edited the Post Model and all works perfectly while i just want to retrieve data, but in the other hand, I'm being crazy by trying to save a new Post with tags ( some are new, other already exists ) and the relation between them.

    At this point, I've few problems:

    - How to save multiple (new or not) tags all at once.
    - How to save only those who doesn't already exists ( without querying db for each )
    - How to do considering that the the relation tab must be filled -after- the new tags and posts have been created ? ( since the relation in constituted by a couple of primary key ( tags & posts )).

    Anyhow, thanx a lot for this tutorial !

  40. Anonymous (not verified) on January 31, 2008 - 11:53am

    Can you please post, how the related-table (foreign-keys) look like for this example?

  41. Ricardo (not verified) on March 7, 2008 - 5:32pm

    Just wanna say thanks for the great explanation :). Im doing some catalogue for personal use and i didnt know how to do the models part on CakePhp, now is all clear to me :).

  42. Anonymous (not verified) on March 14, 2008 - 1:42pm

    Hi, I've followed the steps described but the controllor is not generating the associations when querying. Please see details below:

    DB Structure:

    CREATE TABLE categories (
    id INT(10) NOT NULL AUTO_INCREMENT,
    title VARCHAR( 100 ) NOT NULL,
    created DATETIME NOT NULL ,
    modified DATETIME NOT NULL,
    PRIMARY KEY(id)
    ) ENGINE=INNODB;

    CREATE TABLE blogs (
    id INT(10) NOT NULL AUTO_INCREMENT,
    category_id INT(10) NOT NULL,
    title VARCHAR(100) NOT NULL,
    body TEXT NOT NULL,
    allow_comments INT(1) DEFAULT '0',
    created DATETIME NOT NULL ,
    modified DATETIME NOT NULL,
    PRIMARY KEY(id),
    FOREIGN KEY (category_id) REFERENCES categories(id)
    ) ENGINE=INNODB;

    CREATE TABLE comments (
    id INT(10) NOT NULL AUTO_INCREMENT,
    blog_id INT(10) NOT NULL,
    body TEXT NOT NULL ,
    approved INT(1) DEFAULT '0',
    created DATETIME NOT NULL ,
    modified DATETIME NOT NULL,
    PRIMARY KEY(id),
    FOREIGN KEY (blog_id) REFERENCES blogs(id)
    ) ENGINE=INNODB;

    Models:

    class Category extends AppModel
    {
    var $name = 'Category';
    //Relationships
    var $hasMany = array(
    'Blog' => array(
    'className' => 'Blog',
    'foreignKey' => 'category_id',
    'order' => 'Blog.created DESC',
    'dependent'=> true
    )
    );
    //Rules
    var $validate = array(
    'title' => array(
    'rule' => array('minLength', 1)
    )
    );
    }

    class Blog extends AppModel
    {
    var $name = 'Blog';
    //Relationships
    var $belongsTo = 'Category';
    var $hasMany = array(
    'Comment' => array(
    'className' => 'Comment',
    'conditions' => 'Comment.approved = 1',
    'order' => 'Comment.created DESC'
    )
    );
    //Rules
    var $validate = array(
    'title' => array(
    'rule' => array('minLength', 1)
    ),
    'body' => array(
    'rule' => array('minLength', 1)
    )
    );
    }

    class Comment extends AppModel
    {
    var $name = 'Comment';
    //Relationships
    var $belongsTo = 'Blog';
    //Rules
    var $validate = array(
    'body' => array(
    'rule' => array('minLength', 1)
    )
    );
    }

    Controller:

    class HomeController extends AppController {
    //mandatory
    var $name = 'Home';
    //layout used on view
    var $layout = 'blog';
    //Models used in the controller
    var $uses = array('Categories','Blogs');
    //Helpers used in the view;
    var $helpers = array('Form','Html','Javascript','Ajax','Date');

    //Main method
    function index() {
    //get all categories
    $this->set('cats', $this->Categories->findAll());
    }
    }

    ----------

    Even though everything seems fine, $cats variable does not contains associated nodes! (it should containg all the related blog elements per category!):

    Array
    (
    [0] => Array
    (
    [Categories] => Array
    (
    [id] => 2
    [title] => Other Info
    [created] => 2008-03-12 00:27:36
    [modified] => 2008-03-12 00:27:36
    )

    )

    [1] => Array
    (
    [Categories] => Array
    (
    [id] => 3
    [title] => Oracle SOA Suite 10.1.3.3
    [created] => 2008-03-12 00:27:44
    [modified] => 2008-03-12 00:27:44
    )

    )

    [2] => Array
    (
    [Categories] => Array
    (
    [id] => 4
    [title] => SOA Solution Architecture
    [created] => 2008-03-12 00:27:53
    [modified] => 2008-03-12 00:27:53
    )

    )

    )

  43. Alex Medeiros (not verified) on March 24, 2008 - 9:49am

    I have a problem in my cake aplication, i have a associate table which associate 'pages' and 'module', but in this table i have other columm 'position'. When I save, $this->date dont send de attribute position.

    When cake save, it save only 'page_id' and 'module_id'.

    how i insert this data with 'position'?

  44. Josh on March 29, 2008 - 11:27am

    I haven't done any Cake code in quite a while, but what you're trying to do sounds like you may need a custom query to store the HABTM relationship. If you do some searching on the Cake Google Group you should be able to find what you're looking for. You can also post there, as the community is filled with some very intelligent, helpful people.

    Check this thread: http://groups.google.com/group/cake-php/web/frequent-discussions

  45. Micha (not verified) on June 29, 2008 - 5:57pm

    Hello,

    I can't really figure out how to create a self join relationship.

    Cite:
    'Relatedmovies' => array( 'className' => 'Movie' )

    I guess I need an extra table to store the relations between the movies? How does that table have to look like?

    Another example: Users can send messages to other users, the messages table would point on the users table twice to store who sends and who receives. That's a bit more complicated, isn't it? Unfortunately I'm new to creating database schemes...

  46. Micha (not verified) on June 30, 2008 - 6:25am

    Hello,

    Cite:
    'Relatedmovies' => array( 'className' => 'Movie' )

    How does the table have to like where I can store the relationships between the movies? Cause if I need two foreign keys pointing to the "movies" table, why don't I have to tell cakePHP about that?

    Another Example:
    Users can send messages to other users. Guess I need a table "messages" which points to the table "users" twice. How should I tell cakePHP about this double "User HasMany Messages / Message BelongsTo User" relationship? Do I just have to list both in the modell description defining different foreign keys to name both fields in the "messages" table different?

    Unfortunately I'm new to creating database schemes...

  47. Micha (not verified) on June 30, 2008 - 6:28am

    Hello,

    Cite:
    'Relatedmovies' => array( 'className' => 'Movie' )

    How does the table have to like where I can store the relationships between the movies? Cause if I need two foreign keys pointing to the "movies" table, why don't I have to tell cakePHP about that?

    Another Example:
    Users can send messages to other users. Guess I need a table "messages" which points to the table "users" twice. How should I tell cakePHP about this double "User HasMany Messages / Message BelongsTo User" relationship? Do I just have to list both in the modell description defining different foreign keys to name both fields in the "messages" table different?

    Unfortunately I'm new to creating database schemes...

  48. suman (not verified) on September 24, 2008 - 6:40am

    nice tutorial but I have a question on it. how to insert into the join table?

  49. Josh on September 24, 2008 - 8:29am

    suman,

    That is a little outside the scope of this article, and I haven't really touched CakePHP for about a year (Drupal has ruled my life). But, I believe you'd have to either create a model for the join table itself, or get very clever with overriding the queries on the model(s) that are associated with the join table.

  50. William S. Goss (not verified) on December 22, 2008 - 3:54pm

    Maybe I am a bone head, but I was struggling with the HABTM relationship in a project of mine for 5 days.

    Ten minutes after reading your article it became clear that the save saves the both the parent and the join table from the data array.

    I proved it by changing one of the fields in the data array before doing the save, and voila, both records were saved.

    Thanks,

    Bill Goss

  51. Josh on December 22, 2008 - 4:12pm
    Glad you got it figured out!
  52. Anonymous (not verified) on January 21, 2009 - 2:41am

    Finally, a concise, clear, and specific description of HasOne vs BelongsTo, which, as it turns out, I was constantly getting reversed.

    Thanks!

  53. wayne (not verified) on February 7, 2009 - 1:37pm

    How would you create a rank_the_movie form?

    The user would log in.

    Then they would be presented with a form with check box list of he movies in the left column.

    In the right column would be a radio list of 1,2,3,4,5 stars.

    Example:

    User Movie Ranking Form

    User_ID: 12
    Firstname: Joe
    Lastanme: Smith

    Movie Star Ranking
    [] Termininator O 1 Star O 2 Stars O 3 Stars O 4 Stars O 5 Stars
    [] Twins O 1 Star O 2 Stars O 3 Stars O 4 Stars O 5 Stars
    [] Red Sonja O 1 Star O 2 Stars O 3 Stars O 4 Stars O 5 Stars

    It seems to me I would have to create three more tables:

    user_movies
    |_id primary key null auto increment
    |_movie_id tinyint
    |_user_id tinyint

    rankings
    |_id primary key null auto increment
    |_ranking char tinyint

    rankings_user_movies
    |_id primary key null auto increment
    |_user_movies_id tinyint
    |_rankings_id tinyint

    I have successfully created the form so that it looks right and it processess the Users Checked Movies

    I am am having difficulty processing the checked ranking.

    Wayne

  54. LearningCakePHP (not verified) on March 6, 2009 - 11:57pm

    Interesting guide. Beertigger referenced your mistake, and you seem to have corrected your description and comment. Your code needs to be redone to reflect the fix. Take a look at the joinTable line not having the name correct as genres_movies convention implemented.
    / /We specify the join table here because Cake would expect the table to be called genres_movies from this side
    $hasAndBelongsToMany = array( 'Movies' => array( 'className' => 'Movie',
    'joinTable' => 'movies_genres'

    Thanks,
    This helped me get my mind wrapped around these associations.

  55. jrjb (not verified) on September 1, 2009 - 9:32am

    Has anyone used scaffolding with HABTM associations? I think I have it working, except that I don't see a way in the scaffold to view or edit the HABTM associations. I'm guessing that either means I (1) don't have it working, or (2) the scaffold doesn't allow this? Can anyone confirm that this should work with the scaffold? Using cake 1.2.4.8284.

    Thanks, - jrjb.

  56. raksha.dobhal (not verified) on September 24, 2009 - 9:30am

    Thank for the lovely post, it was really helpful.
    Continue good work.

  57. Dan (not verified) on October 16, 2009 - 10:37pm

    I have this set up and it's not working:

    Artist -> hasMany Album
    Album -> belongsTo Artist
    Album -> hasMany Track
    Track -> belongsTo Album

    And this is where it doesn't work

    I have:

    Label -> hasMany Album
    Album -> ??? Label

    I would like to use "belongsTo" again.. but it doesn't work. I tried using "hasOne" but that doesn't work either.

    Any idea?

  58. nick (not verified) on October 30, 2009 - 12:54am

    For some reason, when I have a hasOne in my model, the page doesn't show up at all. Any idea why? (without seeing my code)

  59. Amit Yadav (not verified) on January 25, 2010 - 7:47am

    I have three tables CITY, STATE & COUNTRY.

    CITY Belongs to STATE
    STATE Belongs to COUNTRY

    Now when i run a find on CITY, i would like to get the country also. How can i create an association like this?

    Thanks, Amit

  60. Josh on January 25, 2010 - 9:43am

    It has been quite some time since I've developed with cake -- but back when I did, there was a manner in which you could load a record (in this case, CITY), and indicate how "deep" Cake should retrieve relationships. In your example, it sounds like a depth of 2.

  61. kike314 (not verified) on February 6, 2010 - 2:01pm

    Thanks Man! This help me a lot for understand cakePHP!

  62. Nacereddine (not verified) on March 15, 2010 - 3:28am

    Thanks A LOT man, you made my day

  63. sophea (not verified) on July 27, 2010 - 11:08pm

    First of all, thanks to everyone for your post.
    However, i am still not sure one thing:
    Is it true that a table must has primary key with only one attribute? bc i see u create primary key with named id in every tables. Usually, for the relationship n:n, the table made from this relationship will has primary key that made from all foreign keys. can i do that in cakephp?

    thanks in advance,

  64. hermawan (not verified) on July 30, 2010 - 11:34am

    HiJosh,

    Thank for your tutorial but I have problem with my idea. In this case genres has category where let say, Music has Rock genre and genre category could be "Slow Rock" or "Hard Rock".

    So the structur will look like these :

    4. Genre
    id
    name
    category_id
    5.
    6. Category
    id
    name

    The Question is how we make model realation beetween Movie and Category in case We want to know what is movie category name?

    Thank for help

  65. Post new comment

    The content of this field is kept private and will not be shown publicly.
    • Web page addresses and e-mail addresses turn into links automatically.
    • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <blockquote>
    • Lines and paragraphs break automatically.
    • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <geshi>, <bash>, <c>, <cpp>, <csharp>, <css>, <drupal5>, <drupal6>, <html>, <js>, <mysql>, <php>, <python>, <rails>, <ruby>, <sql>, <text>, <mssql>, <xml>. Beside the tag style "<foo>" it is also possible to use "[foo]". PHP source code can also be enclosed in <?php ... ?> or <% ... %>.

    More information about formatting options