Fork me on GitHub

Taggable Behavior

Allows active record model to manage tags.

Installation and configuration

Create a table where you want to store tags and cross-table to store tag-model connections. You can use sample SQL from schema.sql file.

In your ActiveRecord model define behaviors() method:

function behaviors() {
    return array(
        'tags' => array(
            'class' => 'ext.yiiext.behaviors.model.taggable.ETaggableBehavior',
            // Table where tags are stored
            'tagTable' => 'Tag',
            // Cross-table that stores tag-model connections.
            // By default it's your_model_tableTag
            'tagBindingTable' => 'PostTag',
            // Foreign key in cross-table.
            // By default it's your_model_tableId
            'modelTableFk' => 'post_id',
            // Tag table PK field
            'tagTablePk' => 'id',
            // Tag name field
            'tagTableName' => 'name',
            // Tag counter field
            // if null (default) does not write tag counts to DB
            'tagTableCount' => 'count',
            // Tag binding table tag ID
            'tagBindingTableTagId' => 'tagId',
            // Caching component ID. If false don't use cache.
            // Defaults to false.
            'cacheID' => 'cache',
 
            // Save nonexisting tags.
            // When false, throws exception when saving nonexisting tag.
            'createTagsAutomatically' => true,
 
            // Default tag selection criteria
            'scope' => array(
                'condition' => ' t.user_id = :user_id ',
                'params' => array( ':user_id' => Yii::app()->user->id ),
            ),
 
            // Values to insert to tag table on adding tag
            'insertValues' => array(
                'user_id' => Yii::app()->user->id,
            ),
        )
    );
}

For using AR model for tags (for example, to bind custom behavior), use EARTaggableBehavior.

To do it add following to your config:

return array(
  // ...
  'import'=>array(
        'application.models.*',
        'application.components.*',
        'ext.yiiext.behaviors.model.taggable.*',
        // ...
        // other imports
    ),
    // ...
);

In your AR model implement behaviors() method:

function behaviors() {
    return array(
        'tags_with_model' => array(
            'class' => 'ext.yiiext.behaviors.model.taggable.EARTaggableBehavior',
            // tag table name
            'tagTable' => 'Tag',
            // tag model class
            'tagModel' => 'Tag',
            // ...
            // other options as shown above
        )
    );
}

Methods

setTags($tags)

Replace model tags with new tags set.

$post = new Post();
$post->setTags('tag1, tag2, tag3')->save();

addTags($tags) or addTag($tags)

Add one or more tags to existing set.

$post->addTags('new1, new2')->save();

removeTags($tags) or removeTag($tags)

Remove tags specified (if they do exist).

$post->removeTags('new1')->save();

removeAllTags()

Remove all tags from the model.

$post->removeAllTags()->save();

getTags()

Get array of model's tags.

$tags = $post->getTags();
foreach($tags as $tag){
  echo $tag;
}

hasTag($tags) или hasTags($tags)

Returns true if all tags specified are assigned to current model and false otherwise.

$post = Post::model()->findByPk(1);
if($post->hasTags("yii, php")){
    //
}

getAllTags()

Get all possible tags for this model class.

$tags = Post::model()->getAllTags();
foreach($tags as $tag){
  echo $tag;
}

getAllTagsWithModelsCount()

Get all possible tags with models count for each for this model class.

$tags = Post::model()->getAllTagsWithModelsCount();
foreach($tags as $tag){
  echo $tag['name']." (".$tag['count'].")";
}

taggedWith($tags) или withTags($tags)

Limits AR query to records with all tags specified.

$posts = Post::model()->taggedWith('php, yii')->findAll();
$postCount = Post::model()->taggedWith('php, yii')->count();

resetAllTagsCache() and resetAllTagsWithModelsCountCache()

could be used to reset getAllTags() or getAllTagsWithModelsCount() cache.

Bonus features

You can print comma separated tags following way:

$post->addTags('new1, new2')->save();
echo $post->tags->toString();

Using multiple tag groups

You can use multiple tag groups for a single model. For example, we will create OS and Category tag groups for Software model.

First we need to create DB tables. Two for each group:

/* Tag table */
CREATE TABLE `Os` (
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(255) NOT NULL,
  PRIMARY KEY  (`id`),
  UNIQUE KEY `Os_name` (`name`)
);
 
/* Tag binding table */
CREATE TABLE `PostOs` (
  `post_id` INT(10) UNSIGNED NOT NULL,
  `osId` INT(10) UNSIGNED NOT NULL,
  PRIMARY KEY  (`post_id`,`osId`)
);
 
/* Tag table */
CREATE TABLE `Category` (
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(255) NOT NULL,
  PRIMARY KEY  (`id`),
  UNIQUE KEY `Category_name` (`name`)
);
 
/* Tag binding table */
CREATE TABLE `PostCategory` (
  `post_id` INT(10) UNSIGNED NOT NULL,
  `categoryId` INT(10) UNSIGNED NOT NULL,
  PRIMARY KEY  (`post_id`,`categoryId`)
);

Then we are attaching behaviors:

return array(
    'categories' => array(
        'class' => 'ext.yiiext.behaviors.model.taggable.ETaggableBehavior',
        'tagTable' => 'Category',
        'tagBindingTable' => 'PostCategory',
        'tagBindingTableTagId' => 'categoryId',
    ),
    'os' => array(
        'class' => 'ext.yiiext.behaviors.model.taggable.ETaggableBehavior',
        'tagTable' => 'Os',
        'tagBindingTable' => 'PostOs',
        'tagBindingTableTagId' => 'osId',
    ),
);

That's it. Now we can use it:

$soft = Software::model()->findByPk(1);
// fist attached taggable behavior is used by default
// so we can use short syntax instead of $soft->categories->addTag("Antivirus"):
$soft->addTag("Antivirus");
$soft->os->addTag("Windows");
$soft->save();

Using taggable with CAutoComplete

<?$this->widget('CAutoComplete', array(
    'name' => 'tags',
    'value' => $model->tags->toString(),
    'url'=>'/autocomplete/tags', //path to autocomplete URL
    'multiple'=>true,
    'mustMatch'=>false,
    'matchCase'=>false,
)) ?>

Saving tags will look like following:

function actionUpdate(){
    $model = Post::model()->findByPk($_GET['id']);
 
    if(isset($_POST['Post'])){
        $model->attributes=$_POST['Post'];
        $model->setTags($_POST['tags']);
 
        // if you have multiple tag fields:
        // $model->tags1->setTags($_POST['tags1']);
        // $model->tags1->setTags($_POST['tags2']);
 
        if($model->save()) $this->redirect(array('index'));
    }
    $this->render('update',array(
        'model'=>$model,
    ));
}

Changelog

1.5

  • updateCount now uses proper PK name (RSol)

1.4

  • Change CacheID defaults to false. If false don't use cache.
  • Fixed phpDoc
  • Fixed cache key generation (Sam Dark)
  • Fixed English docs (Sam Dark)
  • #27: Fixed typos (Sam Dark)

1.3

  • Tag table primary key is no longer hardcoded as id and can be set via tagTablePk.
  • Additional values inserting support (mitallast).
  • Default scope criteria support (mitallast).
  • Better criteria support (mitallast).
  • Files and classnames are renamed to ETaggableBehavior and EARTaggableBehavior.
  • Added criteria support to ETagListWidget.
  • More flexibility for ETagListWidget url.

1.2

  • Fixed getting tags array from string with separator at the end or beginning of line.
  • Fixed getting wrong ids when using withTags() or taggedWith().
  • ETagListWidget.
  • Added getTagsWithModelsCount().
  • getAllTagsWithModelsCount now can accept criteria.
  • Input is being passed through strip_tags now.

1.1

  • DBConnection is now saved in a private variable for better perfomance.
  • Added tagTableName property that allows customizing name field http://code.google.com/p/yiiext/issues/detail?id=12
  • Added tagTableCount property specifying counter field for storing tag count in database http://code.google.com/p/yiiext/issues/detail?id=17
  • New subclass EARTaggableBehaviour for using behavior along with Tags model http://code.google.com/p/yiiext/issues/detail?id=13

1.0.2

  • New manual section: using taggable with CAutoComplete.
  • Renamed __toString to toString since magic was hard to debug in case of failure.
  • Fixed more possible cache keys overlap when using multiple tag sets within one model.

1.0.1

  • New naming conventions.

1.0

  • More PHPDoc documentation.
  • Fixed possible cache keys overlap when using multiple tag sets within one model.

0.9

Warning: this version is not compatible with Yii 1.0.

  • Added resetAllTagsCache() and resetAllTagsWithModelsCountCache().
  • Fixed getAllTags() and getAllTagsWithModelsCount() cache issues.
  • Now tags are saved on save() only if they were changed.
  • Extension is now compatible only with Yii 1.1.
  • Fixed saving empty tags.
  • Fixed caching.

0.8

Warning: this version is not backwards compatible to 0.6.

  • Now you can set tagId field name for binding table.
  • Do not try to delete tag bindings when inserting new record.
  • Added taggedWith() alias withTags().
  • Removed getCountByTags(), findAllByTags(). Use taggedWith().
  • Method chaining: $post->addTags("yii, php")->save();
  • New syntax: $posts = Post::model()->taggedWith(array('php', 'yii'))->findAll();
  • Added parent:: calls in event handlers.
  • Added hasTags() and it's alias hasTag() to check if all tags specified are attached to current model.
  • New syntax: echo $post->tags (or by what name behaviour is attached) will print comma separated tags.
  • getTags now returns array since implode is really easy to do yourself.
  • Removed getTagsArray().
  • addTags(), removeTags(), setTags() are now accept both string and array.
  • Added addTag() as alias of addTags(), removeTag() as alias of removeTags().
  • Some methods are now protected.
  • Added $with to findAllByTags().
  • getAllTags().
  • Unit tests.
  • createTagsAutomatically option allows to throw exception when adding nonexisting tag.

0.6

  • Initial public release.

Provides many useful methods and named scopes to work with model tags.

Documentation

Downloads (Tags)

Resources