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