This behavior helps me to update and delete models with HAS_MANY and MANY_MANY relations. Just give it a try)
Yii 1.0.2 or above
To use this extension, just copy behavior class file to your /components/ directory, add this behavior to each model you would like to extend with new possibilities:
/** * This is the model class for table "blog_category". * * The followings are the available columns in table 'blog_category': * @property integer $id * @property string $parent_id * @property integer $sort */ class BlogCategory extends CActiveRecord { ... public function behaviors() { return array( 'AdvancedRelationsBehavior' => array( 'class' => 'AdvancedRelationsBehavior', 'relations' => array( // HAS_MANY relations 'categories', // MANY_MANY relations 'posts', ), ), ); } ... } /** * This is the model class for table "blog_post_to_category". * * The followings are the available columns in table 'blog_post_to_category': * @property integer $post_id * @property integer $category_id * @property integer $sort */ class BlogPostToCategory extends CActiveRecord { ... } /** * This is the model class for table "blog_post". * * The followings are the available columns in table 'blog_post': * @property integer $id */ class BlogPost extends CActiveRecord { ... public function behaviors() { return array( 'AdvancedRelationsBehavior' => array( 'class' => 'AdvancedRelationsBehavior', 'autoUpdate' => false, // disable automatic update of related records 'relations' => array( // MANY_MANY relations 'categories', 'tags', // same structure as `blog_post_to_category` ), ), ); } ... }
After you've attached this behavior to models, you can use it. For example, to attach category model with PK 3 to categories with PK 1 and 2 now you can use code like this:
$model = BlogCategory::model()->findByPk(3); $model->categories = array(1, 2); $model->save();
Or to move all posts to category with PK 1:
$model = BlogCategory::model()->findByPk(1); $model->posts = BlogPost::model()->findAll(); $model->save();
Also you can use manual update of relations. In the BlogPost model we disable auto-update, so we must call update method updateRelated() to update relations. For example, let's create BlogPost and attach it to all categories:
$model = new BlogPost; $model->categories = BlogCategory::model()->findAll(); // update only `categories` relation, `tags` relation is ignored if($model->save()) $model->updateRelations('categories');
If sort order attribute was specified in the configuration, behavior will update it before save model. To disable this feature, just assign NULL as attribute name.
Note: saveWithRelated() and deleteWithRelated() methods added in version 1.0.5
/** * Task: build this categories tree, add "Welcome" post to each "Posts" sub-category * - News * - Business * - Posts * - Sci/Tech * - Posts * - Entertainment * - Posts * - Sports * - Posts * - Health * - Posts */ // the root category model $rootCategory = new BlogCategory; $rootCategory->title = "News"; // we cannot make direct modifications to the CActiveRecord relations, // so we need to make a temporary buffer for this $categories = array(); foreach(array( 'Business', 'Sci/Tech', 'Entertainment', 'Sports', 'Health' ) as $categoryTitle) { $category = new BlogCategory; $category->title = $categoryTitle; // add sub-category $postsCategory = new BlogCategory; $postsCategory->title = "Posts"; // add "Welcome" post to the "Posts" $post = new BlogPost; $post->title = "Welcome"; $postsCategory->posts = $post; // save "Posts" category with "Welcome" post $postsCategory->saveWithRelated('posts'); $category->categories = $postsCategory; $categories[] = $category; } $rootCategory->categories = $categories; // save the categories tree $rootCategory->saveWithRelated('categories');
Version 1.0.7 restore HAS_ONE relation data format after saving
Version 1.0.6 added support for both types of HAS_ONE and HAS_MANY relations
Version 1.0.5 added saveWithRelated() and deleteWithRelated() methods
Version 1.0.4 algorithm of updating HAS_MANY relations was fixed
Version 1.0.3 {{table}} parser RegExp fixed
Version 1.0.2 {{table}} parser RegExp updated
Version 1.0.1 HAS_MANY related records will be removed before saving.
Total 11 comments
Hi, nlac! Nice to see that AdvancedRelationsBehavior is used not only by me!))) Thanks for your idea, I've also thought about this before and found that if we add this behavior to models from relations this will be done automaticaly.
This will be done here:
When we delete a particular model, all models from relations with behavior AdvancedRelationsBehavior will be handled automaticaly.
So if we delete category PK#1, will be also deleted category PK#2 with all related blog posts.
Hi me again:) i have an another idea for your extension: extending the deleteRelated() with a parameter "$recursive" so this function would be able to delete all records along the HAS_MANY relationship tree, not just the immediate childs. It is simple and cool, the code is:
The function deleteWithRelated() can also be extend with that parameter as well.
I've fix an algorithm of updating HAS_MANY relations and added saveWithRelated() and deleteWithRelated() methods for better usage
sorry, i forgot to write to my previous line: when i tested, the "autoDelete" AdvancedRelationsBehavior property was set for the related models, the current updateRelated() method produces the data losing when that is set.
Hi alexjay, i tested your extension again (actually i decided to use it in my module, i like it). I'm not sure that the proper action is done by updateRelated() method in case of HAS_MANY relations.
If i'm correct, for that case the method does:
1. gathers the records by the $owner->$relationname data, makes an array of clones and sets the $owner->$relationname property to this clone array
2. deletes all records belonging the owner record from the db (the REAL related records, belonging to the owner by foreign keys)
3. saves the clone array to the db as new records
The problem with this approach is, the method does NOT clone the RELATED data when making the clones in step 1 - i mean the relation data of the cloned records! All the relation info will be lost in step 2, the clones will be saved in step 3 without ANY relation info, except the setting the proper PKs through which they relates to the owner.
I suggest not to make any clones in step 1, delete only the necessary related records in step 2 and just set the proper FKs in step 3.
Another small thing: in line 80, there's a condition if(is_integer($model)) ... That will fail if the given variable is a string eg. "2", better to use something like this if (!@$model->attributes) ...
Thanks, nlac. Shame on me)
Current RegExp is:
Hi alexjay, the regexp is buggy (the first group actually - it includes the two closing }} ), i think the following will be correct:
Nice extension!
Thanks for your notice, RegExp fixed.
New expression:
when we use prefix (e.g. 'tbl_'), our relations will look like {{table_name}}(primary_key, foreign_key) and the expression below will go wrong.
Thanks for you notice, fixed
There are a few numeric inconsistencies in the article. Please check the ID numbers for clarity. An example should be perfect and errorless.
Leave a comment
Please login to leave your comment.