6 min read

Defining Many-To-Many Relationship in Models

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.

Time for Action: Defining Many-To-Many Relation

  1. Empty the database tables:
      TRUNCATE TABLE `authors`;
      TRUNCATE TABLE `books`;
  2. Remove the author_id field from the books table:
      ALTER TABLE `books` DROP `author_id`
  3. Create a new table, authors_books:;
      CREATE TABLE `authors_books` (
      `author_id` INT NOT NULL ,
      `book_id` INT NOT NULL
  4. Modify the Author (/app/models/author.php) model:
      <?php
      class Author extends AppModel
      {
      var $name = 'Author';
      var $hasAndBelongsToMany = 'Book';
      }
      ?>
  5. Modify the Book (/app/models/book.php) model:
      <?php
      class Book extends AppModel
      {
      var $name = 'Book';
      var $hasAndBelongsToMany = 'Author';
      }
      ?>
  6. Modify the AuthorsController (/app/controllers/authors_controller.php):
      <?php
      class AuthorsController extends AppController {
      var $name = 'Authors';
      var $scaffold;
      }
      ?>
  7. Modify the BooksController (/app/controllers/books_controller.php):
      <?php
      class BooksController extends AppController {
      var $name = 'Books';
      var $scaffold;
      }
      ?>
  8. Now, visit the following URLs and add some test data into the system:http://localhost/relationship/authors/
    and http://localhost/relationship/books/

What Just Happened?

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:

CakePHP Application Development

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.

Retrieving Related Model Data in Many-To-Many Relation

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.

Time for Action: Retrieving Related Model Data

  1. Take out scaffolding from both of the controllers—AuthorsController (/app/controllers/authors_controller.php) and BooksController (/app/controllers/books_controller.php).
  2. Add an index() action inside the AuthorsController (/app/controllers/authors_controller.php), like the following:
      <?php
      class AuthorsController extends AppController {
      var $name = 'Authors';
      function index() {
      $this->Author->recursive = 1;
      $authors = $this->Author->find('all');
      $this->set('authors', $authors);
      }
      }
      ?>
  3. Create a view file for the /authors/index action (/app/views/authors/index.ctp):
      <?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; ?>
  4. Write down the following code inside the BooksController (/app/controllers/books_controller.php):
      <?php
      class BooksController extends AppController {
      var $name = 'Books';
      function index() {
      $this->Book->recursive = 1;
      $books = $this->Book->find('all');
      $this->set('books', $books);
      }
      }
      ?>
  5. Create a view file for the action /books/index (/app/views/books/index.ctp):
      <?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; ?>
  6. Now, visit the following URLs:http://localhost/relationship/authors/
    http://localhost/relationship/books/

What Just Happened?

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.

CakePHP Application Development

LEAVE A REPLY

Please enter your comment!
Please enter your name here