Completed
Push — master ( 92b49e...228e10 )
by Lars
01:59
created

ActiveRecord::getRelation()   C

Complexity

Conditions 22
Paths 45

Size

Total Lines 69
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 45
CRAP Score 22.118

Importance

Changes 0
Metric Value
dl 0
loc 69
ccs 45
cts 48
cp 0.9375
rs 5.5836
c 0
b 0
f 0
cc 22
eloc 40
nc 45
nop 1
crap 22.118

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 $dbProperty)
12
 * @method $this eq(string $dbProperty, string | null $value = null)
13
 * @method $this from(string $table)
14
 * @method $this where(string $where)
15
 * @method $this having(string $having)
16
 * @method $this limit(int $start, int | null $end = null)
17
 *
18
 * @method $this equal(string $dbProperty, string $value)
19
 * @method $this notEqual(string $dbProperty, string $value)
20
 * @method $this ne(string $dbProperty, string $value)
21
 * @method $this greaterThan(string $dbProperty, int $value)
22
 * @method $this gt(string $dbProperty, int $value)
23
 * @method $this lessThan(string $dbProperty, int $value)
24
 * @method $this lt(string $dbProperty, int $value)
25
 * @method $this greaterThanOrEqual(string $dbProperty, int $value)
26
 * @method $this ge(string $dbProperty, int $value)
27
 * @method $this gte(string $dbProperty, int $value)
28
 * @method $this lessThanOrEqual(string $dbProperty, int $value)
29
 * @method $this le(string $dbProperty, int $value)
30
 * @method $this lte(string $dbProperty, int $value)
31
 * @method $this between(string $dbProperty, array $value)
32
 * @method $this like(string $dbProperty, string $value)
33
 * @method $this in(string $dbProperty, array $value)
34
 * @method $this notIn(string $dbProperty, array $value)
35
 * @method $this isnull(string $dbProperty)
36
 * @method $this isNotNull(string $dbProperty)
37
 * @method $this notNull(string $dbProperty)
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 DB->query(),
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 4
  public function getPrimaryKey()
182
  {
183 4
    $id = $this->{$this->primaryKeyName};
184 4
    if ($id) {
185 4
      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 12
  public function reset()
205
  {
206 12
    $this->params = array();
207 12
    $this->sqlExpressions = array();
208
209 12
    return $this;
210
  }
211
212
  /**
213
   * Reset the dirty data.
214
   *
215
   * @return $this
216
   */
217 4
  public function resetDirty()
218
  {
219 4
    $this->array = array();
220
221 4
    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, otherwise return "false".
241
   */
242 7
  public function fetch($id = null)
243
  {
244 7
    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 2
      $this->reset()->eq($this->primaryKeyName, $id);
246 2
    }
247
248 7
    return self::query(
249 7
        $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...
250
            array(
251 7
                'select',
252 7
                'from',
253 7
                'join',
254 7
                'where',
255 7
                'group',
256 7
                'having',
257 7
                'order',
258 7
                'limit',
259
            )
260 7
        ),
261 7
        $this->params,
262 7
        $this->reset(),
263
        true
264 7
    );
265
  }
266
267
  /**
268
   * Function to find all records in database.
269
   *
270
   * @return array return array of ActiveRecord
271
   */
272 3
  public function fetchAll()
273
  {
274 3
    return self::query(
275 3
        $this->_buildSql(
276
            array(
277 3
                'select',
278 3
                'from',
279 3
                'join',
280 3
                'where',
281 3
                'group',
282 3
                'having',
283 3
                'order',
284 3
                'limit',
285
            )
286 3
        ),
287 3
        $this->params,
288 3
        $this->reset()
289 3
    );
290
  }
291
292
  /**
293
   * Function to delete current record in database.
294
   *
295
   * @return bool
296
   */
297 1
  public function delete()
298
  {
299 1
    return self::execute(
300 1
        $this->eq($this->primaryKeyName, $this->{$this->primaryKeyName})->_buildSql(
301
            array(
302 1
                'delete',
303 1
                'from',
304 1
                'where',
305
            )
306 1
        ),
307 1
        $this->params
308 1
    );
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|int <p>
335
   *                  If update was successful, it will return the affected rows as int,
336
   *                  otherwise it will return false or true (if there are no dirty data).
337
   *                  </p>
338
   */
339 2
  public function update()
340
  {
341 2
    if (count($this->dirty) == 0) {
342
      return true;
343
    }
344
345 2
    foreach ($this->dirty as $field => $value) {
346 2
      $this->addCondition($field, '=', $value, ',', 'set');
347 2
    }
348
349 2
    $result = self::execute(
350 2
        $this->eq($this->primaryKeyName, $this->{$this->primaryKeyName})->_buildSql(
351
            array(
352 2
                'update',
353 2
                'set',
354 2
                'where',
355
            )
356 2
        ),
357 2
        $this->params
358 2
    );
359 2
    if ($result) {
360 2
      $this->resetDirty();
361 2
      $this->reset();
362
363 2
      return $result;
364
    }
365
366
    return false;
367
  }
368
369
  /**
370
   * Function to build insert SQL, and insert current record into database.
371
   *
372
   * @return bool|int <p>
373
   *                  If insert was successful, it will return the new id,
374
   *                  otherwise it will return false or true (if there are no dirty data).
375
   *                  </p>
376
   */
377 2
  public function insert()
378
  {
379 2
    if (!self::$db instanceof DB) {
380
      self::$db = DB::getInstance();
381
    }
382
383 2
    if (count($this->dirty) === 0) {
384
      return true;
385
    }
386
387 2
    $value = $this->_filterParam($this->dirty);
388 2
    $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...
389
        array(
390 2
            'operator' => 'INSERT INTO ' . $this->table,
391 2
            'target'   => new ActiveRecordExpressionsWrap(array('target' => array_keys($this->dirty))),
392
        )
393 2
    );
394 2
    $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...
395
        array(
396 2
            'operator' => 'VALUES',
397 2
            'target'   => new ActiveRecordExpressionsWrap(array('target' => $value)),
398
        )
399 2
    );
400
401 2
    $result = self::execute($this->_buildSql(array('insert', 'values')), $this->params);
402 2
    if ($result) {
403 2
      $this->{$this->primaryKeyName} = $result;
404
405 2
      $this->resetDirty();
406 2
      $this->reset();
407
408 2
      return $result;
409
    }
410
411
    return false;
412
  }
413
414
  /**
415
   * Helper function to exec sql.
416
   *
417
   * @param string $sql   The SQL need to be execute.
418
   * @param array  $param The param will be bind to the sql statement.
419
   *
420
   * @return bool|int|Result              <p>
421
   *                                      "Result" by "<b>SELECT</b>"-queries<br />
422
   *                                      "int" (insert_id) by "<b>INSERT / REPLACE</b>"-queries<br />
423
   *                                      "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
424
   *                                      "true" by e.g. "DROP"-queries<br />
425
   *                                      "false" on error
426
   *                                      </p>
427
   */
428 13
  public static function execute($sql, array $param = array())
429
  {
430 13
    if (!self::$db instanceof DB) {
431 1
      self::$db = DB::getInstance();
432 1
    }
433
434 13
    return self::$db->query($sql, $param);
435
  }
436
437
  /**
438
   * Helper function to query one record by sql and params.
439
   *
440
   * @param string            $sql    <p>
441
   *                                  The SQL query to find the record.
442
   *                                  </p>
443
   * @param array             $param  <p>
444
   *                                  The param will be bind to the $sql query.
445
   *                                  </p>
446
   * @param ActiveRecord|null $obj    <p>
447
   *                                  The object, if find record in database, we will assign the attributes into
448
   *                                  this object.
449
   *                                  </p>
450
   * @param bool              $single <p>
451
   *                                  If set to true, we will find record and fetch in current object, otherwise
452
   *                                  will find all records.
453
   *                                  </p>
454
   *
455
   * @return bool|ActiveRecord|array
456
   */
457 8
  public static function query($sql, array $param = array(), $obj = null, $single = false)
458
  {
459 8
    $result = self::execute($sql, $param);
460
461 8
    if (!$result) {
462
      return false;
463
    }
464
465 8
    $useObject = is_object($obj);
466 8
    if ($useObject === true) {
467 8
      $called_class = $obj;
468 8
    } else {
469
      $called_class = get_called_class();
470
    }
471
472 8
    self::setNewDataAreDirty(false);
473
474 8
    if ($single) {
475 7
      $return = $result->fetchObject($called_class, null, true);
0 ignored issues
show
Bug introduced by
It seems like $called_class defined by $obj on line 467 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...
476 7
    } else {
477 3
      $return = $result->fetchAllObject($called_class, null);
0 ignored issues
show
Bug introduced by
It seems like $called_class defined by $obj on line 467 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...
478
    }
479
480 8
    self::setNewDataAreDirty(true);
481
482 8
    return $return;
483
  }
484
485
  /**
486
   * Helper function to get relation of this object.
487
   * There was three types of relations: {BELONGS_TO, HAS_ONE, HAS_MANY}
488
   *
489
   * @param string $name The name of the relation, the array key when defind the relation.
490
   *
491
   * @return mixed
492
   *
493
   * @throws ActiveRecordException <p>If the relation can't be found .</p>
494
   */
495 3
  protected function &getRelation($name)
496
  {
497 3
    $relation = $this->relations[$name];
498
    if (
499
        $relation instanceof self
500 3
        ||
501
        (
502 2
            is_array($relation)
503 2
            &&
504 2
            $relation[0] instanceof self
505 2
        )
506 3
    ) {
507 3
      return $relation;
508
    }
509
510
    /* @var $obj ActiveRecord */
511 2
    $obj = new $relation[1];
512
513 2
    $this->relations[$name] = $obj;
514 2
    if (isset($relation[3]) && is_array($relation[3])) {
515 1
      foreach ((array)$relation[3] as $func => $args) {
516 1
        call_user_func_array(array($obj, $func), (array)$args);
517 1
      }
518 1
    }
519
520 2
    $backref = isset($relation[4]) ? $relation[4] : '';
521
    if (
522 2
        (!$relation instanceof self)
523 2
        &&
524 2
        self::HAS_ONE == $relation[0]
525 2
    ) {
526
527 1
      $this->relations[$name] = $obj->eq($relation[2], $this->{$this->primaryKeyName})->fetch();
528
529 1
      if ($backref) {
530
        $this->relations[$name] && $backref && $obj->__set($backref, $this);
531
      }
532
533 1
    } elseif (
534 2
        is_array($relation)
535 2
        &&
536 2
        self::HAS_MANY == $relation[0]
537 2
    ) {
538
539 2
      $this->relations[$name] = $obj->eq($relation[2], $this->{$this->primaryKeyName})->fetchAll();
540 2
      if ($backref) {
541 1
        foreach ($this->relations[$name] as $o) {
0 ignored issues
show
Bug introduced by
The expression $this->relations[$name] of type object<voku\db\ActiveRec...veRecord>|boolean|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

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

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

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

Loading history...
542 1
          $o->__set($backref, $this);
543 1
        }
544 1
      }
545
546 2
    } elseif (
547 2
        (!$relation instanceof self)
548 2
        &&
549 2
        self::BELONGS_TO == $relation[0]
550 2
    ) {
551
552 2
      $this->relations[$name] = $obj->eq($obj->primaryKeyName, $this->{$relation[2]})->fetch();
553
554 2
      if ($backref) {
555 1
        $this->relations[$name] && $backref && $obj->__set($backref, $this);
556 1
      }
557
558 2
    } else {
559
      throw new ActiveRecordException("Relation $name not found.");
560
    }
561
562 2
    return $this->relations[$name];
563
  }
564
565
  /**
566
   * Helper function to build SQL with sql parts.
567
   *
568
   * @param string       $n The SQL part will be build.
569
   * @param int          $i The index of $n in $sql array.
570
   * @param ActiveRecord $o The reference to $this
571
   */
572 12
  private function _buildSqlCallback(&$n, $i, $o)
573
  {
574
    if (
575
        'select' === $n
576 12
        &&
577 8
        null === $o->$n
578 12
    ) {
579
580 7
      $n = strtoupper($n) . ' ' . $o->table . '.*';
581
582 7
    } elseif (
583
        (
584
            'update' === $n
585 12
            ||
586
            'from' === $n
587 12
        )
588 12
        &&
589 10
        null === $o->$n
590 12
    ) {
591
592 10
      $n = strtoupper($n) . ' ' . $o->table;
593
594 12
    } elseif ('delete' === $n) {
595
596 1
      $n = strtoupper($n) . ' ';
597
598 1
    } else {
599
600 12
      $n = (null !== $o->$n) ? $o->$n . ' ' : '';
601
602
    }
603 12
  }
604
605
  /**
606
   * Helper function to build SQL with sql parts.
607
   *
608
   * @param array $sqls The SQL part will be build.
609
   *
610
   * @return string
611
   */
612 12
  protected function _buildSql($sqls = array())
613
  {
614 12
    array_walk($sqls, array($this, '_buildSqlCallback'), $this);
615
616
    // DEBUG
617
    // echo 'SQL: ', implode(' ', $sqls), "\n", 'PARAMS: ', implode(', ', $this->params), "\n";
618
619 12
    return implode(' ', $sqls);
620
  }
621
622
  /**
623
   * Magic function to make calls witch in function mapping stored in $operators and $sqlPart.
624
   * also can call function of DB object.
625
   *
626
   * @param string $name function name
627
   * @param array  $args The arguments of the function.
628
   *
629
   * @return $this|mixed Return the result of callback or the current object to make chain method calls.
630
   *
631
   * @throws ActiveRecordException
632
   */
633 9
  public function __call($name, $args)
634
  {
635 9
    if (!self::$db instanceof DB) {
636
      self::$db = DB::getInstance();
637
    }
638
639 9
    $nameTmp = strtolower($name);
640
641 9
    if (array_key_exists($nameTmp, self::$operators)) {
642
643 7
      $this->addCondition(
644 7
          $args[0],
645 7
          self::$operators[$nameTmp],
646 7
          isset($args[1]) ? $args[1] : null,
647 7
          (is_string(end($args)) && 'or' === strtolower(end($args))) ? 'OR' : 'AND'
648 7
      );
649
650 9
    } elseif (array_key_exists($nameTmp = str_replace('by', '', $nameTmp), $this->sqlParts)) {
651
652 7
      $this->$name = new ActiveRecordExpressions(
653
          array(
654 7
              'operator' => $this->sqlParts[$nameTmp],
655 7
              'target'   => implode(', ', $args),
656
          )
657 7
      );
658
659 7
    } elseif (is_callable($callback = array(self::$db, $name))) {
660
661
      return call_user_func_array($callback, $args);
662
663
    } else {
664
665
      throw new ActiveRecordException("Method $name not exist.");
666
667
    }
668
669 9
    return $this;
670
  }
671
672
  /**
673
   * Make wrap when build the SQL expressions of WHERE.
674
   *
675
   * @param string $op If give this param will build one WrapExpressions include the stored expressions add into WHERE.
676
   *                   otherwise wil stored the expressions into array.
677
   *
678
   * @return $this
679
   */
680 1
  public function wrap($op = null)
681
  {
682 1
    if (1 === func_num_args()) {
683 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...
684 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...
685 1
        $this->_addCondition(
686 1
            new ActiveRecordExpressionsWrap(
687
                array(
688 1
                    'delimiter' => ' ',
689 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...
690
                )
691 1
            ), 'or' === strtolower($op) ? 'OR' : 'AND'
692 1
        );
693 1
      }
694 1
      $this->expressions = array();
0 ignored issues
show
Bug introduced by
The property expressions does not seem to exist. Did you mean defaultSqlExpressions?

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

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

Loading history...
695 1
    } else {
696 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...
697
    }
698
699 1
    return $this;
700
  }
701
702
  /**
703
   * Helper function to build place holder when make SQL expressions.
704
   *
705
   * @param mixed $value The value will bind to SQL, just store it in $this->params.
706
   *
707
   * @return mixed $value
708
   */
709 9
  protected function _filterParam($value)
710
  {
711 9
    if (is_array($value)) {
712 3
      foreach ($value as $key => $val) {
713 3
        $this->params[$value[$key] = self::PREFIX . ++self::$count] = $val;
714 3
      }
715 9
    } elseif (is_string($value)) {
716 3
      $this->params[$ph = self::PREFIX . ++self::$count] = $value;
717 3
      $value = $ph;
718 3
    }
719
720 9
    return $value;
721
  }
722
723
  /**
724
   * Helper function to add condition into WHERE.
725
   * create the SQL Expressions.
726
   *
727
   * @param string $field The field name, the source of Expressions
728
   * @param string $operator
729
   * @param mixed  $value The target of the Expressions
730
   * @param string $op    The operator to concat this Expressions into WHERE or SET statement.
731
   * @param string $name  The Expression will contact to.
732
   */
733 7
  public function addCondition($field, $operator, $value, $op = 'AND', $name = 'where')
734
  {
735 7
    $value = $this->_filterParam($value);
736 7
    $exp = new ActiveRecordExpressions(
737
        array(
738 7
            'source'   => ('where' == $name ? $this->table . '.' : '') . $field,
739 7
            'operator' => $operator,
740 7
            'target'   => is_array($value)
741 7
                ? new ActiveRecordExpressionsWrap(
742 1
                    'between' === strtolower($operator)
743 1
                        ? array('target' => $value, 'start' => ' ', 'end' => ' ', 'delimiter' => ' AND ')
744 1
                        : array('target' => $value)
745 7
                ) : $value,
746
        )
747 7
    );
748 7
    if ($exp) {
749 7
      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...
750 7
        $this->_addCondition($exp, $op, $name);
751 7
      } else {
752 1
        $this->_addExpression($exp, $op);
753
      }
754 7
    }
755 7
  }
756
757
  /**
758
   * helper function to add condition into JOIN.
759
   * create the SQL Expressions.
760
   *
761
   * @param string $table The join table name
762
   * @param string $on    The condition of ON
763
   * @param string $type  The join type, like "LEFT", "INNER", "OUTER"
764
   *
765
   * @return $this
766
   */
767 1
  public function join($table, $on, $type = 'LEFT')
768
  {
769 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...
770
        array(
771 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...
772 1
            'operator' => $type . ' JOIN',
773 1
            'target'   => new ActiveRecordExpressions(
774 1
                array('source' => $table, 'operator' => 'ON', 'target' => $on)
775 1
            ),
776
        )
777 1
    );
778
779 1
    return $this;
780
  }
781
782
  /**
783
   * helper function to make wrapper. Stored the expression in to array.
784
   *
785
   * @param ActiveRecordExpressions $exp      The expression will be stored.
786
   * @param string                  $operator The operator to concat this Expressions into WHERE statment.
787
   */
788 1
  protected function _addExpression($exp, $operator)
789
  {
790 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...
791 1
      $this->expressions = array($exp);
0 ignored issues
show
Bug introduced by
The property expressions does not seem to exist. Did you mean defaultSqlExpressions?

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

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

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

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

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

Loading history...
794
    }
795 1
  }
796
797
  /**
798
   * helper function to add condition into WHERE.
799
   *
800
   * @param ActiveRecordExpressions $exp      The expression will be concat into WHERE or SET statment.
801
   * @param string                  $operator the operator to concat this Expressions into WHERE or SET statment.
802
   * @param string                  $name     The Expression will contact to.
803
   */
804 7
  protected function _addCondition($exp, $operator, $name = 'where')
805
  {
806 7
    if (!$this->$name) {
807 7
      $this->$name = new ActiveRecordExpressions(array('operator' => strtoupper($name), 'target' => $exp));
808 7
    } else {
809 4
      $this->$name->target = new ActiveRecordExpressions(
810
          array(
811 4
              'source'   => $this->$name->target,
812 4
              'operator' => $operator,
813 4
              'target'   => $exp,
814
          )
815 4
      );
816
    }
817 7
  }
818
819
  /**
820
   * @return array
821
   */
822 1
  public function getDirty()
823
  {
824 1
    return $this->dirty;
825
  }
826
827
  /**
828
   * @return bool
829
   */
830
  public static function isNewDataAreDirty()
831
  {
832
    return self::$new_data_are_dirty;
833
  }
834
835
  /**
836
   * @param bool $bool
837
   */
838 8
  public static function setNewDataAreDirty($bool)
839
  {
840 8
    self::$new_data_are_dirty = (bool)$bool;
841 8
  }
842
843
  /**
844
   * Magic function to SET values of the current object.
845
   *
846
   * @param mixed $var
847
   * @param mixed $val
848
   */
849 12
  public function __set($var, $val)
850
  {
851
    if (
852 12
        array_key_exists($var, $this->sqlExpressions)
853
        ||
854 12
        array_key_exists($var, $this->defaultSqlExpressions)
855 12
    ) {
856
857 11
      $this->sqlExpressions[$var] = $val;
858
859 11
    } elseif (
860 12
        array_key_exists($var, $this->relations)
861 12
        &&
862
        $val instanceof self
863 12
    ) {
864
865 1
      $this->relations[$var] = $val;
866
867 1
    } else {
868
869 12
      $this->array[$var] = $val;
870
871 12
      if (self::$new_data_are_dirty === true) {
872 5
        $this->dirty[$var] = $val;
873 5
      }
874
875
    }
876 12
  }
877
878
  /**
879
   * Magic function to UNSET values of the current object.
880
   *
881
   * @param mixed $var
882
   */
883 1
  public function __unset($var)
884
  {
885 1
    if (array_key_exists($var, $this->sqlExpressions)) {
886
      unset($this->sqlExpressions[$var]);
887
    }
888
889 1
    if (isset($this->array[$var])) {
890 1
      unset($this->array[$var]);
891 1
    }
892
893 1
    if (isset($this->dirty[$var])) {
894 1
      unset($this->dirty[$var]);
895 1
    }
896 1
  }
897
898
  /**
899
   * Helper function for "GROUP BY".
900
   *
901
   * @param array $args
902
   * @param null  $dummy <p>only needed for API compatibility with Arrayy</p>
903
   *
904
   * @return $this
905
   */
906
  public function group($args, $dummy = null)
907
  {
908
    $this->__call('group', func_get_args());
909
910
    return $this;
911
  }
912
913
  /**
914
   * Helper function for "ORDER BY".
915
   *
916
   * @param $args ...
917
   *
918
   * @return $this
919
   */
920 2
  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...
921
  {
922 2
    $this->__call('order', func_get_args());
923
924 2
    return $this;
925
  }
926
927
  /**
928
   * Magic function to GET the values of current object.
929
   *
930
   * @param $var
931
   *
932
   * @return mixed
933
   */
934 12
  public function &__get($var)
935
  {
936 12
    if (isset($this->dirty[$var])) {
937 12
      return $this->dirty[$var];
938
    }
939
940 12
    if (array_key_exists($var, $this->sqlExpressions)) {
941 11
      return $this->sqlExpressions[$var];
942
    }
943
944 10
    if (array_key_exists($var, $this->relations)) {
945 3
      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 945 which is incompatible with the return type of the parent method Arrayy\Arrayy::__get of type object|integer|double|string|null|boolean.
Loading history...
946
    }
947
948 10
    return parent::__get($var);
949
  }
950
}
951