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
idand can be set viatagTablePk. - 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.