Completed
Push — master ( 3ed1ba...90dd46 )
by Lars
07:46
created

ActiveRecord::_addCondition()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 23
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 2

Importance

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

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
305 11
        true
306
    );
307
  }
308
309
  /**
310
   * @param string $query
311
   *
312
   * @return $this[]
313
   */
314 1
  public function fetchManyByQuery(string $query): array
315
  {
316 1
    $list = $this->fetchByQuery($query);
317
318 1
    if (!$list || empty($list)) {
319
      return [];
320
    }
321
322 1
    return $list;
323
  }
324
325
  /**
326
   * @param string $query
327
   *
328
   * @return $this|null
329
   */
330 1
  public function fetchOneByQuery(string $query)
331
  {
332 1
    $list = $this->fetchByQuery($query);
333
334 1
    if (!$list || empty($list)) {
335
      return null;
336
    }
337
338 1
    if (\is_array($list) && \count($list) > 0) {
339 1
      $this->array = $list[0]->getArray();
340
    } else {
341
      $this->array = $list->getArray();
342
    }
343
344 1
    return $this;
345
  }
346
347
  /**
348
   * @param mixed $id
349
   *
350
   * @return $this
351
   *
352
   * @throws FetchingException <p>Will be thrown, if we can not find the id.</p>
353
   */
354 2
  public function fetchById($id)
355
  {
356 2
    $obj = $this->fetchByIdIfExists($id);
357 2
    if ($obj === null) {
358 1
      throw new FetchingException("No row with primary key '$id' in table '$this->table'.");
359
    }
360
361 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...
362
  }
363
364
  /**
365
   * @param mixed $id
366
   *
367
   * @return $this|null
368
   */
369 4
  public function fetchByIdIfExists($id)
370
  {
371 4
    $list = $this->fetch($id);
372
373 4
    if (!$list || $list->isEmpty()) {
374 2
      return null;
375
    }
376
377 2
    return $list;
378
  }
379
380
  /**
381
   * @param array $ids
382
   *
383
   * @return $this[]
384
   */
385 2
  public function fetchByIds(array $ids): array
386
  {
387 2
    if (empty($ids)) {
388
      return [];
389
    }
390
391 2
    $list = $this->fetchAll($ids);
392 2
    if (\is_array($list) && \count($list) > 0) {
393 1
      return $list;
394
    }
395
396 1
    return [];
397
  }
398
399
  /**
400
   * @param string $query
401
   *
402
   * @return $this[]|$this
403
   */
404 2
  public function fetchByQuery(string $query)
405
  {
406 2
    $list = self::query(
407 2
        $query,
408 2
        $this->params,
409 2
        $this->reset()
0 ignored issues
show
Documentation introduced by
$this->reset() is of type this<voku\db\ActiveRecord>, but the function expects a null|object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
410
    );
411
412 2
    if (\is_array($list)) {
413 2
      if (\count($list) === 0) {
414
        return [];
415
      }
416
417 2
      return $list;
418
    }
419
420
    $this->array = $list->getArray();
421
422
    return $this;
423
  }
424
425
  /**
426
   * @param array $ids
427
   *
428
   * @return $this[]
429
   */
430 1
  public function fetchByIdsPrimaryKeyAsArrayIndex(array $ids): array
431
  {
432 1
    $result = $this->fetchAll($ids);
433
434 1
    $resultNew = [];
435 1
    foreach ($result as $item) {
436 1
      $resultNew[$item->getPrimaryKey()] = $item;
437
    }
438
439 1
    return $resultNew;
440
  }
441
442
  /**
443
   * Function to find all records in database.
444
   *
445
   * @param array|null $ids <p>
446
   *                        If call this function using this param, we will find the record by using this id's.
447
   *                        If not set, just find all records in database.
448
   *                        </p>
449
   *
450
   * @return $this[]
451
   */
452 6 View Code Duplication
  public function fetchAll(array $ids = null): array
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...
453
  {
454 6
    if ($ids) {
455 3
      $this->reset()->in($this->primaryKeyName, $ids);
456
    }
457
458 6
    return self::query(
459 6
        $this->_buildSql(
460
            [
461 6
                'select',
462
                'from',
463
                'join',
464
                'where',
465
                'groupBy',
466
                'having',
467
                'orderBy',
468
                'limit',
469
            ]
470
        ),
471 6
        $this->params,
472 6
        $this->reset()
0 ignored issues
show
Documentation introduced by
$this->reset() is of type this<voku\db\ActiveRecord>, but the function expects a null|object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
473
    );
474
  }
475
476
  /**
477
   * Function to delete current record in database.
478
   *
479
   * @return bool
480
   */
481 1
  public function delete(): bool
482
  {
483 1
    $return = self::execute(
484 1
        $this->eq($this->primaryKeyName, $this->{$this->primaryKeyName})->_buildSql(
485
            [
486 1
                'delete',
487
                'from',
488
                'where',
489
            ]
490
        ),
491 1
        $this->params
492
    );
493
494 1
    return $return !== false;
495
  }
496
497
  /**
498
   * @param string $primaryKeyName
499
   *
500
   * @return $this
501
   */
502
  public function setPrimaryKeyName(string $primaryKeyName)
503
  {
504
    $this->primaryKeyName = $primaryKeyName;
505
506
    return $this;
507
  }
508
509
  /**
510
   * @param string $table
511
   */
512
  public function setTable(string $table)
513
  {
514
    $this->table = $table;
515
  }
516
517
  /**
518
   * Function to build update SQL, and update current record in database, just write the dirty data into database.
519
   *
520
   * @return bool|int <p>
521
   *                  If update was successful, it will return the affected rows as int,
522
   *                  otherwise it will return false or true (if there are no dirty data).
523
   *                  </p>
524
   */
525 2
  public function update()
526
  {
527 2
    if (\count($this->dirty) == 0) {
528
      return true;
529
    }
530
531 2
    foreach ($this->dirty as $field => $value) {
532 2
      $this->addCondition($field, '=', $value, ',', 'set');
533
    }
534
535 2
    $result = self::execute(
536 2
        $this->eq($this->primaryKeyName, $this->{$this->primaryKeyName})->_buildSql(
537
            [
538 2
                'update',
539
                'set',
540
                'where',
541
            ]
542
        ),
543 2
        $this->params
544
    );
545 2
    if ($result !== false) {
546 2
      $this->resetDirty();
547 2
      $this->reset();
548
549 2
      return $result;
550
    }
551
552
    return false;
553
  }
554
555
  /**
556
   * @return $this
557
   */
558 1
  public static function fetchEmpty()
559
  {
560 1
    $class = static::class;
561
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();
0 ignored issues
show
Documentation Bug introduced by
It seems like \voku\db\DB::getInstance() of type object<self> is incompatible with the declared type object<voku\db\DB> of property $db.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
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
        [
586 4
            'operator' => 'INSERT INTO ' . $this->table,
587 4
            'target'   => new ActiveRecordExpressionsWrap(['target' => \array_keys($this->dirty)]),
588
        ]
589
    );
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
        [
592 4
            'operator' => 'VALUES',
593 4
            'target'   => new ActiveRecordExpressionsWrap(['target' => $value]),
594
        ]
595
    );
596
597 4
    $result = self::execute($this->_buildSql(['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 1
  public function copy(bool $insert = true)
618
  {
619 1
    $new = clone $this;
620
621 1
    if ($insert) {
622 1
      $new->setPrimaryKey(null);
623 1
      $id = $new->insert();
624 1
      $new->setPrimaryKey($id);
625
    }
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(string $sql, array $param = [])
645
  {
646 24
    if (!self::$db instanceof DB) {
647 1
      self::$db = DB::getInstance();
0 ignored issues
show
Documentation Bug introduced by
It seems like \voku\db\DB::getInstance() of type object<self> is incompatible with the declared type object<voku\db\DB> of property $db.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
648
    }
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 null|self $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(string $sql, array $param = [], self $obj = null, bool $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
    } else {
685
      $called_class = static::class;
686
    }
687
688 17
    self::setNewDataAreDirty(false);
689
690 17
    if ($single) {
691 11
      $return = $result->fetchObject($called_class, null, true);
692
    } else {
693 8
      $return = $result->fetchAllObject($called_class, null);
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(string $name)
712
  {
713 3
    $relation = $this->relations[$name];
714
    if (
715 3
        $relation instanceof self
716
        ||
717
        (
718 2
            \is_array($relation)
719
            &&
720 3
            $relation[0] instanceof self
721
        )
722
    ) {
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([$obj, $func], (array)$args);
733
      }
734
    }
735
736 2
    $backref = $relation[4] ?? '';
737 2
    $relationInstanceOfSelf = ($relation instanceof self);
738
    if (
739 2
        $relationInstanceOfSelf === false
740
        &&
741 2
        self::HAS_ONE == $relation[0]
742
    ) {
743
744 1
      $this->relations[$name] = $obj->eq((string)$relation[2], $this->{$this->primaryKeyName})->fetch();
745
746 1
      if ($backref) {
747 1
        $this->relations[$name] && $backref && $obj->{$backref} = $this;
748
      }
749
750
    } elseif (
751 2
        \is_array($relation)
752
        &&
753 2
        self::HAS_MANY == $relation[0]
754
    ) {
755
756 2
      $this->relations[$name] = $obj->eq((string)$relation[2], $this->{$this->primaryKeyName})->fetchAll();
757 2
      if ($backref) {
758 1
        foreach ($this->relations[$name] as $o) {
759 2
          $o->{$backref} = $this;
760
        }
761
      }
762
763
    } elseif (
764 2
        $relationInstanceOfSelf === false
765
        &&
766 2
        self::BELONGS_TO == $relation[0]
767
    ) {
768
769 2
      $this->relations[$name] = $obj->eq($obj->primaryKeyName, $this->{$relation[2]})->fetch();
770
771 2
      if ($backref) {
772 2
        $this->relations[$name] && $backref && $obj->{$backref} = $this;
773
      }
774
775
    } 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 $sql_string_part <p>The SQL part will be build.</p>
786
   * @param int    $index           <p>The index of $n in $sql array.</p>
787
   * @param self   $active_record   <p>The reference to $this.</p>
788
   */
789 21
  private function _buildSqlCallback(string &$sql_string_part, $index, self $active_record)
790
  {
791
    if (
792 21
        'select' === $sql_string_part
793
        &&
794 21
        null === $active_record->{$sql_string_part}
795
    ) {
796
797 14
      $sql_string_part = \strtoupper($sql_string_part) . ' ' . $active_record->table . '.*';
798
799
    } elseif (
800
        (
801 21
            'update' === $sql_string_part
802
            ||
803 21
            'from' === $sql_string_part
804
        )
805
        &&
806 21
        null === $active_record->{$sql_string_part}
807
    ) {
808
809 17
      $sql_string_part = \strtoupper($sql_string_part) . ' ' . $active_record->table;
810
811 21
    } elseif ('delete' === $sql_string_part) {
812
813 1
      $sql_string_part = \strtoupper($sql_string_part) . ' ';
814
815
    } else {
816
817 21
      $sql_string_part = (null !== $active_record->{$sql_string_part}) ? $active_record->{$sql_string_part} . ' ' : '';
818
819
    }
820 21
  }
821
822
  /**
823
   * Helper function to build SQL with sql parts.
824
   *
825
   * @param string[] $sql_array <p>The SQL part will be build.</p>
826
   *
827
   * @return string
828
   */
829 21
  protected function _buildSql(array $sql_array = []): string
830
  {
831 21
    \array_walk($sql_array, [$this, '_buildSqlCallback'], $this);
832
833
    // DEBUG
834
    //echo 'SQL: ', implode(' ', $sqls), "\n", 'PARAMS: ', implode(', ', $this->params), "\n";
835
836 21
    return \implode(' ', $sql_array);
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(string $name, array $args = [])
851
  {
852 16
    if (!self::$db instanceof DB) {
853
      self::$db = DB::getInstance();
0 ignored issues
show
Documentation Bug introduced by
It seems like \voku\db\DB::getInstance() of type object<self> is incompatible with the declared type object<voku\db\DB> of property $db.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
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
          $args[1] ?? null,
864 14
          (\is_string(\end($args)) && 'or' === \strtolower(\end($args))) ? 'OR' : 'AND'
865
      );
866
867 11
    } elseif (\array_key_exists($nameTmp = \str_replace('by', '', $nameTmp), $this->sqlParts)) {
868
869 11
      $this->{$name} = new ActiveRecordExpressions(
870
          [
871 11
              'operator' => $this->sqlParts[$nameTmp],
872 11
              'target'   => \implode(', ', $args),
873
          ]
874
      );
875
876
    } elseif (\is_callable($callback = [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
893
   *                   expressions add into WHERE, 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
                [
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
            ),
909 1
            'or' === \strtolower($op) ? 'OR' : 'AND'
910
        );
911
      }
912 1
      $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...
913
    } else {
914 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...
915
    }
916
917 1
    return $this;
918
  }
919
920
  /**
921
   * Helper function to build place holder when make SQL expressions.
922
   *
923
   * @param mixed $value <p>The value will be bind to SQL, just store it in $this->params.</p>
924
   *
925
   * @return mixed $value
926
   */
927 18
  protected function _filterParam($value)
928
  {
929 18
    if (\is_array($value)) {
930 8
      foreach ($value as $key => $val) {
931 8
        $this->params[$value[$key] = self::PREFIX . ++self::$count] = $val;
932
      }
933 11
    } elseif (\is_string($value)) {
934 3
      $this->params[$ph = self::PREFIX . ++self::$count] = $value;
935 3
      $value = $ph;
936
    }
937
938 18
    return $value;
939
  }
940
941
  /**
942
   * Helper function to add condition into WHERE.
943
   *
944
   * @param string $field           <p>The field name, the source of Expressions</p>
945
   * @param string $operator        <p>The operator for this condition.</p>
946
   * @param mixed  $value           <p>The target of the Expressions.</p>
947
   * @param string $operator_concat <p>The operator to concat this Expressions into WHERE or SET statement.</p>
948
   * @param string $name            <p>The Expression will contact to.</p>
949
   */
950 14
  public function addCondition($field, $operator, $value, $operator_concat = 'AND', $name = 'where')
951
  {
952 14
    $value = $this->_filterParam($value);
953 14
    $expression = new ActiveRecordExpressions(
954
        [
955 14
            'source'   => ('where' === strtolower($name) ? $this->table . '.' : '') . $field,
956 14
            'operator' => $operator,
957 14
            'target'   => \is_array($value)
958 4
                ? new ActiveRecordExpressionsWrap(
959 4
                    'between' === \strtolower($operator)
960 1
                        ? ['target' => $value, 'start' => ' ', 'end' => ' ', 'delimiter' => ' AND ']
961 4
                        : ['target' => $value]
962 14
                ) : $value,
963
        ]
964
    );
965
966 14
    if ($expression) {
967 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...
968 14
        $this->_addCondition($expression, $operator_concat, $name);
969
      } else {
970 1
        $this->_addExpression($expression, $operator_concat);
971
      }
972
    }
973 14
  }
974
975
  /**
976
   * Helper function to add condition into JOIN.
977
   *
978
   * @param string $table <p>The join table name.</p>
979
   * @param string $on    <p>The condition of ON.</p>
980
   * @param string $type  <p>The join type, like "LEFT", "INNER", "OUTER".</p>
981
   *
982
   * @return $this
983
   */
984 1
  public function join($table, $on, $type = 'LEFT')
985
  {
986 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...
987
        [
988 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...
989 1
            'operator' => $type . ' JOIN',
990 1
            'target'   => new ActiveRecordExpressions(
991 1
                ['source' => $table, 'operator' => 'ON', 'target' => $on]
992
            ),
993
        ]
994
    );
995
996 1
    return $this;
997
  }
998
999
  /**
1000
   * helper function to make wrapper. Stored the expression in to array.
1001
   *
1002
   * @param ActiveRecordExpressions $exp      <p>The expression will be stored.</p>
1003
   * @param string                  $operator <p>The operator to concat this Expressions into WHERE statement.</p>
1004
   */
1005 1
  protected function _addExpression($exp, $operator)
1006
  {
1007
    if (
1008 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...
1009
        ||
1010 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...
1011
    ) {
1012 1
      $this->expressions = [$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
    } else {
1014 1
      $this->expressions[] = new ActiveRecordExpressions(['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...
1015
    }
1016 1
  }
1017
1018
  /**
1019
   * helper function to add condition into WHERE.
1020
   *
1021
   * @param ActiveRecordExpressions $expression <p>The expression will be concat into WHERE or SET statement.</p>
1022
   * @param string                  $operator   <p>The operator to concat this Expressions into WHERE or SET
1023
   *                                            statement.</p>
1024
   * @param string                  $name       <p>The Expression will contact to.</p>
1025
   */
1026 14
  protected function _addCondition($expression, $operator, $name = 'where')
1027
  {
1028 14
    if (!$this->{$name}) {
1029
1030 14
      $this->{$name} = new ActiveRecordExpressions(
1031
          [
1032 14
              'operator' => \strtoupper($name),
1033 14
              'target'   => $expression,
1034
          ]
1035
      );
1036
1037
    } else {
1038
1039 4
      $this->{$name}->target = new ActiveRecordExpressions(
1040
          [
1041 4
              'source'   => $this->{$name}->target,
1042 4
              'operator' => $operator,
1043 4
              'target'   => $expression,
1044
          ]
1045
      );
1046
1047
    }
1048 14
  }
1049
1050
  /**
1051
   * @return array
1052
   */
1053 1
  public function getDirty(): array
1054
  {
1055 1
    return $this->dirty;
1056
  }
1057
1058
  /**
1059
   * @return bool
1060
   */
1061
  public static function isNewDataAreDirty(): bool
1062
  {
1063
    return self::$new_data_are_dirty;
1064
  }
1065
1066
  /**
1067
   * @param bool $bool
1068
   */
1069 17
  public static function setNewDataAreDirty($bool)
1070
  {
1071 17
    self::$new_data_are_dirty = (bool)$bool;
1072 17
  }
1073
1074
  /**
1075
   * Magic function to SET values of the current object.
1076
   *
1077
   * @param mixed $var
1078
   * @param mixed $val
1079
   */
1080 23
  public function __set($var, $val)
1081
  {
1082
    if (
1083 23
        \array_key_exists($var, $this->sqlExpressions)
1084
        ||
1085 23
        \array_key_exists($var, $this->defaultSqlExpressions)
1086
    ) {
1087
1088 20
      $this->sqlExpressions[$var] = $val;
1089
1090
    } elseif (
1091 20
        \array_key_exists($var, $this->relations)
1092
        &&
1093 20
        $val instanceof self
1094
    ) {
1095
1096 1
      $this->relations[$var] = $val;
1097
1098
    } else {
1099
1100 20
      $this->array[$var] = $val;
1101
1102 20
      if (self::$new_data_are_dirty === true) {
1103 9
        $this->dirty[$var] = $val;
1104
      }
1105
1106
    }
1107 23
  }
1108
1109
  /**
1110
   * Magic function to UNSET values of the current object.
1111
   *
1112
   * @param mixed $var
1113
   */
1114 1
  public function __unset($var)
1115
  {
1116 1
    if (\array_key_exists($var, $this->sqlExpressions)) {
1117
      unset($this->sqlExpressions[$var]);
1118
    }
1119
1120 1
    if (isset($this->array[$var])) {
1121 1
      unset($this->array[$var]);
1122
    }
1123
1124 1
    if (isset($this->dirty[$var])) {
1125 1
      unset($this->dirty[$var]);
1126
    }
1127 1
  }
1128
1129
  /**
1130
   * Helper function for "GROUP BY".
1131
   *
1132
   * @param array $args
1133
   *
1134
   * @return $this
1135
   */
1136
  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...
1137
  {
1138
    $this->__call('groupBy', \func_get_args());
1139
1140
    return $this;
1141
  }
1142
1143
  /**
1144
   * Helper function for "ORDER BY".
1145
   *
1146
   * @param $args ...
1147
   *
1148
   * @return $this
1149
   */
1150 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...
1151
  {
1152 2
    $this->__call('orderBy', \func_get_args());
1153
1154 2
    return $this;
1155
  }
1156
1157
  /**
1158
   * Magic function to GET the values of current object.
1159
   *
1160
   * @param $var
1161
   *
1162
   * @return mixed
1163
   */
1164 23
  public function &__get($var)
1165
  {
1166 23
    if (\array_key_exists($var, $this->sqlExpressions)) {
1167 20
      return $this->sqlExpressions[$var];
1168
    }
1169
1170 23
    if (\array_key_exists($var, $this->relations)) {
1171 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 1171 which is incompatible with the return type of the parent method Arrayy\Arrayy::__get of type object|integer|double|string|null|boolean.
Loading history...
1172
    }
1173
1174 23
    if (isset($this->dirty[$var])) {
1175 4
      return $this->dirty[$var];
1176
    }
1177
1178 23
    return parent::__get($var);
1179
  }
1180
}
1181