Completed
Push — master ( d2d838...15d122 )
by Lars
03:19
created

ActiveRecord   D

Complexity

Total Complexity 107

Size/Duplication

Total Lines 891
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
wmc 107
lcom 1
cbo 5
dl 0
loc 891
rs 4.4444
c 0
b 0
f 0

33 Methods

Rating   Name   Duplication   Size   Complexity  
A getPrimaryKeyName() 0 4 1
A getPrimaryKey() 0 9 2
A getTable() 0 4 1
A reset() 0 7 1
A resetDirty() 0 6 1
A setDb() 0 4 1
B fetch() 0 24 2
A fetchAll() 0 19 1
A delete() 0 13 1
A setPrimaryKeyName() 0 6 1
A setTable() 0 4 1
B update() 0 26 4
B insert() 0 33 4
A execute() 0 8 2
B _query() 0 27 4
C getRelation() 0 69 22
C _buildSqlCallback() 0 32 8
A _buildSql() 0 9 1
C __call() 0 38 8
B wrap() 0 21 5
A _filterParam() 0 13 4
B addCondition() 0 23 6
A join() 0 14 2
A _addExpression() 0 8 3
A _addCondition() 0 14 2
A getDirty() 0 4 1
A isNewDataAreDirty() 0 4 1
A setNewDataAreDirty() 0 4 1
B __set() 0 28 6
A __unset() 0 14 4
A group() 0 6 1
A order() 0 6 1
A __get() 0 16 4

How to fix   Complexity   

Complex Class

Complex classes like ActiveRecord often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ActiveRecord, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace voku\db;
4
5
use Arrayy\Arrayy;
6
use voku\db\exceptions\ActiveRecordException;
7
8
/**
9
 * A simple implement of active record via mysqli + php.
10
 *
11
 * @method $this select(string $stuff1, string|null $stuff2 = null)
12
 * @method $this eq(string $stuff1, string|null $stuff2 = null)
13
 * @method $this from(string $table)
14
 * @method $this where(string $stuff)
15
 * @method $this having(string $stuff)
16
 * @method $this limit(int $start, int|null $end = null)
17
 *
18
 * @method $this equal(string $stuff1, string $stuff2)
19
 * @method $this notequal(string $stuff1, string $stuff2)
20
 * @method $this ne(string $stuff1, string $stuff2)
21
 * @method $this greaterthan(string $stuff1, int $stuff2)
22
 * @method $this gt(string $stuff1, int $stuff2)
23
 * @method $this lessthan(string $stuff1, int $stuff2)
24
 * @method $this lt(string $stuff1, int $stuff2)
25
 * @method $this greaterthanorequal(string $stuff1, int $stuff2)
26
 * @method $this ge(string $stuff1, int $stuff2)
27
 * @method $this gte(string $stuff1, int $stuff2)
28
 * @method $this lessthanorequal(string $stuff1, int $stuff2)
29
 * @method $this le(string $stuff1, int $stuff2)
30
 * @method $this lte(string $stuff1, int $stuff2)
31
 * @method $this between(string $stuff1, array $stuff2)
32
 * @method $this like(string $stuff1, string $stuff2)
33
 * @method $this in(string $stuff1, array $stuff2)
34
 * @method $this notin(string $stuff1, array $stuff2)
35
 * @method $this isnull(string $stuff1)
36
 * @method $this isnotnull(string $stuff1)
37
 * @method $this notnull(string $stuff1)
38
 */
39
abstract class ActiveRecord extends Arrayy
40
{
41
  /**
42
   * @var DB static property to connect database.
43
   */
44
  protected static $db;
45
46
  /**
47
   * @var array maping the function name and the operator, to build Expressions in WHERE condition.
48
   *
49
   * user can call it like this:
50
   * <pre>
51
   *   $user->isnotnull()->eq('id', 1);
52
   * </pre>
53
   *
54
   * will create Expressions can explain to SQL:
55
   * <pre>
56
   *   WHERE user.id IS NOT NULL AND user.id = :ph1
57
   * </pre>
58
   */
59
  protected static $operators = array(
60
      'equal'              => '=',
61
      'eq'                 => '=',
62
      'notequal'           => '<>',
63
      'ne'                 => '<>',
64
      'greaterthan'        => '>',
65
      'gt'                 => '>',
66
      'lessthan'           => '<',
67
      'lt'                 => '<',
68
      'greaterthanorequal' => '>=',
69
      'ge'                 => '>=',
70
      'gte'                => '>=',
71
      'lessthanorequal'    => '<=',
72
      'le'                 => '<=',
73
      'lte'                => '<=',
74
      'between'            => 'BETWEEN',
75
      'like'               => 'LIKE',
76
      'in'                 => 'IN',
77
      'notin'              => 'NOT IN',
78
      'isnull'             => 'IS NULL',
79
      'isnotnull'          => 'IS NOT NULL',
80
      'notnull'            => 'IS NOT NULL',
81
  );
82
83
  /**
84
   * @var array Part of SQL, maping the function name and the operator to build SQL Part.
85
   * <pre>call function like this:
86
   *      $user->order('id DESC', 'name ASC')->limit(2, 1);
87
   *  can explain to SQL:
88
   *      ORDER BY id DESC, name ASC LIMIT 2,1</pre>
89
   */
90
  protected $sqlParts = array(
91
      'select' => 'SELECT',
92
      'from'   => 'FROM',
93
      'set'    => 'SET',
94
      'where'  => 'WHERE',
95
      'group'  => 'GROUP BY',
96
      'having' => 'HAVING',
97
      'order'  => 'ORDER BY',
98
      'limit'  => 'LIMIT',
99
      'top'    => 'TOP',
100
  );
101
102
  /**
103
   * @var array Static property to stored the default Sql Expressions values.
104
   */
105
  protected $defaultSqlExpressions = array(
106
      'expressions' => array(),
107
      'wrap'        => false,
108
      'select'      => null,
109
      'insert'      => null,
110
      'update'      => null,
111
      'set'         => null,
112
      'delete'      => 'DELETE ',
113
      'join'        => null,
114
      'from'        => null,
115
      'values'      => null,
116
      'where'       => null,
117
      'having'      => null,
118
      'limit'       => null,
119
      'order'       => null,
120
      'group'       => null,
121
  );
122
123
  /**
124
   * @var array Stored the Expressions of the SQL.
125
   */
126
  protected $sqlExpressions = array();
127
128
  /**
129
   * @var string  The table name in database.
130
   */
131
  protected $table;
132
133
  /**
134
   * @var string  The primary key of this ActiveRecord, just suport single primary key.
135
   */
136
  protected $primaryKeyName = 'id';
137
138
  /**
139
   * @var array Stored the drity data of this object, when call "insert" or "update" function, will write this data
140
   *      into database.
141
   */
142
  protected $dirty = array();
143
144
  /**
145
   * @var bool
146
   */
147
  protected static $new_data_are_dirty = true;
148
149
  /**
150
   * @var array Stored the params will bind to SQL when call PDOStatement::execute(),
151
   */
152
  protected $params = array();
153
154
  /**
155
   * @var ActiveRecordExpressions[] Stored the configure of the relation, or target of the relation.
156
   */
157
  protected $relations = array();
158
159
  /**
160
   * @var int The count of bind params, using this count and const "PREFIX" (:ph) to generate place holder in SQL.
161
   */
162
  private static $count = 0;
163
164
  const BELONGS_TO = 'belongs_to';
165
  const HAS_MANY   = 'has_many';
166
  const HAS_ONE    = 'has_one';
167
168
  const PREFIX = ':active_record';
169
170
  /**
171
   * @return string
172
   */
173
  public function getPrimaryKeyName()
174
  {
175
    return $this->primaryKeyName;
176
  }
177
178
  /**
179
   * @return mixed|null
180
   */
181
  public function getPrimaryKey()
182
  {
183
    $id = $this->{$this->primaryKeyName};
184
    if ($id) {
185
      return $id;
186
    }
187
188
    return null;
189
  }
190
191
  /**
192
   * @return string
193
   */
194
  public function getTable()
195
  {
196
    return $this->table;
197
  }
198
199
  /**
200
   * Function to reset the $params and $sqlExpressions.
201
   *
202
   * @return $this
203
   */
204
  public function reset()
205
  {
206
    $this->params = array();
207
    $this->sqlExpressions = array();
208
209
    return $this;
210
  }
211
212
  /**
213
   * Reset the dirty data.
214
   *
215
   * @return $this
216
   */
217
  public function resetDirty()
218
  {
219
    $this->array = array();
220
221
    return $this;
222
  }
223
224
  /**
225
   * set the DB connection.
226
   *
227
   * @param DB $db
228
   */
229
  public static function setDb($db)
230
  {
231
    self::$db = $db;
232
  }
233
234
  /**
235
   * function to find one record and assign in to current object.
236
   *
237
   * @param int $id If call this function using this param, will find record by using this id. If not set, just find
238
   *                the first record in database.
239
   *
240
   * @return bool|ActiveRecord if find record, assign in to current object and return it, other wise return "false".
241
   */
242
  public function fetch($id = null)
243
  {
244
    if ($id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $id of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
245
      $this->reset()->eq($this->primaryKeyName, $id);
246
    }
247
248
    return self::_query(
249
        $this->limit(1)->_buildSql(
250
            array(
251
                'select',
252
                'from',
253
                'join',
254
                'where',
255
                'group',
256
                'having',
257
                'order',
258
                'limit',
259
            )
260
        ),
261
        $this->params,
262
        $this->reset(),
263
        true
264
    );
265
  }
266
267
  /**
268
   * Function to find all records in database.
269
   *
270
   * @return array return array of ActiveRecord
271
   */
272
  public function fetchAll()
273
  {
274
    return self::_query(
275
        $this->_buildSql(
276
            array(
277
                'select',
278
                'from',
279
                'join',
280
                'where',
281
                'group',
282
                'having',
283
                'order',
284
                'limit',
285
            )
286
        ),
287
        $this->params,
288
        $this->reset()
289
    );
290
  }
291
292
  /**
293
   * Function to delete current record in database.
294
   *
295
   * @return bool
296
   */
297
  public function delete()
298
  {
299
    return self::execute(
300
        $this->eq($this->primaryKeyName, $this->{$this->primaryKeyName})->_buildSql(
301
            array(
302
                'delete',
303
                'from',
304
                'where',
305
            )
306
        ),
307
        $this->params
308
    );
309
  }
310
311
  /**
312
   * @param string $primaryKeyName
313
   *
314
   * @return $this
315
   */
316
  public function setPrimaryKeyName($primaryKeyName)
317
  {
318
    $this->primaryKeyName = $primaryKeyName;
319
320
    return $this;
321
  }
322
323
  /**
324
   * @param string $table
325
   */
326
  public function setTable($table)
327
  {
328
    $this->table = $table;
329
  }
330
331
  /**
332
   * Function to build update SQL, and update current record in database, just write the dirty data into database.
333
   *
334
   * @return bool|ActiveRecord if update success return current object, other wise return false.
335
   */
336
  public function update()
337
  {
338
    if (count($this->dirty) == 0) {
339
      return true;
340
    }
341
342
    foreach ($this->dirty as $field => $value) {
343
      $this->addCondition($field, '=', $value, ',', 'set');
344
    }
345
346
    $result = self::execute(
347
        $this->eq($this->primaryKeyName, $this->{$this->primaryKeyName})->_buildSql(
348
            array(
349
                'update',
350
                'set',
351
                'where',
352
            )
353
        ),
354
        $this->params
355
    );
356
    if ($result) {
357
      return $this->resetDirty()->reset();
358
    }
359
360
    return false;
361
  }
362
363
  /**
364
   * Function to build insert SQL, and insert current record into database.
365
   *
366
   * @return bool|ActiveRecord if insert success return current object, other wise return false.
367
   */
368
  public function insert()
369
  {
370
    if (!self::$db instanceof DB) {
371
      self::$db = DB::getInstance();
372
    }
373
374
    if (count($this->dirty) === 0) {
375
      return true;
376
    }
377
378
    $value = $this->_filterParam($this->dirty);
379
    $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...
380
        array(
381
            'operator' => 'INSERT INTO ' . $this->table,
382
            'target'   => new ActiveRecordExpressionsWrap(array('target' => array_keys($this->dirty))),
383
        )
384
    );
385
    $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...
386
        array(
387
            'operator' => 'VALUES',
388
            'target'   => new ActiveRecordExpressionsWrap(array('target' => $value)),
389
        )
390
    );
391
392
    $result = self::execute($this->_buildSql(array('insert', 'values')), $this->params);
393
    if ($result) {
394
      $this->{$this->primaryKeyName} = $result;
395
396
      return $this->resetDirty()->reset();
397
    }
398
399
    return false;
400
  }
401
402
  /**
403
   * Helper function to exec sql.
404
   *
405
   * @param string $sql   The SQL need to be execute.
406
   * @param array  $param The param will be bind to the sql statement.
407
   *
408
   * @return bool|int|Result              <p>
409
   *                                      "Result" by "<b>SELECT</b>"-queries<br />
410
   *                                      "int" (insert_id) by "<b>INSERT / REPLACE</b>"-queries<br />
411
   *                                      "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
412
   *                                      "true" by e.g. "DROP"-queries<br />
413
   *                                      "false" on error
414
   *                                      </p>
415
   */
416
  public static function execute($sql, array $param = array())
417
  {
418
    if (!self::$db instanceof DB) {
419
      self::$db = DB::getInstance();
420
    }
421
422
    return self::$db->query($sql, $param);
423
  }
424
425
  /**
426
   * Helper function to query one record by sql and params.
427
   *
428
   * @param string       $sql    The SQL to find record.
429
   * @param array        $param  The param will be bind to PDOStatement.
430
   * @param ActiveRecord $obj    The object, if find record in database, will assign the attributes in to this object.
431
   * @param bool         $single If set to true, will find record and fetch in current object, otherwise will find all
432
   *                             records.
433
   *
434
   * @return bool|ActiveRecord|array
435
   */
436
  public static function _query($sql, array $param = array(), $obj = null, $single = false)
437
  {
438
    $result = self::execute($sql, $param);
439
440
    if (!$result) {
441
      return false;
442
    }
443
444
    $useObject = is_object($obj);
445
    if ($useObject === true) {
446
      $called_class = $obj;
447
    } else {
448
      $called_class = get_called_class();
449
    }
450
451
    self::setNewDataAreDirty(false);
452
453
    if ($single) {
454
      $return = $result->fetchObject($called_class);
0 ignored issues
show
Bug introduced by
It seems like $called_class defined by $obj on line 446 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...
455
    } else {
456
      $return = $result->fetchAllObject($called_class);
0 ignored issues
show
Bug introduced by
It seems like $called_class defined by $obj on line 446 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...
457
    }
458
459
    self::setNewDataAreDirty(true);
460
461
    return $return;
462
  }
463
464
  /**
465
   * Helper function to get relation of this object.
466
   * There was three types of relations: {BELONGS_TO, HAS_ONE, HAS_MANY}
467
   *
468
   * @param string $name The name of the relation, the array key when defind the relation.
469
   *
470
   * @return mixed
471
   *
472
   * @throws ActiveRecordException <p>If the relation can't be found .</p>
473
   */
474
  protected function &getRelation($name)
475
  {
476
    $relation = $this->relations[$name];
477
    if (
478
        $relation instanceof self
479
        ||
480
        (
481
            is_array($relation)
482
            &&
483
            $relation[0] instanceof self
484
        )
485
    ) {
486
      return $relation;
487
    }
488
489
    /* @var $obj ActiveRecord */
490
    $obj = new $relation[1];
491
492
    $this->relations[$name] = $obj;
493
    if (isset($relation[3]) && is_array($relation[3])) {
494
      foreach ((array)$relation[3] as $func => $args) {
495
        call_user_func_array(array($obj, $func), (array)$args);
496
      }
497
    }
498
499
    $backref = isset($relation[4]) ? $relation[4] : '';
500
    if (
501
        (!$relation instanceof self)
502
        &&
503
        self::HAS_ONE == $relation[0]
504
    ) {
505
506
      $this->relations[$name] = $obj->eq($relation[2], $this->{$this->primaryKeyName})->fetch();
507
508
      if ($backref) {
509
        $this->relations[$name] && $backref && $obj->__set($backref, $this);
510
      }
511
512
    } elseif (
513
        is_array($relation)
514
        &&
515
        self::HAS_MANY == $relation[0]
516
    ) {
517
518
      $this->relations[$name] = $obj->eq($relation[2], $this->{$this->primaryKeyName})->fetchAll();
519
      if ($backref) {
520
        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...
521
          $o->__set($backref, $this);
522
        }
523
      }
524
525
    } elseif (
526
        (!$relation instanceof self)
527
        &&
528
        self::BELONGS_TO == $relation[0]
529
    ) {
530
531
      $this->relations[$name] = $obj->eq($obj->primaryKeyName, $this->{$relation[2]})->fetch();
532
533
      if ($backref) {
534
        $this->relations[$name] && $backref && $obj->__set($backref, $this);
535
      }
536
537
    } else {
538
      throw new ActiveRecordException("Relation $name not found.");
539
    }
540
541
    return $this->relations[$name];
542
  }
543
544
  /**
545
   * Helper function to build SQL with sql parts.
546
   *
547
   * @param string       $n The SQL part will be build.
548
   * @param int          $i The index of $n in $sql array.
549
   * @param ActiveRecord $o The reference to $this
550
   */
551
  private function _buildSqlCallback(&$n, $i, $o)
552
  {
553
    if (
554
        'select' === $n
555
        &&
556
        null === $o->$n
557
    ) {
558
559
      $n = strtoupper($n) . ' ' . $o->table . '.*';
560
561
    } elseif (
562
        (
563
            'update' === $n
564
            ||
565
            'from' === $n
566
        )
567
        &&
568
        null === $o->$n
569
    ) {
570
571
      $n = strtoupper($n) . ' ' . $o->table;
572
573
    } elseif ('delete' === $n) {
574
575
      $n = strtoupper($n) . ' ';
576
577
    } else {
578
579
      $n = (null !== $o->$n) ? $o->$n . ' ' : '';
580
581
    }
582
  }
583
584
  /**
585
   * Helper function to build SQL with sql parts.
586
   *
587
   * @param array $sqls The SQL part will be build.
588
   *
589
   * @return string
590
   */
591
  protected function _buildSql($sqls = array())
592
  {
593
    array_walk($sqls, array($this, '_buildSqlCallback'), $this);
594
595
    // DEBUG
596
    // echo 'SQL: ', implode(' ', $sqls), "\n", 'PARAMS: ', implode(', ', $this->params), "\n";
597
598
    return implode(' ', $sqls);
599
  }
600
601
  /**
602
   * Magic function to make calls witch in function mapping stored in $operators and $sqlPart.
603
   * also can call function of PDO object.
604
   *
605
   * @param string $name function name
606
   * @param array  $args The arguments of the function.
607
   *
608
   * @return $this|mixed Return the result of callback or the current object to make chain method calls.
609
   *
610
   * @throws ActiveRecordException
611
   */
612
  public function __call($name, $args)
613
  {
614
    if (!self::$db instanceof DB) {
615
      self::$db = DB::getInstance();
616
    }
617
618
    $nameTmp = strtolower($name);
619
620
    if (array_key_exists($nameTmp, self::$operators)) {
621
622
      $this->addCondition(
623
          $args[0],
624
          self::$operators[$nameTmp],
625
          isset($args[1]) ? $args[1] : null,
626
          (is_string(end($args)) && 'or' === strtolower(end($args))) ? 'OR' : 'AND'
627
      );
628
629
    } elseif (array_key_exists($nameTmp = str_replace('by', '', $nameTmp), $this->sqlParts)) {
630
631
      $this->$name = new ActiveRecordExpressions(
632
          array(
633
              'operator' => $this->sqlParts[$nameTmp],
634
              'target'   => implode(', ', $args),
635
          )
636
      );
637
638
    } elseif (is_callable($callback = array(self::$db, $name))) {
639
640
      return call_user_func_array($callback, $args);
641
642
    } else {
643
644
      throw new ActiveRecordException("Method $name not exist.");
645
646
    }
647
648
    return $this;
649
  }
650
651
  /**
652
   * Make wrap when build the SQL expressions of WHERE.
653
   *
654
   * @param string $op If give this param will build one WrapExpressions include the stored expressions add into WHERE.
655
   *                   otherwise wil stored the expressions into array.
656
   *
657
   * @return $this
658
   */
659
  public function wrap($op = null)
660
  {
661
    if (1 === func_num_args()) {
662
      $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...
663
      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...
664
        $this->_addCondition(
665
            new ActiveRecordExpressionsWrap(
666
                array(
667
                    'delimiter' => ' ',
668
                    '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...
669
                )
670
            ), 'or' === strtolower($op) ? 'OR' : 'AND'
671
        );
672
      }
673
      $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...
674
    } else {
675
      $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...
676
    }
677
678
    return $this;
679
  }
680
681
  /**
682
   * Helper function to build place holder when make SQL expressions.
683
   *
684
   * @param mixed $value The value will bind to SQL, just store it in $this->params.
685
   *
686
   * @return mixed $value
687
   */
688
  protected function _filterParam($value)
689
  {
690
    if (is_array($value)) {
691
      foreach ($value as $key => $val) {
692
        $this->params[$value[$key] = self::PREFIX . ++self::$count] = $val;
693
      }
694
    } elseif (is_string($value)) {
695
      $this->params[$ph = self::PREFIX . ++self::$count] = $value;
696
      $value = $ph;
697
    }
698
699
    return $value;
700
  }
701
702
  /**
703
   * Helper function to add condition into WHERE.
704
   * create the SQL Expressions.
705
   *
706
   * @param string $field The field name, the source of Expressions
707
   * @param string $operator
708
   * @param mixed  $value The target of the Expressions
709
   * @param string $op    The operator to concat this Expressions into WHERE or SET statement.
710
   * @param string $name  The Expression will contact to.
711
   */
712
  public function addCondition($field, $operator, $value, $op = 'AND', $name = 'where')
713
  {
714
    $value = $this->_filterParam($value);
715
    $exp = new ActiveRecordExpressions(
716
        array(
717
            'source'   => ('where' == $name ? $this->table . '.' : '') . $field,
718
            'operator' => $operator,
719
            'target'   => is_array($value)
720
                ? new ActiveRecordExpressionsWrap(
721
                    'between' === strtolower($operator)
722
                        ? array('target' => $value, 'start' => ' ', 'end' => ' ', 'delimiter' => ' AND ')
723
                        : array('target' => $value)
724
                ) : $value,
725
        )
726
    );
727
    if ($exp) {
728
      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...
729
        $this->_addCondition($exp, $op, $name);
730
      } else {
731
        $this->_addExpression($exp, $op);
732
      }
733
    }
734
  }
735
736
  /**
737
   * helper function to add condition into JOIN.
738
   * create the SQL Expressions.
739
   *
740
   * @param string $table The join table name
741
   * @param string $on    The condition of ON
742
   * @param string $type  The join type, like "LEFT", "INNER", "OUTER"
743
   *
744
   * @return $this
745
   */
746
  public function join($table, $on, $type = 'LEFT')
747
  {
748
    $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...
749
        array(
750
            '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...
751
            'operator' => $type . ' JOIN',
752
            'target'   => new ActiveRecordExpressions(
753
                array('source' => $table, 'operator' => 'ON', 'target' => $on)
754
            ),
755
        )
756
    );
757
758
    return $this;
759
  }
760
761
  /**
762
   * helper function to make wrapper. Stored the expression in to array.
763
   *
764
   * @param ActiveRecordExpressions $exp      The expression will be stored.
765
   * @param string                  $operator The operator to concat this Expressions into WHERE statment.
766
   */
767
  protected function _addExpression($exp, $operator)
768
  {
769
    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...
770
      $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...
771
    } else {
772
      $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...
773
    }
774
  }
775
776
  /**
777
   * helper function to add condition into WHERE.
778
   *
779
   * @param ActiveRecordExpressions $exp      The expression will be concat into WHERE or SET statment.
780
   * @param string                  $operator the operator to concat this Expressions into WHERE or SET statment.
781
   * @param string                  $name     The Expression will contact to.
782
   */
783
  protected function _addCondition($exp, $operator, $name = 'where')
784
  {
785
    if (!$this->$name) {
786
      $this->$name = new ActiveRecordExpressions(array('operator' => strtoupper($name), 'target' => $exp));
787
    } else {
788
      $this->$name->target = new ActiveRecordExpressions(
789
          array(
790
              'source'   => $this->$name->target,
791
              'operator' => $operator,
792
              'target'   => $exp,
793
          )
794
      );
795
    }
796
  }
797
798
  /**
799
   * @return array
800
   */
801
  public function getDirty()
802
  {
803
    return $this->dirty;
804
  }
805
806
  /**
807
   * @return bool
808
   */
809
  public static function isNewDataAreDirty()
810
  {
811
    return self::$new_data_are_dirty;
812
  }
813
814
  /**
815
   * @param bool $bool
816
   */
817
  public static function setNewDataAreDirty($bool)
818
  {
819
    self::$new_data_are_dirty = (bool)$bool;
820
  }
821
822
  /**
823
   * Magic function to SET values of the current object.
824
   *
825
   * @param mixed $var
826
   * @param mixed $val
827
   */
828
  public function __set($var, $val)
829
  {
830
    if (
831
        array_key_exists($var, $this->sqlExpressions)
832
        ||
833
        array_key_exists($var, $this->defaultSqlExpressions)
834
    ) {
835
836
      $this->sqlExpressions[$var] = $val;
837
838
    } elseif (
839
        array_key_exists($var, $this->relations)
840
        &&
841
        $val instanceof self
842
    ) {
843
844
      $this->relations[$var] = $val;
845
846
    } else {
847
848
      $this->array[$var] = $val;
849
850
      if (self::$new_data_are_dirty === true) {
851
        $this->dirty[$var] = $val;
852
      }
853
854
    }
855
  }
856
857
  /**
858
   * Magic function to UNSET values of the current object.
859
   *
860
   * @param mixed $var
861
   */
862
  public function __unset($var)
863
  {
864
    if (array_key_exists($var, $this->sqlExpressions)) {
865
      unset($this->sqlExpressions[$var]);
866
    }
867
868
    if (isset($this->array[$var])) {
869
      unset($this->array[$var]);
870
    }
871
872
    if (isset($this->dirty[$var])) {
873
      unset($this->dirty[$var]);
874
    }
875
  }
876
877
  /**
878
   * Helper function for "GROUP BY".
879
   *
880
   * @param array $args
881
   * @param null  $dummy <p>only needed for API compatibility with Arrayy</p>
882
   *
883
   * @return $this
884
   */
885
  public function group($args, $dummy = null)
886
  {
887
    $this->__call('group', func_get_args());
888
889
    return $this;
890
  }
891
892
  /**
893
   * Helper function for "ORDER BY".
894
   *
895
   * @param $args ...
896
   *
897
   * @return $this
898
   */
899
  public function order($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...
900
  {
901
    $this->__call('order', func_get_args());
902
903
    return $this;
904
  }
905
906
  /**
907
   * Magic function to GET the values of current object.
908
   *
909
   * @param $var
910
   *
911
   * @return mixed
912
   */
913
  public function &__get($var)
914
  {
915
    if (isset($this->dirty[$var])) {
916
      return $this->dirty[$var];
917
    }
918
919
    if (array_key_exists($var, $this->sqlExpressions)) {
920
      return $this->sqlExpressions[$var];
921
    }
922
923
    if (array_key_exists($var, $this->relations)) {
924
      return $this->getRelation($var);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->getRelation($var); of type voku\db\ActiveRecordExpr...iveRecord|array|boolean adds the type array to the return on line 924 which is incompatible with the return type of the parent method Arrayy\Arrayy::__get of type object|integer|double|string|null|boolean.
Loading history...
925
    }
926
927
    return parent::__get($var);
928
  }
929
}
930