Completed
Push — master ( 2259aa...04df2d )
by Lars
02:21
created

ORM::_execute()   C

Complexity

Conditions 7
Paths 27

Size

Total Lines 33
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 7.3388

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 33
ccs 17
cts 21
cp 0.8095
rs 6.7273
cc 7
eloc 21
nc 27
nop 3
crap 7.3388
1
<?php
2
3
namespace idiorm\orm;
4
5
/**
6
 *
7
 * Idiorm
8
 *
9
 * http://github.com/j4mie/idiorm/
10
 *
11
 * A single-class super-simple database abstraction layer for PHP.
12
 * Provides (nearly) zero-configuration object-relational mapping
13
 * and a fluent interface for building basic, commonly-used queries.
14
 *
15
 * BSD Licensed.
16
 *
17
 * Copyright (c) 2010, Jamie Matthews
18
 * All rights reserved.
19
 *
20
 * Redistribution and use in source and binary forms, with or without
21
 * modification, are permitted provided that the following conditions are met:
22
 *
23
 * * Redistributions of source code must retain the above copyright notice, this
24
 *   list of conditions and the following disclaimer.
25
 *
26
 * * Redistributions in binary form must reproduce the above copyright notice,
27
 *   this list of conditions and the following disclaimer in the documentation
28
 *   and/or other materials provided with the distribution.
29
 *
30
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
31
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
32
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
33
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
34
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
35
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
36
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
37
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
38
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
39
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40
 *
41
 */
42
class ORM implements \ArrayAccess
43
{
44
45
  // ----------------------- //
46
  // --- CLASS CONSTANTS --- //
47
  // ----------------------- //
48
49
  // WHERE and HAVING condition array keys
50
  const CONDITION_FRAGMENT = 0;
51
  const CONDITION_VALUES   = 1;
52
  const DEFAULT_CONNECTION = 'default';
53
54
  // Limit clause style
55
  const LIMIT_STYLE_TOP_N = "top";
56
  const LIMIT_STYLE_LIMIT = "limit";
57
58
  // ------------------------ //
59
  // --- CLASS PROPERTIES --- //
60
  // ------------------------ //
61
62
  /**
63
   * Class configuration
64
   *
65
   * @var array
66
   */
67
  protected static $_default_config = array(
68
      'connection_string'          => 'sqlite::memory:',
69
      'id_column'                  => 'id',
70
      'id_column_overrides'        => array(),
71
      'error_mode'                 => \PDO::ERRMODE_EXCEPTION,
72
      'username'                   => null,
73
      'password'                   => null,
74
      'driver_options'             => null,
75
      'identifier_quote_character' => null, // if this is null, will be autodetected
76
      'limit_clause_style'         => null, // if this is null, will be autodetected
77
      'logging'                    => false,
78
      'logger'                     => null,
79
      'caching'                    => false,
80
      'caching_auto_clear'         => false,
81
      'return_result_sets'         => false,
82
  );
83
84
  /**
85
   * Map of configuration settings
86
   *
87
   * @var array
88
   */
89
  protected static $_config = array();
90
91
  /**
92
   * Map of database connections, instances of the PDO class
93
   *
94
   * @var array
95
   */
96
  protected static $_db = array();
97
98
  /**
99
   * Last query run, only populated if logging is enabled
100
   *
101
   * @var string
102
   */
103
  protected static $_last_query;
104
105
  /**
106
   * Log of all queries run, mapped by connection key, only populated if logging is enabled
107
   *
108
   * @var array
109
   */
110
  protected static $_query_log = array();
111
112
  /**
113
   * Query cache, only used if query caching is enabled
114
   *
115
   * @var array
116
   */
117
  protected static $_query_cache = array();
118
119
  /**
120
   * Reference to previously used PDOStatement object to enable low-level access, if needed
121
   *
122
   * @var null
123
   */
124
  protected static $_last_statement = null;
125
126
  // --------------------------- //
127
  // --- INSTANCE PROPERTIES --- //
128
  // --------------------------- //
129
130
  /**
131
   * Key name of the connections in static::$_db used by this instance
132
   *
133
   * @var string
134
   */
135
  protected $_connection_name;
136
137
  /**
138
   * The name of the table the current ORM instance is associated with
139
   *
140
   * @var string
141
   */
142
  protected $_table_name;
143
144
  /**
145
   * Alias for the table to be used in SELECT queries
146
   *
147
   * @var null|string
148
   */
149
  protected $_table_alias = null;
150
151
  /**
152
   * Values to be bound to the query
153
   *
154
   * @var array
155
   */
156
  protected $_values = array();
157
158
  /**
159
   * Columns to select in the result
160
   *
161
   * @var array
162
   */
163
  protected $_result_columns = array('*');
164
165
  /**
166
   * Are we using the default result column or have these been manually changed?
167
   *
168
   * @var bool
169
   */
170
  protected $_using_default_result_columns = true;
171
172
  /**
173
   * Join sources
174
   *
175
   * @var array
176
   */
177
  protected $_join_sources = array();
178
179
  /**
180
   * Should the query include a DISTINCT keyword?
181
   *
182
   * @var bool
183
   */
184
  protected $_distinct = false;
185
186
  /**
187
   * Is this a raw query?
188
   *
189
   * @var bool
190
   */
191
  protected $_is_raw_query = false;
192
193
  /**
194
   * The raw query
195
   *
196
   * @var string
197
   */
198
  protected $_raw_query = '';
199
200
  /**
201
   * The raw query parameters
202
   *
203
   * @var array
204
   */
205
  protected $_raw_parameters = array();
206
207
  /**
208
   * Array of WHERE clauses
209
   *
210
   * @var array
211
   */
212
  protected $_where_conditions = array();
213
214
  /**
215
   * LIMIT
216
   *
217
   * @var null
218
   */
219
  protected $_limit = null;
220
221
  /**
222
   * OFFSET
223
   *
224
   * @var null
225
   */
226
  protected $_offset = null;
227
228
  /**
229
   * ORDER BY
230
   *
231
   * @var array
232
   */
233
  protected $_order_by = array();
234
235
  /**
236
   * GROUP BY
237
   *
238
   * @var array
239
   */
240
  protected $_group_by = array();
241
242
  /**
243
   * HAVING
244
   *
245
   * @var array
246
   */
247
  protected $_having_conditions = array();
248
249
  /**
250
   * The data for a hydrated instance of the class
251
   *
252
   * @var array
253
   */
254
  protected $_data = array();
255
256
  /**
257
   * Fields that have been modified during the lifetime of the object
258
   *
259
   * @var array
260
   */
261
  protected $_dirty_fields = array();
262
263
  /**
264
   * Fields that are to be inserted in the DB raw
265
   *
266
   * @var array
267
   */
268
  protected $_expr_fields = array();
269
270
  /**
271
   * Is this a new object (has create() been called)?
272
   *
273
   * @var bool
274
   */
275
  protected $_is_new = false;
276
277
  /**
278
   * Name of the column to use as the primary key for
279
   * this instance only. Overrides the config settings.
280
   *
281
   * @var null|string
282
   */
283
  protected $_instance_id_column = null;
284
285
  /**
286
   * Refresh cache for current query?
287
   *
288
   * @var bool
289
   */
290
  protected $_refresh_cache = false;
291
292
  /**
293
   * Disable caching for current query?
294
   *
295
   * @var bool
296
   */
297
  protected $_no_caching = false;
298
299
  // ---------------------- //
300
  // --- STATIC METHODS --- //
301
  // ---------------------- //
302
303
  /**
304
   * Pass configuration settings to the class in the form of
305
   * key/value pairs. As a shortcut, if the second argument
306
   * is omitted and the key is a string, the setting is
307
   * assumed to be the DSN string used by PDO to connect
308
   * to the database (often, this will be the only configuration
309
   * required to use Idiorm). If you have more than one setting
310
   * you wish to configure, another shortcut is to pass an array
311
   * of settings (and omit the second argument).
312
   *
313
   * @param string $key
314
   * @param mixed  $value
315
   * @param string $connection_name Which connection to use
316
   */
317 138
  public static function configure($key, $value = null, $connection_name = self::DEFAULT_CONNECTION)
318
  {
319 138
    static::_setup_db_config($connection_name); //ensures at least default config is set
320
321 138
    if (is_array($key)) {
322
      // Shortcut: If only one array argument is passed,
323
      // assume it's an array of configuration settings
324
      foreach ($key as $conf_key => $conf_value) {
325
        static::configure($conf_key, $conf_value, $connection_name);
326
      }
327
    } else {
328 138
      if (is_null($value)) {
329
        // Shortcut: If only one string argument is passed,
330
        // assume it's a connection string
331
        $value = $key;
332
        $key = 'connection_string';
333
      }
334 138
      static::$_config[$connection_name][$key] = $value;
335
    }
336 138
  }
337
338
  /**
339
   * Retrieve configuration options by key, or as whole array.
340
   *
341
   * @param string $key
342
   * @param string $connection_name Which connection to use
343
   */
344 2
  public static function get_config($key = null, $connection_name = self::DEFAULT_CONNECTION)
345
  {
346 2
    if ($key) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $key of type string|null is loosely compared to true; this is ambiguous if the string can be empty. 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 string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
347 1
      return static::$_config[$connection_name][$key];
348
    } else {
349 1
      return static::$_config[$connection_name];
350
    }
351
  }
352
353
  /**
354
   * Delete all configs in _config array.
355
   */
356 138
  public static function reset_config()
357
  {
358 138
    static::$_config = array();
359 138
  }
360
361
  /**
362
   * Despite its slightly odd name, this is actually the factory
363
   * method used to acquire instances of the class. It is named
364
   * this way for the sake of a readable interface, ie
365
   * ORM::for_table('table_name')->find_one()-> etc. As such,
366
   * this will normally be the first method called in a chain.
367
   *
368
   * @param string $table_name
369
   * @param string $connection_name Which connection to use
370
   *
371
   * @return ORM
372
   */
373 126
  public static function for_table($table_name, $connection_name = self::DEFAULT_CONNECTION)
374
  {
375 126
    static::_setup_db($connection_name);
376
377 126
    return new static($table_name, array(), $connection_name);
378
  }
379
380
  /**
381
   * Set up the database connection used by the class
382
   *
383
   * @param string $connection_name Which connection to use
384
   */
385 138
  protected static function _setup_db($connection_name = self::DEFAULT_CONNECTION)
386
  {
387 138
    if (!array_key_exists($connection_name, static::$_db) ||
388 138
        !is_object(static::$_db[$connection_name])
389 138
    ) {
390 1
      static::_setup_db_config($connection_name);
391
392 1
      $db = new \PDO(
393 1
          static::$_config[$connection_name]['connection_string'], static::$_config[$connection_name]['username'], static::$_config[$connection_name]['password'], static::$_config[$connection_name]['driver_options']
394 1
      );
395
396 1
      $db->setAttribute(\PDO::ATTR_ERRMODE, static::$_config[$connection_name]['error_mode']);
397 1
      static::set_db($db, $connection_name);
398 1
    }
399 138
  }
400
401
  /**
402
   * Ensures configuration (multiple connections) is at least set to default.
403
   *
404
   * @param string $connection_name Which connection to use
405
   */
406 138
  protected static function _setup_db_config($connection_name)
407
  {
408 138
    if (!array_key_exists($connection_name, static::$_config)) {
409 138
      static::$_config[$connection_name] = static::$_default_config;
410 138
    }
411 138
  }
412
413
  /**
414
   * Set the PDO object used by Idiorm to communicate with the database.
415
   * This is public in case the ORM should use a ready-instantiated
416
   * PDO object as its database connection. Accepts an optional string key
417
   * to identify the connection if multiple connections are used.
418
   *
419
   * @param \PDO   $db
420
   * @param string $connection_name Which connection to use
421
   */
422 138
  public static function set_db($db, $connection_name = self::DEFAULT_CONNECTION)
423
  {
424 138
    static::_setup_db_config($connection_name);
425 138
    static::$_db[$connection_name] = $db;
426 138
    if (!is_null(static::$_db[$connection_name])) {
427 138
      static::_setup_identifier_quote_character($connection_name);
428 138
      static::_setup_limit_clause_style($connection_name);
429 138
    }
430 138
  }
431
432
  /**
433
   * Delete all registered PDO objects in _db array.
434
   */
435 138
  public static function reset_db()
436
  {
437 138
    static::$_db = array();
438 138
  }
439
440
  /**
441
   * Detect and initialise the character used to quote identifiers
442
   * (table names, column names etc). If this has been specified
443
   * manually using ORM::configure('identifier_quote_character', 'some-char'),
444
   * this will do nothing.
445
   *
446
   * @param string $connection_name Which connection to use
447
   */
448 138
  protected static function _setup_identifier_quote_character($connection_name)
449
  {
450 138
    if (is_null(static::$_config[$connection_name]['identifier_quote_character'])) {
451 138
      static::$_config[$connection_name]['identifier_quote_character'] = static::_detect_identifier_quote_character($connection_name);
452 138
    }
453 138
  }
454
455
  /**
456
   * Detect and initialise the limit clause style ("SELECT TOP 5" /
457
   * "... LIMIT 5"). If this has been specified manually using
458
   * ORM::configure('limit_clause_style', 'top'), this will do nothing.
459
   *
460
   * @param string $connection_name Which connection to use
461
   */
462 138
  public static function _setup_limit_clause_style($connection_name)
463
  {
464 138
    if (is_null(static::$_config[$connection_name]['limit_clause_style'])) {
465 138
      static::$_config[$connection_name]['limit_clause_style'] = static::_detect_limit_clause_style($connection_name);
466 138
    }
467 138
  }
468
469
  /**
470
   * Return the correct character used to quote identifiers (table
471
   * names, column names etc) by looking at the driver being used by PDO.
472
   *
473
   * @param string $connection_name Which connection to use
474
   *
475
   * @return string
476
   */
477 138
  protected static function _detect_identifier_quote_character($connection_name)
478
  {
479 138
    switch (static::get_db($connection_name)->getAttribute(\PDO::ATTR_DRIVER_NAME)) {
480 138
      case 'pgsql':
481 138
      case 'sqlsrv':
482 138
      case 'dblib':
483 138
      case 'mssql':
484 138
      case 'sybase':
485 138
      case 'firebird':
486 2
        return '"';
487 136
      case 'mysql':
488 136
      case 'sqlite':
489 136
      case 'sqlite2':
490 136
      default:
491 136
        return '`';
492
    }
493
  }
494
495
  /**
496
   * Returns a constant after determining the appropriate limit clause
497
   * style
498
   *
499
   * @param string $connection_name Which connection to use
500
   *
501
   * @return string Limit clause style keyword/constant
502
   */
503 138
  protected static function _detect_limit_clause_style($connection_name)
504
  {
505 138
    switch (static::get_db($connection_name)->getAttribute(\PDO::ATTR_DRIVER_NAME)) {
506 138
      case 'sqlsrv':
507 138
      case 'dblib':
508 138
      case 'mssql':
509 2
        return ORM::LIMIT_STYLE_TOP_N;
510 136
      default:
511 136
        return ORM::LIMIT_STYLE_LIMIT;
512 136
    }
513
  }
514
515
  /**
516
   * Returns the PDO instance used by the the ORM to communicate with
517
   * the database. This can be called if any low-level DB access is
518
   * required outside the class. If multiple connections are used,
519
   * accepts an optional key name for the connection.
520
   *
521
   * @param string $connection_name Which connection to use
522
   *
523
   * @return \PDO
524
   */
525 138
  public static function get_db($connection_name = self::DEFAULT_CONNECTION)
526
  {
527 138
    static::_setup_db($connection_name); // required in case this is called before Idiorm is instantiated
528 138
    return static::$_db[$connection_name];
529
  }
530
531
  /**
532
   * Executes a raw query as a wrapper for PDOStatement::execute.
533
   * Useful for queries that can't be accomplished through Idiorm,
534
   * particularly those using engine-specific features.
535
   *
536
   * @example raw_execute('SELECT `name`, AVG(`order`) FROM `customer` GROUP BY `name` HAVING AVG(`order`) > 10')
537
   * @example raw_execute('INSERT OR REPLACE INTO `widget` (`id`, `name`) SELECT `id`, `name` FROM `other_table`')
538
   *
539
   * @param string $query           The raw SQL query
540
   * @param array  $parameters      Optional bound parameters
541
   * @param string $connection_name Which connection to use
542
   *
543
   * @return bool Success
544
   */
545 1
  public static function raw_execute($query, $parameters = array(), $connection_name = self::DEFAULT_CONNECTION)
546
  {
547 1
    static::_setup_db($connection_name);
548
549 1
    return static::_execute($query, $parameters, $connection_name);
550
  }
551
552
  /**
553
   * Returns the PDOStatement instance last used by any connection wrapped by the ORM.
554
   * Useful for access to PDOStatement::rowCount() or error information
555
   *
556
   * @return \PDOStatement
557
   */
558 111
  public static function get_last_statement()
559
  {
560 111
    return static::$_last_statement;
561
  }
562
563
  /**
564
   * Internal helper method for executing statements. Logs queries, and
565
   * stores statement object in ::_last_statement, accessible publicly
566
   * through ::get_last_statement()
567
   *
568
   * @param string $query
569
   * @param array  $parameters      An array of parameters to be bound in to the query
570
   * @param string $connection_name Which connection to use
571
   *
572
   * @return bool Response of PDOStatement::execute()
573
   *
574
   * @throws \Exception
575
   */
576 119
  protected static function _execute($query, $parameters = array(), $connection_name = self::DEFAULT_CONNECTION)
577
  {
578 119
    $time = microtime(true);
579
580
    try {
581 119
      $statement = static::get_db($connection_name)->prepare($query);
582 119
      static::$_last_statement = $statement;
0 ignored issues
show
Documentation Bug introduced by
It seems like $statement of type object<PDOStatement> is incompatible with the declared type null of property $_last_statement.

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

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

Loading history...
583
584 119
      foreach ($parameters as $key => &$param) {
585
586 66
        if (is_null($param)) {
587
          $type = \PDO::PARAM_NULL;
588 66
        } else if (is_bool($param)) {
589
          $type = \PDO::PARAM_BOOL;
590 66
        } else if (is_int($param)) {
591 52
          $type = \PDO::PARAM_INT;
592 52
        } else {
593 40
          $type = \PDO::PARAM_STR;
594
        }
595
596
        // TODO? -> ++$key OR $key, $param, $type ...
0 ignored issues
show
Unused Code Comprehensibility introduced by
56% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
597 66
        $statement->bindParam(is_int($key) ? ++$key : $key, $param, $type);
598 119
      }
599
600 119
      $q = $statement->execute();
601 119
      static::_log_query($query, $parameters, $connection_name, (microtime(true) - $time));
602 119
    } catch (\Exception $ex) {
603
      static::_log_query($query, $parameters, $connection_name, (microtime(true) - $time));
604
      throw $ex;
605
    }
606
607 119
    return $q;
608
  }
609
610
  /**
611
   * Add a query to the internal query log. Only works if the
612
   * 'logging' config option is set to true.
613
   *
614
   * This works by manually binding the parameters to the query - the
615
   * query isn't executed like this (PDO normally passes the query and
616
   * parameters to the database which takes care of the binding) but
617
   * doing it this way makes the logged queries more readable.
618
   *
619
   * @param string $query
620
   * @param array  $parameters      An array of parameters to be bound in to the query
621
   * @param string $connection_name Which connection to use
622
   * @param float  $query_time      Query time
623
   *
624
   * @return bool
625
   */
626 119
  protected static function _log_query($query, $parameters, $connection_name, $query_time)
627
  {
628
    // If logging is not enabled, do nothing
629 119
    if (!static::$_config[$connection_name]['logging']) {
630
      return false;
631
    }
632
633 119
    if (!isset(static::$_query_log[$connection_name])) {
634 2
      static::$_query_log[$connection_name] = array();
635 2
    }
636
637
    // Strip out any non-integer indexes from the parameters
638 119
    foreach ($parameters as $key => $value) {
639 66
      if (!is_int($key)) {
640
        unset($parameters[$key]);
641
      }
642 119
    }
643
644 119
    if (count($parameters) > 0) {
645
      // Escape the parameters
646 66
      $parameters = array_map(array(static::get_db($connection_name), 'quote'), $parameters);
647
648
      // Avoid %format collision for vsprintf
649 66
      $query = str_replace("%", "%%", $query);
650
651
      // Replace placeholders in the query for vsprintf
652 66
      if (false !== strpos($query, "'") || false !== strpos($query, '"')) {
653 2
        $query = IdiormString::str_replace_outside_quotes("?", "%s", $query);
654 2
      } else {
655 65
        $query = str_replace("?", "%s", $query);
656
      }
657
658
      // Replace the question marks in the query with the parameters
659 66
      $bound_query = vsprintf($query, $parameters);
660 66
    } else {
661 54
      $bound_query = $query;
662
    }
663
664 119
    static::$_last_query = $bound_query;
665 119
    static::$_query_log[$connection_name][] = $bound_query;
666
667
668 119
    if (is_callable(static::$_config[$connection_name]['logger'])) {
669
      $logger = static::$_config[$connection_name]['logger'];
670
      $logger($bound_query, $query_time);
671
    }
672
673 119
    return true;
674
  }
675
676
  /**
677
   * Get the last query executed. Only works if the
678
   * 'logging' config option is set to true. Otherwise
679
   * this will return null. Returns last query from all connections if
680
   * no connection_name is specified
681
   *
682
   * @param null|string $connection_name Which connection to use
683
   *
684
   * @return string
685
   */
686 111
  public static function get_last_query($connection_name = null)
687
  {
688 111
    if ($connection_name === null) {
689 110
      return static::$_last_query;
690
    }
691 3
    if (!isset(static::$_query_log[$connection_name])) {
692
      return '';
693
    }
694
695 3
    return end(static::$_query_log[$connection_name]);
696
  }
697
698
  /**
699
   * Get an array containing all the queries run on a
700
   * specified connection up to now.
701
   * Only works if the 'logging' config option is
702
   * set to true. Otherwise, returned array will be empty.
703
   *
704
   * @param string $connection_name Which connection to use
705
   *
706
   * @return array
707
   */
708
  public static function get_query_log($connection_name = self::DEFAULT_CONNECTION)
709
  {
710
    if (isset(static::$_query_log[$connection_name])) {
711
      return static::$_query_log[$connection_name];
712
    }
713
714
    return array();
715
  }
716
717
  /**
718
   * Get a list of the available connection names
719
   *
720
   * @return array
721
   */
722
  public static function get_connection_names()
723
  {
724
    return array_keys(static::$_db);
725
  }
726
727
  // ------------------------ //
728
  // --- INSTANCE METHODS --- //
729
  // ------------------------ //
730
731
  /**
732
   * "Private" constructor; shouldn't be called directly.
733
   * Use the ORM::for_table factory method instead.
734
   *
735
   * @param string $table_name
736
   * @param array  $data
737
   * @param string $connection_name
738
   */
739 126
  protected function __construct($table_name, $data = array(), $connection_name = self::DEFAULT_CONNECTION)
740
  {
741 126
    $this->_table_name = $table_name;
742 126
    $this->_data = $data;
743
744 126
    $this->_connection_name = $connection_name;
745 126
    static::_setup_db_config($connection_name);
746 126
  }
747
748
  /**
749
   * Create a new, empty instance of the class. Used
750
   * to add a new row to your database. May optionally
751
   * be passed an associative array of data to populate
752
   * the instance. If so, all fields will be flagged as
753
   * dirty so all will be saved to the database when
754
   * save() is called.
755
   *
756
   * @param mixed $data
757
   *
758
   * @return $this
759
   */
760 11
  public function create($data = null)
761
  {
762 11
    $this->_is_new = true;
763 11
    if (!is_null($data)) {
764 2
      return $this->hydrate($data)->force_all_dirty();
765
    }
766
767 11
    return $this;
768
  }
769
770
  /**
771
   * Specify the ID column to use for this instance or array of instances only.
772
   * This overrides the id_column and id_column_overrides settings.
773
   *
774
   * This is mostly useful for libraries built on top of Idiorm, and will
775
   * not normally be used in manually built queries. If you don't know why
776
   * you would want to use this, you should probably just ignore it.
777
   *
778
   * @param string $id_column
779
   *
780
   * @return $this
781
   */
782 114
  public function use_id_column($id_column)
783
  {
784 114
    $this->_instance_id_column = $id_column;
785
786 114
    return $this;
787
  }
788
789
  /**
790
   * Create an ORM instance from the given row (an associative
791
   * array of data fetched from the database)
792
   *
793
   * @param array $row
794
   *
795
   * @return ORM
796
   */
797 110
  protected function _create_instance_from_row($row)
798
  {
799 110
    $instance = static::for_table($this->_table_name, $this->_connection_name);
800 110
    $instance->use_id_column($this->_instance_id_column);
801 110
    $instance->hydrate($row);
802
803 110
    return $instance;
804
  }
805
806
  /**
807
   * Tell the ORM that you are expecting a single result
808
   * back from your query, and execute it. Will return
809
   * a single instance of the ORM class, or false if no
810
   * rows were returned.
811
   * As a shortcut, you may supply an ID as a parameter
812
   * to this method. This will perform a primary key
813
   * lookup on the table.
814
   *
815
   * @param mixed $id
816
   *
817
   * @return false|ORM false on error
818
   */
819 50
  public function find_one($id = null)
820
  {
821 50
    if (!is_null($id)) {
822 19
      $this->where_id_is($id);
823 19
    }
824 50
    $this->limit(1);
825 50
    $rows = $this->_run();
826
827 50
    if (empty($rows)) {
828
      return false;
829
    }
830
831 50
    return $this->_create_instance_from_row($rows[0]);
832
  }
833
834
  /**
835
   * Tell the ORM that you are expecting multiple results
836
   * from your query, and execute it. Will return an array
837
   * of instances of the ORM class, or an empty array if
838
   * no rows were returned.
839
   *
840
   * @return array|IdiormResultSet
841
   */
842 58
  public function find_many()
843
  {
844 58
    if (static::$_config[$this->_connection_name]['return_result_sets']) {
845 1
      return $this->find_result_set();
846
    }
847
848 58
    return $this->_find_many();
849
  }
850
851
  /**
852
   * Tell the ORM that you are expecting multiple results
853
   * from your query, and execute it. Will return an array
854
   * of instances of the ORM class, or an empty array if
855
   * no rows were returned.
856
   *
857
   * @return array
858
   */
859 60
  protected function _find_many()
860
  {
861 60
    $rows = $this->_run();
862
863 60
    return array_map(array($this, '_create_instance_from_row'), $rows);
864
  }
865
866
  /**
867
   * Tell the ORM that you are expecting multiple results
868
   * from your query, and execute it. Will return a result set object
869
   * containing instances of the ORM class.
870
   *
871
   * @return IdiormResultSet
872
   */
873 3
  public function find_result_set()
874
  {
875 3
    return new IdiormResultSet($this->_find_many());
876
  }
877
878
  /**
879
   * Tell the ORM that you are expecting multiple results
880
   * from your query, and execute it. Will return an array,
881
   * or an empty array if no rows were returned.
882
   *
883
   * @return array
884
   */
885 1
  public function find_array()
886
  {
887 1
    return $this->_run();
888
  }
889
890
  /**
891
   * Tell the ORM that you wish to execute a COUNT query.
892
   * Will return an integer representing the number of
893
   * rows returned.
894
   *
895
   * @param string $column
896
   *
897
   * @return int
898
   */
899 2
  public function count($column = '*')
900
  {
901 2
    return $this->_call_aggregate_db_function(__FUNCTION__, $column);
902
  }
903
904
  /**
905
   * Tell the ORM that you wish to execute a MAX query.
906
   * Will return the max value of the choosen column.
907
   *
908
   * @param string $column
909
   *
910
   * @return int
911
   */
912 1
  public function max($column)
913
  {
914 1
    return $this->_call_aggregate_db_function(__FUNCTION__, $column);
915
  }
916
917
  /**
918
   * Tell the ORM that you wish to execute a MIN query.
919
   * Will return the min value of the choosen column.
920
   *
921
   * @param string $column
922
   *
923
   * @return int
924
   */
925 1
  public function min($column)
926
  {
927 1
    return $this->_call_aggregate_db_function(__FUNCTION__, $column);
928
  }
929
930
  /**
931
   * Tell the ORM that you wish to execute a AVG query.
932
   * Will return the average value of the choosen column.
933
   *
934
   * @param string $column
935
   *
936
   * @return int
937
   */
938 1
  public function avg($column)
939
  {
940 1
    return $this->_call_aggregate_db_function(__FUNCTION__, $column);
941
  }
942
943
  /**
944
   * Tell the ORM that you wish to execute a SUM query.
945
   * Will return the sum of the choosen column.
946
   *
947
   * @param string $column
948
   *
949
   * @return int
950
   */
951 1
  public function sum($column)
952
  {
953 1
    return $this->_call_aggregate_db_function(__FUNCTION__, $column);
954
  }
955
956
  /**
957
   * Execute an aggregate query on the current connection.
958
   *
959
   * @param string $sql_function The aggregate function to call eg. MIN, COUNT, etc
960
   * @param string $column       The column to execute the aggregate query against
961
   *
962
   * @return int
963
   */
964 6
  protected function _call_aggregate_db_function($sql_function, $column)
965
  {
966 6
    $alias = strtolower($sql_function);
967 6
    $sql_function = strtoupper($sql_function);
968 6
    if ('*' != $column) {
969 4
      $column = $this->_quote_identifier($column);
970 4
    }
971 6
    $result_columns = $this->_result_columns;
972 6
    $this->_result_columns = array();
973 6
    $this->select_expr("$sql_function($column)", $alias);
974 6
    $result = $this->find_one();
975 6
    $this->_result_columns = $result_columns;
976
977 6
    $return_value = 0;
978 6
    if ($result !== false && isset($result->$alias)) {
979
      if (!is_numeric($result->$alias)) {
980
        $return_value = $result->$alias;
981
      } elseif ((int)$result->$alias == (float)$result->$alias) {
982
        $return_value = (int)$result->$alias;
983
      } else {
984
        $return_value = (float)$result->$alias;
985
      }
986
    }
987
988 6
    return $return_value;
989
  }
990
991
  /**
992
   * This method can be called to hydrate (populate) this
993
   * instance of the class from an associative array of data.
994
   * This will usually be called only from inside the class,
995
   * but it's public in case you need to call it directly.
996
   *
997
   * @param array $data
998
   *
999
   * @return $this
1000
   */
1001 112
  public function hydrate($data = array())
1002
  {
1003 112
    $this->_data = $data;
1004
1005 112
    return $this;
1006
  }
1007
1008
  /**
1009
   * Force the ORM to flag all the fields in the $data array
1010
   * as "dirty" and therefore update them when save() is called.
1011
   */
1012 2
  public function force_all_dirty()
1013
  {
1014 2
    $this->_dirty_fields = $this->_data;
1015
1016 2
    return $this;
1017
  }
1018
1019
  /**
1020
   * Perform a raw query. The query can contain placeholders in
1021
   * either named or question mark style. If placeholders are
1022
   * used, the parameters should be an array of values which will
1023
   * be bound to the placeholders in the query. If this method
1024
   * is called, all other query building methods will be ignored.
1025
   *
1026
   * @param string $query
1027
   * @param array  $parameters
1028
   *
1029
   * @return $this
1030
   */
1031 2
  public function raw_query($query, $parameters = array())
1032
  {
1033 2
    $this->_is_raw_query = true;
1034 2
    $this->_raw_query = $query;
1035 2
    $this->_raw_parameters = $parameters;
1036
1037 2
    return $this;
1038
  }
1039
1040
  /**
1041
   * Add an alias for the main table to be used in SELECT queries
1042
   *
1043
   * @param string $alias
1044
   *
1045
   * @return $this
1046
   */
1047 2
  public function table_alias($alias)
1048
  {
1049 2
    $this->_table_alias = $alias;
1050
1051 2
    return $this;
1052
  }
1053
1054
  /**
1055
   * Internal method to add an unquoted expression to the set
1056
   * of columns returned by the SELECT query. The second optional
1057
   * argument is the alias to return the expression as.
1058
   *
1059
   * @param string $expr
1060
   * @param mixed  $alias
1061
   *
1062
   * @return $this
1063
   */
1064 19
  protected function _add_result_column($expr, $alias = null)
1065
  {
1066 19
    if (!is_null($alias)) {
1067 10
      $expr .= " AS " . $this->_quote_identifier($alias);
1068 10
    }
1069
1070 19
    if ($this->_using_default_result_columns) {
1071 19
      $this->_result_columns = array($expr);
1072 19
      $this->_using_default_result_columns = false;
1073 19
    } else {
1074 4
      $this->_result_columns[] = $expr;
1075
    }
1076
1077 19
    return $this;
1078
  }
1079
1080
  /**
1081
   * Counts the number of columns that belong to the primary
1082
   * key and their value is null.
1083
   */
1084 6
  public function count_null_id_columns()
1085
  {
1086 6
    if (is_array($this->_get_id_column_name())) {
1087 3
      return count(array_filter($this->id(), 'is_null'));
1088
    } else {
1089 3
      return is_null($this->id()) ? 1 : 0;
1090
    }
1091
  }
1092
1093
  /**
1094
   * Add a column to the list of columns returned by the SELECT
1095
   * query. This defaults to '*'. The second optional argument is
1096
   * the alias to return the column as.
1097
   *
1098
   * @param string $column
1099
   * @param mixed  $alias
1100
   *
1101
   * @return ORM
1102
   */
1103 12
  public function select($column, $alias = null)
1104
  {
1105 12
    $column = $this->_quote_identifier($column);
1106
1107 12
    return $this->_add_result_column($column, $alias);
1108
  }
1109
1110
  /**
1111
   * Add an unquoted expression to the list of columns returned
1112
   * by the SELECT query. The second optional argument is
1113
   * the alias to return the column as.
1114
   *
1115
   * @param string $expr
1116
   * @param mixed  $alias
1117
   *
1118
   * @return ORM
1119
   */
1120 8
  public function select_expr($expr, $alias = null)
1121
  {
1122 8
    return $this->_add_result_column($expr, $alias);
1123
  }
1124
1125
  /**
1126
   * Add columns to the list of columns returned by the SELECT
1127
   * query. This defaults to '*'. Many columns can be supplied
1128
   * as either an array or as a list of parameters to the method.
1129
   *
1130
   * Note that the alias must not be numeric - if you want a
1131
   * numeric alias then prepend it with some alpha chars. eg. a1
1132
   *
1133
   * @example select_many(array('alias' => 'column', 'column2', 'alias2' => 'column3'), 'column4', 'column5');
1134
   * @example select_many('column', 'column2', 'column3');
1135
   * @example select_many(array('column', 'column2', 'column3'), 'column4', 'column5');
1136
   *
1137
   * @return ORM
1138
   */
1139 1 View Code Duplication
  public function select_many()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1140
  {
1141 1
    $columns = func_get_args();
1142 1
    if (!empty($columns)) {
1143 1
      $columns = $this->_normalise_select_many_columns($columns);
1144 1
      foreach ($columns as $alias => $column) {
1145 1
        if (is_numeric($alias)) {
1146 1
          $alias = null;
1147 1
        }
1148 1
        $this->select($column, $alias);
1149 1
      }
1150 1
    }
1151
1152 1
    return $this;
1153
  }
1154
1155
  /**
1156
   * Add an unquoted expression to the list of columns returned
1157
   * by the SELECT query. Many columns can be supplied as either
1158
   * an array or as a list of parameters to the method.
1159
   *
1160
   * Note that the alias must not be numeric - if you want a
1161
   * numeric alias then prepend it with some alpha chars. eg. a1
1162
   *
1163
   * @example select_many_expr(array('alias' => 'column', 'column2', 'alias2' => 'column3'), 'column4', 'column5')
1164
   * @example select_many_expr('column', 'column2', 'column3')
1165
   * @example select_many_expr(array('column', 'column2', 'column3'), 'column4', 'column5')
1166
   *
1167
   * @return ORM
1168
   */
1169 1 View Code Duplication
  public function select_many_expr()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1170
  {
1171 1
    $columns = func_get_args();
1172 1
    if (!empty($columns)) {
1173 1
      $columns = $this->_normalise_select_many_columns($columns);
1174 1
      foreach ($columns as $alias => $column) {
1175 1
        if (is_numeric($alias)) {
1176 1
          $alias = null;
1177 1
        }
1178 1
        $this->select_expr($column, $alias);
1179 1
      }
1180 1
    }
1181
1182 1
    return $this;
1183
  }
1184
1185
  /**
1186
   * Take a column specification for the select many methods and convert it
1187
   * into a normalised array of columns and aliases.
1188
   *
1189
   * It is designed to turn the following styles into a normalised array:
1190
   *
1191
   * array(array('alias' => 'column', 'column2', 'alias2' => 'column3'), 'column4', 'column5'))
1192
   *
1193
   * @param array $columns
1194
   *
1195
   * @return array
1196
   */
1197 2
  protected function _normalise_select_many_columns($columns)
1198
  {
1199 2
    $return = array();
1200 2
    foreach ($columns as $column) {
1201 2
      if (is_array($column)) {
1202 2
        foreach ($column as $key => $value) {
1203 2
          if (!is_numeric($key)) {
1204 2
            $return[$key] = $value;
1205 2
          } else {
1206
            $return[] = $value;
1207
          }
1208 2
        }
1209 2
      } else {
1210 2
        $return[] = $column;
1211
      }
1212 2
    }
1213
1214 2
    return $return;
1215
  }
1216
1217
  /**
1218
   * Add a DISTINCT keyword before the list of columns in the SELECT query
1219
   */
1220 1
  public function distinct()
1221
  {
1222 1
    $this->_distinct = true;
1223
1224 1
    return $this;
1225
  }
1226
1227
  /**
1228
   * Internal method to add a JOIN source to the query.
1229
   *
1230
   * The join_operator should be one of INNER, LEFT OUTER, CROSS etc - this
1231
   * will be prepended to JOIN.
1232
   *
1233
   * The table should be the name of the table to join to.
1234
   *
1235
   * The constraint may be either a string or an array with three elements. If it
1236
   * is a string, it will be compiled into the query as-is, with no escaping. The
1237
   * recommended way to supply the constraint is as an array with three elements:
1238
   *
1239
   * first_column, operator, second_column
1240
   *
1241
   * Example: array('user.id', '=', 'profile.user_id')
1242
   *
1243
   * will compile to
1244
   *
1245
   * ON `user`.`id` = `profile`.`user_id`
1246
   *
1247
   * The final (optional) argument specifies an alias for the joined table.
1248
   *
1249
   * @param string      $join_operator
1250
   * @param string      $table
1251
   * @param string      $constraint
1252
   * @param string|null $table_alias
1253
   *
1254
   * @return $this
1255
   */
1256 10
  protected function _add_join_source($join_operator, $table, $constraint, $table_alias = null)
1257
  {
1258
1259 10
    $join_operator = trim("{$join_operator} JOIN");
1260
1261 10
    $table = $this->_quote_identifier($table);
1262
1263
    // Add table alias if present
1264 10
    if (!is_null($table_alias)) {
1265 2
      $table_alias = $this->_quote_identifier($table_alias);
1266 2
      $table .= " {$table_alias}";
1267 2
    }
1268
1269
    // Build the constraint
1270 10 View Code Duplication
    if (is_array($constraint)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1271 9
      list($first_column, $operator, $second_column) = $constraint;
1272 9
      $first_column = $this->_quote_identifier($first_column);
1273 9
      $second_column = $this->_quote_identifier($second_column);
1274 9
      $constraint = "{$first_column} {$operator} {$second_column}";
1275 9
    }
1276
1277 10
    $this->_join_sources[] = "{$join_operator} {$table} ON {$constraint}";
1278
1279 10
    return $this;
1280
  }
1281
1282
  /**
1283
   * Add a RAW JOIN source to the query
1284
   *
1285
   * @param string $table
1286
   * @param string $constraint
1287
   * @param string $table_alias
1288
   * @param array  $parameters
1289
   *
1290
   * @return $this
1291
   */
1292 3
  public function raw_join($table, $constraint, $table_alias, $parameters = array())
1293
  {
1294
    // Add table alias if present
1295 3
    if (!is_null($table_alias)) {
1296 3
      $table_alias = $this->_quote_identifier($table_alias);
1297 3
      $table .= " {$table_alias}";
1298 3
    }
1299
1300 3
    $this->_values = array_merge($this->_values, $parameters);
1301
1302
    // Build the constraint
1303 3 View Code Duplication
    if (is_array($constraint)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1304 3
      list($first_column, $operator, $second_column) = $constraint;
1305 3
      $first_column = $this->_quote_identifier($first_column);
1306 3
      $second_column = $this->_quote_identifier($second_column);
1307 3
      $constraint = "{$first_column} {$operator} {$second_column}";
1308 3
    }
1309
1310 3
    $this->_join_sources[] = "{$table} ON {$constraint}";
1311
1312 3
    return $this;
1313
  }
1314
1315
  /**
1316
   * Add a simple JOIN source to the query
1317
   *
1318
   * @param string      $table
1319
   * @param string      $constraint
1320
   * @param string|null $table_alias
1321
   *
1322
   * @return ORM
1323
   */
1324 6
  public function join($table, $constraint, $table_alias = null)
1325
  {
1326 6
    return $this->_add_join_source("", $table, $constraint, $table_alias);
1327
  }
1328
1329
  /**
1330
   * Add an INNER JOIN souce to the query
1331
   */
1332
  /**
1333
   * @param string      $table
1334
   * @param string      $constraint
1335
   * @param null|string $table_alias
1336
   *
1337
   * @return ORM
1338
   */
1339 1
  public function inner_join($table, $constraint, $table_alias = null)
1340
  {
1341 1
    return $this->_add_join_source("INNER", $table, $constraint, $table_alias);
1342
  }
1343
1344
  /**
1345
   * Add a LEFT OUTER JOIN souce to the query
1346
   *
1347
   * @param string      $table
1348
   * @param string      $constraint
1349
   * @param null|string $table_alias
1350
   *
1351
   * @return $this|ORM
1352
   */
1353 1
  public function left_outer_join($table, $constraint, $table_alias = null)
1354
  {
1355 1
    return $this->_add_join_source("LEFT OUTER", $table, $constraint, $table_alias);
1356
  }
1357
1358
  /**
1359
   * Add an RIGHT OUTER JOIN souce to the query
1360
   *
1361
   * @param string      $table
1362
   * @param string      $constraint
1363
   * @param null|string $table_alias
1364
   *
1365
   * @return $this|ORM
1366
   */
1367 1
  public function right_outer_join($table, $constraint, $table_alias = null)
1368
  {
1369 1
    return $this->_add_join_source("RIGHT OUTER", $table, $constraint, $table_alias);
1370
  }
1371
1372
  /**
1373
   * Add an FULL OUTER JOIN souce to the query
1374
   */
1375
  /**
1376
   * @param string      $table
1377
   * @param string      $constraint
1378
   * @param null|string $table_alias
1379
   *
1380
   * @return ORM
1381
   */
1382 1
  public function full_outer_join($table, $constraint, $table_alias = null)
1383
  {
1384 1
    return $this->_add_join_source("FULL OUTER", $table, $constraint, $table_alias);
1385
  }
1386
1387
  /**
1388
   * Internal method to add a HAVING condition to the query
1389
   */
1390
  /**
1391
   * @param string $fragment
1392
   * @param array  $values
1393
   *
1394
   * @return ORM
1395
   */
1396 5
  protected function _add_having($fragment, $values = array())
1397
  {
1398 5
    return $this->_add_condition('having', $fragment, $values);
1399
  }
1400
1401
  /**
1402
   * Internal method to add a HAVING condition to the query
1403
   */
1404
  /**
1405
   * @param string|array $column_name
1406
   * @param string       $separator
1407
   * @param mixed        $value
1408
   *
1409
   * @return $this|ORM
1410
   */
1411 7
  protected function _add_simple_having($column_name, $separator, $value)
1412
  {
1413 7
    return $this->_add_simple_condition('having', $column_name, $separator, $value);
1414
  }
1415
1416
  /**
1417
   * Internal method to add a HAVING clause with multiple values (like IN and NOT IN)
1418
   */
1419
  /**
1420
   * @param string|array $column_name
1421
   * @param string       $separator
1422
   * @param mixed        $values
1423
   *
1424
   * @return ORM
1425
   */
1426 2 View Code Duplication
  public function _add_having_placeholder($column_name, $separator, $values)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1427
  {
1428 2
    if (!is_array($column_name)) {
1429 2
      $data = array($column_name => $values);
1430 2
    } else {
1431
      $data = $column_name;
1432
    }
1433
1434 2
    $result = $this;
1435 2
    foreach ($data as $key => $val) {
1436 2
      $column = $result->_quote_identifier($key);
1437 2
      $placeholders = $result->_create_placeholders($val);
1438 2
      $result = $result->_add_having("{$column} {$separator} ({$placeholders})", $val);
1439 2
    }
1440
1441 2
    return $result;
1442
  }
1443
1444
  /**
1445
   * Internal method to add a HAVING clause with no parameters(like IS NULL and IS NOT NULL)
1446
   *
1447
   * @param string|array $column_name
1448
   * @param string       $operator
1449
   *
1450
   * @return ORM
1451
   */
1452 2 View Code Duplication
  public function _add_having_no_value($column_name, $operator)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1453
  {
1454 2
    if (is_array($column_name)) {
1455
      $conditions = $column_name;
1456
    } else {
1457 2
      $conditions = array($column_name);
1458
    }
1459
1460 2
    $result = $this;
1461 2
    foreach ($conditions as $column) {
1462 2
      $column = $this->_quote_identifier($column);
1463 2
      $result = $result->_add_having("{$column} {$operator}");
1464 2
    }
1465
1466 2
    return $result;
1467
  }
1468
1469
  /**
1470
   * Internal method to add a WHERE condition to the query
1471
   *
1472
   * @param string $fragment
1473
   * @param array  $values
1474
   *
1475
   * @return ORM
1476
   */
1477 16
  protected function _add_where($fragment, $values = array())
1478
  {
1479 16
    return $this->_add_condition('where', $fragment, $values);
1480
  }
1481
1482
  /**
1483
   * Internal method to add a WHERE condition to the query
1484
   *
1485
   * @param string|array $column_name
1486
   * @param string       $separator
1487
   * @param mixed        $value
1488
   *
1489
   * @return $this|ORM
1490
   */
1491 38
  protected function _add_simple_where($column_name, $separator, $value)
1492
  {
1493 38
    return $this->_add_simple_condition('where', $column_name, $separator, $value);
1494
  }
1495
1496
  /**
1497
   * Add a WHERE clause with multiple values (like IN and NOT IN)
1498
   *
1499
   * @param string|array $column_name
1500
   * @param string       $separator
1501
   * @param mixed        $values
1502
   *
1503
   * @return ORM
1504
   */
1505 3 View Code Duplication
  public function _add_where_placeholder($column_name, $separator, $values)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1506
  {
1507 3
    if (!is_array($column_name)) {
1508 3
      $data = array($column_name => $values);
1509 3
    } else {
1510
      $data = $column_name;
1511
    }
1512
1513 3
    $result = $this;
1514 3
    foreach ($data as $key => $val) {
1515 3
      $column = $result->_quote_identifier($key);
1516 3
      $placeholders = $result->_create_placeholders($val);
1517 3
      $result = $result->_add_where("{$column} {$separator} ({$placeholders})", $val);
1518 3
    }
1519
1520 3
    return $result;
1521
  }
1522
1523
  /**
1524
   * Add a WHERE clause with no parameters(like IS NULL and IS NOT NULL)
1525
   *
1526
   * @param string $column_name
1527
   * @param string $operator
1528
   *
1529
   * @return ORM
1530
   */
1531 2 View Code Duplication
  public function _add_where_no_value($column_name, $operator)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1532
  {
1533 2
    if (is_array($column_name)) {
1534
      $conditions = $column_name;
1535
    } else {
1536 2
      $conditions = array($column_name);
1537
    }
1538
1539 2
    $result = $this;
1540 2
    foreach ($conditions as $column) {
1541 2
      $column = $this->_quote_identifier($column);
1542 2
      $result = $result->_add_where("{$column} {$operator}");
1543 2
    }
1544
1545 2
    return $result;
1546
  }
1547
1548
  /**
1549
   * Internal method to add a HAVING or WHERE condition to the query
1550
   *
1551
   * @param string $type
1552
   * @param string $fragment
1553
   * @param array  $values
1554
   *
1555
   * @return $this
1556
   */
1557 65
  protected function _add_condition($type, $fragment, $values = array())
1558
  {
1559 65
    $conditions_class_property_name = "_{$type}_conditions";
1560
1561 65
    if (!is_array($values)) {
1562 45
      $values = array($values);
1563 45
    }
1564
1565 65
    array_push(
1566 65
        $this->$conditions_class_property_name,
1567
        array(
1568 65
            static::CONDITION_FRAGMENT => $fragment,
1569 65
            static::CONDITION_VALUES   => $values,
1570
        )
1571 65
    );
1572
1573 65
    return $this;
1574
  }
1575
1576
  /**
1577
   * Helper method to compile a simple COLUMN SEPARATOR VALUE
1578
   * style HAVING or WHERE condition into a string and value ready to
1579
   * be passed to the _add_condition method. Avoids duplication
1580
   * of the call to _quote_identifier
1581
   *
1582
   * If column_name is an associative array, it will add a condition for each column
1583
   *
1584
   * @param string       $type
1585
   * @param string|array $column_name
1586
   * @param string       $separator
1587
   * @param string|int   $value
1588
   *
1589
   * @return $this|ORM
1590
   */
1591 45
  protected function _add_simple_condition($type, $column_name, $separator, $value)
1592
  {
1593 45
    if (is_array($column_name)) {
1594 1
      $multiple = $column_name;
1595 1
    } else {
1596 44
      $multiple = array($column_name => $value);
1597
    }
1598
1599 45
    $result = $this;
1600
1601 45
    foreach ($multiple as $key => $val) {
1602
      // Add the table name in case of ambiguous columns
1603 45
      if (count($result->_join_sources) > 0 && strpos($key, '.') === false) {
1604 2
        $table = $result->_table_name;
1605 2
        if (!is_null($result->_table_alias)) {
1606 1
          $table = $result->_table_alias;
1607 1
        }
1608
1609 2
        $key = "{$table}.{$key}";
1610 2
      }
1611 45
      $key = $result->_quote_identifier($key);
1612 45
      $result = $result->_add_condition($type, "{$key} {$separator} ?", $val);
1613 45
    }
1614
1615 45
    return $result;
1616
  }
1617
1618
  /**
1619
   * Return a string containing the given number of question marks,
1620
   * separated by commas. Eg "?, ?, ?"
1621
   *
1622
   * @param mixed $fields
1623
   *
1624
   * @return string
1625
   */
1626 11
  protected function _create_placeholders($fields)
1627
  {
1628 11
    if (!empty($fields)) {
1629 11
      $db_fields = array();
1630 11
      foreach ($fields as $key => $value) {
1631
        // Process expression fields directly into the query
1632 11
        if (array_key_exists($key, $this->_expr_fields)) {
1633 1
          $db_fields[] = $value;
1634 1
        } else {
1635 11
          $db_fields[] = '?';
1636
        }
1637 11
      }
1638
1639 11
      return implode(', ', $db_fields);
1640
    } else {
1641 1
      return '';
1642
    }
1643
  }
1644
1645
  /**
1646
   * Helper method that filters a column/value array returning only those
1647
   * columns that belong to a compound primary key.
1648
   *
1649
   * If the key contains a column that does not exist in the given array,
1650
   * a null value will be returned for it.
1651
   *
1652
   * @param mixed $value
1653
   *
1654
   * @return array
1655
   */
1656 2
  protected function _get_compound_id_column_values($value)
1657
  {
1658 2
    $filtered = array();
1659 2
    foreach ($this->_get_id_column_name() as $key) {
1660 2
      $filtered[$key] = isset($value[$key]) ? $value[$key] : null;
1661 2
    }
1662
1663 2
    return $filtered;
1664
  }
1665
1666
  /**
1667
   * Helper method that filters an array containing compound column/value
1668
   * arrays.
1669
   *
1670
   * @param array $values
1671
   *
1672
   * @return array
1673
   */
1674 1
  protected function _get_compound_id_column_values_array($values)
1675
  {
1676 1
    $filtered = array();
1677 1
    foreach ($values as $value) {
1678 1
      $filtered[] = $this->_get_compound_id_column_values($value);
1679 1
    }
1680
1681 1
    return $filtered;
1682
  }
1683
1684
  /**
1685
   * Add a WHERE column = value clause to your query. Each time
1686
   * this is called in the chain, an additional WHERE will be
1687
   * added, and these will be ANDed together when the final query
1688
   * is built.
1689
   *
1690
   * If you use an array in $column_name, a new clause will be
1691
   * added for each element. In this case, $value is ignored.
1692
   *
1693
   * @param string $column_name
1694
   * @param mixed  $value
1695
   *
1696
   * @return $this|ORM
1697
   */
1698 31
  public function where($column_name, $value = null)
1699
  {
1700 31
    return $this->where_equal($column_name, $value);
1701
  }
1702
1703
  /**
1704
   * More explicitly named version of for the where() method.
1705
   * Can be used if preferred.
1706
   *
1707
   * @param string $column_name
1708
   * @param mixed  $value
1709
   *
1710
   * @return $this|ORM
1711
   */
1712 33
  public function where_equal($column_name, $value = null)
1713
  {
1714 33
    return $this->_add_simple_where($column_name, '=', $value);
1715
  }
1716
1717
  /**
1718
   * Add a WHERE column != value clause to your query.
1719
   *
1720
   * @param string $column_name
1721
   * @param mixed  $value
1722
   *
1723
   * @return $this|ORM
1724
   */
1725 1
  public function where_not_equal($column_name, $value = null)
1726
  {
1727 1
    return $this->_add_simple_where($column_name, '!=', $value);
1728
  }
1729
1730
  /**
1731
   * Special method to query the table by its primary key
1732
   *
1733
   * If primary key is compound, only the columns that
1734
   * belong to they key will be used for the query
1735
   *
1736
   * @param mixed $id
1737
   *
1738
   * @return $this|ORM
1739
   */
1740 20 View Code Duplication
  public function where_id_is($id)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1741
  {
1742 20
    if (is_array($this->_get_id_column_name())) {
1743 1
      return $this->where($this->_get_compound_id_column_values($id), null);
0 ignored issues
show
Documentation introduced by
$this->_get_compound_id_column_values($id) is of type array, but the function expects a string.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1744
    } else {
1745 19
      return $this->where($this->_get_id_column_name(), $id);
1746
    }
1747
  }
1748
1749
  /**
1750
   * Allows adding a WHERE clause that matches any of the conditions
1751
   * specified in the array. Each element in the associative array will
1752
   * be a different condition, where the key will be the column name.
1753
   *
1754
   * By default, an equal operator will be used against all columns, but
1755
   * it can be overridden for any or every column using the second parameter.
1756
   *
1757
   * Each condition will be ORed together when added to the final query.
1758
   *
1759
   * @param array  $values
1760
   * @param string $operator
1761
   *
1762
   * @return $this|ORM
1763
   */
1764 4
  public function where_any_is($values, $operator = '=')
1765
  {
1766 4
    $data = array();
1767 4
    $query = array("((");
1768 4
    $first = true;
1769 4
    foreach ($values as $item) {
1770 4
      if ($first) {
1771 4
        $first = false;
1772 4
      } else {
1773 4
        $query[] = ") OR (";
1774
      }
1775 4
      $firstsub = true;
1776
1777
      // TODO? -> $item
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1778 4
      foreach ($item as $key => $subItem) {
1779
1780 4
        if (is_string($operator)) {
1781 3
          $op = $operator;
1782 3
        } else {
1783 1
          $op = (isset($operator[$key]) ? $operator[$key] : '=');
1784
        }
1785
1786 4
        if ($firstsub) {
1787 4
          $firstsub = false;
1788 4
        } else {
1789 4
          $query[] = "AND";
1790
        }
1791
1792 4
        $query[] = $this->_quote_identifier($key);
1793 4
        $data[] = $subItem;
1794 4
        $query[] = $op . " ?";
1795 4
      }
1796 4
    }
1797 4
    $query[] = "))";
1798
1799 4
    return $this->where_raw(join($query, ' '), $data);
1800
  }
1801
1802
  /**
1803
   * Similar to where_id_is() but allowing multiple primary keys.
1804
   *
1805
   * If primary key is compound, only the columns that
1806
   * belong to they key will be used for the query
1807
   *
1808
   * @param mixed $ids
1809
   *
1810
   * @return $this|ORM
1811
   */
1812 2
  public function where_id_in($ids)
1813
  {
1814 2
    if (is_array($this->_get_id_column_name())) {
1815 1
      return $this->where_any_is($this->_get_compound_id_column_values_array($ids));
1816
    } else {
1817 1
      return $this->where_in($this->_get_id_column_name(), $ids);
1818
    }
1819
  }
1820
1821
  /**
1822
   * Add a WHERE ... LIKE clause to your query.
1823
   *
1824
   * @param string $column_name
1825
   * @param mixed  $value
1826
   *
1827
   * @return $this|ORM
1828
   */
1829 1
  public function where_like($column_name, $value = null)
1830
  {
1831 1
    return $this->_add_simple_where($column_name, 'LIKE', $value);
1832
  }
1833
1834
  /**
1835
   * Add where WHERE ... NOT LIKE clause to your query.
1836
   *
1837
   * @param string $column_name
1838
   * @param mixed  $value
1839
   *
1840
   * @return $this|ORM
1841
   */
1842 1
  public function where_not_like($column_name, $value = null)
1843
  {
1844 1
    return $this->_add_simple_where($column_name, 'NOT LIKE', $value);
1845
  }
1846
1847
  /**
1848
   * Add a WHERE ... > clause to your query
1849
   *
1850
   * @param string $column_name
1851
   * @param mixed  $value
1852
   *
1853
   * @return $this|ORM
1854
   */
1855 1
  public function where_gt($column_name, $value = null)
1856
  {
1857 1
    return $this->_add_simple_where($column_name, '>', $value);
1858
  }
1859
1860
  /**
1861
   * Add a WHERE ... < clause to your query
1862
   *
1863
   * @param string $column_name
1864
   * @param mixed  $value
1865
   *
1866
   * @return $this|ORM
1867
   */
1868 1
  public function where_lt($column_name, $value = null)
1869
  {
1870 1
    return $this->_add_simple_where($column_name, '<', $value);
1871
  }
1872
1873
  /**
1874
   * Add a WHERE ... >= clause to your query
1875
   *
1876
   * @param string $column_name
1877
   * @param mixed  $value
1878
   *
1879
   * @return $this|ORM
1880
   */
1881 1
  public function where_gte($column_name, $value = null)
1882
  {
1883 1
    return $this->_add_simple_where($column_name, '>=', $value);
1884
  }
1885
1886
  /**
1887
   * Add a WHERE ... <= clause to your query
1888
   *
1889
   * @param string $column_name
1890
   * @param mixed  $value
1891
   *
1892
   * @return $this|ORM
1893
   */
1894 1
  public function where_lte($column_name, $value = null)
1895
  {
1896 1
    return $this->_add_simple_where($column_name, '<=', $value);
1897
  }
1898
1899
  /**
1900
   * Add a WHERE ... IN clause to your query
1901
   *
1902
   * @param string $column_name
1903
   * @param mixed  $values
1904
   *
1905
   * @return $this|ORM
1906
   */
1907 2
  public function where_in($column_name, $values)
1908
  {
1909 2
    return $this->_add_where_placeholder($column_name, 'IN', $values);
1910
  }
1911
1912
  /**
1913
   * Add a WHERE ... NOT IN clause to your query
1914
   *
1915
   * @param string $column_name
1916
   * @param mixed  $values
1917
   *
1918
   * @return $this|ORM
1919
   */
1920 1
  public function where_not_in($column_name, $values)
1921
  {
1922 1
    return $this->_add_where_placeholder($column_name, 'NOT IN', $values);
1923
  }
1924
1925
  /**
1926
   * Add a WHERE column IS NULL clause to your query
1927
   *
1928
   * @param string $column_name
1929
   *
1930
   * @return $this|ORM
1931
   */
1932 1
  public function where_null($column_name)
1933
  {
1934 1
    return $this->_add_where_no_value($column_name, "IS NULL");
1935
  }
1936
1937
  /**
1938
   * Add a WHERE column IS NOT NULL clause to your query
1939
   *
1940
   * @param string $column_name
1941
   *
1942
   * @return $this|ORM
1943
   */
1944 1
  public function where_not_null($column_name)
1945
  {
1946 1
    return $this->_add_where_no_value($column_name, "IS NOT NULL");
1947
  }
1948
1949
  /**
1950
   * Add a raw WHERE clause to the query. The clause should
1951
   * contain question mark placeholders, which will be bound
1952
   * to the parameters supplied in the second argument.
1953
   *
1954
   * @param       $clause
1955
   * @param array $parameters
1956
   *
1957
   * @return $this|ORM
1958
   */
1959 11
  public function where_raw($clause, $parameters = array())
1960
  {
1961 11
    return $this->_add_where($clause, $parameters);
1962
  }
1963
1964
  /**
1965
   * Add a LIMIT to the query
1966
   *
1967
   * @param int $limit
1968
   *
1969
   * @return $this
1970
   */
1971 55
  public function limit($limit)
1972
  {
1973 55
    $this->_limit = $limit;
0 ignored issues
show
Documentation Bug introduced by
It seems like $limit of type integer is incompatible with the declared type null of property $_limit.

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

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

Loading history...
1974
1975 55
    return $this;
1976
  }
1977
1978
  /**
1979
   * Add an OFFSET to the query
1980
   *
1981
   * @param $offset
1982
   *
1983
   * @return $this
1984
   */
1985 2
  public function offset($offset)
1986
  {
1987 2
    $this->_offset = $offset;
1988
1989 2
    return $this;
1990
  }
1991
1992
  /**
1993
   * Add an ORDER BY clause to the query
1994
   *
1995
   * @param string $column_name
1996
   * @param string $ordering
1997
   *
1998
   * @return $this
1999
   */
2000 5
  protected function _add_order_by($column_name, $ordering)
2001
  {
2002 5
    $column_name = $this->_quote_identifier($column_name);
2003 5
    $this->_order_by[] = "{$column_name} {$ordering}";
2004
2005 5
    return $this;
2006
  }
2007
2008
  /**
2009
   * Add an ORDER BY column DESC clause
2010
   *
2011
   * @param string $column_name
2012
   *
2013
   * @return $this|ORM
2014
   */
2015 3
  public function order_by_desc($column_name)
2016
  {
2017 3
    return $this->_add_order_by($column_name, 'DESC');
2018
  }
2019
2020
  /**
2021
   * Add an ORDER BY column ASC clause
2022
   *
2023
   * @param string $column_name
2024
   *
2025
   * @return $this|ORM
2026
   */
2027 3
  public function order_by_asc($column_name)
2028
  {
2029 3
    return $this->_add_order_by($column_name, 'ASC');
2030
  }
2031
2032
  /**
2033
   * Add an unquoted expression as an ORDER BY clause
2034
   *
2035
   * @param $clause
2036
   *
2037
   * @return $this
2038
   */
2039 1
  public function order_by_expr($clause)
2040
  {
2041 1
    $this->_order_by[] = $clause;
2042
2043 1
    return $this;
2044
  }
2045
2046
  /**
2047
   * Reset the ORDER BY clause
2048
   */
2049 1
  public function reset_order_by()
2050
  {
2051 1
    $this->_order_by = array();
2052
2053 1
    return $this;
2054
  }
2055
2056
  /**
2057
   * Add a column to the list of columns to GROUP BY
2058
   *
2059
   * @param string $column_name
2060
   *
2061
   * @return $this
2062
   */
2063 14
  public function group_by($column_name)
2064
  {
2065 14
    $column_name = $this->_quote_identifier($column_name);
2066 14
    $this->_group_by[] = $column_name;
2067
2068 14
    return $this;
2069
  }
2070
2071
  /**
2072
   * Add an unquoted expression to the list of columns to GROUP BY
2073
   *
2074
   * @param string $expr
2075
   *
2076
   * @return $this
2077
   */
2078 1
  public function group_by_expr($expr)
2079
  {
2080 1
    $this->_group_by[] = $expr;
2081
2082 1
    return $this;
2083
  }
2084
2085
  /**
2086
   * Add a HAVING column = value clause to your query. Each time
2087
   * this is called in the chain, an additional HAVING will be
2088
   * added, and these will be ANDed together when the final query
2089
   * is built.
2090
   *
2091
   * If you use an array in $column_name, a new clause will be
2092
   * added for each element. In this case, $value is ignored.
2093
   *
2094
   * @param string $column_name
2095
   * @param null   $value
2096
   *
2097
   * @return $this|ORM
2098
   */
2099 2
  public function having($column_name, $value = null)
2100
  {
2101 2
    return $this->having_equal($column_name, $value);
2102
  }
2103
2104
  /**
2105
   * More explicitly named version of for the having() method.
2106
   * Can be used if preferred.
2107
   *
2108
   * @param string $column_name
2109
   * @param null   $value
2110
   *
2111
   * @return $this|ORM
2112
   */
2113 2
  public function having_equal($column_name, $value = null)
2114
  {
2115 2
    return $this->_add_simple_having($column_name, '=', $value);
2116
  }
2117
2118
  /**
2119
   * Add a HAVING column != value clause to your query.
2120
   *
2121
   * @param string $column_name
2122
   * @param null   $value
2123
   *
2124
   * @return $this|ORM
2125
   */
2126 1
  public function having_not_equal($column_name, $value = null)
2127
  {
2128 1
    return $this->_add_simple_having($column_name, '!=', $value);
2129
  }
2130
2131
  /**
2132
   * Special method to query the table by its primary key.
2133
   *
2134
   * If primary key is compound, only the columns that
2135
   * belong to they key will be used for the query
2136
   *
2137
   * @param $id
2138
   *
2139
   * @return $this|ORM
2140
   */
2141 View Code Duplication
  public function having_id_is($id)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
2142
  {
2143
    if (is_array($this->_get_id_column_name())) {
2144
      return $this->having($this->_get_compound_id_column_values($id));
0 ignored issues
show
Documentation introduced by
$this->_get_compound_id_column_values($id) is of type array, but the function expects a string.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2145
    } else {
2146
      return $this->having($this->_get_id_column_name(), $id);
2147
    }
2148
  }
2149
2150
  /**
2151
   * Add a HAVING ... LIKE clause to your query.
2152
   *
2153
   * @param string $column_name
2154
   * @param mixed  $value
2155
   *
2156
   * @return $this|ORM
2157
   */
2158 1
  public function having_like($column_name, $value = null)
2159
  {
2160 1
    return $this->_add_simple_having($column_name, 'LIKE', $value);
2161
  }
2162
2163
  /**
2164
   * Add where HAVING ... NOT LIKE clause to your query.
2165
   *
2166
   * @param string $column_name
2167
   * @param mixed  $value
2168
   *
2169
   * @return $this|ORM
2170
   */
2171 1
  public function having_not_like($column_name, $value = null)
2172
  {
2173 1
    return $this->_add_simple_having($column_name, 'NOT LIKE', $value);
2174
  }
2175
2176
  /**
2177
   * Add a HAVING ... > clause to your query
2178
   *
2179
   * @param string $column_name
2180
   * @param mixed  $value
2181
   *
2182
   * @return $this|ORM
2183
   */
2184 1
  public function having_gt($column_name, $value = null)
2185
  {
2186 1
    return $this->_add_simple_having($column_name, '>', $value);
2187
  }
2188
2189
  /**
2190
   * Add a HAVING ... < clause to your query
2191
   *
2192
   * @param string $column_name
2193
   * @param mixed  $value
2194
   *
2195
   * @return $this|ORM
2196
   */
2197 1
  public function having_lt($column_name, $value = null)
2198
  {
2199 1
    return $this->_add_simple_having($column_name, '<', $value);
2200
  }
2201
2202
  /**
2203
   * Add a HAVING ... >= clause to your query
2204
   *
2205
   * @param string $column_name
2206
   * @param mixed  $value
2207
   *
2208
   * @return $this|ORM
2209
   */
2210 1
  public function having_gte($column_name, $value = null)
2211
  {
2212 1
    return $this->_add_simple_having($column_name, '>=', $value);
2213
  }
2214
2215
  /**
2216
   * Add a HAVING ... <= clause to your query
2217
   *
2218
   * @param string $column_name
2219
   * @param mixed  $value
2220
   *
2221
   * @return $this|ORM
2222
   */
2223 1
  public function having_lte($column_name, $value = null)
2224
  {
2225 1
    return $this->_add_simple_having($column_name, '<=', $value);
2226
  }
2227
2228
  /**
2229
   * Add a HAVING ... IN clause to your query
2230
   *
2231
   * @param string $column_name
2232
   * @param mixed  $values
2233
   *
2234
   * @return $this|ORM
2235
   */
2236 1
  public function having_in($column_name, $values = null)
2237
  {
2238 1
    return $this->_add_having_placeholder($column_name, 'IN', $values);
2239
  }
2240
2241
  /**
2242
   * Add a HAVING ... NOT IN clause to your query
2243
   *
2244
   * @param string $column_name
2245
   * @param mixed  $values
2246
   *
2247
   * @return $this|ORM
2248
   */
2249 1
  public function having_not_in($column_name, $values = null)
2250
  {
2251 1
    return $this->_add_having_placeholder($column_name, 'NOT IN', $values);
2252
  }
2253
2254
  /**
2255
   * Add a HAVING column IS NULL clause to your query
2256
   *
2257
   * @param string $column_name
2258
   *
2259
   * @return $this|ORM
2260
   */
2261 1
  public function having_null($column_name)
2262
  {
2263 1
    return $this->_add_having_no_value($column_name, 'IS NULL');
2264
  }
2265
2266
  /**
2267
   * Add a HAVING column IS NOT NULL clause to your query
2268
   *
2269
   * @param string $column_name
2270
   *
2271
   * @return $this|ORM
2272
   */
2273 1
  public function having_not_null($column_name)
2274
  {
2275 1
    return $this->_add_having_no_value($column_name, 'IS NOT NULL');
2276
  }
2277
2278
  /**
2279
   * Add a raw HAVING clause to the query. The clause should
2280
   * contain question mark placeholders, which will be bound
2281
   * to the parameters supplied in the second argument.
2282
   *
2283
   * @param       $clause
2284
   * @param array $parameters
2285
   *
2286
   * @return $this|ORM
2287
   */
2288 1
  public function having_raw($clause, $parameters = array())
2289
  {
2290 1
    return $this->_add_having($clause, $parameters);
2291
  }
2292
2293
  /**
2294
   * Activate cache refreshing for current query
2295
   *
2296
   * @return ORM
2297
   */
2298
  public function refreshCache()
2299
  {
2300
    $this->_refresh_cache = true;
2301
2302
    return $this;
2303
  }
2304
2305
  /**
2306
   * Disable caching for current query
2307
   *
2308
   * @return ORM
2309
   */
2310
  public function noCaching()
2311
  {
2312
    $this->_no_caching = true;
2313
2314
    return $this;
2315
  }
2316
2317
  /**
2318
   * Build a SELECT statement based on the clauses that have
2319
   * been passed to this instance by chaining method calls.
2320
   */
2321 111
  protected function _build_select()
2322
  {
2323
    // If the query is raw, just set the $this->_values to be
2324
    // the raw query parameters and return the raw query
2325 111
    if ($this->_is_raw_query) {
2326 2
      $this->_values = $this->_raw_parameters;
2327
2328 2
      return $this->_raw_query;
2329
    }
2330
2331
    // Build and return the full SELECT statement by concatenating
2332
    // the results of calling each separate builder method.
2333 109
    return $this->_join_if_not_empty(
2334 109
        " ", array(
2335 109
               $this->_build_select_start(),
2336 109
               $this->_build_join(),
2337 109
               $this->_build_where(),
2338 109
               $this->_build_group_by(),
2339 109
               $this->_build_having(),
2340 109
               $this->_build_order_by(),
2341 109
               $this->_build_limit(),
2342 109
               $this->_build_offset(),
2343
           )
2344 109
    );
2345
  }
2346
2347
  /**
2348
   * Build the start of the SELECT statement
2349
   */
2350 109
  protected function _build_select_start()
2351
  {
2352 109
    $fragment = 'SELECT ';
2353 109
    $result_columns = join(', ', $this->_result_columns);
2354
2355 109
    if (!is_null($this->_limit) &&
2356 55
        static::$_config[$this->_connection_name]['limit_clause_style'] === ORM::LIMIT_STYLE_TOP_N
2357 109
    ) {
2358 2
      $fragment .= "TOP {$this->_limit} ";
2359 2
    }
2360
2361 109
    if ($this->_distinct) {
2362 1
      $result_columns = 'DISTINCT ' . $result_columns;
2363 1
    }
2364
2365 109
    $fragment .= "{$result_columns} FROM " . $this->_quote_identifier($this->_table_name);
2366
2367 109
    if (!is_null($this->_table_alias)) {
2368 2
      $fragment .= " " . $this->_quote_identifier($this->_table_alias);
2369 2
    }
2370
2371 109
    return $fragment;
2372
  }
2373
2374
  /**
2375
   * Build the JOIN sources
2376
   */
2377 109
  protected function _build_join()
2378
  {
2379 109
    if (count($this->_join_sources) === 0) {
2380 96
      return '';
2381
    }
2382
2383 13
    return join(" ", $this->_join_sources);
2384
  }
2385
2386
  /**
2387
   * Build the WHERE clause(s)
2388
   */
2389 110
  protected function _build_where()
2390
  {
2391 110
    return $this->_build_conditions('where');
2392
  }
2393
2394
  /**
2395
   * Build the HAVING clause(s)
2396
   */
2397 109
  protected function _build_having()
2398
  {
2399 109
    return $this->_build_conditions('having');
2400
  }
2401
2402
  /**
2403
   * Build GROUP BY
2404
   */
2405 109
  protected function _build_group_by()
2406
  {
2407 109
    if (count($this->_group_by) === 0) {
2408 94
      return '';
2409
    }
2410
2411 15
    return "GROUP BY " . join(", ", $this->_group_by);
2412
  }
2413
2414
  /**
2415
   * Build a WHERE or HAVING clause
2416
   *
2417
   * @param string $type
2418
   *
2419
   * @return string
2420
   */
2421 110
  protected function _build_conditions($type)
2422
  {
2423 110
    $conditions_class_property_name = "_{$type}_conditions";
2424
    // If there are no clauses, return empty string
2425 110
    if (count($this->$conditions_class_property_name) === 0) {
2426 109
      return '';
2427
    }
2428
2429 65
    $conditions = array();
2430 65
    foreach ($this->$conditions_class_property_name as $condition) {
2431 65
      $conditions[] = $condition[static::CONDITION_FRAGMENT];
2432 65
      $this->_values = array_merge($this->_values, $condition[static::CONDITION_VALUES]);
2433 65
    }
2434
2435 65
    return strtoupper($type) . " " . join(" AND ", $conditions);
2436
  }
2437
2438
  /**
2439
   * Build ORDER BY
2440
   */
2441 109
  protected function _build_order_by()
2442
  {
2443 109
    if (count($this->_order_by) === 0) {
2444 104
      return '';
2445
    }
2446
2447 5
    return "ORDER BY " . join(", ", $this->_order_by);
2448
  }
2449
2450
  /**
2451
   * Build LIMIT
2452
   */
2453 109
  protected function _build_limit()
2454
  {
2455 109
    $fragment = '';
2456 109
    if (!is_null($this->_limit) &&
2457 55
        static::$_config[$this->_connection_name]['limit_clause_style'] == ORM::LIMIT_STYLE_LIMIT
2458 109
    ) {
2459 53
      if (static::get_db($this->_connection_name)->getAttribute(\PDO::ATTR_DRIVER_NAME) == 'firebird') {
2460
        $fragment = 'ROWS';
2461
      } else {
2462 53
        $fragment = 'LIMIT';
2463
      }
2464 53
      $fragment .= " {$this->_limit}";
2465 53
    }
2466
2467 109
    return $fragment;
2468
  }
2469
2470
  /**
2471
   * Build OFFSET
2472
   */
2473 109
  protected function _build_offset()
2474
  {
2475 109
    if (!is_null($this->_offset)) {
2476 2
      $clause = 'OFFSET';
2477 2
      if (static::get_db($this->_connection_name)->getAttribute(\PDO::ATTR_DRIVER_NAME) == 'firebird') {
2478
        $clause = 'TO';
2479
      }
2480
2481 2
      return "$clause " . $this->_offset;
2482
    }
2483
2484 107
    return '';
2485
  }
2486
2487
  /**
2488
   * Wrapper around PHP's join function which
2489
   * only adds the pieces if they are not empty.
2490
   *
2491
   * @param string $glue
2492
   * @param array  $pieces
2493
   *
2494
   * @return string
2495
   */
2496 110
  protected function _join_if_not_empty($glue, $pieces)
2497
  {
2498 110
    $filtered_pieces = array();
2499 110
    foreach ($pieces as $piece) {
2500 110
      if (is_string($piece)) {
2501 110
        $piece = trim($piece);
2502 110
      }
2503 110
      if (!empty($piece)) {
2504 110
        $filtered_pieces[] = $piece;
2505 110
      }
2506 110
    }
2507
2508 110
    return join($glue, $filtered_pieces);
2509
  }
2510
2511
  /**
2512
   * Quote a string that is used as an identifier
2513
   * (table names, column names etc). This method can
2514
   * also deal with dot-separated identifiers eg table.column
2515
   *
2516
   * @param string $identifier
2517
   *
2518
   * @return string
2519
   */
2520 117
  protected function _quote_one_identifier($identifier)
2521
  {
2522 117
    $parts = explode('.', $identifier);
2523 117
    $parts = array_map(array($this, '_quote_identifier_part'), $parts);
2524
2525 117
    return join('.', $parts);
2526
  }
2527
2528
  /**
2529
   * Quote a string that is used as an identifier
2530
   * (table names, column names etc) or an array containing
2531
   * multiple identifiers. This method can also deal with
2532
   * dot-separated identifiers eg table.column
2533
   *
2534
   * @param array|string $identifier
2535
   *
2536
   * @return string
2537
   */
2538 117
  protected function _quote_identifier($identifier)
2539
  {
2540 117
    if (is_array($identifier)) {
2541 1
      $result = array_map(array($this, '_quote_one_identifier'), $identifier);
2542
2543 1
      return join(', ', $result);
2544
    } else {
2545 116
      return $this->_quote_one_identifier($identifier);
2546
    }
2547
  }
2548
2549
  /**
2550
   * This method performs the actual quoting of a single
2551
   * part of an identifier, using the identifier quote
2552
   * character specified in the config (or autodetected).
2553
   *
2554
   * @param string $part
2555
   *
2556
   * @return string
2557
   */
2558 117
  protected function _quote_identifier_part($part)
2559
  {
2560 117
    if ($part === '*') {
2561 1
      return $part;
2562
    }
2563
2564 117
    $quote_character = static::$_config[$this->_connection_name]['identifier_quote_character'];
2565
2566
    // double up any identifier quotes to escape them
2567
    return $quote_character .
2568 117
           str_replace(
2569 117
               $quote_character, $quote_character . $quote_character, $part
2570 117
           ) . $quote_character;
2571
  }
2572
2573
  /**
2574
   * Create a cache key for the given query and parameters.
2575
   *
2576
   * @param string      $query
2577
   * @param array       $parameters
2578
   * @param null|string $table_name
2579
   * @param string      $connection_name
2580
   *
2581
   * @return mixed|string
2582
   */
2583 2
  protected static function _create_cache_key($query, $parameters, $table_name = null, $connection_name = self::DEFAULT_CONNECTION)
2584
  {
2585 2
    if (isset(static::$_config[$connection_name]['create_cache_key']) and is_callable(static::$_config[$connection_name]['create_cache_key'])) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
2586
      return call_user_func_array(
2587
          static::$_config[$connection_name]['create_cache_key'],
2588
          array(
2589
              $query,
2590
              $parameters,
2591
              $table_name,
2592
              $connection_name,
2593
          )
2594
      );
2595
    }
2596 2
    $parameter_string = join(',', $parameters);
2597 2
    $key = $query . ':' . $parameter_string;
2598
2599 2
    return sha1($key);
2600
  }
2601
2602
  /**
2603
   * Check the query cache for the given cache key. If a value
2604
   * is cached for the key, return the value. Otherwise, return false.
2605
   *
2606
   * @param string      $cache_key
2607
   * @param null|string $table_name
2608
   * @param string      $connection_name
2609
   *
2610
   * @return bool|mixed
2611
   */
2612 2
  protected static function _check_query_cache($cache_key, $table_name = null, $connection_name = self::DEFAULT_CONNECTION)
2613
  {
2614
    if (
2615 2
        isset(static::$_config[$connection_name]['check_query_cache'])
2616 2
        and
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
2617
        is_callable(static::$_config[$connection_name]['check_query_cache'])
2618 2
    ) {
2619
      return call_user_func_array(
2620
          static::$_config[$connection_name]['check_query_cache'],
2621
          array(
2622
              $cache_key,
2623
              $table_name,
2624
              $connection_name,
2625
          )
2626
      );
2627 2
    } elseif (isset(static::$_query_cache[$connection_name][$cache_key])) {
2628 2
      return static::$_query_cache[$connection_name][$cache_key];
2629
    }
2630
2631 2
    return false;
2632
  }
2633
2634
  /**
2635
   * Clear the query cache
2636
   *
2637
   * @param null|string $table_name
2638
   * @param string      $connection_name
2639
   *
2640
   * @return bool|mixed
2641
   */
2642
  public static function clear_cache($table_name = null, $connection_name = self::DEFAULT_CONNECTION)
2643
  {
2644
    // init
2645
    static::$_query_cache = array();
2646
2647
    if (
2648
        isset(static::$_config[$connection_name]['clear_cache'])
2649
        &&
2650
        is_callable(static::$_config[$connection_name]['clear_cache'])
2651
    ) {
2652
      return call_user_func_array(
2653
          static::$_config[$connection_name]['clear_cache'],
2654
          array(
2655
              $table_name,
2656
              $connection_name,
2657
          )
2658
      );
2659
    }
2660
2661
    return false;
2662
  }
2663
2664
  /**
2665
   * Add the given value to the query cache.
2666
   *
2667
   * @param string      $cache_key
2668
   * @param string      $value
2669
   * @param null|string $table_name
2670
   * @param string      $connection_name
2671
   *
2672
   * @return bool|mixed
2673
   */
2674 2
  protected static function _cache_query_result($cache_key, $value, $table_name = null, $connection_name = self::DEFAULT_CONNECTION)
2675
  {
2676
    if (
2677 2
        isset(static::$_config[$connection_name]['cache_query_result'])
2678 2
        and
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
2679
        is_callable(static::$_config[$connection_name]['cache_query_result'])
2680 2
    ) {
2681
2682
      return call_user_func_array(
2683
          static::$_config[$connection_name]['cache_query_result'],
2684
          array(
2685
              $cache_key,
2686
              $value,
2687
              $table_name,
2688
              $connection_name,
2689
          )
2690
      );
2691
2692 2
    } elseif (!isset(static::$_query_cache[$connection_name])) {
2693 2
      static::$_query_cache[$connection_name] = array();
2694 2
    }
2695
2696 2
    static::$_query_cache[$connection_name][$cache_key] = $value;
2697
2698 2
    return true;
2699
  }
2700
2701
  /**
2702
   * Execute the SELECT query that has been built up by chaining methods
2703
   * on this class. Return an array of rows as associative arrays.
2704
   */
2705 111
  protected function _run()
2706
  {
2707
    // init
2708 111
    $cache_key = false;
2709
2710 111
    $query = $this->_build_select();
2711 111
    $caching_enabled = static::$_config[$this->_connection_name]['caching'];
2712
2713 111
    if ($caching_enabled && !$this->_no_caching) {
2714 2
      $cache_key = static::_create_cache_key($query, $this->_values, $this->_table_name, $this->_connection_name);
2715
2716 2
      if (!$this->_refresh_cache) {
2717 2
        $cached_result = static::_check_query_cache($cache_key, $this->_table_name, $this->_connection_name);
2718
2719 2
        if ($cached_result !== false) {
2720 2
          return $cached_result;
2721
        }
2722 2
      }
2723 2
    }
2724
2725 111
    static::_execute($query, $this->_values, $this->_connection_name);
2726 111
    $statement = static::get_last_statement();
2727
2728 111
    $rows = array();
2729 111
    while ($row = $statement->fetch(\PDO::FETCH_ASSOC)) {
2730 110
      $rows[] = $row;
2731 110
    }
2732
2733 111
    if ($cache_key) {
2734 2
      static::_cache_query_result($cache_key, $rows, $this->_table_name, $this->_connection_name);
0 ignored issues
show
Documentation introduced by
$rows is of type array, but the function expects a string.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2735 2
    }
2736
2737
    // reset Idiorm after executing the query
2738 111
    $this->_values = array();
2739 111
    $this->_result_columns = array('*');
2740 111
    $this->_using_default_result_columns = true;
2741
2742 111
    return $rows;
2743
  }
2744
2745
  /**
2746
   * Return the raw data wrapped by this ORM
2747
   * instance as an associative array. Column
2748
   * names may optionally be supplied as arguments,
2749
   * if so, only those keys will be returned.
2750
   */
2751
  public function as_array()
2752
  {
2753
    if (func_num_args() === 0) {
2754
      return $this->_data;
2755
    }
2756
    $args = func_get_args();
2757
2758
    return array_intersect_key($this->_data, array_flip($args));
2759
  }
2760
2761
  /**
2762
   * Return the value of a property of this object (database row)
2763
   * or null if not present.
2764
   *
2765
   * If a column-names array is passed, it will return a associative array
2766
   * with the value of each column or null if it is not present.
2767
   *
2768
   * @param mixed $key
2769
   *
2770
   * @return mixed
2771
   */
2772 21
  public function get($key)
2773
  {
2774 21
    if (is_array($key)) {
2775 4
      $result = array();
2776 4
      foreach ($key as $column) {
2777 4
        $result[$column] = isset($this->_data[$column]) ? $this->_data[$column] : null;
2778 4
      }
2779
2780 4
      return $result;
2781
    } else {
2782 17
      return isset($this->_data[$key]) ? $this->_data[$key] : null;
2783
    }
2784
  }
2785
2786
  /**
2787
   * Return the name of the column in the database table which contains
2788
   * the primary key ID of the row.
2789
   */
2790 33
  protected function _get_id_column_name()
2791
  {
2792 33
    if (!is_null($this->_instance_id_column)) {
2793 13
      return $this->_instance_id_column;
2794
    }
2795 20
    if (isset(static::$_config[$this->_connection_name]['id_column_overrides'][$this->_table_name])) {
2796 2
      return static::$_config[$this->_connection_name]['id_column_overrides'][$this->_table_name];
2797
    }
2798
2799 18
    return static::$_config[$this->_connection_name]['id_column'];
2800
  }
2801
2802
  /**
2803
   * Get the primary key ID of this object.
2804
   *
2805
   * @param bool $disallow_null
2806
   *
2807
   * @return mixed
2808
   *
2809
   * @throws \Exception
2810
   */
2811 19
  public function id($disallow_null = false)
2812
  {
2813 19
    $id = $this->get($this->_get_id_column_name());
2814
2815 19
    if ($disallow_null) {
2816 15
      if (is_array($id)) {
2817 3
        foreach ($id as $id_part) {
2818 3
          if ($id_part === null) {
2819 1
            throw new \Exception('Primary key ID contains null value(s)');
2820
          }
2821 3
        }
2822 14
      } else if ($id === null) {
2823 3
        throw new \Exception('Primary key ID missing from row or is null');
2824
      }
2825 11
    }
2826
2827 15
    return $id;
2828
  }
2829
2830
  /**
2831
   * Set a property to a particular value on this object.
2832
   * To set multiple properties at once, pass an associative array
2833
   * as the first parameter and leave out the second parameter.
2834
   * Flags the properties as 'dirty' so they will be saved to the
2835
   * database when save() is called.
2836
   *
2837
   * @param mixed $key
2838
   * @param mixed $value
2839
   *
2840
   * @return $this|ORM
2841
   */
2842 17
  public function set($key, $value = null)
2843
  {
2844 17
    return $this->_set_orm_property($key, $value);
2845
  }
2846
2847
  /**
2848
   * Set a property to a particular value on this object.
2849
   * To set multiple properties at once, pass an associative array
2850
   * as the first parameter and leave out the second parameter.
2851
   * Flags the properties as 'dirty' so they will be saved to the
2852
   * database when save() is called.
2853
   *
2854
   * @param string|array $key
2855
   * @param string|null  $value
2856
   *
2857
   * @return ORM
2858
   */
2859 5
  public function set_expr($key, $value = null)
2860
  {
2861 5
    return $this->_set_orm_property($key, $value, true);
2862
  }
2863
2864
  /**
2865
   * Set a property on the ORM object.
2866
   *
2867
   * @param  string|array $key
2868
   * @param string|null   $value
2869
   * @param bool          $expr
2870
   *
2871
   * @return $this
2872
   */
2873 18
  protected function _set_orm_property($key, $value = null, $expr = false)
2874
  {
2875 18
    if (!is_array($key)) {
2876 16
      $key = array($key => $value);
2877 16
    }
2878 18
    foreach ($key as $field => $value) {
2879 18
      $this->_data[$field] = $value;
2880 18
      $this->_dirty_fields[$field] = $value;
2881 18
      if (false === $expr && isset($this->_expr_fields[$field])) {
2882 1
        unset($this->_expr_fields[$field]);
2883 18
      } else if (true === $expr) {
2884 5
        $this->_expr_fields[$field] = true;
2885 5
      }
2886 18
    }
2887
2888 18
    return $this;
2889
  }
2890
2891
  /**
2892
   * Check whether the given field has been changed since this
2893
   * object was saved.
2894
   *
2895
   * @param string $key
2896
   *
2897
   * @return bool
2898
   */
2899 1
  public function is_dirty($key)
2900
  {
2901 1
    return array_key_exists($key, $this->_dirty_fields);
2902
  }
2903
2904
  /**
2905
   * Check whether the model was the result of a call to create() or not
2906
   *
2907
   * @return bool
2908
   */
2909 2
  public function is_new()
2910
  {
2911 2
    return $this->_is_new;
2912
  }
2913
2914
  /**
2915
   * Save any fields which have been modified on this object
2916
   * to the database.
2917
   *
2918
   * @return bool
2919
   *
2920
   * @throws \Exception
2921
   */
2922 15
  public function save()
2923
  {
2924
    // remove any expression fields as they are already baked into the query
2925 15
    $values = array_values(array_diff_key($this->_dirty_fields, $this->_expr_fields));
2926
2927 15
    if (!$this->_is_new) {
2928
2929
      // UPDATE
2930
2931
      // If there are no dirty values, do nothing
2932 10
      if (empty($values) && empty($this->_expr_fields)) {
2933
        return true;
2934
      }
2935
2936 10
      $query = $this->_build_update();
2937 10
      $id = $this->id(true);
2938
2939 9
      if (is_array($id)) {
2940 1
        $values = array_merge($values, array_values($id));
2941 1
      } else {
2942 8
        $values[] = $id;
2943
      }
2944
2945 9
    } else {
2946
2947
      // INSERT
2948 6
      $query = $this->_build_insert();
2949
    }
2950
2951 14
    $success = static::_execute($query, $values, $this->_connection_name);
2952 14
    $caching_auto_clear_enabled = static::$_config[$this->_connection_name]['caching_auto_clear'];
2953
2954 14
    if ($caching_auto_clear_enabled) {
2955
      static::clear_cache($this->_table_name, $this->_connection_name);
2956
    }
2957
2958
    // If we've just inserted a new record, set the ID of this object
2959 14
    if ($success && $this->_is_new) {
2960
2961 6
      $this->_is_new = false;
2962 6
      if ($this->count_null_id_columns() != 0) {
2963 3
        $db = static::get_db($this->_connection_name);
2964
2965 3
        if ($db->getAttribute(\PDO::ATTR_DRIVER_NAME) == 'pgsql') {
2966
2967
          // it may return several columns if a compound primary
2968
          // key is used
2969
          $row = static::get_last_statement()->fetch(\PDO::FETCH_ASSOC);
2970
          foreach ($row as $key => $value) {
2971
            $this->_data[$key] = $value;
2972
          }
2973
2974
        } else {
2975 3
          $column = $this->_get_id_column_name();
2976
          // if the primary key is compound, assign the last inserted id
2977
          // to the first column
2978 3
          if (is_array($column)) {
2979
            $column = reset($column);
2980
          }
2981 3
          $this->_data[$column] = $db->lastInsertId();
2982
        }
2983 3
      }
2984 6
    }
2985
2986 14
    $this->_dirty_fields = $this->_expr_fields = array();
2987
2988 14
    return $success;
2989
  }
2990
2991
  /**
2992
   * Add a WHERE clause for every column that belongs to the primary key
2993
   *
2994
   * @param array $query warning: this is a reference
2995
   */
2996 13
  public function _add_id_column_conditions(&$query)
2997
  {
2998 13
    $query[] = "WHERE";
2999
3000 13
    if (is_array($this->_get_id_column_name())) {
3001 2
      $keys = $this->_get_id_column_name();
3002 2
    } else {
3003 11
      $keys = array($this->_get_id_column_name());
3004
    }
3005
3006 13
    $first = true;
3007 13
    foreach ($keys as $key) {
3008
3009 13
      if ($first) {
3010 13
        $first = false;
3011 13
      } else {
3012 2
        $query[] = "AND";
3013
      }
3014
3015 13
      $query[] = $this->_quote_identifier($key);
3016 13
      $query[] = "= ?";
3017 13
    }
3018 13
  }
3019
3020
  /**
3021
   * Build an UPDATE query
3022
   */
3023 10
  protected function _build_update()
3024
  {
3025 10
    $query = array();
3026 10
    $query[] = "UPDATE {$this->_quote_identifier($this->_table_name)} SET";
3027
3028 10
    $field_list = array();
3029 10
    foreach ($this->_dirty_fields as $key => $value) {
3030
3031 10
      if (!array_key_exists($key, $this->_expr_fields)) {
3032 9
        $value = '?';
3033 9
      }
3034
3035 10
      $field_list[] = "{$this->_quote_identifier($key)} = $value";
3036 10
    }
3037
3038 10
    $query[] = join(", ", $field_list);
3039 10
    $this->_add_id_column_conditions($query);
3040
3041 10
    return join(" ", $query);
3042
  }
3043
3044
  /**
3045
   * Build an INSERT query
3046
   */
3047 6
  protected function _build_insert()
3048
  {
3049 6
    $query[] = "INSERT INTO";
0 ignored issues
show
Coding Style Comprehensibility introduced by
$query was never initialized. Although not strictly required by PHP, it is generally a good practice to add $query = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
3050 6
    $query[] = $this->_quote_identifier($this->_table_name);
3051 6
    $field_list = array_map(array($this, '_quote_identifier'), array_keys($this->_dirty_fields));
3052 6
    $query[] = "(" . join(", ", $field_list) . ")";
3053 6
    $query[] = "VALUES";
3054
3055 6
    $placeholders = $this->_create_placeholders($this->_dirty_fields);
3056 6
    $query[] = "({$placeholders})";
3057
3058 6
    if (static::get_db($this->_connection_name)->getAttribute(\PDO::ATTR_DRIVER_NAME) == 'pgsql') {
3059
      $query[] = 'RETURNING ' . $this->_quote_identifier($this->_get_id_column_name());
3060
    }
3061
3062 6
    return join(" ", $query);
3063
  }
3064
3065
  /**
3066
   * Delete this record from the database
3067
   */
3068 3
  public function delete()
3069
  {
3070
    $query = array(
3071 3
        "DELETE FROM",
3072 3
        $this->_quote_identifier($this->_table_name),
3073 3
    );
3074 3
    $this->_add_id_column_conditions($query);
3075
3076 3
    return static::_execute(
3077 3
        join(" ", $query), is_array($this->id(true)) ?
3078 2
        array_values($this->id(true)) :
3079 2
        array($this->id(true)), $this->_connection_name
3080 2
    );
3081
  }
3082
3083
  /**
3084
   * Delete many records from the database
3085
   */
3086 1
  public function delete_many()
3087
  {
3088
    // Build and return the full DELETE statement by concatenating
3089
    // the results of calling each separate builder method.
3090 1
    $query = $this->_join_if_not_empty(
3091 1
        " ",
3092
        array(
3093 1
            "DELETE FROM",
3094 1
            $this->_quote_identifier($this->_table_name),
3095 1
            $this->_build_where(),
3096
        )
3097 1
    );
3098
3099 1
    return static::_execute($query, $this->_values, $this->_connection_name);
3100
  }
3101
3102
  // --------------------- //
3103
  // ---  ArrayAccess  --- //
3104
  // --------------------- //
3105
3106 8
  public function offsetExists($key)
3107
  {
3108 8
    return array_key_exists($key, $this->_data);
3109
  }
3110
3111 2
  public function offsetGet($key)
3112
  {
3113 2
    return $this->get($key);
3114
  }
3115
3116 9
  public function offsetSet($key, $value)
3117
  {
3118 9
    if (is_null($key)) {
3119
      throw new \InvalidArgumentException('You must specify a key/array index.');
3120
    }
3121 9
    $this->set($key, $value);
3122 9
  }
3123
3124 1
  public function offsetUnset($key)
3125
  {
3126 1
    unset($this->_data[$key]);
3127 1
    unset($this->_dirty_fields[$key]);
3128 1
  }
3129
3130
  // --------------------- //
3131
  // --- MAGIC METHODS --- //
3132
  // --------------------- //
3133 1
  public function __get($key)
3134
  {
3135 1
    return $this->offsetGet($key);
3136
  }
3137
3138 7
  public function __set($key, $value)
3139
  {
3140 7
    $this->offsetSet($key, $value);
3141 7
  }
3142
3143
  public function __unset($key)
3144
  {
3145
    $this->offsetUnset($key);
3146
  }
3147
3148 7
  public function __isset($key)
3149
  {
3150 7
    return $this->offsetExists($key);
3151
  }
3152
3153
  /**
3154
   * Magic method to capture calls to undefined class methods.
3155
   * In this case we are attempting to convert camel case formatted
3156
   * methods into underscore formatted methods.
3157
   *
3158
   * This allows us to call ORM methods using camel case and remain
3159
   * backwards compatible.
3160
   *
3161
   * @param  string $name
3162
   * @param  array  $arguments
3163
   *
3164
   * @return ORM
3165
   *
3166
   * @throws IdiormMethodMissingException
3167
   */
3168 2
  public function __call($name, $arguments)
3169
  {
3170 2
    $method = strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $name));
3171
3172 2 View Code Duplication
    if (method_exists($this, $method)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
3173 1
      return call_user_func_array(array($this, $method), $arguments);
3174
    } else {
3175 1
      throw new IdiormMethodMissingException("Method $name() does not exist in class " . get_class($this));
3176
    }
3177
  }
3178
3179
  /**
3180
   * Magic method to capture calls to undefined static class methods.
3181
   * In this case we are attempting to convert camel case formatted
3182
   * methods into underscore formatted methods.
3183
   *
3184
   * This allows us to call ORM methods using camel case and remain
3185
   * backwards compatible.
3186
   *
3187
   * @param  string $name
3188
   * @param  array  $arguments
3189
   *
3190
   * @return ORM
3191
   */
3192
  public static function __callStatic($name, $arguments)
3193
  {
3194
    $method = strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $name));
3195
3196
    return call_user_func_array(array('ORM', $method), $arguments);
3197
  }
3198
3199
}
3200