Working with Complex Associations using CakePHP

Exclusive offer: get 50% off this eBook here
CakePHP Application Development

CakePHP Application Development — Save 50%

Step-by-step introduction to rapid web development using the open-source MVC CakePHP framework

$23.99    $12.00
by Ahsanul Bari Anupom Syam | December 2008 | MySQL Open Source PHP

A many-to-many relation requires an additional table to relate the two tables in relationship. In this article, by Ahsanul Bari and Anupom Syam, we will learn how to define associations in models for many-to-many relations. Then, we will look at how to retrieve, delete, and save related data from and into database tables using model association in this special type of relation.

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

CakePHP Application Development Step-by-step introduction to rapid web development using the open-source MVC CakePHP framework
Published: July 2008
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:

Saving Related Model Data in Many-To-Many Relation

Like the simple relations, saving related data in many-to-many relation is also fairly easy. We will now modify our code to add functionality to save a book and relate multiple authors with this book while saving.

Time for Action: Saving Related Model Data

  1. Add a controller action add() in BooksController (/app/controllers/books_controller.php),
      <?php
      class BooksController extends AppController {
      var $name = 'Books';
      var $helpers = array( 'Form' );
      function index() {
      $this->Book->recursive = 1;
      $books = $this->Book->find('all');
      $this->set('books', $books);
      }
      function add() {
      if (!empty($this->data)) {
      $this->Book->create();
      $this->Book->save($this->data);
      $this->redirect(array('action'=>'index'), null, true);
      }
      $authors = $this->Book->Author->generateList();
      $this->set('authors', $authors);
      }
      }
      ?>
  2. Create a view with a form, for the /books/add/ action (/app/views/books/add.ctp).
      <?php echo $form->create('Book');?>
      <fieldset>
      <legend>Add New Book</legend>
      <?php
      echo $form->input('isbn');
      echo $form->input('title');
      echo $form->input('description');
      echo $form->input('Author');
      ?>
      </fieldset>
  3. Point your browser to the following URL and add a book:
    http://localhost/relationship/books/add

What Just Happened?

Author model's generateList() function is used to get a list of all the authors. This list is passed to the view through $authors variable. A form is created in the view with input fields to take book information. In this form, we also created a select list to take all the related authors' names—multiple authors can be selected from this list.

Specifically, the line $form->input('Author'); creates the select list and automatically. In the input method of the form helper, the related model name: Author is supplied as parameter. It tells the method that it has to create a multiple select list from an array $authors.

When the form is submitted, the Book model's save() function automatically gets all the related authors' information along with the book information. The save() function first saves the book information and then creates entries in the join table to establish the relations between the book and the selected authors.

Deleting Associated Data

In case of one-to-one and one-to-many relation, to turn on the cascade delete option, we have to set the dependent key to true in the association definition array of the model. To enable the cascade delete option in Author model for its association with the Book model, we would add a dependent key in the association definition array and set its value to true like the following:

var $hasMany = array(  
      'Book' => array(  
          'className'     => 'Book',  
          'dependent'=> true  
      )  
  );

When the cascade delete is turned on, if we call the delete() method of the Author model to delete an author, then all the associated Book records related to that particular author are also deleted. To avoid cascade delete even when dependent is set to true, we can make use of the second parameter $cascade of the delete() method—which by default has a true value. To turn off the cascade delete on the fly, we just have to set this second parameter to false.

In case of many-to-many relation, the HABTM association logically does not have any such key/value pair to turn on/off the cascade delete. It is quite rational as in case of HABTM association, we will not like to delete a book if any of its related authors is deleted. Rather, we will want to delete the author and the records in the join table that relates some books with that author. We don't even have to think about that, Cake will automatically delete all such join table entries in delete() operations.

Changing Association On The Fly

Let's assume that we have two associations in the Author model at the same time:

  • hasMany association with Book model.
  • hasOne association with Profile model.

Now, to fetch out all authors and their profile information, we would write the following code:

$this->Author->recursive = 1;
$authors = $this->Author->find('all');

But this find('all') request will also return all associated book information as both Profile and Book models are on the first level of recursion. We can though ignore all Book model data and only use the User and Profile model data. But it can be costly to performance. We need to somehow destroy the association between the Author and Book model before the find('all') call, so that only the needed data are returned.

The unbindAll() method becomes handy in such situations. The following code snippet shows how we can temporarily remove the association between the Author and the Book model.

$this->Author->recursive = 1;
$this->Author->unbindModel(  
      array(
'hasMany' => array(  
              'Book' => array(
  
                  'className' => 'Book'
  
              )
  
          )
  
      )
  
  );
$authors = $this->Author->find('all');

Sometimes, we may want to temporarily add an association. If we want to temporarily add a hasMany association between the Author and the Tutorial model, we can do it using the model method bindModel() like the following:

$this->Author->bindModel(  
      array(
'hasMany' => array(  
              'Tutorial' => array(
  
                  'className' => 'Tutorial'  
              )  
          )  
      )  
 );

Creating or destroying associations using bindModel()and unbindModel() only works for the subsequent model operation unless the second parameter of these functions has been set to true. If the second parameter has been set to true, the bind remains in place for the remainder of the request.

CakePHP Application Development Step-by-step introduction to rapid web development using the open-source MVC CakePHP framework
Published: July 2008
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:

About the Author :


Ahsanul Bari

Ahsanul Bari is a web application developer from Dhaka, Bangladesh. After graduating from North South University with a bachelor's degree in Computer Science, he has been involved in developing various web applications for local businesses. At a very early stage of his career he felt the need for tools and techniques to build structured and maintainable web applications. That is when he found out about CakePHP. It was love at first sight and he decided to use CakePHP for his future projects. He never had to look back, and from then on he has been heavily using CakePHP for all kinds of projects. Most notably, using CakePHP, he developed an ERP solution for companies involved in urban and land development.

Apart from that, he has also 'irregularly' contributed to the CakePHP Documentation Team. He is also an 'irregular' blogger (http://ahsanity.com and http://ahsanity.wordpress.com). Just when people start to think that he has given up blogging, he is known to write a post from nowhere! Among his friends and colleagues, he is known as a fanboy for CakePHP.

Currently he is working at Trippert Labs, where he has been involved in making a travel-based blogging system, http://www.trippert.com.

Anupom Syam

Anupom Syam is a web application developer from Dhaka, Bangladesh. He started programming back in 1998 in C when he was a high school kid. In his early university years, he met Java and fell in love immediately. Through the years he has become proficient in various aspects of Java (ME, SE, and EE). Early in his career he was engaged mainly in building localized mobile applications. Over time his interest in web technologies grew and he did not hesitate to jump onto the Web 2.0 bandwagon. Over the last five years he has been working with different startups and building web/mobile applications. He currently works as a Development Engineer at Trippert, Inc. where he has been involved in developing a travel-based blogging system http://www.trippert.com (which is developed using CakePHP) as the lead back-end programmer.

He loves to build rich-client web apps with JavaScript/AJAX in the front end and CakePHP/RoR/MySQL in the back end. He still uses Java heavily for his personal fun-time projects. He also maintains blogs: http://anupom.wordpress.com and http://syamantics.com. Besides programming he is interested in many things, ranging from the most recent scientific discoveries to ancient Vedic philosophies.

Books From Packt

Building Powerful and Robust Websites with Drupal 6
Building Powerful and Robust Websites with Drupal 6

PHP 5 CMS Framework Development
PHP 5 CMS Framework Development

Drupal for Education and E-Learning
Drupal for Education and E-Learning

Building Websites with Joomla! 1.5
Building Websites with Joomla! 1.5

Mastering phpMyAdmin 2.11 for Effective MySQL Management
Mastering phpMyAdmin 2.11 for Effective MySQL Management

Expert Python Programming
Expert Python Programming

Drupal Multimedia
Drupal Multimedia

Creating your MySQL Database: Practical Design Tips and Techniques
Creating your MySQL Database: Practical Design Tips and Techniques

Your rating: None Average: 4.8 (6 votes)
nice article by
This article is wonderful. Exactly what i needed

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
F
y
b
E
B
S
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software