Working with HABTM Form Data in CakePHP

Working with HABTM Form Data in CakePHP

I would like to document several speedy ways I have of working with HABTM data.

The code includes: HABTM Select, HABTM Checkbox, HABTM Text Add, HABTM TextArea Edit.

The Models

Before we get started, lets setup the models.

models/post.php
class Post extends AppModel {
        var $name = 'Post';
       
        var $hasAndBelongsToMany = array(
                'Tag' => array(
                        'className' => 'Tag',
                        'joinTable' => 'posts_to_tags',
                        'foreignKey' => 'post_id',
                        'associationForeignKey' => 'tag_id',
                        'with' => 'PostToTag',
                        'unique' => false, // important to set this so cake does not auto-delete records on update
                ),
        );     
       
}
models/tag.php
class Tag extends AppModel {
        var $name = 'Tag';
}

HABTM Select

This is the default CakePHP way of handling HABTM forms.

It will allow you to add or remove HABTM data using a multiple select box (by holding CTRL).

views/posts/form.ctp
<?php echo $form->create('Post');?>
        <fieldset>
                <legend><?php __('Post Details');?></legend>
        <?php
                echo $form->input('id');
                echo $form->input('name');
                echo $form->input('Tag.Tag');
        ?>
        </fieldset>
<?php echo $form->end('Submit');?>
controllers/posts_controller.php
class PostsController extends AppController {
        var $name = 'Posts';

        function form($id = null) {
                if (!empty($this->data)) {
                        // form sends data as:
                        // $this->data['Post']['Tag']['Tag'][1] = 1;
                        // $this->data['Post']['Tag']['Tag'][3] = 3;
                       
                        // save the data (auto-handles habtm save)
                        $this->Post->create();
                        if ($this->Post->save($this->data)) {
                                $this->_flash(__('The Post has been saved.', true),'success');
                                $this->redirect(array('action'=>'view',$this->Post->id));
                        }
                        else {
                                $this->_flash(__('The Post could not be saved. Please, try again.', true),'warning');
                        }
                }
                if (empty($this->data)) {
                        $this->data = $this->Post->read(null, $id);
                }
        }
}

HABTM Checkbox

If you have too many options to list in a select box, or if the labels in the select options do not suit your needs then you may want to try HABTM Checkbox.

It will allow you to add or remove HABTM data using a checkbox for each HABTM item.

views/posts/form.ctp
<?php echo $form->create('Post');?>
        <fieldset>
                <legend><?php __('Post Details');?></legend>
        <?php
                echo $form->input('id');
                echo $form->input('name');
        ?>
        </fieldset>
        <fieldset>
                <legend><?php __('Tags');?></legend>
        <?php
                $checked = $form->value('Tag.Tag');
                foreach ($tags as $id=>$label) {
                        echo $form->input("Tag.checkbox.$id", array(
                                'label'=>$label,
                                'type'=>'checkbox',
                                'checked'=>(isset($checked[$id])?'checked':false),
                        ));
                }
        ?>
        </fieldset>
<?php echo $form->end('Submit');?>
controllers/posts_controller.php
class PostsController extends AppController {
        var $name = 'Posts';

        function form($id = null) {
                if (!empty($this->data)) {

                        // get the tags from the checkbox data
                        $this->data['Tag']['Tag'] = array();
                        foreach($this->data['Tag']['checkbox'] as $k=>$v) {
                                if ($v) $this->data['Tag']['Tag'][] = $k;
                        }
                       
                        // save the data
                        $this->Post->create();
                        if ($this->Post->save($this->data)) {
                                $this->_flash(__('The Post has been saved.', true),'success');
                                $this->redirect(array('action'=>'view',$this->Post->id));
                        }
                        else {
                                $this->_flash(__('The Post could not be saved. Please, try again.', true),'warning');
                        }
                }
                if (empty($this->data)) {
                        $this->data = $this->Post->read(null, $id);
                }
        }
}

HABTM Text Add

If you want to allow users to enter tags that are added to the current tags.

It will allow you to add but not remove HABTM data using a text input with comma seperated values.

views/posts/form.ctp
<?php echo $form->create('Post');?>
        <fieldset>
                <legend><?php __('Post Details');?></legend>
        <?php
                echo $form->input('id');
                echo $form->input('name');
        ?>
        </fieldset>
        <fieldset>
                <legend><?php __('Tags');?></legend>
        <?php
                // display current tags
                $links = array();
                foreach($post['Tag'] as $k=>$row)
                        $links[] = $html->link(__($row['name'],true),array('controller'=>'postss','action'=>'index','tag_id'=>$row['id']));
                echo '<div style="margin:10px 5px;font-weight:bold;font-size:12px;">'.implode(', ',$links).'</div>';
       
                // display tag add form
            echo $form->create('Post', array('url'=>array('action' => 'view', $post['Post']['id'])));
            echo $form->input('Tag.tags',array('label'=>__('Add Tags',true),'after'=>__('Seperate each tag with a comma.  Eg: agility, training, ball control',true)));
            echo $form->end('Save');
        ?>
        </fieldset>
<?php echo $form->end('Submit');?>
controllers/posts_controller.php
class PostsController extends AppController {
        var $name = 'Posts';

        function form($id = null) {
                if (!empty($this->data)) {

                        // get the tags from the text data
                        if ($this->data['Tag.tags']) {
                                $this->data['Post']['id'] = $id;
                                $tags = explode(',',$this->data['Tag']['tags']);
                                foreach($tags as $_tag) {
                                        $_tag = strtolower(trim($_tag));
                                        // check if the tag exists
                                        $this->Post->Tag->recursive = -1;
                                        $tag = $this->Post->Tag->findByName($_tag);
                                        if (!$tag) {
                                                // create new tag
                                                $this->Post->Tag->create();
                                                $tag = $this->Post->Tag->save(array('name'=>$_tag));
                                                $tag['Tag']['id'] = $this->Post->Tag->id;
                                                if (!$tag) {
                                                        $this->_flash(__(sprintf('The Tag %s could not be saved.',$_tag), true),'success');
                                                }
                                        }
                                        if ($tag) {
                                                // use current tag
                                                $this->data['Tag']['Tag'][$tag['Tag']['id']] = $tag['Tag']['id'];
                                        }
                                }
                        }

                        // save the data
                        $this->Post->create();
                        if ($this->Post->save($this->data)) {
                                $this->_flash(__('The Post has been saved.', true),'success');
                                $this->redirect(array('action'=>'view',$this->Post->id));
                        }
                        else {
                                $this->_flash(__('The Post could not be saved. Please, try again.', true),'warning');
                        }
                }
                if (empty($this->data)) {
                        $this->data = $this->Post->read(null, $id);
                }
               
                // get the posts current tags
                $post = $id ? $this->Post->find(array('Post.id'=>$id)) : false;
               
                $this->set(compact('post'));
        }
}

HABTM TextArea Edit

Finally, your admin may ask you if they can just edit all of the data as a comma seperated list.

It will allow you to add and remove HABTM data using a textarea input with comma seperated values.

views/posts/form.ctp
<?php echo $form->create('Post');?>
        <fieldset>
                <legend><?php __('Post Details');?></legend>
        <?php
                echo $form->input('id');
                echo $form->input('name');
        ?>
        </fieldset>
        <fieldset>
                <legend><?php __('Tags');?></legend>
        <?php
                // display current tags
                $links = array();
                foreach($post['Tag'] as $k=>$row)
                        $links[] = $html->link(__($row['name'],true),array('controller'=>'postss','action'=>'index','tag_id'=>$row['id']));
                echo '<div style="margin:10px 5px;font-weight:bold;font-size:12px;">'.implode(', ',$links).'</div>';
       
                // display tag add form
            echo $form->create('Post', array('url'=>array('action' => 'view', $post['Post']['id'])));
            echo $form->input('Tag.tags',array('label'=>__('Add Tags',true),'after'=>__('Seperate each tag with a comma.  Eg: agility, training, ball control',true)));
            echo $form->end('Save');
        ?>
        </fieldset>
<?php echo $form->end('Submit');?>
controllers/posts_controller.php
class PostsController extends AppController {
        var $name = 'Posts';

        function form($id = null) {
                if (!empty($this->data)) {

                        // get the tags from the textarea data
                        $tags = explode(',',$this->data['Post']['tags']);
                        foreach($tags as $_tag) {
                                $_tag = strtolower(trim($_tag));
                                $this->Post->Tag->recursive = -1;
                                $tag = $this->Post->Tag->findByName($_tag);
                                if (!$tag) {
                                        $this->Post->Tag->create();
                                        $tag = $this->Post->Tag->save(array('name'=>$_tag));
                                        $tag['Tag']['id'] = $this->Post->Tag->id;
                                        if (!$tag) {
                                                $this->_flash(__(sprintf('The Tag %s could not be saved.',$_tag), true),'success');
                                        }
                                }
                                if ($tag) {
                                        $this->data['Tag']['Tag'][$tag['Tag']['id']] = $tag['Tag']['id'];
                                }
                        }
                        // delete old tags
                        $this->Post->PostToTag->deleteAll(array('post_id'=>$this->data['Post']['id']));

                        // save the data
                        $this->Post->create();
                        if ($this->Post->save($this->data)) {
                                $this->_flash(__('The Post has been saved.', true),'success');
                                $this->redirect(array('action'=>'view',$this->Post->id));
                        }
                        else {
                                $this->_flash(__('The Post could not be saved. Please, try again.', true),'warning');
                        }
                }
                if (empty($this->data)) {
                        $this->data = $this->Post->read(null, $id);
                        // load the habtm data for the textarea
                        $tags = array();
                        if (isset($this->data['Tag']) && !empty($this->data['Tag'])) {
                                foreach($this->data['Tag'] as $tag) {
                                        $tags[] = $tag['name'];
                                }
                                $this->data['Post']['tags'] = implode(', ',$tags);
                        }
                }
               
                // get the posts current tags
                $post = $id ? $this->Post->find(array('Post.id'=>$id)) : false;
               
                $this->set(compact('post'));
        }
}

In Closing

There are many great ways to work with HABTM data, these are just a few examples of what can be done. Enjoy your CakePHP.

Post new comment

  • Allowed HTML tags: <b> <br> <p> <a> <strong> <cite> <em> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
  • You may use [img:xx] tags to display uploaded files or images inline.
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <css>, <diff>, <drupal5>, <html>, <javascript>, <php>. 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