1 <?php
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 class CSaveRelationsBehavior extends CActiveRecordBehavior {
60
61 public $relations = array();
62 public $transactional = true;
63 public $hasError = false;
64 public $deleteRelatedRecords = true;
65 private $transaction;
66
67 private function initSaveRelation($relation){
68 $model = $this->owner;
69 if(!array_key_exists($relation,$model->relations()))
70 throw new CDbException('CSaveRelatedBehavior could not find the "'.$relation.'" relation in the model.');
71 if(!array_key_exists($relation,$this->relations)) {
72 Yii::trace("Init {$relation} relation",'application.components.CSaveRelatedBehavior');
73 $this->relations[$relation]=array();
74 }
75 }
76
77 public function setRelationRecords($relation,$data=null,$merge=false) {
78
79 $this->addSaveRelation($relation);
80 $model = $this->owner;
81 $activeRelation = $model->getActiveRelation($relation);
82 if($activeRelation instanceOf CHasManyRelation || $activeRelation instanceOf CManyManyRelation) {
83 if(!$merge) $model->{$relation} = array();
84 $relationClassName = $activeRelation->className;
85 $relationForeignKey = $activeRelation->foreignKey;
86 $criteria = array();
87 if($activeRelation instanceOf CManyManyRelation) {
88 $schema = $model->getCommandBuilder()->getSchema();
89 preg_match('/^\s*(.*?)\((.*)\)\s*$/',$relationForeignKey,$matches);
90 $joinTable=$schema->getTable($matches[1]);
91 $fks=preg_split('/[\s,]+/',$matches[2],-1,PREG_SPLIT_NO_EMPTY);
92 $relModel = new $relationClassName;
93 $pks = array();
94 $fkDefined=true;
95 foreach($fks as $i=>$fk) {
96 if(isset($joinTable->foreignKeys[$fk])) {
97 list($tableName,$pk)=$joinTable->foreignKeys[$fk];
98 if($schema->compareTableNames($relModel->tableSchema->rawName,$tableName)) {
99 $pks[] = $pk;
100 }
101 }
102 else {
103 $fkDefined=false;
104 break;
105 }
106 }
107 if(!$fkDefined) {
108 $pks = array();
109 foreach($fks as $i=>$fk)
110 {
111 if($i<count($model->tableSchema->primaryKey))
112 {
113 $pks[] = is_array($model->tableSchema->primaryKey) ? $model->tableSchema->primaryKey[$i] : $model->tableSchema->primaryKey;
114 }
115 }
116 }
117 if(!is_null($data)) {
118 foreach($data as $key=>$value) {
119 $relobj = null;
120 $relModel = new $relationClassName;
121 if(is_array($value)) {
122 foreach($pks as $pk) {
123 $criteria[$pk] = $value[$pk];
124 }
125 }
126 else {
127 $criteria[$pks[0]] = $value;
128 }
129 $relobj = $relModel->findByAttributes($criteria);
130 if(!($relobj instanceof $relationClassName)) $relobj = new $relationClassName;
131 $relobj->attributes = $value;
132 $model->addRelatedRecord($relation,$relobj,$key);
133 }
134 }
135 }
136 else {
137 $fks=preg_split('/[\s,]+/',$relationForeignKey,-1,PREG_SPLIT_NO_EMPTY);
138 if(!is_null($data)) {
139 foreach($data as $key=>$value) {
140 $relobj = null;
141 if(!$model->isNewRecord) {
142 $criteria = array();
143 $relModel = new $relationClassName;
144 $relationPrimaryKeys = $relModel->tableSchema->primaryKey;
145 if(is_array($value)) {
146 if(is_array($relationPrimaryKeys)) {
147 foreach($relationPrimaryKeys as $relationPrimaryKey){
148 if(!in_array($relationPrimaryKey,$fks)) {
149 if(isset($value[$relationPrimaryKey])) $criteria[$relationPrimaryKey] = $value[$relationPrimaryKey];
150 }
151 else {
152 $criteria[$relationPrimaryKey] = $model->primaryKey;
153 }
154 }
155 }
156 else{
157 if(!in_array($relationPrimaryKeys,$fks)) {
158 if(isset($value[$relationPrimaryKeys])) $criteria[$relationPrimaryKeys] = $value[$relationPrimaryKeys];
159 }
160 else {
161 $criteria[$relationPrimaryKeys] = $model->primaryKey;
162 }
163 }
164 }
165 else {
166 $criteria = array($relationPrimaryKeys=>$value);
167 }
168 if(count($criteria)) $relobj = $relModel->findByAttributes($criteria);
169 }
170 if(!($relobj instanceof $relationClassName)) $relobj = new $relationClassName;
171 foreach($value as $prop=>$val) $relobj->{$prop} = $val;
172 $model->addRelatedRecord($relation,$relobj,$key);
173 }
174 }
175 }
176 }
177 }
178
179 public function addSaveRelation($relation,$message=null){
180 $this->initSaveRelation($relation);
181 $this->relations[$relation] = CMap::mergeArray($this->relations[$relation],array('save'=>true));
182 if(!is_null($message)) $this->setSaveRelationMessage($relation,$message);
183 }
184
185 public function removeSaveRelation($relation){
186 $model = $this->owner;
187 if(!array_key_exists($relation,$model->relations()))
188 throw new CDbException('CSaveRelatedBehavior could not find the "'.$relation.'" relation in the model.');
189 if(array_key_exists($relation,$this->relations)) {
190 Yii::trace("Removing {$relation} relation to save",'application.components.CSaveRelatedBehavior');
191 $this->relations[$relation] = CMap::mergeArray($this->relations[$relation],array('save'=>false));
192 }
193 }
194
195 public function setRelationScenario($relation,$scenario){
196 $this->initSaveRelation($relation);
197 $this->relations[$relation] = CMap::mergeArray($this->relations[$relation],array('scenario'=>$scenario));
198 }
199
200 public function setSaveRelationMessage($relation,$message) {
201 $this->initSaveRelation($relation);
202 $this->relations[$relation] = CMap::mergeArray($this->relations[$relation],array('message'=>$message));
203 }
204
205 public function beforeValidate($event) {
206 $model = $this->owner;
207 foreach($this->relations as $relation=>$params) {
208 if(isset($params['save']) && $params['save']==true) {
209 $activeRelation = $model->getActiveRelation($relation);
210 $validRelation = true;
211 if(!$activeRelation instanceOf CManyManyRelation) {
212 foreach($model->{$relation} as $relatedRecord) {
213 if(isset($params['scenario'])) $relatedRecord->scenario = $params['scenario'];
214 $validRelation = $validRelation && $relatedRecord->validate();
215 }
216 if(!$validRelation)
217 $model->addError($relation,isset($params['message']) ? $params['message'] : "An error occured during the save of {$relation}");
218 }
219 $this->relations[$relation]['valid'] = $validRelation;
220 }
221 }
222 }
223
224 public function beforeSave($event) {
225 $model = $this->owner;
226 $valid = true;
227 foreach($this->relations as $relation=>$params) {
228 if(isset($params['save']) && $params['save']==true) {
229 $valid = $valid && $this->relations[$relation]['valid'];
230 }
231 }
232 if($valid && $this->transactional && !$model->dbConnection->currentTransaction) {
233 Yii::trace("beforeSave start transaction",'application.components.CSaveRelatedBehavior');
234 $this->transaction=$model->dbConnection->beginTransaction();
235 }
236 $event->isValid = $valid;
237 }
238
239 public function afterSave($event) {
240 $model = $this->owner;
241 try{
242 foreach($this->relations as $relation=>$params) {
243 if(isset($params['save']) && $params['save']==true) {
244 Yii::trace("saving {$relation} related records.",'application.components.CSaveRelatedBehavior');
245 $activeRelation = $model->getActiveRelation($relation);
246 $relationClassName = $activeRelation->className;
247 $relationForeignKey = $activeRelation->foreignKey;
248 $keysToKeep = array();
249 if($activeRelation instanceOf CManyManyRelation) {
250
251 $schema = $model->getCommandBuilder()->getSchema();
252 preg_match('/^\s*(.*?)\((.*)\)\s*$/',$relationForeignKey,$matches);
253 $joinTable=$schema->getTable($matches[1]);
254 $fks=preg_split('/[\s,]+/',$matches[2],-1,PREG_SPLIT_NO_EMPTY);
255 $fksFieldNames = array();
256 $fksParamNames = array();
257 foreach($fks as $fk) {
258 $fksFieldNames[] = $schema->quoteColumnName($fk);
259 $fksParamNames[] = ':'.$fk;
260 }
261 $sql="INSERT IGNORE INTO ".$joinTable->rawName." (".implode(', ',$fksFieldNames).") VALUES(".implode(', ',$fksParamNames).")";
262 $baseParams = array();
263 $baseCriteriaCondition = array();
264 reset($fks);
265 foreach($fks as $i=>$fk) {
266 if(isset($joinTable->foreignKeys[$fk])) {
267 list($tableName,$pk)=$joinTable->foreignKeys[$fk];
268 if($schema->compareTableNames($model->tableSchema->rawName,$tableName)) {
269 $baseCriteriaCondition[$fk] = $baseParams[':'.$fk] = $model->{$pk};
270 }
271 }
272 }
273 $relModel = new $relationClassName;
274 foreach($model->{$relation} as $idx=>$relatedRecord) {
275 $relParams = array();
276 reset($fks);
277 foreach($fks as $i=>$fk) {
278 if(isset($joinTable->foreignKeys[$fk])) {
279 list($tableName,$pk)=$joinTable->foreignKeys[$fk];
280 if($schema->compareTableNames($relModel->tableSchema->rawName,$tableName)) {
281 $keysToKeep[$fk][] = $relParams[':'.$fk] = $relatedRecord->{$pk};
282 }
283 }
284 }
285 $model->getCommandBuilder()->createSqlCommand($sql,$baseParams+$relParams)->execute();
286 }
287
288 $criteria = new CDbCriteria;
289 $criteria->addColumnCondition($baseCriteriaCondition);
290 foreach($keysToKeep as $fk=>$values)
291 $criteria->addInCondition($fk,$values,'AND NOT');
292 $model->getCommandBuilder()->createDeleteCommand($joinTable->name,$criteria)->execute();
293 }
294 else {
295
296 foreach($model->{$relation} as $relatedRecord) {
297 if($relatedRecord->isNewRecord) {
298 if(is_array($relationForeignKey)) {
299 foreach($relationForeignKey as $fk) {
300 $relatedRecord->{$fk} = $model->primaryKey[$fk];
301 }
302 }
303 else {
304 $relatedRecord->{$relationForeignKey} = $model->primaryKey;
305 }
306 }
307 if($relatedRecord->save()) {
308 $relationPrimaryKeys = $relatedRecord->tableSchema->primaryKey;
309 if(is_array($relationPrimaryKeys)) {
310 foreach($relationPrimaryKeys as $relationPrimaryKey){
311 if($relationPrimaryKey!=$relationForeignKey) $keysToKeep[$relationPrimaryKey][] = $relatedRecord->{$relationPrimaryKey};
312 }
313 }
314 else{
315 $keysToKeep[$relationPrimaryKeys][] = $relatedRecord->{$relationPrimaryKeys};
316 }
317 }
318 else {
319 throw new CException("Invalid related record");
320 }
321 }
322 $relatedRecord = new $relationClassName;
323 $criteria = new CDbCriteria;
324 $criteria->addColumnCondition(array($relationForeignKey=>$model->primaryKey));
325 foreach($keysToKeep as $fk=>$values)
326 $criteria->addInCondition($fk,$values,'AND NOT');
327 $relatedRecord->deleteAll($criteria);
328 }
329 }
330 }
331 unset($relation);
332 if($this->transactional && $this->transaction) $this->transaction->commit();
333 }
334 catch(Exception $e)
335 {
336 Yii::trace("An error occured during the save operation for related records : ".$e->getMessage(),'application.components.CSaveRelatedBehavior');
337 $this->hasError = true;
338 if(isset($relation)) $model->addError($relation,isset($this->relations[$relation]['message']) ? $this->relations[$relation]['message'] : "An error occured during the save of {$relation}");
339 if($this->transactional && $this->transaction) $this->transaction->rollBack();
340 }
341 }
342
343 public function beforeDelete($event) {
344 $model = $this->owner;
345 if($this->transactional && !$model->dbConnection->currentTransaction) {
346 Yii::trace("beforeDelete start transaction",'application.components.CSaveRelatedBehavior');
347 $this->transaction=$model->dbConnection->beginTransaction();
348 }
349 }
350
351 public function afterDelete($event) {
352 if($this->deleteRelatedRecords) {
353 $model = $this->owner;
354 try{
355 foreach($model->relations() as $relation=>$params) {
356 $activeRelation = $model->getActiveRelation($relation);
357 if(is_object($activeRelation) && ($activeRelation instanceOf CManyManyRelation || $activeRelation instanceOf CHasManyRelation || $activeRelation instanceOf CHasOneRelation)) {
358 Yii::trace("deleting {$relation} related records.",'application.components.CSaveRelatedBehavior');
359 $relationClassName = $activeRelation->className;
360 $relationForeignKey = $activeRelation->foreignKey;
361 if($activeRelation instanceOf CManyManyRelation) {
362
363 $schema = $model->getCommandBuilder()->getSchema();
364 preg_match('/^\s*(.*?)\((.*)\)\s*$/',$relationForeignKey,$matches);
365 $joinTable=$schema->getTable($matches[1]);
366 $fks=preg_split('/[\s,]+/',$matches[2],-1,PREG_SPLIT_NO_EMPTY);
367 $baseParams = array();
368 $baseCriteriaCondition = array();
369 reset($fks);
370 foreach($fks as $i=>$fk) {
371 if(isset($joinTable->foreignKeys[$fk])) {
372 list($tableName,$pk)=$joinTable->foreignKeys[$fk];
373 if($schema->compareTableNames($model->tableSchema->rawName,$tableName)) {
374 $baseCriteriaCondition[$fk] = $baseParams[':'.$fk] = $model->{$pk};
375 }
376 }
377 }
378
379 $criteria = new CDbCriteria;
380 $criteria->addColumnCondition($baseCriteriaCondition);
381 $model->getCommandBuilder()->createDeleteCommand($joinTable->name,$criteria)->execute();
382 }
383 else {
384
385 $relatedRecord = new $relationClassName;
386 $criteria = new CDbCriteria;
387 $criteria->addColumnCondition(array($relationForeignKey=>$model->primaryKey));
388 $relatedRecord->deleteAll($criteria);
389 }
390 }
391 }
392 unset($relation);
393 if($this->transactional && $this->transaction) $this->transaction->commit();
394 }
395 catch(Exception $e)
396 {
397 Yii::trace("An error occured during the delete operation for related records : ".$e->getMessage(),'application.components.CSaveRelatedBehavior');
398 $this->hasError = true;
399 if(isset($relation)) $model->addError($relation,"An error occured during the delete operation of {$relation}");
400 if($this->transactional && $this->transaction) $this->transaction->rollBack();
401 }
402 }
403 }
404 }