Simple Tagging Behavior

by dooltaz
So far after looking at other tagging code in this site, I have not seen tags done properly when using normal database form. So this will be a basic, simple tagging system that allows you to use both a tags field in your table and a separate tagging table.
First create three tables.

The first table will be your tags table. This table will consist of the following fields id, tag.

The second table will be the table you would like to associate with tags. For this example let's use the table posts, with the required fields id, name, tags.

Third, we will need to build a connector table to link tags with posts. We will call this table posts_tags.

Please also build the proper hasAndBelongsToMany relationship for your model. The SQL and the Model Definition can be found in the Cake Manual for Models under Section 4. Look for the heading "HABTM Join Tables: Sample models and their join table names"

http://manual.cakephp.org/chapter/models

Models


/app/models/post.php

Model Class:

<?php 
<?php
class Post extends AppModel
{
    var 
$name 'Post';
    var 
$hasAndBelongsToMany = array('Tag' =>
                               array(
'className'    => 'Tag',
                                     
'joinTable'    => 'posts_tags',
                                     
'foreignKey'   => 'post_id',
                                     
'associationForeignKey'=> 'tag_id',
                                     
'conditions'   => '',
                                     
'order'        => '',
                                     
'limit'        => '',
                                     
'unique'       => true,
                                     
'finderQuery'  => '',
                                     
'deleteQuery'  => '',
                               )
                               );
}
?>
?>

/app/models/post_tag.php

Model Class:

<?php 
<?php
class PostTag extends AppModel
{
    var 
$name 'PostTag';
}
?>
?>

/app/models/tag.php

Model Class:

<?php 
<?php
class Tag extends AppModel
{
    var 
$name 'Tag';
}
?>
?>

Tag Behavior


/app/models/behaviors/tag.php

<?php /**
 * Tag Behavior class file.
 *
 * Model Behavior to support tags.
 *
 * @filesource
 * @package    app
 * @subpackage    models.behaviors
 */
 
/**
 * Add tag behavior to a model.
 * 
 */
class TagBehavior extends ModelBehavior {
    
/**
     * Initiate behaviour for the model using specified settings.
     *
     * @param object $model    Model using the behaviour
     * @param array $settings    Settings to override for model.
     *
     * @access public
     */
    
function setup(&$model$settings = array()) {

    
        
$default = array( 'table_label' => 'tags''tag_label' => 'tag''separator' => ',');
        
        if (!isset(
$this->settings[$model->name])) {
            
$this->settings[$model->name] = $default;
        }
        
    
$this->settings[$model->name] = array_merge($this->settings[$model->name], ife(is_array($settings), $settings, array()));

    }
    
    
/**
     * Run before a model is saved, used to set up tag for model.
     *
     * @param object $model    Model about to be saved.
     *
     * @access public
     * @since 1.0
     */
    
function beforeSave(&$model) {
    
// Define the new tag model
    
$Tag =& new Tag;
        if (
$model->hasField($this->settings[$model->name]['table_label']) 
        && 
$Tag->hasField($this->settings[$model->name]['tag_label'])) {


        
// Parse out all of the 
        
$tag_list $this->_parseTag($model->data[$model->name][$this->settings[$model->name]['table_label']], $this->settings[$model->name]);
        
$tag_info = array(); // New tag array to store tag id and names from db
        
foreach($tag_list as $t) {
            if (
$res $Tag->find($this->settings[$model->name]['tag_label'] . " LIKE '" $t "'")) {
                
$tag_info[] = $res['Tag']['id'];
            } else {
                
$Tag->save(array('id'=>'',$this->settings[$model->name]['tag_label']=>$t));
                
$tag_info[] = sprintf($Tag->getLastInsertID());
            }
            unset(
$res);
        }

        
// This prepares the linking table data...
        
$model->data['Tag']['Tag'] = $tag_info;
        
// This formats the tags field before save...
        
$model->data[$model->name][$this->settings[$model->name]['table_label']] = implode(', '$tag_list);
    }
    return 
true;
    }


    
/**
     * Parse the tag string and return a properly formatted array
     *
     * @param string $string    String.
     * @param array $settings    Settings to use (looks for 'separator' and 'length')
     *
     * @return string    Tag for given string.
     *
     * @access private
     */
    
function _parseTag($string$settings) {
        
$string strtolower($string);
       
        
$string preg_replace('/[^a-z0-9' $settings['separator'] . ' ]/i'''$string);
        
$string preg_replace('/' $settings['separator'] . '[' $settings['separator'] . ']*/'$settings['separator'], $string);

    
$string_array preg_split('/' $settings['separator'] . '/'$string);
    
$return_array = array();

    foreach(
$string_array as $t) {
        
$t ucwords(trim($t));
        if (
strlen($t)>0) {
            
$return_array[] = $t;
        }
    }
    
        return 
$return_array;
    }
}

?>


Usage



/app/models/post.php (REVISION)

Model Class:

<?php 
<?php
class Post extends AppModel
{
    var 
$name 'Post';

    var 
$actAs = array('Tag'=>array('table_label'=>'tags''tags_label'=>'tag''separator'=>',');

    var 
$hasAndBelongsToMany = array('Tag' =>
...
?>
?>

Telling the Post model to "act as" a tag behavior will automatically take a comma delimited tags field from the Posts table and when it is saved, it will parse out the tags, save them to the tags table, and save the associated links.

This can work in one table or multiple tables that want to use the same set of tags.

Views

Here is the implementation:
/app/views/posts/add.ctp

View Template:


<?php echo $form->create('Posts');?>
<?php 
echo $form->input('title');?>
<?php 
echo $form->input('tags');?>
<?php 
echo $form->input('body');?>
</form>

/app/views/posts/edit.ctp

View Template:


<?php echo $form->create('Posts');?>
<?php 
echo $form->input('id');?>
<?php 
echo $form->input('title');?>
<?php 
echo $form->input('tags');?>
<?php 
echo $form->input('body');?>
</form>

Controller


Controller Class:

<?php 
<?php
class PostsController extends AppController {
    var 
$name 'Posts';
    var 
$helpers = array('Html''Form' );

    function 
index() {
        
$this->Post->recursive 0;
        
$this->set('posts'$this->paginate());
    }

    function 
add() {
        if(!empty(
$this->data)) {
            
$this->cleanUpFields();
            
$this->Post->create();
            if(
$this->Post->save($this->data)) {
                
$this->Session->setFlash('The Post has been saved');
                
$this->redirect(array('action'=>'index'), nulltrue);
            } else {
                
$this->Session->setFlash('The Post could not be saved. Please, try again.');
            }
        }
    }
    function 
edit($id null) {
        if(!
$id && empty($this->data)) {
            
$this->Session->setFlash('Invalid Post');
            
$this->redirect(array('action'=>'index'), nulltrue);
        }
        if(!empty(
$this->data)) {
            
$this->cleanUpFields();
            if(
$this->Post->save($this->data)) {
                
$this->Session->setFlash('The Post saved');
                
$this->redirect(array('action'=>'index'), nulltrue);
            } else {
                
$this->Session->setFlash('The Post could not be saved. Please, try again.');
            }
        }
        if(empty(
$this->data)) {
            
$this->data $this->Post->read(null$id);
        }
    }

}
?>


Hope this helps someone.

Report

More on Behaviors

Advertising

Comments

  • iamjonesy posted on 08/22/10 04:21:55 AM
    Yeah I've followed the tutorial but my tags input is showing up as a dropdown list instead of a text field. has this happened to anyone else?
  • Vladislav posted on 02/06/09 09:32:44 AM
    May be it's good idea to add after
    $tag_info = array(); // New tag array to store tag id and names from db
    string:
    $tag_list = array_unique ($tag_list);
    ?

    Some users is not so brainy and made mistake like:
    tags: news, car, engine, car

    What do you think? ;-)
  • infantigniter posted on 07/02/08 01:10:04 AM
    I'm having trouble getting it to work, though. It doesn't save any tags when I call $this->Model->save(). ($this->Model->Tag->save() doesn't work correctly either.) Any possible reason off the top of your head? I can paste my code if needed.
  • arn_e posted on 06/25/08 09:57:29 AM
    if you reallly wanted to make it even more efficient...

    +++Articles+++
    id
    type_id
    name

    +++ContentTags+++
    id
    type_id
    tag_id
    deleted

    +++Tags+++
    id
    name
    no_occurances

    +++Types+++
    id
    name [ARTICLE/PHOTO/etc]
  • arn_e posted on 06/25/08 09:51:38 AM
    I like the idea but it seems that this is against all normalization rules

    Totally agree. Exploding strings is a bit messy. IMHO i would structure a tagging to be done like so..

    eg. Tagging an Article / Photo

    +++Articles+++
    id
    name
    [HAS-MANY: ContentTags]
    +++ContentTags+++
    id
    content_type [ENUM (ARTICLE/PHOTO/OTHER-MEDIA-YOU-WISH-TO-TAG)] content_id [JOINS TO Article.id / Photo.id / etc] tag_id
    deleted

    +++Tags+++
    id
    name
    no_occurances [Each time a tag is add/deleted regardless of content type it would be updated]
    i think this would provide a more efficient means of searching/generating tag clouds...



  • bgmill posted on 05/09/08 08:59:53 AM
    It's good practice to give credit when you have based your code on somebody elses work:

    http://bakery.cakephp.org/articles/view/simple-tagging-component
  • gregor posted on 04/20/08 10:01:33 AM
    i have set up tagging for an Image model and it is working like a charm. i just have one question: how do i query the Image model for a given tag name? i am looking for x in this call:

    $this->Image->findall(x);
  • Vladislav posted on 03/10/08 03:27:48 PM
    1) model/posts_tag.php
    2) var $actsAs = ...
    3) Don't forget to put HABTM into tag.php - if you want to see all post relate to this tag ;-)

    Today, I'm finishing to write tagcloud "helper". For this behaviors.

    Together it's look too nice ;-)

    Thanks, DW!
  • abbajbryant posted on 11/17/07 11:34:40 PM
    I understand the need to generate a 'tags' field in the view for which to enter tag data into the database but wouldn't it make more sense for the behavior to simply the the models data on beforeSave for the 'tags' field in the array and parse and save them that way?

    It could also generate a fake 'tags' field in the data on find operations but it doesn't need to be a real field.
  • buckyball posted on 10/15/07 07:36:10 AM
    First of all, really a nice behavior, works great 4 me and
    gave me a slight insight of how to use behaviors.

    What I'm missing now is how to make one of the famous
    tag clouds out of all that data ? Is there an example how to
    accomplish that in your special case with 3 DB tables ?


    -S.
  • posted on 07/18/07 12:00:34 PM
    I have the same problem as you chris. I've setup everything correctly, but after a little debugging I see that the code is not getting passed this step in the beforeSave>

    if ($model->hasField($this->settings[$model->name]['table_label']) && $Tag->hasField($this->settings[$model->name]['tag_label'])) {

    it seems to fail this check and I'm not sure why. I've set everything else up correctly, and I know it's calling beforeSave because I can print text before that if statement is called but not after.

    EDIT: I fixed it. Make sure you have the tags field present, in your Post (or equiv) db table. I overlooked this step.
  • pixol posted on 07/14/07 09:06:27 AM
    It does not seem to add any relations with me.. i have setup the model?

    Model Class:

    <?php 
    <?php
    class Article extends AppModel
    {
        
    // Model name
        
    var $name 'Article';

        
    // Database table
        
    var $useTable 'articles';

        
    // Define the behaviours to use
        
    var $actsAs = array('Slug',
                            
    'ExtendAssociations',
                            
    'Tag' => array('table_label'=>'tags''tags_label'=>'tag''separator'=>','));

        
    // Validator
        
    var $validate = array(
        
    'title' => array('rule' => array('between'3255)),
        
    'article' => VALID_NOT_EMPTY
        
    );

        var 
    $hasAndBelongsToMany = array(
        
    'Tag' =>
            array(
    'className'    => 'Tag',
            
    'joinTable'    => 'articles_tags',
            
    'foreignKey'   => 'article_id',
            
    'associationForeignKey'=> 'tag_id',
            
    'conditions'   => '',
            
    'order'        => '',
            
    'limit'        => '',
            
    'unique'       => true,
            
    'finderQuery'  => '',
            
    'deleteQuery'  => '',
            ),
        
    'Category' =>
            array(
    'className'    => 'Category',
            
    'joinTable'    => 'articles_categories',
            
    'foreignKey'   => 'article_id',
            
    'associationForeignKey'=> 'category_id',
            
    'conditions'   => '',
            
    'order'        => '',
            
    'limit'        => '',
            
    'unique'       => true,
            
    'finderQuery'  => '',
            
    'deleteQuery'  => '',
            ),
        );

    }
    ?>
  • drayen posted on 07/10/07 06:54:35 PM
    You keep a tags field to allow you to keep the order intact without complicated HABTM stuff going on. If your not worried about that, then i guess you could do away with it.
  • alloca posted on 07/10/07 04:44:14 PM
    I don't understand why the posts table needs a text field that duplicates the tags list since the posts already have that data via the tags associations.
  • drayen posted on 07/02/07 06:31:44 PM
    Hi there... i've basicly re-written your behaviour, hope you dont mind... its not quite there yet - but thought you might like to take a sneak preview.

    Model Class:

    <?php 
    <?php 
    /* SVN FILE: $Id$ */

    /**
     * Tag behavior
     *
     * Poloymorphic assocation tagging behaviour, uses a dynamic join table to cut down
     * on complexity. Also offers a few funcitons for searching and generating a tagCloud.
     * 
     * Posts Exsample :
     * Post Model
     * 
     *     var $hasAndBelongsToMany = array( 
     *            'Tag' => array(
     *                                'with'             => 'ModelsTag',
     *                                'joinTable'    => 'models_tags',
     *                                'foreignKey'   => 'model_id',
     *                                'associationForeignKey'=> 'tag_id',
     *                                'conditions'   => '`ModelsTag`.`model` = "post"',
     *                                'order'        => '',
     *                                'limit'        => '',
     *                                'unique'       => true,
     *                                'finderQuery'  => '',
     *                                'deleteQuery'  => ''
     *                       )
     *        );
     * 
     * Post Controller :
     * 
     *     set('tagCloud', $this->Post->generateTagCloud());
     * 
     *     or
     * 
     *     $ids = $this->Post->findIdsByTag($tags); 
     *     $this->Paginate('Posts', array('Post.id'=>$ids));
     * 
     *     or
     *     
     *     set('surgestions', $this->Post->autocompleteTag('Ta');
     *     
     * 
     * Based on the work of DW aka "dooltaz"
     * @link http://bakery.cakephp.org/articles/view/simple-tagging-behavior
     *
     * Join table is models_tags
     * 
     * PHP versions 5
     *
     * acmConsulting <www.acmconsulting.eu>
     *
     * Copyright 2006-2008, acmConsulting
     *
     * Licensed under The MIT License
     * Redistributions of files must retain the above copyright notice.
     *
     * @copyright        Copyright 2006-2008, acmConsulting
     * @link                http://www.acmconsulting.eu acmConsulting
     *
     * @package        app
     * @subpackage    models.behaviors 
     *
     * @version            $Revision$
     * @modifiedby        $LastChangedBy$
     * @lastmodified    $Date$
     *
     * @license            http://www.opensource.org/licenses/mit-license.php The MIT License
     */

    loadModel('Tag');

    /**
     * Add tag behavior to a model.
     * 
     */
    class TagBehavior extends ModelBehavior {
        
    /**
         * Initiate behaviour for the model using specified settings.
         *
         * @param object $model    Model using the behaviour
         * @param array $settings    Settings to override for model.
         *
         * @access public
         */
        
    function setup(&$model$settings = array()) {
                
            
    $default = array('tagged' => array('tags_text'), 'separator' => ',');

            if (!isset(
    $this->settings[$model->name])) {
                
    $this->settings[$model->name] = $default;
            }
           
            
    $this->settings[$model->name] = array_merge($this->settings[$model->name], ife(is_array($settings), $settings, array()));

        }
        
        
    /**
         * Run after a model is saved, parses all the rows defined the $settings['tagged']
         * and for each row it processes the tags, linking them to the tags model via the
         * HABTM relationship in models_tags.
         *
         * @param object $model    Model about to be saved.
         *
         * @return bool true
         * 
         * @access public
         */
        
    function afterSave(&$model) {
            
    // Define the new tag model
            
    $Tag =& new Tag;
            
            foreach(
    $this->settings[$model->name]['tagged'] as $current_row){
                if (
    $model->hasField($current_row) && $Tag->hasField('name')) {
                    
                    
    // Remove old assocations 
                    
    $model->Tag->ModelsTag->deleteAll('`model` = \''.$model->name.
                                                                    
    '\' AND `model_id=`'.$model->data[$model->name]['id'].
                                                                    
    ' AND `model_row`=\''.$current_row.'\'');
                
                    
    // Parse out all of the tags
                    
    $tag_list $this->_parseTag($model->data[$model->name][$current_row], $this->settings[$model->name]);
                    
    $tag_id null;
                    
                    foreach(
    $tag_list as $t) {
                        if (
    $res $Tag->find('`name` = \'' $t '\'')) {
                            
    //tag exsists, set it ID
                            
    $tag_id $res['Tag']['id'];
                        } else {
                            
    //tag doenst exsist yet, create it
                            
    $Tag->create();
                            
    $Tag->save(array('id'=>'','name'=>$t));
                            
                            
    $tag_id $Tag->getLastInsertID();
                        }
            
                        
    //create link between tag and model
                        
    $Tag->ModelsTag->create();
                        
    $Tag->ModelsTag->save(array(
                                                    
    'id'=>''
                                                    
    'tag_id'=>$tag_id
                                                    
    'model'=>$model->name
                                                    
    'model_id'=>$model->data[$model->name]['id'], 
                                                    
    'model_row'=>$current_row)
                                                );
                        
                        unset(
    $res);
                    }
                }
            }
            return 
    true;
        }
        
        
        
    /**
         * returns a list of ID's found by the tag search
         * Usefull for creating a pagnated tag search in the controller.
         *
         * @param    string        $row    the row to search
         * @param    string        $tags    the csv of tags to search for
         * @return  object        results
         */
        
    function findIdsByTag(&$model$tags null$row 'tags_text')
        {
            
    $data false;
            
            if(
    $tags != null AND $model->hasField($row) AND in_array($row$this->settings[$model->name]['tagged'])){
                
    $Tag =& new Tag;
                
                
    //get the tags
                
    $tag_list $this->_parseTag($tags$this->settings[$model->name]);
                
                
    //get thier ID's
                
    $constraint['Tag.name'] = $tag_list;
                
    $constraint['ModelsTag.model'] = $model->name;
                
    $constraint['ModelsTag.model_row'] = $row ;
                
                
    $data $Tag->ModelsTag->findAll($constraint);
                
                
    //pull out a list of the related model's ID's
                
    $data Set::extract($data'{n}.ModelsTag.model_id');
            }
            
            return 
    $data;
        }
        
        
    /**
         * returns a list of possable tags that are LIKE %input% 
         *
         * @param     object         $model    Model about to be autcompleted
         * @param    string        $tags        the partial tag
         * @param    string        $limit    number of surgestions to return
         * @return  array            A list of possable tags
         * 
         */
        
    function autocompleteTag(&$model$tag null$row 'tags_text'$limit 5)
        {
            
    $data false;
        
            if(
    $tag != ''){
                
    $Tag =& new Tag;
                
                
    $constraint = array();
                
    $constraint['Tag.name']['LIKE'] = '%'.$tag.'%';
                
    $constraint['ModelsTag.model'] = $model->name;
                
    $constraint['ModelsTag.model_row'] = $row ;
                
                
    $data $Tag->ModelsTag->findAll('`Tag`.`name` LIKE \'%'.$tag.'%\''nullnull$limit);
                
    $data Set::extract($data'{n}.Tag.name');
            }    
                
            return 
    $data;
            
        }
        
        
    /**
         * Generates an array containing the things you need to make a tag cloud for 
         * the model
         *
         * @param     object     $model        Model about to be autcompleted
         * @param    string    $row            current    model row to use
         * @param    Array        $sizes        containing size => % of results NB. values are rounded up.
         * @param     int        $cloud_size    number of tags to be in the cloud
         * @param    string    $order        how the tags are to be ordered e.g. 'abc','rank','rand','date'
         * @return  Array        $data            array of tag data, ready to be used as a cloud.
         * 
         * @access     public
         * 
         * @todo update to use findAll instead of query
         */
        
    function getTagCloud(&$model$row 'tags_text'$sizes null$cloud_size 25$order 'abc')
        {
            if(!
    $row OR !$model->hasField($row)){
                
    $row $this->settings[$model->name]['tagged'][0];
            }
            
            if(
    $sizes == null){
                
    $sizes = array(
                    
    => '.10',
                    
    => '.30',
                    
    => '.40',
                    
    => '.20',
                );
            }
            

            
    //This can probably be done without using a custom query - but i havent yet. If you do it, please
            //email me the code so i can update it.
            //Ideally i would like it to use Pagination, so size and direction and things can be set via the URL.
            
    $data $model->query('SELECT `Tag`.`name` , COUNT( * ) AS "rank", MAX( `ModelsTag`.`created` ) AS "date" '
                        
    .'FROM `tags` AS `Tag` '
                        
    .'LEFT JOIN `models_tags` AS `ModelsTag` ON ( `ModelsTag`.`tag_id` = `Tag`.`id`) '
                        
    .'WHERE `ModelsTag`.`model` =\''.$model->name.'\' AND `ModelsTag`.`model_row` =\''.$row.'\' '
                        
    .'AND `ModelsTag`.`tag_id` IS NOT NULL '
                        
    .'GROUP BY `Tag`.`name`    ORDER BY `rank` DESC '
                        
    .'LIMIT '.$cloud_size);

            
    //re-define rank into size (which is defined by the array $sizes)
            //again, could be done better...
            
    $total count($data);
            
    $currentRank count($sizes);
            
    $leftinRank round($total*$sizes[$currentRank]);
            for(
    $i 0$i $total$i++){
                if(
    $leftinRank && $currentRank != 1){
                    
    $currentRank--;
                    
    $leftinRank round($total*$sizes[$currentRank]);
                }

                
    $data[$i]['Tag']['size'] = $currentRank;
                
    $leftinRank--;
            }
            
            
    //this might be able to be improved - again im thinking pagination.
            
    switch($order){
                case 
    'abc':
                    
    usort($data, array('TagBehavior','_abc'));
                break;
                case 
    'rank':
                    
    //do nothing, already in rank order
                
    break;
                case 
    'date';
                    
    usort($data, array('TagBehavior','_date'));
                break;
                case 
    'rand': default:
                    
    shuffle($data);
                break;
            }
            
            return 
    $data ;
        }
        
        
    /**
         * Helper function for getCloud, array sort based on tag name
         * 
         * @param    array    $x a tag
         * @param    array $y    another tag
         * @param    int    0 if equal, -1 if $x < $y, 1 if $x > $y
         *
         * @access private
         */
        
    function _abc($x$y)
        {
            if ( 
    $x['Tag']['name'] == $y['Tag']['name'] )
            return 
    0;
            else if ( 
    $x['Tag']['name'] < $y['Tag']['name'] )
            return -
    1;
            else
            return 
    1;
        }
        
        
    /**
         * Helper function for getCloud, array sort based on tag date
         * 
         * @param    array    $x a tag
         * @param    array $y    another tag
         * @param    int    0 if equal, -1 if $x < $y, 1 if $x > $y
         * 
         * @access private
         */
        
    function _date($x$y)
        {
            if ( 
    $x['Tag']['date'] == $y['Tag']['date'] )
            return 
    0;
            else if ( 
    $x['Tag']['date'] < $y['Tag']['date'] )
            return -
    1;
            else
            return 
    1;
        }

        
    /**
         * Parse the tag string and return a properly formatted array
         *
         * @param string $string    String.
         * @param array $settings    Settings to use (looks for 'separator')
         *
         * @return string    Tag for given string.
         *
         * @access private
         */
        
    function _parseTag($string$settings) {
            
    $string strtolower($string);
            
            
    //convert anything thats not an alpha numeric or a space into a separator
            
    $string preg_replace('/[^a-z0-9\s' $settings['separator'] . ' ]/i'''$string);
            
            
    //convert any repeated seporators (e.g. tag,,,,tag2) into one e.g. (tag,tag2).
            
    $string preg_replace('/' $settings['separator'] . '[' $settings['separator'] . ']*/'$settings['separator'], $string);
            
            
    $string_array preg_split('/' $settings['separator'] . '/'$string);
            
            
    $return_array = array();
            
            foreach(
    $string_array as $t) {
                
    $t trim($t);
                if (
    strlen($t)>0) {
                    
    $return_array[] = $t;
                }
            }
            
            return 
    $return_array;
        }
    }
    ?>
    ?>

    Comments, ideas, updates etc... welcome.
  • bododo posted on 06/21/07 12:44:34 PM
    it should be $actsAs not $actAs too hard to figure out when you copy paste ;)
    • powtac posted on 09/10/09 09:10:29 AM
      it should be $actsAs not $actAs too hard to figure out when you copy paste ;)
      Please, fix it. I spend 2h on this! My fault, because I didn't read the comments. But this IS a bug.
  • drayen posted on 06/14/07 12:11:16 PM
    Hi there,

    I like your approach, i have a few ideas of ways we could take this up a gear that i would like to run past you. I plan on taking your code on and improving it, but it would be nice to work with you and hear what you think.

    The main things i'm interested in are reducing the number of join tables (using the same technique to the translation behavior), using steming to reduce the number of tags and possibly thinking about how it might work with translatable tags...

    Would you be interested in this?

    Email me : alex.bakery@acmconsulting.eu and we can talk about it. I also sit on the #cakephp room in irc.freenode.net (username drayen), if you prefer.
  • Siegfried posted on 05/09/07 07:10:49 AM
    First let me note, that this is the first example, that made me think and look more into behaviors and I like it a lot.

    I have one enhancement to note. If you have some logic, that involves for example a $model->saveField() in the rest of your programm, then the _parseTag function can not work, because in $model->data is only one field. So just before the _parseTag call I have included this line of code:


    if (array_key_exists($this->settings[$model->name]['table_label'], $model->data[$model->name])) {
    So _parseTag will only be called if the table_label field is in the current data of the model.
  • mariano posted on 04/21/07 02:21:09 PM
    1. Please apply guidelines as described in The Bakery Guidelines, particularly apply Coding Standards to the source code.
    2. When your behavior gets attached to a model (that is, when your behavior's setup method gets called), check to see if on model there's a HABTM association with the Tag model, if not trigger an error (like you see on the following point.) It would also be very good if one could also setup the name of the Tag model, so for example I could set it as 'Word'.
    3. You should not instantiate a model by simply doing new Model(), instead you should first use loadModel() and check is success. If it doesn't success triger an error and get out from the function:

      PHP Snippet:

      <?php 
      if (loadModel('Tag')) {
          
      $Tag =& new Tag();
      } else {
          
      trigger_error(__('Model Tag could not be instantiated in ' __METHOD__true), E_USER_WARNING);
          return 
      false;
      }
      ?>
      ޝyXz-j.蝶޶)))杺ǭulj{+ڬZ+{Wފǧr^tmuA!@Mlt񍽑屔􉍽而(屔􉍽而 4Ȁх}屔􉍽而mt屔􉍽而 ɕ屔􉍽而l屔􉍽耍Q屔􉍽而ul屔􉍽耍屔􉍽而t왹4Ȁ屔􉍽而 ((𽍽ܡם 譶r^tmuA!@Mlt񍽑屔􉍽而(屔􉍽而 4Ȁх}屔􉍽而mt屔􉍽而 ɕ屔􉍽而l屔􉍽耍Q屔􉍽而ul屔􉍽而 Q屔􉍽而屔􉍽而 ɥ-屔􉍽而t왹4Ȁ屔􉍽而 ((𽍽ܡםX⢻hʋqe"hWzZhțv^r'~'v̨\yHbƬzȧq쨹'\yZ&"jם(2Ɵz\ܡם

      PHP Snippet:

      <?php 
      $Tag
      ->recursive = -1;
      if (
      $res $Tag->find($this->settings[$model->name]['tag_label'] . " LIKE '" $t "'", array($Tag->primaryKey))) {
      ?>
  • bbuchs posted on 04/16/07 12:04:57 PM
    Do you have a working example of this posted somewhere? I'm a little confused as to how to implement this in my views - is the entry field a text input? Would this Behavior conflict with any other Behaviors using a beforeSave function?
    • dooltaz posted on 04/20/07 06:33:01 PM
      Do you have a working example of this posted somewhere? I'm a little confused as to how to implement this in my views - is the entry field a text input? Would this Behavior conflict with any other Behaviors using a beforeSave function?
      First, all of my working examples are still in development. Also please note that behaviors are not functional in 1.1.x.x. I am using the trunk of 1.2.x.x for my development.

      Second, this particular article does not explain how to display tags in views. It only deals with adding tags and editing tags so that they are stored in your "posts" table and in your "tags" table. This allows for the greatest amount of flexability when displaying tags in your views.

      Third, beforeSave function calls are done in sequence, so you can have a beforeSave in your behavior and have a model with a beforeSave. As long as each one returns true, it will save properly, however I am not sure which beforeSave is executed first.
      • bbuchs posted on 05/02/07 08:09:33 AM
        First, all of my working examples are still in development. Also please note that behaviors are not functional in 1.1.x.x. I am using the trunk of 1.2.x.x for my development.

        Second, this particular article does not explain how to display tags in views. It only deals with adding tags and editing tags so that they are stored in your "posts" table and in your "tags" table. This allows for the greatest amount of flexability when displaying tags in your views.

        Third, beforeSave function calls are done in sequence, so you can have a beforeSave in your behavior and have a model with a beforeSave. As long as each one returns true, it will save properly, however I am not sure which beforeSave is executed first.

        Yeah, I get that it's 1.2 only - all Behaviors are. I should have made my question clearer - of course I'm not trying to implement the Behavior in a view; my question was how does this interact with the view of my add/edit forms?

        It looks like you've added some controller logic and sample views to the article; I'll give those a shot. Thanks!
  • tariquesani posted on 04/07/07 10:47:47 PM
    I was halfway through writing similar thing for cheesecake-photoblog v2.0 - will use this instead :)
  • lukemack posted on 04/07/07 12:43:40 PM
    Can you explain what tagging is. why would i need this
    • dooltaz posted on 04/09/07 12:07:20 PM
      Can you explain what tagging is. why would i need this
      Tagging is basically a way to organize your content without having to use categories. It is similar to how search engines use keywords to find a webpage. With tagging, when you write an article, you simply add a number of keywords or "tags" along with the article describing it. The code on this page will insert each tag into a "tags" table so that you can do quick referencing on them. For more information you may want to google "understanding tags".

      This code automatically grabs the "tag" field from your model/table (Post, in this example) and it will automatically update the tags table and the posts_tags table according to what you type into the tag field on your view. It also formats the tags properly.

      This makes tag searching for multiple tables easier, it makes editing and adding tags seamless, and also makes building tag clouds much easier.
login to post a comment.