





















































In the previous article in this series on Working with Simple Associations using CakePHP, we assumed that a book can have only one author. But in real life scenario, a book may also have more than one author. In that case, the relation between authors and books is many-to-many. We are now going to see how to define associations for a many-to-many relation. We will modify our existing code-base that we were working on in the previous article to set up the associations needed to represent a many-to-many relation.
TRUNCATE TABLE `authors`;
TRUNCATE TABLE `books`;
ALTER TABLE `books` DROP `author_id`
CREATE TABLE `authors_books` (
`author_id` INT NOT NULL ,
`book_id` INT NOT NULL
<?php
class Author extends AppModel
{
var $name = 'Author';
var $hasAndBelongsToMany = 'Book';
}
?>
<?php
class Book extends AppModel
{
var $name = 'Book';
var $hasAndBelongsToMany = 'Author';
}
?>
<?php
class AuthorsController extends AppController {
var $name = 'Authors';
var $scaffold;
}
?>
<?php
class BooksController extends AppController {
var $name = 'Books';
var $scaffold;
}
?>
We first emptied the database and then dropped the field author_id from the books table. Then we added a new join table authors_books that will be used to establish a many-to-many relation between authors and books. The following diagram shows how a join table relates two tables in many-to-many relation:
In a many-to-many relation, one record of any of the tables can be related to multiple records of the other table. To establish this link, a join table is used—a join table contains two fields to hold the primary-keys of both of the records in relation.
CakePHP has certain conventions for naming a join table—join tables should be named after the tables in relation, in alphabetical order, with underscores in between. The join table between authors and books tables should be named authors_books, not books_authors. Also by Cake convention, the default value for the foreign keys used in the join table must be underscored, singular name of the models in relation, suffixed with _id.
After creating the join table, we defined associations in the models, so that our models also know about the new relationship that they have. We added hasAndBelongsToMany (HABTM) associations in both of the models. HABTM is a special type of association used to define a many-to-many relation in models. Both the models have HABTM associations to define the many-to-many relationship from both ends. After defining the associations in the models, we created two controllers for these two models and put in scaffolding in them to see the association working.
We could also use an array to set up the HABTM association in the models. Following code segment shows how to use an array for setting up an HABTM association between authors and books in the Author model:
var $hasAndBelongsToMany = array(
'Book' =>
array(
'className' => 'Book',
'joinTable' => 'authors_books',
'foreignKey' => 'author_id',
'associationForeignKey' => 'book_id'
)
);
Like, simple relationships, we can also override default association characteristics by adding/modifying key/value pairs in the associative array. The foreignKey key/value pair holds the name of the foreign-key found in the current model—default is underscored, singular name of the current model suffixed with _id. Whereas, associationForeignKey key/value pair holds the foreign-key name found in the corresponding table of the other model—default is underscored, singular name of the associated model suffixed with _id. We can also have conditions, fields, and order key/value pairs to customize the relationship in more detail.
Like one-to-one and one-to-many relations, once the associations are defined, CakePHP will automatically fetch the related data in many-to-many relation.
<?php
class AuthorsController extends AppController {
var $name = 'Authors';
function index() {
$this->Author->recursive = 1;
$authors = $this->Author->find('all');
$this->set('authors', $authors);
}
}
?>
<?php foreach($authors as $author): ?>
<h2><?php echo $author['Author']['name'] ?></h2>
<hr />
<h3>Book(s):</h3>
<ul>
<?php foreach($author['Book'] as $book): ?>
<li><?php echo $book['title'] ?></li>
<?php endforeach; ?>
</ul>
<?php endforeach; ?>
<?php
class BooksController extends AppController {
var $name = 'Books';
function index() {
$this->Book->recursive = 1;
$books = $this->Book->find('all');
$this->set('books', $books);
}
}
?>
<?php foreach($books as $book): ?>
<h2><?php echo $book['Book']['title'] ?></h2>
<hr />
<h3>Author(s):</h3>
<ul>
<?php foreach($book['Author'] as $author): ?>
<li><?php echo $author['name'] ?></li>
<?php endforeach; ?>
</ul>
<?php endforeach; ?>
In both of the models, we first set the value of $recursive attributes to 1 and then we called the respective models find('all') functions. So, these subsequent find('all') operations return all associated model data that are related directly to the respective models. These returned results of the find('all') requests are then passed to the corresponding view files. In the view files, we looped through the returned results and printed out the models and their related data.
In the BooksController, this returned data from find('all') is stored in a variable $books. This find('all') returns an array of books and every element of that array contains information about one book and its related authors.
Array
(
[0] => Array
(
[Book] => Array
(
[id] => 1
[title] => Book Title
...
)
[Author] => Array
(
[0] => Array
(
[id] => 1
[name] => Author Name
...
)
[1] => Array
(
[id] => 3
... 54 54
...
...
)
Same for the Author model, the returned data is an array of authors. Every element of that array contains two arrays: one contains the author information and the other contains an array of books related to this author. These arrays are very much like what we got from a find('all') call in case of the hasMany association.