Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like SQL_Model often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use SQL_Model, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
23 | class SQL_Model extends Model implements Serializable |
||
24 | { |
||
25 | /** |
||
26 | * Master DSQL record which will be cloned by other operations. |
||
27 | * For low level use only. Use $this->dsql() when in doubt. |
||
28 | * |
||
29 | * @var DB_dsql |
||
30 | */ |
||
31 | protected $dsql; |
||
32 | |||
33 | /** |
||
34 | * Default Field class name |
||
35 | * |
||
36 | * @var string |
||
37 | */ |
||
38 | public $field_class = 'Field'; |
||
39 | |||
40 | /** |
||
41 | * If you wish that alias is used for the table when selected, you can define it here. |
||
42 | * This will help to keep SQL syntax shorter, but will not impact functionality. |
||
43 | * |
||
44 | * @var string |
||
45 | */ |
||
46 | public $table_alias = null; |
||
47 | |||
48 | /** |
||
49 | * @deprecated 4.3.0 Use $table instead |
||
50 | */ |
||
51 | public $entity_code = null; |
||
52 | |||
53 | /** |
||
54 | * Joins |
||
55 | * |
||
56 | * @var array |
||
57 | */ |
||
58 | public $relations = array(); |
||
59 | |||
60 | /** |
||
61 | * Call $model->debug(true|false) to turn on|off debug mode |
||
62 | * |
||
63 | * @var bool |
||
64 | */ |
||
65 | public $debug = false; |
||
66 | |||
67 | /** |
||
68 | * Set to use different database connection |
||
69 | * |
||
70 | * @var DB |
||
71 | */ |
||
72 | public $db = null; |
||
73 | |||
74 | /** |
||
75 | * Set this to true to speed up model, but sacrifice some of the consistency |
||
76 | * |
||
77 | * @var bool |
||
78 | */ |
||
79 | public $fast = null; |
||
80 | |||
81 | /** |
||
82 | * False: finished iterating. True, reset not yet fetched. Object=DSQL |
||
83 | * |
||
84 | * @var bool|DB_dsql |
||
85 | */ |
||
86 | protected $_iterating = false; |
||
87 | |||
88 | |||
89 | |||
90 | // {{{ Basic Functionality, query initialization and actual field handling |
||
91 | |||
92 | /** Initialization of ID field, which must always be defined */ |
||
93 | public function __construct($options = array()) |
||
94 | { |
||
95 | // for compatibility |
||
96 | if ($this->entity_code) { |
||
|
|||
97 | $this->table = $this->entity_code; |
||
98 | unset($this->entity_code); |
||
99 | } |
||
100 | |||
101 | parent::__construct($options); |
||
102 | } |
||
103 | /** |
||
104 | * {@inheritdoc} |
||
105 | */ |
||
106 | public function init() |
||
107 | { |
||
108 | parent::init(); |
||
109 | |||
110 | if (!$this->db) { |
||
111 | $this->db = $this->app->db; |
||
112 | } |
||
113 | |||
114 | if ($this->owner instanceof Field_Reference && !empty($this->owner->owner->relations)) { |
||
115 | $this->relations = &$this->owner->owner->relations; |
||
116 | } |
||
117 | } |
||
118 | |||
119 | /** |
||
120 | * Adds field to model |
||
121 | * |
||
122 | * @param string $name |
||
123 | * @param string $actual_field |
||
124 | * |
||
125 | * @return Field |
||
126 | */ |
||
127 | public function addField($name, $actual_field = null) |
||
128 | { |
||
129 | if ($this->hasElement($name)) { |
||
130 | if ($name == $this->id_field) { |
||
131 | return $this->getElement($name); |
||
132 | } |
||
133 | throw $this->exception('Field with this name is already defined') |
||
134 | ->addMoreInfo('field', $name); |
||
135 | } |
||
136 | if ($name == 'deleted' && isset($this->app->compat)) { |
||
137 | /** @type Field_Deleted $f */ |
||
138 | $f = $this->add('Field_Deleted', $name); |
||
139 | return $f->enum(array('Y', 'N')); |
||
140 | } |
||
141 | |||
142 | // $f=parent::addField($name); |
||
143 | /** @type Field $f */ |
||
144 | $f = $this->add($this->field_class, $name); |
||
145 | // |
||
146 | |||
147 | if (!is_null($actual_field)) { |
||
148 | $f->actual($actual_field); |
||
149 | } |
||
150 | |||
151 | return $f; |
||
152 | } |
||
153 | |||
154 | /** exception() will automatically add information about current model and will allow to turn on "debug" mode */ |
||
155 | public function exception() |
||
156 | { |
||
157 | return call_user_func_array(array('parent', __FUNCTION__), func_get_args()) |
||
158 | ->addThis($this) |
||
159 | ; |
||
160 | } |
||
161 | /** Initializes base query for this model. |
||
162 | * @link http://agiletoolkit.org/doc/modeltable/dsql */ |
||
163 | public function initQuery() |
||
164 | { |
||
165 | if (!$this->table) { |
||
166 | throw $this->exception('$table property must be defined'); |
||
167 | } |
||
168 | $this->dsql = $this->db->dsql(); |
||
169 | $this->dsql->debug($this->debug); |
||
170 | $this->dsql->table($this->table, $this->table_alias); |
||
171 | $this->dsql->default_field = $this->dsql->expr('*,'. |
||
172 | $this->dsql->bt($this->table_alias ?: $this->table).'.'. |
||
173 | $this->dsql->bt($this->id_field)) |
||
174 | ; |
||
175 | $this->dsql->id_field = $this->id_field; |
||
176 | |||
177 | return $this; |
||
178 | } |
||
179 | |||
180 | /** |
||
181 | * Use this instead of accessing dsql directly. |
||
182 | * This will initialize $dsql property if it does not exist yet. |
||
183 | * |
||
184 | * @return DB_dsql |
||
185 | */ |
||
186 | public function _dsql() |
||
194 | |||
195 | /** |
||
196 | * Clone DSQL |
||
197 | */ |
||
198 | public function __clone() |
||
204 | |||
205 | /** |
||
206 | * Produces a clone of Dynamic SQL object configured with table, conditions and joins of this model. |
||
207 | * Use for statements you are going to execute manually. |
||
208 | * |
||
209 | * @return DB_dsql |
||
210 | */ |
||
211 | public function dsql() |
||
215 | |||
216 | /** |
||
217 | * Turns debugging mode on|off for this model. All database operations will be outputed. |
||
218 | * |
||
219 | * @param bool $enabled |
||
220 | * |
||
221 | * @return $this |
||
222 | */ |
||
223 | public function debug($enabled = true) |
||
236 | |||
237 | /** |
||
238 | * Completes initialization of dsql() by adding fields and expressions. |
||
239 | * |
||
240 | * @param array $fields |
||
241 | * |
||
242 | * @return DB_dsql |
||
243 | */ |
||
244 | public function selectQuery($fields = null) |
||
245 | { |
||
246 | /**/$this->app->pr->start('selectQuery/getActualF'); |
||
247 | |||
248 | $actual_fields = $fields ?: $this->getActualFields(); |
||
249 | |||
250 | if ($this->fast && $this->_selectQuery) { |
||
251 | return $this->_selectQuery(); |
||
252 | } |
||
253 | |||
254 | $this->_selectQuery = $select = $this->_dsql()->del('fields'); |
||
255 | |||
256 | /**/$this->app->pr->next('selectQuery/addSystemFields'); |
||
257 | // add system fields into select |
||
258 | foreach ($this->elements as $el) { |
||
259 | if ($el instanceof Field) { |
||
260 | if ($el->system() && !in_array($el->short_name, $actual_fields)) { |
||
261 | $actual_fields[] = $el->short_name; |
||
262 | } |
||
263 | } |
||
264 | } |
||
265 | /**/$this->app->pr->next('selectQuery/updateQuery'); |
||
266 | |||
267 | // add actual fields |
||
268 | foreach ($actual_fields as $field) { |
||
269 | /** @type Field $field */ |
||
270 | $field = $this->hasElement($field); |
||
271 | if (!$field) { |
||
272 | continue; |
||
273 | } |
||
274 | |||
275 | $field->updateSelectQuery($select); |
||
276 | } |
||
277 | /**/$this->app->pr->stop(); |
||
278 | |||
279 | return $select; |
||
280 | } |
||
281 | /** Return query for a specific field. All other fields are ommitted. */ |
||
282 | public function fieldQuery($field) |
||
292 | /** Returns query which selects title field */ |
||
293 | public function titleQuery() |
||
294 | { |
||
295 | $query = $this->dsql()->del('fields'); |
||
296 | /** @type Field $el */ |
||
297 | $el = $this->hasElement($this->title_field); |
||
298 | if ($this->title_field && $el) { |
||
299 | $el->updateSelectQuery($query); |
||
300 | |||
301 | return $query; |
||
302 | } |
||
303 | |||
304 | return $query->field($query->concat('Record #', $this->getElement($this->id_field))); |
||
305 | } |
||
306 | // }}} |
||
307 | |||
308 | // {{{ SQL_Model supports more than just fields. Expressions, References and Joins can be added |
||
309 | |||
310 | /** |
||
311 | * Adds and returns SQL-calculated expression as a read-only field. |
||
312 | * |
||
313 | * See Field_Expression class. |
||
314 | * |
||
315 | * @param string $name |
||
316 | * @param mixed $expression |
||
317 | * |
||
318 | * @return DB_dsql |
||
319 | */ |
||
320 | public function addExpression($name, $expression = null) |
||
321 | { |
||
322 | /** @type Field_Expression $f */ |
||
323 | $f = $this->add('Field_Expression', $name); |
||
324 | |||
325 | return $f->set($expression); |
||
326 | } |
||
327 | |||
328 | /** |
||
329 | * Constructs model from multiple tables. |
||
330 | * Queries will join tables, inserts, updates and deletes will be applied on both tables |
||
331 | */ |
||
332 | public function join( |
||
333 | $foreign_table, |
||
334 | $master_field = null, |
||
335 | $join_kind = null, |
||
336 | $_foreign_alias = null, |
||
337 | $relation = null |
||
338 | ) { |
||
339 | if (!$_foreign_alias) { |
||
340 | $_foreign_alias = '_'.$foreign_table[0]; |
||
341 | } |
||
342 | $_foreign_alias = $this->_unique($this->relations, $_foreign_alias); |
||
343 | |||
344 | /** @type SQL_Relation $rel */ |
||
345 | $rel = $this->add('SQL_Relation', $_foreign_alias); |
||
346 | |||
347 | return $this->relations[$_foreign_alias] = $rel |
||
348 | ->set($foreign_table, $master_field, $join_kind, $relation); |
||
349 | } |
||
350 | |||
351 | /** |
||
352 | * Creates weak join between tables. |
||
353 | * The foreign table may be absent and will not be automatically deleted. |
||
354 | */ |
||
355 | public function leftJoin( |
||
356 | $foreign_table, |
||
357 | $master_field = null, |
||
358 | $join_kind = null, |
||
359 | $_foreign_alias = null, |
||
360 | $relation = null |
||
361 | ) { |
||
362 | if (!$join_kind) { |
||
363 | $join_kind = 'left'; |
||
364 | } |
||
365 | $res = $this->join($foreign_table, $master_field, $join_kind, $_foreign_alias, $relation); |
||
366 | $res->delete_behaviour = 'ignore'; |
||
367 | |||
368 | return $res; |
||
369 | } |
||
370 | /** Defines one to many association */ |
||
371 | public function hasOne($model, $our_field = null, $display_field = null, $as_field = null) |
||
372 | { |
||
373 | |||
374 | // register reference, but don't create any fields there |
||
375 | // parent::hasOne($model,null); |
||
376 | // model, our_field |
||
377 | $this->_references[null] = $model; |
||
378 | |||
379 | if (!$our_field) { |
||
380 | if (!is_object($model)) { |
||
381 | $tmp = $this->app->normalizeClassName($model, 'Model'); |
||
382 | $tmp = new $tmp(); // avoid recursion |
||
383 | } else { |
||
384 | $tmp = $model; |
||
385 | } |
||
386 | $our_field = ($tmp->table).'_id'; |
||
387 | } |
||
388 | |||
389 | /** @type Field_Reference $r */ |
||
390 | $r = $this->add('Field_Reference', array('name' => $our_field, 'dereferenced_field' => $as_field)); |
||
391 | $r->setModel($model, $display_field); |
||
392 | $r->system(true)->editable(true); |
||
393 | |||
394 | return $r; |
||
395 | } |
||
396 | /** Defines many to one association */ |
||
397 | public function hasMany($model, $their_field = null, $our_field = null, $as_field = null) |
||
398 | { |
||
399 | if (!$our_field) { |
||
400 | $our_field = $this->id_field; |
||
401 | } |
||
402 | if (!$their_field) { |
||
403 | $their_field = ($this->table).'_id'; |
||
404 | } |
||
405 | /** @type SQL_Many $rel */ |
||
406 | $rel = $this->add('SQL_Many', $as_field ?: $model); |
||
407 | $rel->set($model, $their_field, $our_field); |
||
408 | |||
409 | return $rel; |
||
410 | } |
||
411 | /** Defines contained model for field */ |
||
412 | View Code Duplication | public function containsOne($field, $model) |
|
413 | { |
||
414 | if (is_array($field) && $field[0]) { |
||
415 | $field['name'] = $field[0]; |
||
416 | unset($field[0]); |
||
417 | } |
||
418 | if ($e = $this->hasElement(is_string($field) ? $field : $field['name'])) { |
||
419 | $e->destroy(); |
||
420 | } |
||
421 | $this->add('Relation_ContainsOne', $field) |
||
422 | ->setModel($model); |
||
423 | } |
||
424 | /** Defines multiple contained models for field */ |
||
425 | View Code Duplication | public function containsMany($field, $model) |
|
437 | /** Traverses references. Use field name for hasOne() relations. Use model name for hasMany() */ |
||
438 | public function ref($name, $load = null) |
||
439 | { |
||
440 | if (!$name) { |
||
441 | return $this; |
||
442 | } |
||
443 | |||
444 | /** @type Field $field */ |
||
445 | $field = $this->getElement($name); |
||
446 | |||
447 | return $field->ref($load); |
||
448 | } |
||
449 | /** Returns Model with SQL join usable for subqueries. */ |
||
450 | public function refSQL($name, $load = null) |
||
451 | { |
||
452 | /** @type Field_Reference $ref */ |
||
453 | $ref = $this->getElement($name); |
||
454 | |||
455 | return $ref->refSQL($load); |
||
456 | } |
||
457 | /** @obsolete - return model referenced by a field. Use model name for one-to-many relations */ |
||
458 | public function getRef($name, $load = null) |
||
462 | /** |
||
463 | * Adds "WHERE" condition / conditions in underlying DSQL. |
||
464 | * |
||
465 | * It tries to be smart about where and how the field is defined. |
||
466 | * |
||
467 | * $field can be passed as: |
||
468 | * - string (field name in this model) |
||
469 | * - Field object |
||
470 | * - DSQL expression |
||
471 | * - array (see note below) |
||
472 | * |
||
473 | * $cond can be passed as: |
||
474 | * - string ('=', '>', '<=', etc.) |
||
475 | * - value can be passed here, then it's used as $value with condition '=' |
||
476 | * |
||
477 | * $value can be passed as: |
||
478 | * - string, integer, boolean or any other simple data type |
||
479 | * - Field object |
||
480 | * - DSQL expreession |
||
481 | * |
||
482 | * NOTE: $field can be passed as array of conditions. Then all conditions |
||
483 | * will be joined with `OR` using DSQLs orExpr method. |
||
484 | * For example, |
||
485 | * $model->addCondition(array( |
||
486 | * array('profit', '=', null), |
||
487 | * array('profit', '<', 1000), |
||
488 | * )); |
||
489 | * will generate "WHERE profit is null OR profit < 1000" |
||
490 | * |
||
491 | * EXAMPLES: |
||
492 | * you can pass [dsql, dsql, dsql ...] and this will be treated |
||
493 | * as (dsql OR dsql OR dsql) ... |
||
494 | * |
||
495 | * you can pass [[field,cond,value], [field,cond,value], ...] and this will |
||
496 | * be treated as (field=value OR field=value OR ...) |
||
497 | * |
||
498 | * BTW, you can mix these too :) |
||
499 | * [[field,cond,value], dsql, [field,cond,value], ...] |
||
500 | * will become (field=value OR dsql OR field=value) |
||
501 | * |
||
502 | * Value also can be DSQL expression, so following will work nicely: |
||
503 | * [dsql,'>',dsql] will become (dsql > dsql) |
||
504 | * [dsql, dsql] will become (dsql = dsql) |
||
505 | * [field, cond, dsql] will become (field = dsql) |
||
506 | * |
||
507 | * @todo Romans: refactor using parent::conditions (through array) |
||
508 | * |
||
509 | * @param mixed $field Field for comparing or array of conditions |
||
510 | * @param mixed $cond Condition |
||
511 | * @param mixed $value Value for comparing |
||
512 | * @param DB_dsql $dsql DSQL object to which conditions will be added |
||
513 | * |
||
514 | * @return $this |
||
515 | */ |
||
516 | public function addCondition($field, $cond = UNDEFINED, $value = UNDEFINED, $dsql = null) |
||
517 | { |
||
518 | // by default add condition to models DSQL |
||
519 | if (!$dsql) { |
||
520 | $dsql = $this->_dsql(); |
||
521 | } |
||
522 | |||
523 | // if array passed, then create multiple conditions joined with OR |
||
524 | if (is_array($field)) { |
||
525 | $or = $this->dsql()->orExpr(); |
||
526 | |||
527 | foreach ($field as $row) { |
||
528 | if (!is_array($row)) { |
||
529 | $row = array($row); |
||
530 | } |
||
531 | // add each condition to OR expression (not models DSQL) |
||
532 | $f = $row[0]; |
||
533 | $c = array_key_exists(1, $row) ? $row[1] : UNDEFINED; |
||
534 | $v = array_key_exists(2, $row) ? $row[2] : UNDEFINED; |
||
535 | |||
536 | // recursively calls addCondition method, but adds conditions |
||
537 | // to OR expression not models DSQL object |
||
538 | $this->addCondition($f, $c, $v, $or); |
||
539 | } |
||
540 | |||
541 | // pass generated DSQL expression as "field" |
||
542 | $field = $or; |
||
543 | $cond = $value = UNDEFINED; |
||
544 | } |
||
545 | |||
546 | // You may pass DSQL expression as a first argument |
||
547 | if ($field instanceof DB_dsql) { |
||
548 | $dsql->where($field, $cond, $value); |
||
549 | |||
550 | return $this; |
||
551 | } |
||
552 | |||
553 | // value should be specified |
||
554 | if ($cond === UNDEFINED && $value === UNDEFINED) { |
||
555 | throw $this->exception('Incorrect condition. Please specify value'); |
||
556 | } |
||
557 | |||
558 | // get model field object |
||
559 | if (!$field instanceof Field) { |
||
560 | $field = $this->getElement($field); |
||
561 | } |
||
562 | |||
563 | /** @type Field $field */ |
||
564 | |||
565 | if ($cond !== UNDEFINED && $value === UNDEFINED) { |
||
566 | $value = $cond; |
||
567 | $cond = '='; |
||
568 | } |
||
569 | if ($field->type() == 'boolean') { |
||
570 | $value = $field->getBooleanValue($value); |
||
571 | } |
||
572 | |||
573 | if ($cond === '=' && !is_array($value)) { |
||
574 | $field->defaultValue($value)->system(true)->editable(false); |
||
575 | } |
||
576 | |||
577 | $f = $field->actual_field ?: $field->short_name; |
||
578 | |||
579 | if ($field instanceof Field_Expression) { |
||
580 | // TODO: should we use expression in where? |
||
581 | |||
582 | $dsql->where($field->getExpr(), $cond, $value); |
||
583 | //$dsql->having($f, $cond, $value); |
||
584 | //$field->updateSelectQuery($this->dsql); |
||
585 | } elseif ($field->relation) { |
||
586 | $dsql->where($field->relation->short_name.'.'.$f, $cond, $value); |
||
587 | } elseif (!empty($this->relations)) { |
||
588 | $dsql->where(($this->table_alias ?: $this->table).'.'.$f, $cond, $value); |
||
589 | } else { |
||
590 | $dsql->where(($this->table_alias ?: $this->table).'.'.$f, $cond, $value); |
||
591 | } |
||
592 | |||
593 | return $this; |
||
594 | } |
||
595 | /** Sets limit on query */ |
||
596 | public function setLimit($count, $offset = null) |
||
602 | /** Sets an order on the field. Field must be properly defined */ |
||
603 | public function setOrder($field, $desc = null) |
||
604 | { |
||
605 | if (!$field instanceof Field) { |
||
606 | if (is_object($field)) { |
||
607 | $this->_dsql()->order($field, $desc); |
||
608 | |||
609 | return $this; |
||
610 | } |
||
611 | |||
612 | View Code Duplication | if (is_string($field) && strpos($field, ',') !== false) { |
|
613 | $field = explode(',', $field); |
||
614 | } |
||
615 | View Code Duplication | if (is_array($field)) { |
|
616 | if (!is_null($desc)) { |
||
617 | throw $this->exception('If first argument is array, second argument must not be used'); |
||
618 | } |
||
619 | |||
620 | foreach (array_reverse($field) as $o) { |
||
621 | $this->setOrder($o); |
||
622 | } |
||
623 | |||
624 | return $this; |
||
625 | } |
||
626 | |||
627 | View Code Duplication | if (is_null($desc) && is_string($field) && strpos($field, ' ') !== false) { |
|
628 | list($field, $desc) = array_map('trim', explode(' ', trim($field), 2)); |
||
629 | } |
||
630 | |||
631 | /** @type Field $field */ |
||
632 | $field = $this->getElement($field); |
||
633 | } |
||
634 | |||
635 | $this->_dsql()->order($field, $desc); |
||
636 | |||
637 | return $this; |
||
638 | } |
||
639 | /** @deprecated use two-argument addCondition. Always keep $field equals to $value for queries and new data */ |
||
640 | public function setMasterField($field, $value) |
||
641 | { |
||
642 | return $this->addCondition($field, $value); |
||
643 | } |
||
644 | // }}} |
||
645 | |||
646 | // {{{ Iterator support |
||
647 | public function rewind() |
||
681 | public function current() |
||
682 | { |
||
683 | return $this; |
||
684 | } |
||
685 | public function key() |
||
689 | public function valid() |
||
690 | { |
||
691 | /* |
||
692 | if(!$this->_iterating){ |
||
693 | $this->next(); |
||
694 | $this->_iterating=$this->selectQuery(); |
||
695 | } |
||
696 | */ |
||
697 | if ($this->_iterating === true) { |
||
698 | $this->next(); |
||
699 | } |
||
700 | |||
701 | return $this->loaded(); |
||
702 | } |
||
703 | |||
704 | // }}} |
||
705 | |||
706 | // {{{ Multiple ways to load data by a model |
||
707 | |||
708 | /** Loads all matching data into array of hashes */ |
||
709 | public function getRows($fields = null) |
||
719 | /** |
||
720 | * Returns dynamic query selecting number of entries in the database. |
||
721 | * |
||
722 | * @param string $alias Optional alias of count expression |
||
723 | * |
||
724 | * @return DB_dsql |
||
725 | */ |
||
726 | public function count($alias = null) |
||
734 | /** |
||
735 | * Returns dynamic query selecting sum of particular field or fields. |
||
736 | * |
||
737 | * @param string|array|Field $field |
||
738 | * |
||
739 | * @return DB_dsql |
||
740 | */ |
||
741 | public function sum($field) |
||
762 | /** @obsolete same as loaded() - returns if any record is currently loaded. */ |
||
763 | public function isInstanceLoaded() |
||
767 | /** Loads the first matching record from the model */ |
||
768 | public function loadAny() |
||
776 | /** Try to load a matching record for the model. Will not raise exception if no records are found */ |
||
777 | public function tryLoadAny() |
||
781 | /** Loads random entry into model */ |
||
782 | public function tryLoadRandom() |
||
792 | public function loadRandom() |
||
793 | { |
||
794 | $this->tryLoadRandom(); |
||
795 | if (!$this->loaded()) { |
||
796 | throw $this->exception('Unable to load random entry'); |
||
797 | } |
||
798 | |||
799 | return $this; |
||
800 | } |
||
801 | /** Try to load a record by specified ID. Will not raise exception if record is not found */ |
||
802 | public function tryLoad($id) |
||
810 | /** Loads record specified by ID. */ |
||
811 | public function load($id) |
||
819 | /** |
||
820 | * Similar to loadAny() but will apply condition before loading. |
||
821 | * Condition is temporary. Fails if record is not loaded. |
||
822 | */ |
||
823 | View Code Duplication | public function loadBy($field, $cond = UNDEFINED, $value = UNDEFINED) |
|
824 | { |
||
825 | $q = $this->dsql; |
||
826 | $this->dsql = $this->dsql(); |
||
827 | $this->addCondition($field, $cond, $value); |
||
828 | $this->loadAny(); |
||
829 | $this->dsql = $q; |
||
830 | |||
831 | return $this; |
||
832 | } |
||
833 | /** Attempt to load using a specified condition, but will not fail if such record is not found */ |
||
834 | View Code Duplication | public function tryLoadBy($field, $cond = UNDEFINED, $value = UNDEFINED) |
|
844 | /** Loads data record and return array of that data. Will not affect currently loaded record. */ |
||
845 | public function getBy($field, $cond = UNDEFINED, $value = UNDEFINED) |
||
858 | /** Internal loading funciton. Do not use. OK to override. */ |
||
859 | protected function _load($id, $ignore_missing = false) |
||
860 | { |
||
861 | /**/$this->app->pr->start('load/selectQuery'); |
||
862 | $this->unload(); |
||
863 | $load = $this->selectQuery(); |
||
864 | /**/$this->app->pr->next('load/clone'); |
||
865 | $p = ''; |
||
866 | if (!empty($this->relations)) { |
||
867 | $p = ($this->table_alias ?: $this->table).'.'; |
||
868 | } |
||
869 | /**/$this->app->pr->next('load/where'); |
||
870 | if (!is_null($id)) { |
||
871 | $load->where($p.$this->id_field, $id); |
||
872 | } |
||
873 | |||
874 | /**/$this->app->pr->next('load/beforeLoad'); |
||
875 | $this->hook('beforeLoad', array($load, $id)); |
||
876 | |||
877 | if (!$this->loaded()) { |
||
878 | /**/$this->app->pr->next('load/get'); |
||
879 | $s = $load->stmt; |
||
880 | $l = $load->args['limit']; |
||
881 | $load->stmt = null; |
||
882 | $data = $load->limit(1)->getHash(); |
||
883 | $load->stmt = $s; |
||
884 | $load->args['limit'] = $l; |
||
885 | |||
886 | if (!is_null($id)) { |
||
887 | array_pop($load->args['where']); |
||
888 | } // remove where condition |
||
889 | /**/$this->app->pr->next('load/ending'); |
||
890 | $this->reset(); |
||
891 | |||
892 | if (@!$data) { |
||
893 | if ($ignore_missing) { |
||
894 | return $this; |
||
895 | } else { |
||
896 | throw $this->exception('Record could not be loaded', 'Exception_NoRecord') |
||
897 | ->addMoreInfo('model', $this) |
||
898 | ->addMoreInfo('id', $id); |
||
899 | } |
||
900 | } |
||
901 | |||
902 | $this->data = $data; // avoid using set() for speed and to avoid field checks |
||
903 | $this->id = $this->data[$this->id_field]; |
||
904 | } |
||
905 | |||
906 | $this->hook('afterLoad'); |
||
907 | /**/$this->app->pr->stop(); |
||
908 | |||
909 | return $this; |
||
910 | } |
||
911 | /** |
||
912 | * @deprecated 4.3.3 Backward-compatible. Will attempt to load but will not fail |
||
913 | */ |
||
914 | public function loadData($id = null) |
||
922 | // }}} |
||
923 | |||
924 | // {{{ Saving Data |
||
925 | /** Save model into database and don't try to load it back */ |
||
926 | public function saveAndUnload() |
||
934 | /** |
||
935 | * Save model into database and try to load it back as a new model of specified class. |
||
936 | * Instance of new class is returned. |
||
937 | */ |
||
938 | public function saveAs($model) |
||
950 | /** |
||
951 | * Save model into database and load it back. |
||
952 | * If for some reason it won't load, whole operation is undone. |
||
953 | */ |
||
954 | public function save() |
||
971 | /** |
||
972 | * Internal function which performs insert of data. Use save() instead. OK to override. |
||
973 | * Will return new object if saveAs() is used. |
||
974 | */ |
||
975 | private function insert() |
||
976 | { |
||
977 | $insert = $this->dsql(); |
||
978 | |||
979 | // Performs the actual database changes. Throw exception if problem occurs |
||
980 | foreach ($this->elements as $name => $f) { |
||
981 | if ($f instanceof Field) { |
||
982 | if (!$f->editable() && !$f->system()) { |
||
983 | continue; |
||
984 | } |
||
985 | if (!isset($this->dirty[$name]) && $f->defaultValue() === null) { |
||
986 | continue; |
||
987 | } |
||
988 | |||
989 | $f->updateInsertQuery($insert); |
||
990 | } |
||
991 | } |
||
992 | $this->hook('beforeInsert', array(&$insert)); |
||
993 | //delayed is not supported by INNODB, but what's worse - it shows error. |
||
994 | //if($this->_save_as===false)$insert->option_insert('delayed'); |
||
995 | $id = $insert->insert(); |
||
996 | if ($id == 0) { |
||
997 | // no auto-increment column present |
||
998 | $id = $this->get($this->id_field); |
||
999 | |||
1000 | if ($id === null && $this->_save_as !== false) { |
||
1001 | throw $this->exception('Please add auto-increment ID column to your table or specify ID manually'); |
||
1002 | } |
||
1003 | } |
||
1004 | $res = $this->hook('afterInsert', array($id)); |
||
1005 | if ($res === false) { |
||
1006 | return $this; |
||
1007 | } |
||
1008 | |||
1009 | if ($this->_save_as === false) { |
||
1010 | return $this->unload(); |
||
1011 | } |
||
1012 | if ($this->_save_as) { |
||
1013 | $this->unload(); |
||
1014 | } |
||
1015 | $o = $this->_save_as ?: $this; |
||
1016 | |||
1017 | if ($this->fast && !$this->_save_as) { |
||
1018 | $this[$this->id_field] = $this->id = $id; |
||
1019 | |||
1020 | return $this; |
||
1021 | } |
||
1022 | $res = $o->tryLoad($id); |
||
1023 | if (!$res->loaded()) { |
||
1024 | throw $this->exception('Saved model did not match conditions. Save aborted.'); |
||
1025 | } |
||
1026 | |||
1027 | return $res; |
||
1028 | } |
||
1029 | /** |
||
1030 | * Internal function which performs modification of existing data. Use save() instead. OK to override. |
||
1031 | * Will return new object if saveAs() is used. |
||
1032 | */ |
||
1033 | private function modify() |
||
1073 | /** |
||
1074 | * @deprecated 4.3.1 Use set() then save(). |
||
1075 | */ |
||
1076 | public function update($data = array()) |
||
1077 | { |
||
1078 | if (!empty($data)) { |
||
1079 | $this->set($data); |
||
1080 | } |
||
1081 | |||
1082 | return $this->save(); |
||
1083 | } |
||
1084 | |||
1085 | // }}} |
||
1086 | |||
1087 | // {{{ Unloading and Deleting data |
||
1088 | |||
1089 | /** forget currently loaded record and it's ID. Will not affect database */ |
||
1090 | public function unload() |
||
1116 | /** Tries to delete record, but does nothing if not found */ |
||
1117 | public function tryDelete($id = null) |
||
1128 | /** Deletes record matching the ID */ |
||
1129 | public function delete($id = null) |
||
1154 | /** Deletes all records matching this model. Use with caution. */ |
||
1155 | public function deleteAll() |
||
1167 | |||
1168 | // }}} |
||
1169 | |||
1170 | // Override all methods to keep back-compatible |
||
1171 | public function set($name, $value = UNDEFINED) |
||
1172 | { |
||
1173 | if (is_array($name)) { |
||
1174 | foreach ($name as $key => $val) { |
||
1175 | $this->set($key, $val); |
||
1176 | } |
||
1177 | |||
1178 | return $this; |
||
1179 | } |
||
1180 | if ($name === false || $name === null) { |
||
1181 | return $this->reset(); |
||
1182 | } |
||
1183 | |||
1184 | // Verify if such a filed exists |
||
1185 | View Code Duplication | if ($this->strict_fields && !$this->hasElement($name)) { |
|
1186 | throw $this->exception('No such field', 'Logic') |
||
1187 | ->addMoreInfo('name', $name); |
||
1188 | } |
||
1189 | |||
1190 | if ($value !== UNDEFINED |
||
1191 | && ( |
||
1192 | is_object($value) |
||
1193 | || is_object($this->data[$name]) |
||
1194 | || is_array($value) |
||
1195 | || is_array($this->data[$name]) |
||
1196 | || (string) $value != (string) $this->data[$name] // this is not nice.. |
||
1197 | || $value !== $this->data[$name] // considers case where value = false and data[$name] = null |
||
1198 | || !isset($this->data[$name]) // considers case where data[$name] is not initialized at all |
||
1199 | // (for example in model using array controller) |
||
1200 | ) |
||
1201 | ) { |
||
1202 | $this->data[$name] = $value; |
||
1203 | $this->setDirty($name); |
||
1204 | } |
||
1205 | |||
1206 | return $this; |
||
1207 | } |
||
1208 | |||
1209 | public function get($name = null) |
||
1210 | { |
||
1211 | if ($name === null) { |
||
1212 | return $this->data; |
||
1213 | } |
||
1214 | |||
1215 | /** @type Field $f */ |
||
1216 | $f = $this->hasElement($name); |
||
1217 | |||
1218 | if ($this->strict_fields && !$f) { |
||
1219 | throw $this->exception('No such field', 'Logic')->addMoreInfo('field', $name); |
||
1220 | } |
||
1221 | |||
1222 | // See if we have data for the field |
||
1223 | View Code Duplication | if (!$this->loaded() && !isset($this->data[$name])) { // && !$this->hasElement($name)) |
|
1224 | |||
1225 | if ($f && $f->has_default_value) { |
||
1226 | return $f->defaultValue(); |
||
1227 | } |
||
1228 | |||
1229 | if ($this->strict_fields) { |
||
1230 | throw $this->exception('Model field was not loaded') |
||
1231 | ->addMoreInfo('id', $this->id) |
||
1232 | ->addMoreinfo('field', $name); |
||
1233 | } |
||
1234 | |||
1235 | return; |
||
1236 | } |
||
1237 | |||
1238 | return $this->data[$name]; |
||
1239 | } |
||
1240 | |||
1241 | public function getActualFields($group = UNDEFINED) |
||
1242 | { |
||
1243 | if ($group === UNDEFINED && !empty($this->actual_fields)) { |
||
1244 | return $this->actual_fields; |
||
1245 | } |
||
1246 | |||
1247 | $fields = array(); |
||
1248 | |||
1249 | if (strpos($group, ',') !== false) { |
||
1250 | $groups = explode(',', $group); |
||
1251 | |||
1252 | foreach ($groups as $group) { |
||
1253 | if ($group[0] == '-') { |
||
1254 | $el = $this->getActualFields(substr($group, 1)); |
||
1255 | $fields = array_diff($fields, $el); |
||
1256 | } else { |
||
1257 | $el = $this->getActualFields($group); |
||
1258 | $fields = array_merge($fields, $el); |
||
1259 | } |
||
1260 | } |
||
1261 | } |
||
1262 | |||
1263 | foreach ($this->elements as $el) { |
||
1264 | if ($el instanceof Field && !$el->hidden()) { |
||
1265 | if ($group === UNDEFINED || |
||
1266 | $el->group() == $group || |
||
1267 | (strtolower($group == 'visible') && $el->visible()) || |
||
1268 | (strtolower($group == 'editable') && $el->editable()) |
||
1269 | ) { |
||
1270 | $fields[] = $el->short_name; |
||
1271 | } |
||
1272 | } |
||
1273 | } |
||
1274 | |||
1275 | return $fields; |
||
1276 | } |
||
1277 | |||
1278 | public function setActualFields(array $fields) |
||
1284 | |||
1285 | public function setDirty($name) |
||
1291 | View Code Duplication | public function isDirty($name) |
|
1292 | { |
||
1293 | /** @type Field $f */ |
||
1294 | $f = $this->getElement($name); |
||
1295 | |||
1296 | return $this->dirty[$name] || |
||
1297 | (!$this->loaded() && $f->has_default_value); |
||
1298 | } |
||
1299 | public function reset() |
||
1303 | |||
1304 | public function offsetExists($name) |
||
1305 | { |
||
1306 | return (bool) $this->hasElement($name); |
||
1307 | } |
||
1308 | public function offsetGet($name) |
||
1320 | |||
1321 | public function setSource($controller, $table = null, $id = null) |
||
1322 | { |
||
1323 | View Code Duplication | if (is_string($controller)) { |
|
1324 | $controller = $this->app->normalizeClassName($controller, 'Data'); |
||
1325 | } elseif (!$controller instanceof Controller_Data) { |
||
1326 | throw $this->exception('Inappropriate Controller. Must extend Controller_Data'); |
||
1327 | } |
||
1328 | |||
1329 | $this->controller = $this->setController($controller); |
||
1330 | /** @type Controller @this->controller */ |
||
1331 | |||
1332 | $this->controller->setSource($this, $table); |
||
1333 | |||
1334 | if ($id) { |
||
1335 | $this->load($id); |
||
1336 | } |
||
1337 | |||
1338 | return $this; |
||
1339 | } |
||
1340 | /** |
||
1341 | * @todo This is something wierd. Method addHooks is not defined anywhere in ATK source |
||
1342 | */ |
||
1343 | View Code Duplication | public function addCache($controller, $table = null, $priority = 5) |
|
1351 | |||
1352 | public function each($callable) |
||
1374 | |||
1375 | public function newField($name) |
||
1387 | public function _ref($ref, $class, $field, $val) |
||
1388 | { |
||
1389 | /** @type Model $m */ |
||
1390 | $m = $this->add($this->app->normalizeClassName($class, 'Model')); |
||
1391 | $m = $m->ref($ref); |
||
1392 | |||
1393 | // For one to many relation, create condition, otherwise do nothing, |
||
1394 | // as load will follow |
||
1395 | if ($field) { |
||
1396 | $m->addCondition($field, $val); |
||
1397 | } |
||
1398 | |||
1399 | return $m; |
||
1400 | } |
||
1401 | |||
1402 | /** |
||
1403 | * Strange method. Uses undefined $field variable, undefined refBind() method etc. |
||
1404 | * https://github.com/atk4/atk4/issues/711 |
||
1405 | */ |
||
1406 | public function _refBind($field_in, $expression, $field_out = null) |
||
1459 | |||
1460 | public function serialize() |
||
1467 | |||
1468 | public function unserialize($data) |
||
1474 | } |
||
1475 |
This property has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.