-
Notifications
You must be signed in to change notification settings - Fork 1
/
CAdvancedArBehavior.php
199 lines (180 loc) · 6.5 KB
/
CAdvancedArBehavior.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
<?php
/**
* CAdvancedArBehavior class file.
*
* @author Herbert Maschke <[email protected]>
* @link http://www.yiiframework.com/
* @version 0.3
*/
/* The CAdvancedArBehavior extension adds up some functionality to the default
* possibilites of yii´s ActiveRecord implementation.
*
* To use this extension, just copy this file to your extensions/ directory,
* add 'import' => 'application.extensions.CAdvancedArBehavior', [...] to your
* config/main.php and add this behavior to each model you would like to
* inherit the new possibilities:
*
* public function behaviors(){
* return array( 'CAdvancedArBehavior' => array(
* 'class' => 'application.extensions.CAdvancedArBehavior'));
* }
*
*
* Automatically sync your Database Schema when setting new fields by
* activating $syncdb
*
* Better support of MANY_TO_MANY relations:
*
* When we have defined a MANY_MANY relation in our relations() function, we
* are now able to add up instances of the foreign Model on the fly while
* saving our Model to the Database. Let´s assume the following Relation:
*
* Post has:
* 'categories'=>array(self::MANY_MANY, 'Category',
* 'tbl_post_category(post_id, category_id)')
*
* Category has:
* 'posts'=>array(self::MANY_MANY, 'Post',
* 'tbl_post_category(category_id, post_id)')
*
* Now we can use the attribute 'categories' of our Post model to add up new
* rows to our MANY_MANY connection Table:
*
* $post = new Post();
* $post->categories = Category::model()->findAll();
* $post->save();
*
* This will save our new Post in the table Post, and in addition to this it
* updates our N:M-Table with every Category available in the Database.
*
* We can further limit the Objects given to the attribute, and can also go
* the other Way around:
*
* $category = new Category();
* $category->posts = array(5, 6, 7, 10);
* $caregory->save();
*
* We can pass Object instances like in the first example, or a list of
* integers that representates the Primary key of the Foreign Table, so that
* the Posts with the id 5, 6, 7 and 10 get´s added up to our new Category.
*
* 5 Queries will be performed here, one for the Category-Model and four for
* the N:M-Table tbl_post_category. Note that this behavior could be tuned
* further in the future, so only one query get´s executed for the MANY_MANY
* Table.
*
* We can also pass a _single_ object or an single integer:
*
* $category = new Category();
* $category->posts = Post::model()->findByPk(12);
* $category->posts = 12;
* $category->save();
*
* Assign -1 to a attribute to let it be untouched by the behavior.
*/
class CAdvancedArbehavior extends CActiveRecordBehavior
{
// Set this to false to disable tracing of changes
public $trace = true;
// If you want to ignore some relations, set them here.
public $ignoreRelations = array();
// After the save process of the model this behavior is attached to
// is finished, we begin saving our MANY_MANY related data
public function afterSave($event)
{
if(!is_array($this->ignoreRelations))
throw new CException('ignoreRelations of CAdvancedArBehavior needs to be an array');
$this->writeManyManyTables();
parent::afterSave($event);
return true;
}
protected function writeManyManyTables()
{
if($this->trace)
Yii::trace('writing MANY_MANY data for '.get_class($this->owner),
'system.db.ar.CActiveRecord');
foreach($this->getRelations() as $relation)
{
$this->cleanRelation($relation);
$this->writeRelation($relation);
}
}
/* A relation will have the following format:
$relation['m2mTable'] = the tablename of the foreign object
$relation['m2mThisField'] = the column in the many2many table that represents the primary Key of the object that this behavior is attached to
$relation['m2mForeignField'] = the column in the many2many table that represents the foreign object.
Written in Yii relation syntax, it would be like this
'relationname' => array('foreignobject', 'column', 'm2mTable(m2mThisField, m2mForeignField) */
protected function getRelations()
{
$relations = array();
foreach ($this->owner->relations() as $key => $relation)
{
if ($relation[0] == CActiveRecord::MANY_MANY &&
!in_array($key, $this->ignoreRelations) &&
$this->owner->hasRelated($key) &&
$this->owner->$key != -1)
{
$info = array();
$info['key'] = $key;
$info['foreignTable'] = $relation[1];
if (preg_match('/^(.+)\((.+)\s*,\s*(.+)\)$/s', $relation[2], $pocks))
{
$info['m2mTable'] = $pocks[1];
$info['m2mThisField'] = $pocks[2];
$info['m2mForeignField'] = $pocks[3];
}
else
{
$info['m2mTable'] = $relation[2];
$info['m2mThisField'] = $this->owner->tableSchema->PrimaryKey;
$info['m2mForeignField'] = CActiveRecord::model($relation[1])->tableSchema->primaryKey;
}
$relations[$key] = $info;
}
}
return $relations;
}
/** writeRelation's job is to check if the user has given an array or an
* single Object, and executes the needed query */
protected function writeRelation($relation)
{
$key = $relation['key'];
// Only an object or primary key id is given
if(!is_array($this->owner->$key) && $this->owner->$key != array())
$this->owner->$key = array($this->owner->$key);
// An array of objects is given
foreach((array)$this->owner->$key as $foreignobject)
{
if(!is_numeric($foreignobject) && is_object($foreignobject))
$foreignobject = $foreignobject->{$foreignobject->$relation['m2mForeignField']};
$this->execute(
$this->makeManyManyInsertCommand($relation, $foreignobject));
}
}
/* before saving our relation data, we need to clean up exsting relations so
* they are synchronized */
protected function cleanRelation($relation)
{
$this->execute($this->makeManyManyDeleteCommand($relation));
}
// A wrapper function for execution of SQL queries
public function execute($query) {
return Yii::app()->db->createCommand($query)->execute();
}
public function makeManyManyInsertCommand($relation, $value) {
return sprintf("insert into %s (%s, %s) values ('%s', '%s')",
$relation['m2mTable'],
$relation['m2mThisField'],
$relation['m2mForeignField'],
$this->owner->{$this->owner->tableSchema->primaryKey},
$value);
}
public function makeManyManyDeleteCommand($relation) {
return sprintf("delete ignore from %s where %s = '%s'",
$relation['m2mTable'],
$relation['m2mThisField'],
$this->owner->{$this->owner->tableSchema->primaryKey}
);
}
}