Completed
Push — master ( 664179...bf47ef )
by Lars
02:51
created

ActiveRecord::__call()   C

Complexity

Conditions 8
Paths 8

Size

Total Lines 38
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 8.4423

Importance

Changes 0
Metric Value
dl 0
loc 38
ccs 17
cts 21
cp 0.8095
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 20
nc 8
nop 2
crap 8.4423
1
<?php
2
3
namespace voku\db;
4
5
use Arrayy\Arrayy;
6
use voku\db\exceptions\ActiveRecordException;
7
use voku\db\exceptions\FetchingException;
8
9
/**
10
 * A simple implement of active record via Arrayy.
11
 *
12
 * @method $this select(string $dbProperty)
13
 * @method $this eq(string $dbProperty, string | null $value = null)
14
 * @method $this from(string $table)
15
 * @method $this where(string $where)
16
 * @method $this having(string $having)
17
 * @method $this limit(int $start, int | null $end = null)
18
 *
19
 * @method $this equal(string $dbProperty, string $value)
20
 * @method $this notEqual(string $dbProperty, string $value)
21
 * @method $this ne(string $dbProperty, string $value)
22
 * @method $this greaterThan(string $dbProperty, int $value)
23
 * @method $this gt(string $dbProperty, int $value)
24
 * @method $this lessThan(string $dbProperty, int $value)
25
 * @method $this lt(string $dbProperty, int $value)
26
 * @method $this greaterThanOrEqual(string $dbProperty, int $value)
27
 * @method $this ge(string $dbProperty, int $value)
28
 * @method $this gte(string $dbProperty, int $value)
29
 * @method $this lessThanOrEqual(string $dbProperty, int $value)
30
 * @method $this le(string $dbProperty, int $value)
31
 * @method $this lte(string $dbProperty, int $value)
32
 * @method $this between(string $dbProperty, array $value)
33
 * @method $this like(string $dbProperty, string $value)
34
 * @method $this in(string $dbProperty, array $value)
35
 * @method $this notIn(string $dbProperty, array $value)
36
 * @method $this isnull(string $dbProperty)
37
 * @method $this isNotNull(string $dbProperty)
38
 * @method $this notNull(string $dbProperty)
39
 */
40
abstract class ActiveRecord extends Arrayy
41
{
42
  /**
43
   * @var DB static
44
   */
45
  protected static $db;
46
47
  /**
48
   * @var array <p>Mapping the function name and the operator, to build Expressions in WHERE condition.</p>
49
   *
50
   * call the function like this:
51
   * <pre>
52
   *   $user->isNotNull()->eq('id', 1);
53
   * </pre>
54
   *
55
   * the result in SQL:
56
   * <pre>
57
   *   WHERE user.id IS NOT NULL AND user.id = :ph1
58
   * </pre>
59
   */
60
  protected static $operators = array(
61
      'equal'              => '=',
62
      'eq'                 => '=',
63
      'notequal'           => '<>',
64
      'ne'                 => '<>',
65
      'greaterthan'        => '>',
66
      'gt'                 => '>',
67
      'lessthan'           => '<',
68
      'lt'                 => '<',
69
      'greaterthanorequal' => '>=',
70
      'ge'                 => '>=',
71
      'gte'                => '>=',
72
      'lessthanorequal'    => '<=',
73
      'le'                 => '<=',
74
      'lte'                => '<=',
75
      'between'            => 'BETWEEN',
76
      'like'               => 'LIKE',
77
      'in'                 => 'IN',
78
      'notin'              => 'NOT IN',
79
      'isnull'             => 'IS NULL',
80
      'isnotnull'          => 'IS NOT NULL',
81
      'notnull'            => 'IS NOT NULL',
82
  );
83
84
  /**
85
   * @var array <p>Part of the SQL, mapping the function name and the operator to build SQL Part.</p>
86
   *
87
   * <br />
88
   *
89
   * call the function like this:
90
   * <pre>
91
   *      $user->orderBy('id DESC', 'name ASC')->limit(2, 1);
92
   * </pre>
93
   *
94
   * the result in SQL:
95
   * <pre>
96
   *      ORDER BY id DESC, name ASC LIMIT 2,1
97
   * </pre>
98
   */
99
  protected $sqlParts = array(
100
      'select' => 'SELECT',
101
      'from'   => 'FROM',
102
      'set'    => 'SET',
103
      'where'  => 'WHERE',
104
      'group'  => 'GROUP BY',
105
      'having' => 'HAVING',
106
      'order'  => 'ORDER BY',
107
      'limit'  => 'LIMIT',
108
      'top'    => 'TOP',
109
  );
110
111
  /**
112
   * @var array <p>The default sql expressions values.</p>
113
   */
114
  protected $defaultSqlExpressions = array(
115
      'expressions' => array(),
116
      'wrap'        => false,
117
      'select'      => null,
118
      'insert'      => null,
119
      'update'      => null,
120
      'set'         => null,
121
      'delete'      => 'DELETE ',
122
      'join'        => null,
123
      'from'        => null,
124
      'values'      => null,
125
      'where'       => null,
126
      'having'      => null,
127
      'limit'       => null,
128
      'order'       => null,
129
      'group'       => null,
130
  );
131
132
  /**
133
   * @var array <p>Stored the Expressions of the SQL.</p>
134
   */
135
  protected $sqlExpressions = array();
136
137
  /**
138
   * @var string <p>The table name in database.</p>
139
   */
140
  protected $table;
141
142
  /**
143
   * @var string  <p>The primary key of this ActiveRecord, just support single primary key.</p>
144
   */
145
  protected $primaryKeyName = 'id';
146
147
  /**
148
   * @var array <p>Stored the dirty data of this object, when call "insert" or "update" function, will write this data
149
   *      into database.</p>
150
   */
151
  protected $dirty = array();
152
153
  /**
154
   * @var bool
155
   */
156
  protected static $new_data_are_dirty = true;
157
158
  /**
159
   * @var array <p>Stored the params will bind to SQL when call DB->query().</p>
160
   */
161
  protected $params = array();
162
163
  /**
164
   * @var ActiveRecordExpressions[] <p>Stored the configure of the relation, or target of the relation.</p>
165
   */
166
  protected $relations = array();
167
168
  /**
169
   * @var int <p>The count of bind params, using this count and const "PREFIX" (:ph) to generate place holder in SQL.</p>
170
   */
171
  private static $count = 0;
172
173
  const BELONGS_TO = 'belongs_to';
174
  const HAS_MANY   = 'has_many';
175
  const HAS_ONE    = 'has_one';
176
177
  const PREFIX = ':active_record';
178
179
  /**
180
   * @return array
181
   */
182
  public function getParams()
183
  {
184
    return $this->params;
185
  }
186
187
  /**
188
   * @return string
189
   */
190
  public function getPrimaryKeyName()
191
  {
192
    return $this->primaryKeyName;
193
  }
194
195
  /**
196
   * @return mixed|null
197
   */
198 13
  public function getPrimaryKey()
199
  {
200 13
    $id = $this->{$this->primaryKeyName};
201 13
    if ($id) {
202 12
      return $id;
203
    }
204
205 1
    return null;
206
  }
207
208
  /**
209
   * @param mixed $primaryKey
210
   * @param bool  $dirty
211
   *
212
   * @return $this
213
   */
214 1
  public function setPrimaryKey($primaryKey, $dirty = true)
215
  {
216 1
    if ($dirty === true) {
217 1
      $this->dirty[$this->primaryKeyName] = $primaryKey;
218 1
    } else {
219
      $this->array[$this->primaryKeyName] = $primaryKey;
220
    }
221
222 1
    return $this;
223
  }
224
225
  /**
226
   * @return string
227
   */
228
  public function getTable()
229
  {
230
    return $this->table;
231
  }
232
233
  /**
234
   * Function to reset the $params and $sqlExpressions.
235
   *
236
   * @return $this
237
   */
238 23
  public function reset()
239
  {
240 23
    $this->params = array();
241 23
    $this->sqlExpressions = array();
242
243 23
    return $this;
244
  }
245
246
  /**
247
   * Reset the dirty data.
248
   *
249
   * @return $this
250
   */
251 6
  public function resetDirty()
252
  {
253 6
    $this->dirty = array();
254
255 6
    return $this;
256
  }
257
258
  /**
259
   * set the DB connection.
260
   *
261
   * @param DB $db
262
   */
263
  public static function setDb($db)
264
  {
265
    self::$db = $db;
266
  }
267
268
  /**
269
   * Function to find one record and assign in to current object.
270
   *
271
   * @param mixed $id <p>
272
   *                  If call this function using this param, we will find the record by using this id.
273
   *                  If not set, just find the first record in database.
274
   *                  </p>
275
   *
276
   * @return false|$this <p>
277
   *                     If we could find the record, assign in to current object and return it,
278
   *                     otherwise return "false".
279
   *                     </p>
280
   */
281 11 View Code Duplication
  public function fetch($id = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
282
  {
283 11
    if ($id) {
284 6
      $this->reset()->eq($this->primaryKeyName, $id);
285 6
    }
286
287 11
    return self::query(
288 11
        $this->limit(1)->_buildSql(
0 ignored issues
show
Bug introduced by
The call to limit() misses a required argument $|.

This check looks for function calls that miss required arguments.

Loading history...
289
            array(
290 11
                'select',
291 11
                'from',
292 11
                'join',
293 11
                'where',
294 11
                'group',
295 11
                'having',
296 11
                'order',
297 11
                'limit',
298
            )
299 11
        ),
300 11
        $this->params,
301 11
        $this->reset(),
302
        true
303 11
    );
304
  }
305
306
  /**
307
   * @param string $query
308
   *
309
   * @return $this[]
310
   */
311 1
  public function fetchManyByQuery($query)
312
  {
313 1
    $list = $this->fetchByQuery($query);
314
315 1
    if (!$list || empty($list)) {
316
      return array();
317
    }
318
319 1
    return $list;
320
  }
321
322
  /**
323
   * @param string $query
324
   *
325
   * @return $this|null
326
   */
327 1
  public function fetchOneByQuery($query)
328
  {
329 1
    $list = $this->fetchByQuery($query);
330
331 1
    if (!$list || empty($list)) {
332
      return null;
333
    }
334
335 1
    if (is_array($list) && count($list) > 0) {
336 1
      $this->array = $list[0]->getArray();
337 1
    } else {
338
      $this->array = $list->getArray();
339
    }
340
341 1
    return $this;
342
  }
343
344
  /**
345
   * @param mixed $id
346
   *
347
   * @return $this
348
   *
349
   * @throws FetchingException <p>Will be thrown, if we can not find the id.</p>
350
   */
351 2
  public function fetchById($id)
352
  {
353 2
    $obj = $this->fetchByIdIfExists($id);
354 2
    if ($obj === null) {
355 1
      throw new FetchingException("No row with primary key '$id' in table '$this->table'.");
356
    }
357
358 1
    return $obj;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $obj; (boolean|voku\db\ActiveRecord|array) is incompatible with the return type documented by voku\db\ActiveRecord::fetchById of type voku\db\ActiveRecord.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
359
  }
360
361
  /**
362
   * @param mixed $id
363
   *
364
   * @return $this|null
365
   */
366 4
  public function fetchByIdIfExists($id)
367
  {
368 4
    $list = $this->fetch($id);
369
370 4
    if (!$list || $list->isEmpty()) {
371 2
      return null;
372
    }
373
374 2
    return $list;
375
  }
376
377
  /**
378
   * @param array $ids
379
   *
380
   * @return $this[]
381
   */
382 2
  public function fetchByIds(array $ids)
383
  {
384 2
    if (empty($ids)) {
385
      return array();
386
    }
387
388 2
    $list = $this->fetchAll($ids);
389 2
    if (is_array($list) && count($list) > 0) {
390 1
      return $list;
391
    }
392
393 1
    return array();
394
  }
395
396
  /**
397
   * @param string $query
398
   *
399
   * @return $this[]|$this
400
   */
401 2
  public function fetchByQuery($query)
402
  {
403 2
    $list = self::query(
404 2
        $query,
405 2
        $this->params,
406 2
        $this->reset()
407 2
    );
408
409 2
    if (is_array($list)) {
410 2
      if (count($list) === 0) {
411
        return array();
412
      }
413
414 2
      return $list;
415
    }
416
417
    $this->array = $list->getArray();
418
419
    return $this;
420
  }
421
422
  /**
423
   * @param array $ids
424
   *
425
   * @return $this[]
426
   */
427 1
  public function fetchByIdsPrimaryKeyAsArrayIndex(array $ids)
428
  {
429 1
    $result = $this->fetchAll($ids);
430
431 1
    $resultNew = array();
432 1
    foreach ($result as $item) {
0 ignored issues
show
Bug introduced by
The expression $result of type boolean|object<voku\db\ActiveRecord>|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
433 1
      $resultNew[$item->getPrimaryKey()] = $item;
434 1
    }
435
436 1
    return $resultNew;
437
  }
438
439
  /**
440
   * Function to find all records in database.
441
   *
442
   * @param array|null $ids <p>
443
   *                        If call this function using this param, we will find the record by using this id's.
444
   *                        If not set, just find all records in database.
445
   *                        </p>
446
   *
447
   * @return $this[]
448
   */
449 6 View Code Duplication
  public function fetchAll(array $ids = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
450
  {
451 6
    if ($ids) {
452 3
      $this->reset()->in($this->primaryKeyName, $ids);
453 3
    }
454
455 6
    return self::query(
456 6
        $this->_buildSql(
457
            array(
458 6
                'select',
459 6
                'from',
460 6
                'join',
461 6
                'where',
462 6
                'groupBy',
463 6
                'having',
464 6
                'orderBy',
465 6
                'limit',
466
            )
467 6
        ),
468 6
        $this->params,
469 6
        $this->reset()
470 6
    );
471
  }
472
473
  /**
474
   * Function to delete current record in database.
475
   *
476
   * @return bool
477
   */
478 1
  public function delete()
479
  {
480 1
    $return = self::execute(
481 1
        $this->eq($this->primaryKeyName, $this->{$this->primaryKeyName})->_buildSql(
482
            array(
483 1
                'delete',
484 1
                'from',
485 1
                'where',
486
            )
487 1
        ),
488 1
        $this->params
489 1
    );
490
491 1
    if ($return !== false) {
492 1
      return true;
493
    }
494
495
    return false;
496
  }
497
498
  /**
499
   * @param string $primaryKeyName
500
   *
501
   * @return $this
502
   */
503
  public function setPrimaryKeyName($primaryKeyName)
504
  {
505
    $this->primaryKeyName = $primaryKeyName;
506
507
    return $this;
508
  }
509
510
  /**
511
   * @param string $table
512
   */
513
  public function setTable($table)
514
  {
515
    $this->table = $table;
516
  }
517
518
  /**
519
   * Function to build update SQL, and update current record in database, just write the dirty data into database.
520
   *
521
   * @return bool|int <p>
522
   *                  If update was successful, it will return the affected rows as int,
523
   *                  otherwise it will return false or true (if there are no dirty data).
524
   *                  </p>
525
   */
526 2
  public function update()
527
  {
528 2
    if (count($this->dirty) == 0) {
529
      return true;
530
    }
531
532 2
    foreach ($this->dirty as $field => $value) {
533 2
      $this->addCondition($field, '=', $value, ',', 'set');
534 2
    }
535
536 2
    $result = self::execute(
537 2
        $this->eq($this->primaryKeyName, $this->{$this->primaryKeyName})->_buildSql(
538
            array(
539 2
                'update',
540 2
                'set',
541 2
                'where',
542
            )
543 2
        ),
544 2
        $this->params
545 2
    );
546 2
    if ($result !== false) {
547 2
      $this->resetDirty();
548 2
      $this->reset();
549
550 2
      return $result;
551
    }
552
553
    return false;
554
  }
555
556
  /**
557
   * @return $this
558
   */
559 1
  public static function fetchEmpty()
560
  {
561 1
    $class = get_called_class();
562 1
    return new $class;
563
  }
564
565
  /**
566
   * Function to build insert SQL, and insert current record into database.
567
   *
568
   * @return bool|int <p>
569
   *                  If insert was successful, it will return the new id,
570
   *                  otherwise it will return false or true (if there are no dirty data).
571
   *                  </p>
572
   */
573 4
  public function insert()
574
  {
575 4
    if (!self::$db instanceof DB) {
576
      self::$db = DB::getInstance();
577
    }
578
579 4
    if (count($this->dirty) === 0) {
580
      return true;
581
    }
582
583 4
    $value = $this->_filterParam($this->dirty);
584 4
    $this->insert = new ActiveRecordExpressions(
0 ignored issues
show
Documentation introduced by
The property insert does not exist on object<voku\db\ActiveRecord>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
585
        array(
586 4
            'operator' => 'INSERT INTO ' . $this->table,
587 4
            'target'   => new ActiveRecordExpressionsWrap(array('target' => array_keys($this->dirty))),
588
        )
589 4
    );
590 4
    $this->values = new ActiveRecordExpressions(
0 ignored issues
show
Documentation introduced by
The property values does not exist on object<voku\db\ActiveRecord>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
591
        array(
592 4
            'operator' => 'VALUES',
593 4
            'target'   => new ActiveRecordExpressionsWrap(array('target' => $value)),
594
        )
595 4
    );
596
597 4
    $result = self::execute($this->_buildSql(array('insert', 'values')), $this->params);
598 4
    if ($result !== false) {
599 4
      $this->{$this->primaryKeyName} = $result;
600
601 4
      $this->resetDirty();
602 4
      $this->reset();
603
604 4
      return $result;
605
    }
606
607
    return false;
608
  }
609
610
  /**
611
   * Helper function to copy an existing active record (and insert it into the database).
612
   *
613
   * @param bool $insert
614
   *
615
   * @return $this
616
   */
617 2
  public function copy($insert = true)
618
  {
619 1
    $new = clone $this;
620
621 2
    if ($insert) {
622 1
      $new->setPrimaryKey(null);
623 1
      $id = $new->insert();
624 1
      $new->setPrimaryKey($id);
625 1
    }
626
627 1
    return $new;
628
  }
629
630
  /**
631
   * Helper function to exec sql.
632
   *
633
   * @param string $sql   <p>The SQL need to be execute.</p>
634
   * @param array  $param <p>The param will be bind to the sql statement.</p>
635
   *
636
   * @return bool|int|Result              <p>
637
   *                                      "Result" by "<b>SELECT</b>"-queries<br />
638
   *                                      "int" (insert_id) by "<b>INSERT / REPLACE</b>"-queries<br />
639
   *                                      "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
640
   *                                      "true" by e.g. "DROP"-queries<br />
641
   *                                      "false" on error
642
   *                                      </p>
643
   */
644 24
  public static function execute($sql, array $param = array())
645
  {
646 24
    if (!self::$db instanceof DB) {
647 1
      self::$db = DB::getInstance();
648 1
    }
649
650 24
    return self::$db->query($sql, $param);
651
  }
652
653
  /**
654
   * Helper function to query one record by sql and params.
655
   *
656
   * @param string            $sql    <p>
657
   *                                  The SQL query to find the record.
658
   *                                  </p>
659
   * @param array             $param  <p>
660
   *                                  The param will be bind to the $sql query.
661
   *                                  </p>
662
   * @param ActiveRecord|null $obj    <p>
663
   *                                  The object, if find record in database, we will assign the attributes into
664
   *                                  this object.
665
   *                                  </p>
666
   * @param bool              $single <p>
667
   *                                  If set to true, we will find record and fetch in current object, otherwise
668
   *                                  will find all records.
669
   *                                  </p>
670
   *
671
   * @return bool|$this|array
672
   */
673 17
  public static function query($sql, array $param = array(), ActiveRecord $obj = null, $single = false)
674
  {
675 17
    $result = self::execute($sql, $param);
676
677 17
    if ($result === false) {
678
      return false;
679
    }
680
681 17
    $useObject = is_object($obj);
682 17
    if ($useObject === true) {
683 17
      $called_class = $obj;
684 17
    } else {
685
      $called_class = get_called_class();
686
    }
687
688 17
    self::setNewDataAreDirty(false);
689
690 17
    if ($single) {
691 11
      $return = $result->fetchObject($called_class, null, true);
0 ignored issues
show
Bug introduced by
It seems like $called_class defined by $obj on line 683 can also be of type null; however, voku\db\Result::fetchObject() does only seem to accept string|object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
692 11
    } else {
693 8
      $return = $result->fetchAllObject($called_class, null);
0 ignored issues
show
Bug introduced by
It seems like $called_class defined by $obj on line 683 can also be of type null; however, voku\db\Result::fetchAllObject() does only seem to accept string|object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
694
    }
695
696 17
    self::setNewDataAreDirty(true);
697
698 17
    return $return;
699
  }
700
701
  /**
702
   * Helper function to get relation of this object.
703
   * There was three types of relations: {BELONGS_TO, HAS_ONE, HAS_MANY}
704
   *
705
   * @param string $name <p>The name of the relation (the array key from the definition).</p>
706
   *
707
   * @return mixed
708
   *
709
   * @throws ActiveRecordException <p>If the relation can't be found .</p>
710
   */
711 3
  protected function &getRelation($name)
712
  {
713 3
    $relation = $this->relations[$name];
714
    if (
715
        $relation instanceof self
716 3
        ||
717
        (
718 2
            is_array($relation)
719 2
            &&
720 2
            $relation[0] instanceof self
721 2
        )
722 3
    ) {
723 3
      return $relation;
724
    }
725
726
    /* @var $obj ActiveRecord */
727 2
    $obj = new $relation[1];
728
729 2
    $this->relations[$name] = $obj;
730 2
    if (isset($relation[3]) && is_array($relation[3])) {
731 1
      foreach ((array)$relation[3] as $func => $args) {
732 1
        call_user_func_array(array($obj, $func), (array)$args);
733 1
      }
734 1
    }
735
736 2
    $backref = isset($relation[4]) ? $relation[4] : '';
737 2
    $relationInstanceOfSelf = ($relation instanceof self);
738
    if (
739
        $relationInstanceOfSelf === false
740 2
        &&
741 2
        self::HAS_ONE == $relation[0]
742 2
    ) {
743
744 1
      $this->relations[$name] = $obj->eq($relation[2], $this->{$this->primaryKeyName})->fetch();
745
746 1
      if ($backref) {
747
        $this->relations[$name] && $backref && $obj->__set($backref, $this);
748
      }
749
750 1
    } elseif (
751 2
        is_array($relation)
752 2
        &&
753 2
        self::HAS_MANY == $relation[0]
754 2
    ) {
755
756 2
      $this->relations[$name] = $obj->eq($relation[2], $this->{$this->primaryKeyName})->fetchAll();
757 2
      if ($backref) {
758 1
        foreach ($this->relations[$name] as $o) {
0 ignored issues
show
Bug introduced by
The expression $this->relations[$name] of type object<voku\db\ActiveRec...veRecord>|boolean|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
759 1
          $o->__set($backref, $this);
760 1
        }
761 1
      }
762
763 2
    } elseif (
764
        $relationInstanceOfSelf === false
765 2
        &&
766 2
        self::BELONGS_TO == $relation[0]
767 2
    ) {
768
769 2
      $this->relations[$name] = $obj->eq($obj->primaryKeyName, $this->{$relation[2]})->fetch();
770
771 2
      if ($backref) {
772 1
        $this->relations[$name] && $backref && $obj->__set($backref, $this);
773 1
      }
774
775 2
    } else {
776
      throw new ActiveRecordException("Relation $name not found.");
777
    }
778
779 2
    return $this->relations[$name];
780
  }
781
782
  /**
783
   * Helper function to build SQL with sql parts.
784
   *
785
   * @param string       $n <p>The SQL part will be build.</p>
786
   * @param int          $i <p>The index of $n in $sql array.</p>
787
   * @param ActiveRecord $o <p>The reference to $this.</p>
788
   */
789 21
  private function _buildSqlCallback(&$n, $i, $o)
790
  {
791
    if (
792
        'select' === $n
793 21
        &&
794 15
        null === $o->$n
795 21
    ) {
796
797 14
      $n = strtoupper($n) . ' ' . $o->table . '.*';
798
799 14
    } elseif (
800
        (
801
            'update' === $n
802 21
            ||
803
            'from' === $n
804 21
        )
805 21
        &&
806 17
        null === $o->$n
807 21
    ) {
808
809 17
      $n = strtoupper($n) . ' ' . $o->table;
810
811 21
    } elseif ('delete' === $n) {
812
813 1
      $n = strtoupper($n) . ' ';
814
815 1
    } else {
816
817 21
      $n = (null !== $o->$n) ? $o->$n . ' ' : '';
818
819
    }
820 21
  }
821
822
  /**
823
   * Helper function to build SQL with sql parts.
824
   *
825
   * @param array $sqls <p>The SQL part will be build.</p>
826
   *
827
   * @return string
828
   */
829 21
  protected function _buildSql($sqls = array())
830
  {
831 21
    array_walk($sqls, array($this, '_buildSqlCallback'), $this);
832
833
    // DEBUG
834
    //echo 'SQL: ', implode(' ', $sqls), "\n", 'PARAMS: ', implode(', ', $this->params), "\n";
835
836 21
    return implode(' ', $sqls);
837
  }
838
839
  /**
840
   * Magic function to make calls witch in function mapping stored in $operators and $sqlPart.
841
   * also can call function of DB object.
842
   *
843
   * @param string $name <p>The name of the function.</p>
844
   * @param array  $args <p>The arguments of the function.</p>
845
   *
846
   * @return $this|mixed <p>Return the result of callback or the current object to make chain method calls.</p>
847
   *
848
   * @throws ActiveRecordException
849
   */
850 16
  public function __call($name, $args)
851
  {
852 16
    if (!self::$db instanceof DB) {
853
      self::$db = DB::getInstance();
854
    }
855
856 16
    $nameTmp = strtolower($name);
857
858 16
    if (array_key_exists($nameTmp, self::$operators)) {
859
860 14
      $this->addCondition(
861 14
          $args[0],
862 14
          self::$operators[$nameTmp],
863 14
          isset($args[1]) ? $args[1] : null,
864 14
          (is_string(end($args)) && 'or' === strtolower(end($args))) ? 'OR' : 'AND'
865 14
      );
866
867 16
    } elseif (array_key_exists($nameTmp = str_replace('by', '', $nameTmp), $this->sqlParts)) {
868
869 11
      $this->$name = new ActiveRecordExpressions(
870
          array(
871 11
              'operator' => $this->sqlParts[$nameTmp],
872 11
              'target'   => implode(', ', $args),
873
          )
874 11
      );
875
876 11
    } elseif (is_callable($callback = array(self::$db, $name))) {
877
878
      return call_user_func_array($callback, $args);
879
880
    } else {
881
882
      throw new ActiveRecordException("Method $name not exist.");
883
884
    }
885
886 16
    return $this;
887
  }
888
889
  /**
890
   * Make wrap when build the SQL expressions of WHERE.
891
   *
892
   * @param string $op <p>If given, this param will build one "ActiveRecordExpressionsWrap" and include the stored expressions add into WHERE,
893
   *                   otherwise it will stored the expressions into an array.</p>
894
   *
895
   * @return $this
896
   */
897 1
  public function wrap($op = null)
898
  {
899 1
    if (1 === func_num_args()) {
900 1
      $this->wrap = false;
0 ignored issues
show
Documentation introduced by
The property wrap does not exist on object<voku\db\ActiveRecord>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
901 1
      if (is_array($this->expressions) && count($this->expressions) > 0) {
0 ignored issues
show
Bug introduced by
The property expressions does not seem to exist. Did you mean defaultSqlExpressions?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
902 1
        $this->_addCondition(
903 1
            new ActiveRecordExpressionsWrap(
904
                array(
905 1
                    'delimiter' => ' ',
906 1
                    'target'    => $this->expressions,
0 ignored issues
show
Bug introduced by
The property expressions does not seem to exist. Did you mean defaultSqlExpressions?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
907
                )
908 1
            ), 'or' === strtolower($op) ? 'OR' : 'AND'
909 1
        );
910 1
      }
911 1
      $this->expressions = array();
0 ignored issues
show
Bug introduced by
The property expressions does not seem to exist. Did you mean defaultSqlExpressions?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
912 1
    } else {
913 1
      $this->wrap = true;
0 ignored issues
show
Documentation introduced by
The property wrap does not exist on object<voku\db\ActiveRecord>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
914
    }
915
916 1
    return $this;
917
  }
918
919
  /**
920
   * Helper function to build place holder when make SQL expressions.
921
   *
922
   * @param mixed $value <p>The value will be bind to SQL, just store it in $this->params.</p>
923
   *
924
   * @return mixed $value
925
   */
926 18
  protected function _filterParam($value)
927
  {
928 18
    if (is_array($value)) {
929 8
      foreach ($value as $key => $val) {
930 8
        $this->params[$value[$key] = self::PREFIX . ++self::$count] = $val;
931 8
      }
932 18
    } elseif (is_string($value)) {
933 3
      $this->params[$ph = self::PREFIX . ++self::$count] = $value;
934 3
      $value = $ph;
935 3
    }
936
937 18
    return $value;
938
  }
939
940
  /**
941
   * Helper function to add condition into WHERE.
942
   *
943
   * @param string $field <p>The field name, the source of Expressions</p>
944
   * @param string $operator
945
   * @param mixed  $value <p>The target of the Expressions.</p>
946
   * @param string $op    <p>The operator to concat this Expressions into WHERE or SET statement.</p>
947
   * @param string $name  <p>The Expression will contact to.</p>
948
   */
949 14
  public function addCondition($field, $operator, $value, $op = 'AND', $name = 'where')
950
  {
951 14
    $value = $this->_filterParam($value);
952 14
    $exp = new ActiveRecordExpressions(
953
        array(
954 14
            'source'   => ('where' == $name ? $this->table . '.' : '') . $field,
955 14
            'operator' => $operator,
956 14
            'target'   => is_array($value)
957 14
                ? new ActiveRecordExpressionsWrap(
958 4
                    'between' === strtolower($operator)
959 4
                        ? array('target' => $value, 'start' => ' ', 'end' => ' ', 'delimiter' => ' AND ')
960 4
                        : array('target' => $value)
961 14
                ) : $value,
962
        )
963 14
    );
964 14
    if ($exp) {
965 14
      if (!$this->wrap) {
0 ignored issues
show
Documentation introduced by
The property wrap does not exist on object<voku\db\ActiveRecord>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
966 14
        $this->_addCondition($exp, $op, $name);
967 14
      } else {
968 1
        $this->_addExpression($exp, $op);
969
      }
970 14
    }
971 14
  }
972
973
  /**
974
   * Helper function to add condition into JOIN.
975
   *
976
   * @param string $table <p>The join table name.</p>
977
   * @param string $on    <p>The condition of ON.</p>
978
   * @param string $type  <p>The join type, like "LEFT", "INNER", "OUTER".</p>
979
   *
980
   * @return $this
981
   */
982 1
  public function join($table, $on, $type = 'LEFT')
983
  {
984 1
    $this->join = new ActiveRecordExpressions(
0 ignored issues
show
Documentation introduced by
The property join does not exist on object<voku\db\ActiveRecord>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
985
        array(
986 1
            'source'   => $this->join ?: '',
0 ignored issues
show
Documentation introduced by
The property join does not exist on object<voku\db\ActiveRecord>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
987 1
            'operator' => $type . ' JOIN',
988 1
            'target'   => new ActiveRecordExpressions(
989 1
                array('source' => $table, 'operator' => 'ON', 'target' => $on)
990 1
            ),
991
        )
992 1
    );
993
994 1
    return $this;
995
  }
996
997
  /**
998
   * helper function to make wrapper. Stored the expression in to array.
999
   *
1000
   * @param ActiveRecordExpressions $exp      <p>The expression will be stored.</p>
1001
   * @param string                  $operator <p>The operator to concat this Expressions into WHERE statement.</p>
1002
   */
1003 1
  protected function _addExpression($exp, $operator)
1004
  {
1005
    if (
1006 1
        !is_array($this->expressions)
0 ignored issues
show
Bug introduced by
The property expressions does not seem to exist. Did you mean defaultSqlExpressions?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1007 1
        ||
1008 1
        count($this->expressions) === 0
0 ignored issues
show
Bug introduced by
The property expressions does not seem to exist. Did you mean defaultSqlExpressions?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1009 1
    ) {
1010 1
      $this->expressions = array($exp);
0 ignored issues
show
Bug introduced by
The property expressions does not seem to exist. Did you mean defaultSqlExpressions?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1011 1
    } else {
1012 1
      $this->expressions[] = new ActiveRecordExpressions(array('operator' => $operator, 'target' => $exp));
0 ignored issues
show
Bug introduced by
The property expressions does not seem to exist. Did you mean defaultSqlExpressions?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1013
    }
1014 1
  }
1015
1016
  /**
1017
   * helper function to add condition into WHERE.
1018
   *
1019
   * @param ActiveRecordExpressions $exp      <p>The expression will be concat into WHERE or SET statement.</p>
1020
   * @param string                  $operator <p>The operator to concat this Expressions into WHERE or SET statement.</p>
1021
   * @param string                  $name     <p>The Expression will contact to.</p>
1022
   */
1023 14
  protected function _addCondition($exp, $operator, $name = 'where')
1024
  {
1025 14
    if (!$this->$name) {
1026 14
      $this->$name = new ActiveRecordExpressions(array('operator' => strtoupper($name), 'target' => $exp));
1027 14
    } else {
1028 4
      $this->$name->target = new ActiveRecordExpressions(
1029
          array(
1030 4
              'source'   => $this->$name->target,
1031 4
              'operator' => $operator,
1032 4
              'target'   => $exp,
1033
          )
1034 4
      );
1035
    }
1036 14
  }
1037
1038
  /**
1039
   * @return array
1040
   */
1041 1
  public function getDirty()
1042
  {
1043 1
    return $this->dirty;
1044
  }
1045
1046
  /**
1047
   * @return bool
1048
   */
1049
  public static function isNewDataAreDirty()
1050
  {
1051
    return self::$new_data_are_dirty;
1052
  }
1053
1054
  /**
1055
   * @param bool $bool
1056
   */
1057 17
  public static function setNewDataAreDirty($bool)
1058
  {
1059 17
    self::$new_data_are_dirty = (bool)$bool;
1060 17
  }
1061
1062
  /**
1063
   * Magic function to SET values of the current object.
1064
   *
1065
   * @param mixed $var
1066
   * @param mixed $val
1067
   */
1068 23
  public function __set($var, $val)
1069
  {
1070
    if (
1071 23
        array_key_exists($var, $this->sqlExpressions)
1072
        ||
1073 23
        array_key_exists($var, $this->defaultSqlExpressions)
1074 23
    ) {
1075
1076 20
      $this->sqlExpressions[$var] = $val;
1077
1078 20
    } elseif (
1079 20
        array_key_exists($var, $this->relations)
1080 20
        &&
1081
        $val instanceof self
1082 20
    ) {
1083
1084 1
      $this->relations[$var] = $val;
1085
1086 1
    } else {
1087
1088 20
      $this->array[$var] = $val;
1089
1090 20
      if (self::$new_data_are_dirty === true) {
1091 9
        $this->dirty[$var] = $val;
1092 9
      }
1093
1094
    }
1095 23
  }
1096
1097
  /**
1098
   * Magic function to UNSET values of the current object.
1099
   *
1100
   * @param mixed $var
1101
   */
1102 1
  public function __unset($var)
1103
  {
1104 1
    if (array_key_exists($var, $this->sqlExpressions)) {
1105
      unset($this->sqlExpressions[$var]);
1106
    }
1107
1108 1
    if (isset($this->array[$var])) {
1109 1
      unset($this->array[$var]);
1110 1
    }
1111
1112 1
    if (isset($this->dirty[$var])) {
1113 1
      unset($this->dirty[$var]);
1114 1
    }
1115 1
  }
1116
1117
  /**
1118
   * Helper function for "GROUP BY".
1119
   *
1120
   * @param array $args
1121
   *
1122
   * @return $this
1123
   */
1124
  public function groupBy($args)
0 ignored issues
show
Unused Code introduced by
The parameter $args is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1125
  {
1126
    $this->__call('groupBy', func_get_args());
1127
1128
    return $this;
1129
  }
1130
1131
  /**
1132
   * Helper function for "ORDER BY".
1133
   *
1134
   * @param $args ...
1135
   *
1136
   * @return $this
1137
   */
1138 2
  public function orderBy($args)
0 ignored issues
show
Unused Code introduced by
The parameter $args is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1139
  {
1140 2
    $this->__call('orderBy', func_get_args());
1141
1142 2
    return $this;
1143
  }
1144
1145
  /**
1146
   * Magic function to GET the values of current object.
1147
   *
1148
   * @param $var
1149
   *
1150
   * @return mixed
1151
   */
1152 23
  public function &__get($var)
1153
  {
1154 23
    if (array_key_exists($var, $this->sqlExpressions)) {
1155 20
      return $this->sqlExpressions[$var];
1156
    }
1157
1158 23
    if (array_key_exists($var, $this->relations)) {
1159 3
      return $this->getRelation($var);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->getRelation($var); of type voku\db\ActiveRecordExpr...iveRecord|boolean|array adds the type array to the return on line 1159 which is incompatible with the return type of the parent method Arrayy\Arrayy::__get of type object|integer|double|string|null|boolean.
Loading history...
1160
    }
1161
1162 23
    if (isset($this->dirty[$var])) {
1163 4
      return $this->dirty[$var];
1164
    }
1165
1166 23
    return parent::__get($var);
1167
  }
1168
}
1169