WithRelatedBehavior
The most powerful and smart expansion for validate/saving/linking/unlinking.
This behavior allows you to validate, insert, update and save a model along with models from its relations. It supports all relation types. All DB queries are wrapped into transactions automatically but there's a support for manual transaction handling. Composite keys are supported as well.
Installation and configuration
Copy behavior to extensions/wr
directory located inside your application and add
it to the model of your choice the following way:
<?php
...
public function behaviors()
{
return array(
'withRelated'=>array(
'class'=>'ext.wr.WithRelatedBehavior',
),
);
}
...
Models that will be used in this doc examples
In the models below real DB fields are marked with @property
.
Post
<?php
class Post extends CActiveRecord
{
/**
* @property integer id
* @property integer author_id
* @property string title
* @property string content
*/
...
public function relations()
{
return array(
'author'=>array(self::BELONGS_TO,'User','author_id'),
'comments'=>array(self::HAS_MANY,'Comment','article_id'),
'tags'=>array(self::MANY_MANY,'Tag','post_tag(post_id,tag_id)'),
);
}
...
}
Comment
<?php
class Comment extends CActiveRecord
{
/**
* @property integer id
* @property integer post_id
* @property string content
*/
...
}
Tag
<?php
class Tag extends CActiveRecord
{
/**
* @property integer id
* @property string name
*/
...
}
User
<?php
class User extends CActiveRecord
{
/**
* @property integer id
* @property string username
* @property string password
* @property string email
*/
...
public function relations()
{
return array(
'profile'=>array(self::HAS_ONE,'Profile','user_id'),
);
}
...
}
Profile
<?php
class Profile extends CActiveRecord
{
/**
* @property integer user_id
* @property string firstname
* @property string lastname
*/
...
}
Format of the $data parameter for all methods where it's used
This parameter accepts an associative array where values are attribute or relation names.
<?php
$post->withRelated->save(true,array(
'id','title', // model attributes
'comments','tags' // model relations
));
The name of the relation can be specified as a key. In this case its value is another
$data
array with the same rules. So you there's no limit in how many times you can
nest these.
<?php
$post->withRelated->save(true,array(
'comments'=>array(
'id','content', // comments relation attributes
'author', // author relation inside comments relation models
),
));
Note: If you'll not specify any attributes, all attributes will be saved. For relations it's the opposite: you should specify relations explicitly in order for these to be saved.
Usage
Relation types
HAS_ONE
<?php
$user=new User;
$user->username='creocoder';
$user->email='[email protected]';
$user->profile=new Profile;
$user->profile->firstname='Alexander';
$user->profile->lastname='Kochetov';
$user->withRelated->save(true,array('profile'));
HAS_MANY
<?php
$post=new Post;
$post->title='Relational saving is not a dream anymore.';
$post->content='Since WithRelatedBehavior released...';
$comment1=new Comment;
$comment1->content='Was it hard?';
$comment2=new Comment;
$comment2->content='Yes, but we made it.';
$post->comments=array($comment1,$comment2);
$post->withRelated->save(true,array('comments'));
MANY_MANY
<?php
$post=new Post;
$post->title='Relational saving is not a dream anymore.';
$post->content='Since WithRelatedBehavior released...';
$tag1=new Tag;
$tag1->name='relation';
$tag2=new Tag;
$tag2->name='save';
$post->tags=array($tag1,$tag2);
$post->withRelated->save(true,array('tags'));
BELONGS_TO
<?php
$post=new Post;
$post->title='Relational saving is not a dream anymore.';
$post->content='Since WithRelatedBehavior released...';
$post->author=new User;
$post->author->username='creocoder';
$post->author->email='[email protected]';
$post->withRelated->save(true,array('author'));
Note: As you can see, API stays the same no matter which relation type is used.
Also it worth mentioning that a transaction is started before saving if DB supports it.
If transaction was already started manually, behavior detects it and doesn't
start its own transaction. By default, same as CActiveRecord::save()
does,
WithRelatedBehavior::save()
validates data and starts saving it only if all
models it's going to save are valid. You can disable validation by passing false
to $runValidation
parameter.
Recursive composite validation
As opposed to standard CModel::validate()
method, WithRelatedBehavior::validate()
does composite model validation. That means it validates all related models as
well. Validation result is returned as a boolean value. If any of the models is not valid
than result will be false
. If all models are valid than result will be true
.
Additionally you can limit validation to model attributes as follows:
<?php
$post=new Post;
$post->title='Relational validation is not a dream anymore.';
$post->content='Since WithRelatedBehavior released...';
$comment1=new Comment;
$comment1->content='Was it hard?';
$comment2=new Comment;
$comment2->content='Yes, but we made it.';
$post->comments=array($comment1,$comment2);
$result=$post->withRelated->validate(array(
'title', // only `title` attribute of the Post model will be validated
'comments'=>array(
'content', // only `content` attribute of the Comment model will be validated
),
));
Linking and unlinking
TBD.
Advanced usage
An advanced usage example
<?php
$post=new Post;
$post->title='Post title';
$post->content='Post content';
$user=new User;
$user->username='someuser';
$user->email='[email protected]';
$user->profile=new Profile;
$user->profile->firstname='Vasya';
$user->profile->lastname='Pupkin';
$post->author=$user;
$comment1=new Comment;
$comment1->author=$user;
$comment1->content='Some content 1';
$comment2=new Comment;
$comment2->author=$user;
$comment2->content='Some content 2';
$post->comments=array($comment1,$comment2);
$tag1=new Tag;
$tag1->name='tag1';
$tag2=new Tag;
$tag2->name='tag2';
$post->tags=array($tag1,$tag2);
$post->withRelated->save(true,array(
'author'=>array(
'profile',
),
'comments'=>array(
'author',
),
'tags',
));
In order to save post
and related models an extension builds saving plan first.
In the example above that before saving we need to save user
model and its related
profile
. After doing it we'll be able to save post
. Then goes comments
(author
is the same user
). Last tags
is saved. Actions mentioned are executed
inside a transaction. Extension takes care about all these.
Compare with other solutions
TBD.