Yii 1.1: taggable

Taggable Behavior
56 followers

Note: Latest release and documentation are available from extension GitHub page.

This extension allows active record model to manage tags.

Resources

Documentation

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,
    ));
}

Change Log

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 with 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.

Total 20 comments

#14690 report it
ibrahimd at 2013/09/03 03:42pm
passing values dynamically

Thanks for great extension. How can I pass values dynamically by using insertValues? Or another way; how can I pass the model attributes value to behavior?

My tag table has additional columns (such as sef_url, meta_description etc) and I need to set them automatically.

#14042 report it
Luiz at 2013/07/14 06:44pm
Thanks

Fits like a glove to my project!

#12349 report it
Tpoxa at 2013/03/15 08:37am
InsertValues

Thanks for the extension. For better flexibility would be great to have insertvalues parameter also for binding table and criteria scope too

#9621 report it
julianm at 2012/08/29 12:39pm
Getting all tags where binded table has a status = "active"

Great extension. Thanks.

However, I have the following question. How can I get all the tags with a custom criteria? For example, if I have another taggable model or table and I want to get all tags for such model having a status = "active" condition?

#8483 report it
julianm at 2012/06/06 05:58pm
Slug

Any idea how we can implement slug for tags? For example, if I want to use "link building" as a tag, then want to create the slug version "link-building".. would be that possible? I thought in using Sluggable Extension but any hint will be appreciated. Thanks!

#6342 report it
szako at 2011/12/31 03:29am
Exception when saving empty list

Hello!

First of all, thanks for the extension!

I use an input field for saving tags for a model, so when I save the input field empty, I get an exception:

throw new Exception("Tag \"$tag\" does not exist. Please add it before assigning or enable createTagsAutomatically.");

I have createTagsAutomatically OFF, but it should be working anyway. I use ETaggableBehavior::setTags() method and inside it calls ETaggableBehavior::toTagsArray() method which uses explode() function, so when the string argument is empty, it creates a non-empty array with one empty value. So the foreach iterates the array and there comes the exception raising.

I solved it by including a check before calling setTags()

if (empty($_POST['devicetags'])) {
    $tags = array();
} else {
    $tags = $_POST['devicetags'];
}
$model->tags->setTags($tags);

Regards, szako

#6338 report it
Tropi at 2011/12/30 04:55pm
Using taggable with CDbCriteria

I had problems using taggable behavior with CDbCriteria. I have a class Event that uses taggable behavior. The example code shown in the description worked fine:

Event::model()->taggedWith('MyTag')->find()

However using

$criteria = new CDbCritera();
$criteria->with = array('tags');
$criteria->addSearchCondition('tags.name', 'MyTag');

lead to

Cdbcommand Failed To Execute The Sql Statement: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'tags.name' in 'where clause'.

Although I had the correct relation for "tags". After some research I found out that one has to use

$criteria->together = true;

in order to work correctly.

Hope this helps someone.

#5823 report it
migajek at 2011/11/16 11:48am
Possible serious bug while building Criteria

Hi, I'm using Taggable 1.5 and Yii 1.1.8

It seems that while using "taggedWith" method, there's some issue with clearing scope or whatever. Have a look at the following code: (note: findScopedAll is the method which returns CActiveDataProvider based on current model's criteria)

    $first = News::model()->taggedWith('first');    
    echo $first->getDbCriteria(true)->join;
    echo "<hr />";
    $second = News::model()->taggedWith('second');
    echo $second->getDbCriteria(true)->join;

The output is following: JOIN news_tags bt0 ON t.id = bt0.news_id JOIN tags tag0 ON tag0.id = bt0.tag_id AND tag0.name = 'first'0

JOIN news_tags bt0 ON t.id = bt0.news_id JOIN tags tag0 ON tag0.id = bt0.tag_id AND tag0.name = 'first' JOIN news_tags bt0 ON t.id = bt0.news_id JOIN tags tag0 ON tag0.id = bt0.tag_id AND tag0.name = 'second'

as you can see, the second is invalid - it's simply a merge of previous and new one.

calling resetScope() right after second initialization fixes the problem, but I don't think it is expected way to go

#4156 report it
migajek at 2011/06/10 05:02am
seems to be great

especially combined with tag-it :)

#4149 report it
Nayjest at 2011/06/09 09:11am
Fail

You put files to yiiext\behaviors\model\taggable\yiiext\behaviors\model\taggable in taggable_1.4.zip

Too much recursion, fix it :)

#3403 report it
G&#33;&#33; at 2011/04/10 09:18am
follow up on "jquery-ui-min.js ... does not exist"

I commented the two existing lines for jquery-ui:

$jui=Yii::app()->getAssetManager()->publish(Yii::getPathOfAlias('zii.vendors.jui').'/js/jquery-ui.min.js');
$cs->registerScriptFile($jui);

And added:

$cs->registerCoreScript('jquery-ui');

Thanks for the extension!

#3356 report it
G&#33;&#33; at 2011/04/05 02:31pm
jquery-ui-min.js ... does not exist

Trying to implement this extension and I am getting a CException error message to my page.

The asset "...\framework\zii\vendors\jui/js/jquery-ui.min.js" to be published does not exist.

Otherwise, getTags seems to be working just fine so I think I've got the extension configured correctly, except for this:).

Any advice on overcoming this is appreciated. Thank you.

#2466 report it
b3atb0x at 2011/01/06 10:18am
getFindByTagsCriteria() bug

Hello, nice extension. I've got a bug report/fix: If you use Taggable to filter out records using multiple tag groups and write something like this:

$baseNodeFinder = Post::model()->ongoing();
if($_GET['tag']) {
    $baseNodeFinder->taggedWith($_GET['tag']);
}
if($_GET['category_id']) {
    $baseNodeFinder->boundCategories->taggedWith($_GET['category_id']);
}
$results = $baseNodeFinder->findAll();

You'll get CDbException:

Syntax Error Or Access Violation: 1066 Not unique table/alias: 'bt0'

My quick fix for this is to alter ETaggableBehavior::getFindByTagsCriteria() like this:

/**
     * Get criteria to limit query by tags
     *
     * @access private
     * @param array $tags
     * @return CDbCriteria
     */
    protected function getFindByTagsCriteria($tags) {
        $criteria = new CDbCriteria();
 
        $iFix = $this->getIFix();
        $pk = $this->getOwner()->tableSchema->primaryKey;
 
        if(!empty($tags)){
            $conn = $this->getConnection();
            $criteria->select = 't.*';
            for($i = 0, $count = count($tags); $i < $count; $i++){
                $tag = $conn->quoteValue($tags[$i]);
                $criteria->join .=
                        "JOIN {$this->getTagBindingTableName()} bt$i$iFix ON t.{$pk} = bt$i$iFix.{$this->getModelTableFkName()}
                     JOIN {$this->tagTable} tag$i$iFix ON tag$i$iFix.{$this->tagTablePk} = bt$i$iFix.{$this->tagBindingTableTagId} AND tag$i$iFix.`{$this->tagTableName}` = $tag";
            }
        }
 
        if($this->getScopeCriteria()){
            $criteria->mergeWith($this->getScopeCriteria());
        }
 
        return $criteria;
    }
    /**
     * Fix for multiple tag groups search
     * @author b3atb0x
     */
    protected function getIFix()
    {
        static $iFixLog;
        $i = mt_rand(1,99999999);
        if(in_array($i, $iFixLog)) {
            return $this->getIFix();
        } else {
            $iFixLog[] = $i;
            return $i;
        }
    }
#2316 report it
Trejder at 2010/12/11 07:52pm
Some Russian addings! :)

samdark, this extension is perfect! :) I only want to add that in some parts (i.e. some headers) you added some Russian (Cyrillic) letters, like "или" which probably should stand for "and". Am I right? :)

#84 report it
DarkNSF at 2010/09/20 12:07pm
Great extension

Slight documentation error,

EARTaggableBehaviour should be EARTaggableBehavior

otherwise you'll get an import error

#125 report it
blindMoe at 2010/09/02 08:28pm
Figured out the 'duplicate key' problem

This problem has nothing to do with the Taggable extension. I feel bad having left that comment now. Basically the column wasn't set to 'auto_increment' so it was my fault. This extension is amazing! Thanks for the contribution to the Yii community!

#126 report it
blindMoe at 2010/09/02 08:21pm
Great addition to any site but receiving duplicate key error

I am running into the same problem as Mech7. I have this addition coupled with the Tag-widget and it looks great... only problem is when I try to save the tags I get a duplicate key error. Can you help us clear this up?

#623 report it
mech7 at 2010/04/07 12:50am
Error

When i use it in the aftersave i always get:

CDbCommand failed to execute the SQL statement: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '11' for key 'PRIMARY'

#628 report it
Blizz at 2010/04/05 12:39pm
Excellent but small bug

In your function getAllTags(), you always overwrite the $criteria argument. It's impossible that way to use it for autocomplete.

#799 report it
Alexander Ivanov at 2010/02/27 03:36am
Thanks!

Very usefull extesion. Please change private functions to protected type, that they could be redefined by inheritors.

Leave a comment

Please to leave your comment.

Create extension
Downloads
No downloadable files yet
  • Yii Version: 1.1
  • License: New BSD License
  • Developed by: samdark
  • Category: Database
  • Votes: +27 / -1
  • Downloaded: 1,495 times
  • Created on: Dec 8, 2009
  • Last updated: Jun 23, 2013
  • Tags: model