SQL_Model   F
last analyzed

Complexity

Total Complexity 261

Size/Duplication

Total Lines 1452
Duplicated Lines 5.79 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 0
Metric Value
dl 84
loc 1452
rs 3.5897
c 0
b 0
f 0
wmc 261
lcom 1
cbo 11

78 Methods

Rating   Name   Duplication   Size   Complexity  
A _dsql() 0 8 2
A __clone() 0 6 2
A dsql() 0 4 1
A debug() 0 13 3
A fieldQuery() 0 10 2
B containsMany() 9 12 5
A getRef() 0 4 1
A setLimit() 0 6 1
A rewind() 0 4 1
A _preexec() 0 7 1
A next() 0 23 3
A key() 0 4 1
A getRows() 0 10 1
A count() 0 8 1
A sum() 0 21 4
A isInstanceLoaded() 0 4 1
A loadAny() 0 8 2
A tryLoadAny() 0 4 1
A tryLoadRandom() 0 10 2
A tryLoad() 0 8 2
A load() 0 8 2
A tryLoadBy() 8 10 1
A getBy() 0 13 1
A loadData() 0 8 2
A saveAndUnload() 0 8 1
A saveAs() 0 12 2
A save() 0 17 2
D modify() 0 40 10
B unload() 0 26 4
A tryDelete() 0 11 3
B delete() 0 25 3
A deleteAll() 0 12 1
A setActualFields() 0 6 1
A setDirty() 0 6 1
A reset() 0 4 1
A offsetGet() 0 4 1
A offsetSet() 0 4 1
A offsetUnset() 0 4 1
A addCache() 8 8 1
B each() 0 22 6
A newField() 0 4 1
A hasField() 0 4 1
A getField() 0 4 1
C _refBind() 0 53 9
A serialize() 0 7 1
A unserialize() 0 6 1
A leftJoin() 0 15 2
A loadRandom() 0 9 2
A __construct() 0 10 2
A init() 0 12 4
B addField() 0 26 6
A exception() 0 6 1
A initQuery() 0 16 3
D selectQuery() 0 37 10
A titleQuery() 0 13 3
A addExpression() 0 7 1
A join() 0 18 2
B hasOne() 0 25 3
A hasMany() 0 14 4
B containsOne() 9 12 5
A ref() 0 11 2
A refSQL() 0 7 1
F addCondition() 0 79 22
C setOrder() 13 36 11
A setMasterField() 0 4 1
A current() 0 4 1
A valid() 0 14 2
A loadBy() 8 10 1
C _load() 0 52 8
C insert() 0 54 17
A update() 0 8 2
C set() 2 37 15
D get() 14 31 9
C getActualFields() 0 36 15
A isDirty() 8 8 3
A offsetExists() 0 4 1
A setSource() 5 19 4
A _ref() 0 14 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

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 Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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
2
/**
3
 * Implementation of a Relational SQL-backed Model.
4
 *
5
 * SQL_Model allows you to take advantage of relational SQL database without neglecting
6
 * powerful functionality of your RDBMS. On top of basic load/save/delete operations, you can
7
 * pefrorm multi-row operations, traverse relations, or use SQL expressions
8
 *
9
 * The $table property of SQL_Model always contains the primary table. The $this->id will
10
 * always correspond with ID field of that table and when inserting record will always be
11
 * placed inside primary table first.
12
 *
13
 * Use:
14
 * class Model_User extends SQL_Model {
15
 *     public $table='user';
16
 *     function init(){
17
 *         parent::init();
18
 *         $this->addField('name');
19
 *         $this->addField('email');
20
 *     }
21
 * }
22
 */
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) {
0 ignored issues
show
Deprecated Code introduced by
The property SQL_Model::$entity_code has been deprecated with message: 4.3.0 Use $table instead

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.

Loading history...
97
            $this->table = $this->entity_code;
0 ignored issues
show
Deprecated Code introduced by
The property SQL_Model::$entity_code has been deprecated with message: 4.3.0 Use $table instead

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.

Loading history...
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()
187
    {
188
        if (!$this->dsql) {
189
            $this->initQuery();
190
        }
191
192
        return $this->dsql;
193
    }
194
195
    /**
196
     * Clone DSQL
197
     */
198
    public function __clone()
199
    {
200
        if (is_object($this->dsql)) {
201
            $this->dsql = clone $this->dsql;
202
        }
203
    }
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()
212
    {
213
        return clone $this->_dsql();
214
    }
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)
224
    {
225
        if ($enabled === true) {
226
            $this->debug = $enabled;
227
            if ($this->dsql) {
228
                $this->dsql->debug($enabled);
229
            }
230
        } else {
231
            parent::debug($enabled);
232
        }
233
234
        return $this;
235
    }
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) {
0 ignored issues
show
Bug introduced by
The property _selectQuery does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
251
            return $this->_selectQuery();
0 ignored issues
show
Bug introduced by
The method _selectQuery() does not exist on SQL_Model. Did you maybe mean selectQuery()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
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)
283
    {
284
        $query = $this->dsql()->del('fields');
285
        if (is_string($field)) {
286
            $field = $this->getElement($field);
287
        }
288
        $field->updateSelectQuery($query);
289
290
        return $query;
291
    }
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)
426
    {
427
        if (is_array($field) && $field[0]) {
428
            $field['name'] = $field[0];
429
            unset($field[0]);
430
        }
431
        if ($e = $this->hasElement(is_string($field) ? $field : $field['name'])) {
432
            $e->destroy();
433
        }
434
        $this->add('Relation_ContainsMany', $field)
435
            ->setModel($model);
436
    }
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);
0 ignored issues
show
Documentation Bug introduced by
The method ref does not exist on object<Field>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
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)
459
    {
460
        return $this->ref($name, $load);
461
    }
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)
597
    {
598
        $this->_dsql()->limit($count, $offset);
599
600
        return $this;
601
    }
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()
648
    {
649
        $this->_iterating = true;
650
    }
651
    public function _preexec()
652
    {
653
        $this->_iterating = $this->selectQuery();
654
        $this->hook('beforeLoad', array($this->_iterating));
655
656
        return $this->_iterating;
657
    }
658
    public function next()
659
    {
660
        if ($this->_iterating === true) {
661
            $this->_iterating = $this->selectQuery();
662
            $this->_iterating->stmt = null;
663
            $this->_iterating->rewind();
664
            $this->hook('beforeLoad', array($this->_iterating));
665
        }
666
        $this->_iterating->next();
667
        $this->data = $this->_iterating->current();
668
669
        if ($this->data === false) {
670
            $this->unload();
671
            $this->_iterating = false;
672
673
            return;
674
        }
675
676
        $this->id = @$this->data[$this->id_field];
677
        $this->dirty = array();
678
679
        $this->hook('afterLoad');
680
    }
681
    public function current()
682
    {
683
        return $this;
684
    }
685
    public function key()
686
    {
687
        return $this->id;
688
    }
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)
710
    {
711
        /**/$this->app->pr->start('getRows/selecting');
712
        $a = $this->selectQuery($fields);
713
        /**/$this->app->pr->next('getRows/fetching');
714
        $a = $a->get();
715
        $this->app->pr->stop();
716
717
        return $a;
718
    }
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)
727
    {
728
        // prepare new query
729
        $q = $this->dsql()->del('fields')->del('order');
730
731
        // add expression field to query
732
        return $q->field($q->count(), $alias);
733
    }
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)
742
    {
743
        // prepare new query
744
        $q = $this->dsql()->del('fields')->del('order');
745
746
        // put field in array if it's not already
747
        if (!is_array($field)) {
748
            $field = array($field);
749
        }
750
751
        // add all fields to query
752
        foreach ($field as $f) {
753
            if (!is_object($f)) {
754
                $f = $this->getElement($f);
755
            }
756
            $q->field($q->sum($f), $f->short_name);
757
        }
758
759
        // return query
760
        return $q;
761
    }
762
    /** @obsolete same as loaded() - returns if any record is currently loaded. */
763
    public function isInstanceLoaded()
764
    {
765
        return $this->loaded();
766
    }
767
    /** Loads the first matching record from the model */
768
    public function loadAny()
769
    {
770
        try {
771
            return $this->_load(null);
772
        } catch (Exception_NoRecord $e) {
773
            throw $this->exception('No matching records found', null, 404);
774
        }
775
    }
776
    /** Try to load a matching record for the model. Will not raise exception if no records are found */
777
    public function tryLoadAny()
778
    {
779
        return $this->_load(null, true);
780
    }
781
    /** Loads random entry into model */
782
    public function tryLoadRandom()
783
    {
784
        // get ID first
785
        $id = $this->dsql()->order('rand()')->limit(1)->field($this->getElement($this->id_field))->getOne();
786
        if ($id) {
787
            $this->load($id);
788
        }
789
790
        return $this;
791
    }
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)
803
    {
804
        if (is_null($id)) {
805
            throw $this->exception('Record ID must be specified, otherwise use loadAny()');
806
        }
807
808
        return $this->_load($id, true);
809
    }
810
    /** Loads record specified by ID. */
811
    public function load($id)
812
    {
813
        if (is_null($id)) {
814
            throw $this->exception('Record ID must be specified, otherwise use loadAny()');
815
        }
816
817
        return $this->_load($id);
818
    }
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)
835
    {
836
        $q = $this->dsql;
837
        $this->dsql = $this->dsql();
838
        $this->addCondition($field, $cond, $value);
839
        $this->tryLoadAny();
840
        $this->dsql = $q;
841
842
        return $this;
843
    }
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)
846
    {
847
        $data = $this->data;
848
        $id = $this->id;
849
850
        $this->tryLoadBy($field, $cond, $value);
851
        $row = $this->data;
852
853
        $this->data = $data;
854
        $this->id = $id;
855
856
        return $row;
857
    }
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)
915
    {
916
        if ($id) {
917
            $this->tryLoad($id);
918
        }
919
920
        return $this;
921
    }
922
    // }}}
923
924
    // {{{ Saving Data
925
    /** Save model into database and don't try to load it back */
926
    public function saveAndUnload()
927
    {
928
        $this->_save_as = false;
929
        $this->save();
930
        $this->_save_as = null;
931
932
        return $this;
933
    }
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)
939
    {
940
        if (is_string($model)) {
941
            $model = $this->app->normalizeClassName($model, 'Model');
942
            $model = $this->add($model);
943
        }
944
        $this->_save_as = $model;
945
        $res = $this->save();
946
        $this->_save_as = null;
947
948
        return $res;
949
    }
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()
955
    {
956
        $this->_dsql()->owner->beginTransaction();
957
        $this->hook('beforeSave');
958
959
        // decide, insert or modify
960
        if ($this->loaded()) {
961
            $res = $this->modify();
962
        } else {
963
            $res = $this->insert();
964
        }
965
966
        $res->hook('afterSave');
967
        $this->_dsql()->owner->commit();
968
969
        return $res;
970
    }
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()
1034
    {
1035
        $modify = $this->dsql()->del('where');
1036
        $modify->where($this->getElement($this->id_field), $this->id);
1037
1038
        if (empty($this->dirty)) {
1039
            return $this;
1040
        }
1041
1042
        foreach ($this->dirty as $name => $junk) {
1043
            if ($el = $this->hasElement($name)) {
1044
                if ($el instanceof Field) {
1045
                    $el->updateModifyQuery($modify);
1046
                }
1047
            }
1048
        }
1049
1050
        // Performs the actual database changes. Throw exceptions if problem occurs
1051
        $this->hook('beforeModify', array($modify));
1052
        if ($modify->args['set']) {
1053
            $modify->update();
1054
        }
1055
1056
        if ($this->dirty[$this->id_field]) {
1057
            $this->id = $this->get($this->id_field);
1058
        }
1059
1060
        $this->hook('afterModify');
1061
1062
        if ($this->_save_as === false) {
1063
            return $this->unload();
1064
        }
1065
        $id = $this->id;
1066
        if ($this->_save_as) {
1067
            $this->unload();
1068
        }
1069
        $o = $this->_save_as ?: $this;
1070
1071
        return $o->load($id);
1072
    }
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()
1091
    {
1092
        if ($this->_save_later) {
1093
            $this->_save_later = false;
1094
            $this->saveAndUnload();
1095
        }
1096
        $this->hook('beforeUnload');
1097
        $this->id = null;
1098
        // parent::unload();
1099
1100
        if ($this->_save_later) {
1101
            $this->_save_later = false;
1102
            $this->saveAndUnload();
1103
        }
1104
        if ($this->loaded()) {
1105
            $this->hook('beforeUnload');
1106
        }
1107
        $this->data = $this->dirty = array();
1108
        $this->id = null;
1109
        $this->hook('afterUnload');
1110
        //
1111
1112
        $this->hook('afterUnload');
1113
1114
        return $this;
1115
    }
1116
    /** Tries to delete record, but does nothing if not found */
1117
    public function tryDelete($id = null)
1118
    {
1119
        if (!is_null($id)) {
1120
            $this->tryLoad($id);
1121
        }
1122
        if ($this->loaded()) {
1123
            $this->delete();
1124
        }
1125
1126
        return $this;
1127
    }
1128
    /** Deletes record matching the ID */
1129
    public function delete($id = null)
1130
    {
1131
        if (!is_null($id)) {
1132
            $this->load($id);
1133
        }
1134
        if (!$this->loaded()) {
1135
            throw $this->exception('Unable to determine which record to delete');
1136
        }
1137
1138
        $tmp = $this->dsql;
1139
1140
        $this->initQuery();
1141
        $delete = $this->dsql->where($this->id_field, $this->id);
1142
1143
        $delete->owner->beginTransaction();
1144
        $this->hook('beforeDelete', array($delete));
1145
        $delete->delete();
1146
        $this->hook('afterDelete');
1147
        $delete->owner->commit();
1148
1149
        $this->dsql = $tmp;
1150
        $this->unload();
1151
1152
        return $this;
1153
    }
1154
    /** Deletes all records matching this model. Use with caution. */
1155
    public function deleteAll()
1156
    {
1157
        $delete = $this->dsql();
1158
        $delete->owner->beginTransaction();
1159
        $this->hook('beforeDeleteAll', array($delete));
1160
        $delete->delete();
1161
        $this->hook('afterDeleteAll');
1162
        $delete->owner->commit();
1163
        $this->reset();
1164
1165
        return $this;
1166
    }
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)
1279
    {
1280
        $this->actual_fields = $fields;
1281
1282
        return $this;
1283
    }
1284
1285
    public function setDirty($name)
1286
    {
1287
        $this->dirty[$name] = true;
1288
1289
        return $this;
1290
    }
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()
1300
    {
1301
        return $this->unload();
1302
    }
1303
1304
    public function offsetExists($name)
1305
    {
1306
        return (bool) $this->hasElement($name);
1307
    }
1308
    public function offsetGet($name)
1309
    {
1310
        return $this->get($name);
1311
    }
1312
    public function offsetSet($name, $val)
1313
    {
1314
        $this->set($name, $val);
1315
    }
1316
    public function offsetUnset($name)
1317
    {
1318
        unset($this->dirty[$name]);
1319
    }
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);
0 ignored issues
show
Documentation Bug introduced by
The method setSource does not exist on object<AbstractController>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
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)
1344
    {
1345
        $controller = $this->app->normalizeClassName($controller, 'Data');
1346
1347
        return $this->setController($controller)
0 ignored issues
show
Documentation Bug introduced by
The method addHooks does not exist on object<AbstractController>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1348
            ->addHooks($this, $priority)
1349
            ->setSource($this, $table);
1350
    }
1351
1352
    public function each($callable)
1353
    {
1354
        if (!($this instanceof Iterator)) {
1355
            throw $this->exception('Calling each() on non-iterative model');
1356
        }
1357
1358
        if (is_string($callable)) {
1359
            foreach ($this as $value) {
1360
                $this->$callable();
1361
            }
1362
1363
            return $this;
1364
        }
1365
1366
        foreach ($this as $value) {
1367
            if (call_user_func($callable, $this) === false) {
1368
                break;
1369
            }
1370
        }
1371
1372
        return $this;
1373
    }
1374
1375
    public function newField($name)
1376
    {
1377
        return $this->addField($name);
1378
    }
1379
    public function hasField($name)
1380
    {
1381
        return $this->hasElement($name);
1382
    }
1383
    public function getField($f)
1384
    {
1385
        return $this->getElement($f);
1386
    }
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)
1407
    {
1408
        if ($this->controller) {
1409
            return $this->controller->refBind($this, $field, $expression);
0 ignored issues
show
Bug introduced by
The variable $field does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Documentation Bug introduced by
The method refBind does not exist on object<Controller_Data>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1410
        }
1411
1412
        list($myref, $rest) = explode('/', $ref, 2);
0 ignored issues
show
Bug introduced by
The variable $ref does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
1413
1414
        if (!$this->_references[$myref]) {
1415
            throw $this->exception('No such relation')
1416
            ->addMoreInfo('ref', $myref)
1417
            ->addMoreInfo('rest', $rest);
1418
        }
1419
        // Determine and populate related model
1420
1421
        if (is_array($this->_references[$myref])) {
1422
            $m = $this->_references[$myref][0];
1423
        } else {
1424
            $m = $this->_references[$myref];
1425
        }
1426
        $m = $this->add($m);
1427
        if ($rest) {
1428
            $m = $m->_ref($rest);
0 ignored issues
show
Documentation Bug introduced by
The method _ref does not exist on object<AbstractObject>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1429
        }
1430
        $this->_refGlue();
0 ignored issues
show
Documentation Bug introduced by
The method _refGlue does not exist on object<SQL_Model>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1431
1432
        if (!isset($this->_references[$ref])) {
1433
            throw $this->exception('Unable to traverse, no reference defined by this name')
1434
            ->addMoreInfo('name', $ref);
1435
        }
1436
1437
        $r = $this->_references[$ref];
1438
1439
        if (is_array($r)) {
1440
            list($m, $our_field, $their_field) = $r;
1441
1442
            if (is_string($m)) {
1443
                $m = $this->add($m);
1444
            } else {
1445
                $m = $m->newInstance();
1446
            }
1447
1448
            return $m->addCondition($their_field, $this[$our_field]);
1449
        }
1450
1451
        if (is_string($m)) {
1452
            $m = $this->add($m);
1453
        } else {
1454
            $m = $m->newInstance();
1455
        }
1456
1457
        return $m->load($this[$our_field]);
0 ignored issues
show
Bug introduced by
The variable $our_field seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1458
    }
1459
1460
    public function serialize()
1461
    {
1462
        return serialize(array(
1463
            'id' => $this->id,
1464
            'data' => $this->data,
1465
        ));
1466
    }
1467
1468
    public function unserialize($data)
1469
    {
1470
        $data = unserialize($data);
1471
        $this->id = $data['id'];
1472
        $this->data = $data['data'];
1473
    }
1474
}
1475