Completed
Push — master ( f7bbc9...2208d2 )
by Lars
01:44
created

ActiveRecord::reset()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 0
1
<?php
2
3
namespace voku\db;
4
5
use Arrayy\Arrayy;
6
7
/**
8
 * Simple implement of active record in PHP.<br />
9
 * Using magic function to implement more smarty functions.<br />
10
 * Can using chain method calls, to build concise and compactness program.<br />
11
 *
12
 * @method $this select(string $stuff1, string | null $stuff2 = null)
13
 * @method $this eq(string $stuff1, string | null $stuff2 = null)
14
 * @method $this from(string $table)
15
 * @method $this where()
16
 * @method $this group()
17
 * @method $this having()
18
 * @method $this order()
19
 * @method $this limit(int $start, int | null $end = null)
20
 *
21
 * @method $this equal(string $stuff1, string $stuff2)
22
 * @method $this notequal(string $stuff1, string $stuff2)
23
 * @method $this ne(string $stuff1, string $stuff2)
24
 * @method $this greaterthan(string $stuff1, int $stuff2)
25
 * @method $this gt(string $stuff1, int $stuff2)
26
 * @method $this lessthan(string $stuff1, int $stuff2)
27
 * @method $this lt(string $stuff1, int $stuff2)
28
 * @method $this greaterthanorequal(string $stuff1, int $stuff2)
29
 * @method $this ge(string $stuff1, int $stuff2)
30
 * @method $this gte(string $stuff1, int $stuff2)
31
 * @method $this lessthanorequal(string $stuff1, int $stuff2)
32
 * @method $this le(string $stuff1, int $stuff2)
33
 * @method $this lte(string $stuff1, int $stuff2)
34
 * @method $this between(string $stuff1, array $stuff2)
35
 * @method $this like(string $stuff1, string $stuff2)
36
 * @method $this in(string $stuff1, array $stuff2)
37
 * @method $this notin(string $stuff1, array $stuff2)
38
 * @method $this isnull(string $stuff1)
39
 * @method $this isnotnull(string $stuff1)
40
 * @method $this notnull(string $stuff1)
41
 */
42
abstract class ActiveRecord extends Arrayy
43
{
44
  /**
45
   * @var DB static property to connect database.
46
   */
47
  public static $db;
48
49
  /**
50
   * @var array maping the function name and the operator, to build Expressions in WHERE condition.
51
   * <pre>user can call it like this:
52
   *      $user->isnotnull()->eq('id', 1);
53
   * will create Expressions can explain to SQL:
54
   *      WHERE user.id IS NOT NULL AND user.id = :ph1</pre>
55
   */
56
  public static $operators = array(
57
      'equal'              => '=',
58
      'eq'                 => '=',
59
      'notequal'           => '<>',
60
      'ne'                 => '<>',
61
      'greaterthan'        => '>',
62
      'gt'                 => '>',
63
      'lessthan'           => '<',
64
      'lt'                 => '<',
65
      'greaterthanorequal' => '>=',
66
      'ge'                 => '>=',
67
      'gte'                => '>=',
68
      'lessthanorequal'    => '<=',
69
      'le'                 => '<=',
70
      'lte'                => '<=',
71
      'between'            => 'BETWEEN',
72
      'like'               => 'LIKE',
73
      'in'                 => 'IN',
74
      'notin'              => 'NOT IN',
75
      'isnull'             => 'IS NULL',
76
      'isnotnull'          => 'IS NOT NULL',
77
      'notnull'            => 'IS NOT NULL',
78
  );
79
80
  /**
81
   * @var array Part of SQL, maping the function name and the operator to build SQL Part.
82
   * <pre>call function like this:
83
   *      $user->order('id desc', 'name asc')->limit(2,1);
84
   *  can explain to SQL:
85
   *      ORDER BY id desc, name asc limit 2,1</pre>
86
   */
87
  public static $sqlParts = array(
88
      'select'  => 'SELECT',
89
      'from'    => 'FROM',
90
      'set'     => 'SET',
91
      'where'   => 'WHERE',
92
      'group'   => 'GROUP BY',
93
      'groupby' => 'GROUP BY',
94
      'having'  => 'HAVING',
95
      'order'   => 'ORDER BY',
96
      'orderby' => 'ORDER BY',
97
      'limit'   => 'limit',
98
      'top'     => 'TOP',
99
  );
100
101
  /**
102
   * @var array Static property to stored the default Sql Expressions values.
103
   */
104
  public static $defaultSqlExpressions = array(
105
      'expressions' => array(),
106
      'wrap'        => false,
107
      'select'      => null,
108
      'insert'      => null,
109
      'update'      => null,
110
      'set'         => null,
111
      'delete'      => 'DELETE ',
112
      'join'        => null,
113
      'from'        => null,
114
      'values'      => null,
115
      'where'       => null,
116
      'having'      => null,
117
      'limit'       => null,
118
      'order'       => null,
119
      'group'       => null,
120
  );
121
122
  /**
123
   * @var array Stored the Expressions of the SQL.
124
   */
125
  protected $sqlExpressions = array();
126
127
  /**
128
   * @var string  The table name in database.
129
   */
130
  public $table;
131
132
  /**
133
   * @var string  The primary key of this ActiveRecord, just suport single primary key.
134
   */
135
  public $primaryKey = 'id';
136
137
  /**
138
   * @var array Stored the drity data of this object, when call "insert" or "update" function, will write this data
139
   *      into database.
140
   */
141
  public $dirty = array();
142
143
  /**
144
   * @var array Stored the params will bind to SQL when call PDOStatement::execute(),
145
   */
146
  public $params = array();
147
148
  /**
149
   * @var Arrayy[] Stored the configure of the relation, or target of the relation.
150
   */
151
  public $relations = array();
152
153
  /**
154
   * @var int The count of bind params, using this count and const "PREFIX" (:ph) to generate place holder in SQL.
155
   */
156
  public static $count = 0;
157
158
  const BELONGS_TO = 'belongs_to';
159
  const HAS_MANY   = 'has_many';
160
  const HAS_ONE    = 'has_one';
161
162
  const PREFIX = ':ph';
163
164
  /**
165
   * Function to reset the $params and $sqlExpressions.
166
   *
167
   * @return $this
168
   */
169
  public function reset()
170
  {
171
    $this->params = array();
172
    $this->sqlExpressions = array();
173
174
    return $this;
175
  }
176
177
  /**
178
   * function to SET or RESET the dirty data.
179
   *
180
   * @param array $dirty The dirty data will be set, or empty array to reset the dirty data.
181
   *
182
   * @return $this
183
   */
184
  public function dirty(array $dirty = array())
185
  {
186
    $this->array = array_merge($this->array, $this->dirty = $dirty);
187
188
    return $this;
189
  }
190
191
  /**
192
   * set the DB connection.
193
   *
194
   * @param DB $db
195
   */
196
  public static function setDb($db)
197
  {
198
    self::$db = $db;
199
  }
200
201
  /**
202
   * function to find one record and assign in to current object.
203
   *
204
   * @param int $id If call this function using this param, will find record by using this id. If not set, just find
205
   *                the first record in database.
206
   *
207
   * @return bool|ActiveRecord if find record, assign in to current object and return it, other wise return "false".
208
   */
209
  public function fetch($id = null)
210
  {
211
    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...
212
      $this->reset()->eq($this->primaryKey, $id);
213
    }
214
215
    return self::_query(
216
        $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...
217
            array(
218
                'select',
219
                'from',
220
                'join',
221
                'where',
222
                'group',
223
                'having',
224
                'order',
225
                'limit',
226
            )
227
        ),
228
        $this->params,
229
        $this->reset(),
230
        true
231
    );
232
  }
233
234
  /**
235
   * Function to find all records in database.
236
   *
237
   * @return array return array of ActiveRecord
238
   */
239
  public function fetchAll()
240
  {
241
    return self::_query(
242
        $this->_buildSql(
243
            array(
244
                'select',
245
                'from',
246
                'join',
247
                'where',
248
                'group',
249
                'having',
250
                'order',
251
                'limit',
252
            )
253
        ),
254
        $this->params,
255
        $this->reset()
256
    );
257
  }
258
259
  /**
260
   * Function to delete current record in database.
261
   *
262
   * @return bool
263
   */
264
  public function delete()
265
  {
266
    return self::execute(
267
        $this->eq($this->primaryKey, $this->{$this->primaryKey})->_buildSql(
268
            array(
269
                'delete',
270
                'from',
271
                'where',
272
            )
273
        ),
274
        $this->params
275
    );
276
  }
277
278
  /**
279
   * Function to build update SQL, and update current record in database, just write the dirty data into database.
280
   *
281
   * @return bool|ActiveRecord if update success return current object, other wise return false.
282
   */
283
  public function update()
284
  {
285
    if (count($this->dirty) == 0) {
286
      return true;
287
    }
288
289
    foreach ($this->dirty as $field => $value) {
290
      $this->addCondition($field, '=', $value, ',', 'set');
291
    }
292
293
    if (self::execute(
294
        $this->eq($this->primaryKey, $this->{$this->primaryKey})->_buildSql(
295
            array(
296
                'update',
297
                'set',
298
                'where',
299
            )
300
        ),
301
        $this->params
302
    )) {
303
      return $this->dirty()->reset();
304
    }
305
306
    return false;
307
  }
308
309
  /**
310
   * Function to build insert SQL, and insert current record into database.
311
   *
312
   * @return bool|ActiveRecord if insert success return current object, other wise return false.
313
   */
314
  public function insert()
315
  {
316
    if (!self::$db instanceof DB) {
317
      self::$db = DB::getInstance();
318
    }
319
320
    if (count($this->dirty) === 0) {
321
      return true;
322
    }
323
324
    $value = $this->_filterParam($this->dirty);
325
    $this->insert = new Expressions(
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...
326
        array(
327
            'operator' => 'INSERT INTO ' . $this->table,
328
            'target'   => new WrapExpressions(array('target' => array_keys($this->dirty))),
329
        )
330
    );
331
    $this->values = new Expressions(
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...
332
        array(
333
            'operator' => 'VALUES',
334
            'target'   => new WrapExpressions(array('target' => $value)),
335
        )
336
    );
337
338
    $result = self::execute($this->_buildSql(array('insert', 'values')), $this->params);
339
    if ($result) {
340
      $this->{$this->primaryKey} = $result;
341
342
      return $this->dirty()->reset();
343
    }
344
345
    return false;
346
  }
347
348
  /**
349
   * Helper function to exec sql.
350
   *
351
   * @param string $sql   The SQL need to be execute.
352
   * @param array  $param The param will be bind to the sql statement.
353
   *
354
   * @return bool|int|Result              <p>
355
   *                                      "Result" by "<b>SELECT</b>"-queries<br />
356
   *                                      "int" (insert_id) by "<b>INSERT / REPLACE</b>"-queries<br />
357
   *                                      "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
358
   *                                      "true" by e.g. "DROP"-queries<br />
359
   *                                      "false" on error
360
   *                                      </p>
361
   */
362
  public static function execute($sql, array $param = array())
363
  {
364
    if (!self::$db instanceof DB) {
365
      self::$db = DB::getInstance();
366
    }
367
368
    return self::$db->query($sql, $param);
369
  }
370
371
  /**
372
   * Helper function to query one record by sql and params.
373
   *
374
   * @param string       $sql    The SQL to find record.
375
   * @param array        $param  The param will be bind to PDOStatement.
376
   * @param ActiveRecord $obj    The object, if find record in database, will assign the attributes in to this object.
377
   * @param bool         $single If set to true, will find record and fetch in current object, otherwise will find all
378
   *                             records.
379
   *
380
   * @return bool|ActiveRecord|array
381
   */
382
  public static function _query($sql, array $param = array(), $obj = null, $single = false)
383
  {
384
    $result = self::execute($sql, $param);
385
386
    if (!$result) {
387
      return false;
388
    }
389
390
    if ($obj && class_exists($obj)) {
391
      $called_class = $obj;
392
    } else {
393
      $called_class = get_called_class();
394
    }
395
396
    if ($single) {
397
      return $result->fetchObject($called_class);
0 ignored issues
show
Bug introduced by
It seems like $called_class defined by $obj on line 391 can also be of type object<voku\db\ActiveRecord>; however, voku\db\Result::fetchObject() does only seem to accept string, 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...
398
    }
399
400
    return $result->fetchAllObject($called_class);
0 ignored issues
show
Bug introduced by
It seems like $called_class defined by $obj on line 391 can also be of type object<voku\db\ActiveRecord>; however, voku\db\Result::fetchAllObject() does only seem to accept string, 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...
401
  }
402
403
  /**
404
   * Helper function to get relation of this object.
405
   * There was three types of relations: {BELONGS_TO, HAS_ONE, HAS_MANY}
406
   *
407
   * @param string $name The name of the relation, the array key when defind the relation.
408
   *
409
   * @return mixed
410
   *
411
   * @throws \Exception
412
   */
413
  protected function &getRelation($name)
414
  {
415
    $relation = $this->relations[$name];
416
    if (
417
        $relation instanceof self
418
        ||
419
        (
420
            is_array($relation)
421
            &&
422
            $relation[0] instanceof self
423
        )
424
    ) {
425
      return $relation;
426
    }
427
428
    /* @var $obj ActiveRecord */
429
    $obj = new $relation[1];
430
431
    $this->relations[$name] = $obj;
432
    if (isset($relation[3]) && is_array($relation[3])) {
433
      foreach ((array)$relation[3] as $func => $args) {
434
        call_user_func_array(array($obj, $func), (array)$args);
435
      }
436
    }
437
438
    $backref = isset($relation[4]) ? $relation[4] : '';
439
    if (
440
        (!$relation instanceof self)
441
        &&
442
        self::HAS_ONE == $relation[0]
443
    ) {
444
445
      $this->relations[$name] = $obj->eq($relation[2], $this->{$this->primaryKey})->fetch();
446
447
      if ($backref) {
448
        $this->relations[$name] && $backref && $obj->__set($backref, $this);
449
      }
450
451
    } elseif (
452
        is_array($relation)
453
        &&
454
        self::HAS_MANY == $relation[0]
455
    ) {
456
457
      $this->relations[$name] = $obj->eq($relation[2], $this->{$this->primaryKey})->fetchAll();
458
      if ($backref) {
459
        foreach ($this->relations[$name] as $o) {
0 ignored issues
show
Bug introduced by
The expression $this->relations[$name] of type boolean|array|object<Arrayy\Arrayy> 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...
460
          $o->__set($backref, $this);
461
        }
462
      }
463
464
    } elseif (
465
        (!$relation instanceof self)
466
        &&
467
        self::BELONGS_TO == $relation[0]
468
    ) {
469
470
      $this->relations[$name] = $obj->eq($obj->primaryKey, $this->{$relation[2]})->fetch();
471
472
      if ($backref) {
473
        $this->relations[$name] && $backref && $obj->__set($backref, $this);
474
      }
475
476
    } else {
477
      throw new \Exception("Relation $name not found.");
478
    }
479
480
    return $this->relations[$name];
481
  }
482
483
  /**
484
   * Helper function to build SQL with sql parts.
485
   *
486
   * @param string       $n The SQL part will be build.
487
   * @param int          $i The index of $n in $sql array.
488
   * @param ActiveRecord $o The reference to $this
489
   */
490
  private function _buildSqlCallback(&$n, $i, $o)
491
  {
492
    if ('select' === $n && null == $o->$n) {
493
      $n = strtoupper($n) . ' ' . $o->table . '.*';
494
    } elseif (('update' === $n || 'from' === $n) && null == $o->$n) {
495
      $n = strtoupper($n) . ' ' . $o->table;
496
    } elseif ('delete' === $n) {
497
      $n = strtoupper($n) . ' ';
498
    } else {
499
      $n = (null !== $o->$n) ? $o->$n . ' ' : '';
500
    }
501
  }
502
503
  /**
504
   * Helper function to build SQL with sql parts.
505
   *
506
   * @param array $sqls The SQL part will be build.
507
   *
508
   * @return string
509
   */
510
  protected function _buildSql($sqls = array())
511
  {
512
    array_walk($sqls, array($this, '_buildSqlCallback'), $this);
513
514
    // DEBUG
515
    echo 'SQL: ', implode(' ', $sqls), "\n", "PARAMS: ", implode(', ', $this->params), "\n";
516
517
    return implode(' ', $sqls);
518
  }
519
520
  /**
521
   * Magic function to make calls witch in function mapping stored in $operators and $sqlPart.
522
   * also can call function of PDO object.
523
   *
524
   * @param string $name function name
525
   * @param array  $args The arguments of the function.
526
   *
527
   * @return $this|mixed Return the result of callback or the current object to make chain method calls.
528
   *
529
   * @throws \Exception
530
   */
531
  public function __call($name, $args)
532
  {
533
    if (!self::$db instanceof DB) {
534
      self::$db = DB::getInstance();
535
    }
536
537
    if (array_key_exists($name = strtolower($name), self::$operators)) {
538
539
      $this->addCondition($args[0], self::$operators[$name], isset($args[1]) ? $args[1] : null, (is_string(end($args)) && 'or' === strtolower(end($args))) ? 'OR' : 'AND');
540
541
    } else if (array_key_exists($name = str_replace('by', '', $name), self::$sqlParts)) {
542
543
      $this->$name = new Expressions(array('operator' => self::$sqlParts[$name], 'target' => implode(', ', $args)));
544
545
    } else if (is_callable($callback = array(self::$db, $name))) {
546
547
      return call_user_func_array($callback, $args);
548
549
    } else {
550
551
      throw new \Exception("Method $name not exist.");
552
553
    }
554
555
    return $this;
556
  }
557
558
  /**
559
   * Make wrap when build the SQL expressions of WHERE.
560
   *
561
   * @param string $op If give this param will build one WrapExpressions include the stored expressions add into WHERE.
562
   *                   otherwise wil stored the expressions into array.
563
   *
564
   * @return $this
565
   */
566
  public function wrap($op = null)
567
  {
568
    if (1 === func_num_args()) {
569
      $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...
570
      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...
571
        $this->_addCondition(
572
            new WrapExpressions(
573
                array(
574
                    'delimiter' => ' ',
575
                    '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...
576
                )
577
            ), 'or' === strtolower($op) ? 'OR' : 'AND'
578
        );
579
      }
580
      $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...
581
    } else {
582
      $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...
583
    }
584
585
    return $this;
586
  }
587
588
  /**
589
   * Helper function to build place holder when make SQL expressions.
590
   *
591
   * @param mixed $value The value will bind to SQL, just store it in $this->params.
592
   *
593
   * @return mixed $value
594
   */
595
  protected function _filterParam($value)
596
  {
597
    if (is_array($value)) {
598
      foreach ($value as $key => $val) {
599
        $this->params[$value[$key] = self::PREFIX . ++self::$count] = $val;
600
      }
601
    } else if (is_string($value)) {
602
      $this->params[$ph = self::PREFIX . ++self::$count] = $value;
603
      $value = $ph;
604
    }
605
606
    return $value;
607
  }
608
609
  /**
610
   * Helper function to add condition into WHERE.
611
   * create the SQL Expressions.
612
   *
613
   * @param string $field The field name, the source of Expressions
614
   * @param string $operator
615
   * @param mixed  $value The target of the Expressions
616
   * @param string $op    The operator to concat this Expressions into WHERE or SET statement.
617
   * @param string $name  The Expression will contact to.
618
   */
619
  public function addCondition($field, $operator, $value, $op = 'AND', $name = 'where')
620
  {
621
    $value = $this->_filterParam($value);
622
    $exp = new Expressions(
623
        array(
624
            'source'   => ('where' == $name ? $this->table . '.' : '') . $field,
625
            'operator' => $operator,
626
            'target'   => is_array($value)
627
                ? new WrapExpressions(
628
                    'between' === strtolower($operator)
629
                        ? array('target' => $value, 'start' => ' ', 'end' => ' ', 'delimiter' => ' AND ')
630
                        : array('target' => $value)
631
                ) : $value,
632
        )
633
    );
634
    if ($exp) {
635
      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...
636
        $this->_addCondition($exp, $op, $name);
637
      } else {
638
        $this->_addExpression($exp, $op);
639
      }
640
    }
641
  }
642
643
  /**
644
   * helper function to add condition into JOIN.
645
   * create the SQL Expressions.
646
   *
647
   * @param string $table The join table name
648
   * @param string $on    The condition of ON
649
   * @param string $type  The join type, like "LEFT", "INNER", "OUTER"
650
   *
651
   * @return $this
652
   */
653
  public function join($table, $on, $type = 'LEFT')
654
  {
655
    $this->join = new Expressions(
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...
656
        array(
657
            '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...
658
            'operator' => $type . ' JOIN',
659
            'target'   => new Expressions(
660
                array('source' => $table, 'operator' => 'ON', 'target' => $on)
661
            ),
662
        )
663
    );
664
665
    return $this;
666
  }
667
668
  /**
669
   * helper function to make wrapper. Stored the expression in to array.
670
   *
671
   * @param Expressions $exp      The expression will be stored.
672
   * @param string      $operator The operator to concat this Expressions into WHERE statment.
673
   */
674
  protected function _addExpression($exp, $operator)
675
  {
676
    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...
677
      $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...
678
    } else {
679
      $this->expressions[] = new Expressions(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...
680
    }
681
  }
682
683
  /**
684
   * helper function to add condition into WHERE.
685
   *
686
   * @param Expressions $exp      The expression will be concat into WHERE or SET statment.
687
   * @param string      $operator the operator to concat this Expressions into WHERE or SET statment.
688
   * @param string      $name     The Expression will contact to.
689
   */
690
  protected function _addCondition($exp, $operator, $name = 'where')
691
  {
692
    if (!$this->$name) {
693
      $this->$name = new Expressions(array('operator' => strtoupper($name), 'target' => $exp));
694
    } else {
695
      $this->$name->target = new Expressions(
696
          array(
697
              'source'   => $this->$name->target,
698
              'operator' => $operator,
699
              'target'   => $exp,
700
          )
701
      );
702
    }
703
  }
704
705
  /**
706
   * Magic function to SET values of the current object.
707
   *
708
   * @param mixed $var
709
   * @param mixed $val
710
   */
711
  public function __set($var, $val)
712
  {
713
    if (
714
        array_key_exists($var, $this->sqlExpressions)
715
        ||
716
        array_key_exists($var, self::$defaultSqlExpressions)
717
    ) {
718
719
      $this->sqlExpressions[$var] = $val;
720
721
    } else if (
722
        array_key_exists($var, $this->relations)
723
        &&
724
        $val instanceof self
725
    ) {
726
727
      $this->relations[$var] = $val;
728
729
    } else {
730
731
      $this->dirty[$var] = $this->array[$var] = $val;
732
733
    }
734
  }
735
736
  /**
737
   * Magic function to UNSET values of the current object.
738
   *
739
   * @param mixed $var
740
   */
741
  public function __unset($var)
742
  {
743
    if (array_key_exists($var, $this->sqlExpressions)) {
744
      unset($this->sqlExpressions[$var]);
745
    }
746
747
    if (isset($this->array[$var])) {
748
      unset($this->array[$var]);
749
    }
750
751
    if (isset($this->dirty[$var])) {
752
      unset($this->dirty[$var]);
753
    }
754
  }
755
756
  /**
757
   * Magic function to GET the values of current object.
758
   *
759
   * @param $var
760
   *
761
   * @return mixed
762
   */
763
  public function &__get($var)
764
  {
765
    if (array_key_exists($var, $this->sqlExpressions)) {
766
      return $this->sqlExpressions[$var];
767
    }
768
769
    if (array_key_exists($var, $this->relations)) {
770
      return $this->getRelation($var);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->getRelation($var); of type Arrayy\Arrayy|array|boolean adds the type array to the return on line 770 which is incompatible with the return type of the parent method Arrayy\Arrayy::__get of type object|integer|double|string|null|boolean.
Loading history...
771
    }
772
773
    if (isset($this->dirty[$var])) {
774
      return $this->dirty[$var];
775
    }
776
777
    return parent::__get($var);
778
  }
779
}
780