ORM::max()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
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|\PDOStatement
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|int
218
   */
219
  protected $_limit = null;
220
221
  /**
222
   * OFFSET
223
   *
224
   * @var null|int
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|array $key
314
   * @param mixed        $value
315
   * @param string       $connection_name Which connection to use
316
   */
317 244
  public static function configure($key, $value = null, $connection_name = self::DEFAULT_CONNECTION)
318
  {
319 244
    static::_setup_db_config($connection_name); //ensures at least default config is set
320
321 244
    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 244
      if (null === $value) {
329
        // Shortcut: If only one string argument is passed,
330
        // assume it's a connection string
331 5
        $value = $key;
332 5
        $key = 'connection_string';
333 5
      }
334 244
      static::$_config[$connection_name][$key] = $value;
335
    }
336 244
  }
337
338
  /**
339
   * Retrieve configuration options by key, or as whole array.
340
   *
341
   * @param string|null $key
342
   * @param string      $connection_name Which connection to use
343
   *
344
   * @return string|array
345
   */
346 3
  public static function get_config($key = null, $connection_name = self::DEFAULT_CONNECTION)
347
  {
348 3
    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...
349 2
      return static::$_config[$connection_name][$key];
350
    } else {
351 1
      return static::$_config[$connection_name];
352
    }
353
  }
354
355
  /**
356
   * Delete all configs in _config array.
357
   */
358 156
  public static function reset_config()
359
  {
360 156
    static::$_config = array();
361 156
  }
362
363
  /**
364
   * Despite its slightly odd name, this is actually the factory
365
   * method used to acquire instances of the class. It is named
366
   * this way for the sake of a readable interface, ie
367
   * ORM::for_table('table_name')->find_one()-> etc. As such,
368
   * this will normally be the first method called in a chain.
369
   *
370
   * @param string $table_name
371
   * @param string $connection_name Which connection to use
372
   *
373
   * @return static
374
   */
375 229
  public static function for_table($table_name, $connection_name = self::DEFAULT_CONNECTION)
376
  {
377 229
    static::_setup_db($connection_name);
378
379 229
    return new static($table_name, array(), $connection_name);
380
  }
381
382
  /**
383
   * Set up the database connection used by the class
384
   *
385
   * @param string $connection_name Which connection to use
386
   */
387 243
  protected static function _setup_db($connection_name = self::DEFAULT_CONNECTION)
388
  {
389
    if (
390 243
        !array_key_exists($connection_name, static::$_db)
391 243
        ||
392 243
        !is_object(static::$_db[$connection_name])
393 243
    ) {
394 5
      static::_setup_db_config($connection_name);
395
396 5
      $db = new \PDO(
397 5
          static::$_config[$connection_name]['connection_string'], static::$_config[$connection_name]['username'], static::$_config[$connection_name]['password'], static::$_config[$connection_name]['driver_options']
398 5
      );
399
400 5
      $db->setAttribute(\PDO::ATTR_ERRMODE, static::$_config[$connection_name]['error_mode']);
401 5
      static::set_db($db, $connection_name);
402 5
    }
403 243
  }
404
405
  /**
406
   * Ensures configuration (multiple connections) is at least set to default.
407
   *
408
   * @param string $connection_name Which connection to use
409
   */
410 244
  protected static function _setup_db_config($connection_name)
411
  {
412 244
    if (!array_key_exists($connection_name, static::$_config)) {
413 156
      static::$_config[$connection_name] = static::$_default_config;
414 156
    }
415 244
  }
416
417
  /**
418
   * Set the PDO object used by Idiorm to communicate with the database.
419
   * This is public in case the ORM should use a ready-instantiated
420
   * PDO object as its database connection. Accepts an optional string key
421
   * to identify the connection if multiple connections are used.
422
   *
423
   * @param \PDO   $db
424
   * @param string $connection_name Which connection to use
425
   */
426 244
  public static function set_db($db, $connection_name = self::DEFAULT_CONNECTION)
427
  {
428 244
    static::_setup_db_config($connection_name);
429 244
    static::$_db[$connection_name] = $db;
430
431 244
    if (null !== static::$_db[$connection_name]) {
432 244
      static::_setup_identifier_quote_character($connection_name);
433 244
      static::_setup_limit_clause_style($connection_name);
434 244
    }
435 244
  }
436
437
  /**
438
   * Delete all registered PDO objects in _db array.
439
   */
440 156
  public static function reset_db()
441
  {
442 156
    static::$_db = array();
443 156
  }
444
445
  /**
446
   * Detect and initialise the character used to quote identifiers
447
   * (table names, column names etc). If this has been specified
448
   * manually using ORM::configure('identifier_quote_character', 'some-char'),
449
   * this will do nothing.
450
   *
451
   * @param string $connection_name Which connection to use
452
   */
453 244
  protected static function _setup_identifier_quote_character($connection_name)
454
  {
455 244
    if (null === static::$_config[$connection_name]['identifier_quote_character']) {
456 156
      static::$_config[$connection_name]['identifier_quote_character'] = static::_detect_identifier_quote_character($connection_name);
457 156
    }
458 244
  }
459
460
  /**
461
   * Detect and initialise the limit clause style ("SELECT TOP 5" /
462
   * "... LIMIT 5"). If this has been specified manually using
463
   * ORM::configure('limit_clause_style', 'top'), this will do nothing.
464
   *
465
   * @param string $connection_name Which connection to use
466
   */
467 244
  public static function _setup_limit_clause_style($connection_name)
468
  {
469 244
    if (null === static::$_config[$connection_name]['limit_clause_style']) {
470 156
      static::$_config[$connection_name]['limit_clause_style'] = static::_detect_limit_clause_style($connection_name);
471 156
    }
472 244
  }
473
474
  /**
475
   * Return the correct character used to quote identifiers (table
476
   * names, column names etc) by looking at the driver being used by PDO.
477
   *
478
   * @param string $connection_name Which connection to use
479
   *
480
   * @return string
481
   */
482 156
  protected static function _detect_identifier_quote_character($connection_name)
483
  {
484 156
    switch (static::get_db($connection_name)->getAttribute(\PDO::ATTR_DRIVER_NAME)) {
485 156
      case 'pgsql':
486 156
      case 'sqlsrv':
487 156
      case 'dblib':
488 156
      case 'mssql':
489 156
      case 'sybase':
490 156
      case 'firebird':
491 2
        return '"';
492 154
      case 'mysql':
493 154
      case 'sqlite':
494 154
      case 'sqlite2':
495 154
      default:
496 154
        return '`';
497
    }
498
  }
499
500
  /**
501
   * Returns a constant after determining the appropriate limit clause
502
   * style
503
   *
504
   * @param string $connection_name Which connection to use
505
   *
506
   * @return string Limit clause style keyword/constant
507
   */
508 156
  protected static function _detect_limit_clause_style($connection_name)
509
  {
510 156
    switch (static::get_db($connection_name)->getAttribute(\PDO::ATTR_DRIVER_NAME)) {
511 156
      case 'sqlsrv':
512 156
      case 'dblib':
513 156
      case 'mssql':
514 2
        return self::LIMIT_STYLE_TOP_N;
515 154
      default:
516 154
        return self::LIMIT_STYLE_LIMIT;
517 154
    }
518
  }
519
520
  /**
521
   * Returns the PDO instance used by the the ORM to communicate with
522
   * the database. This can be called if any low-level DB access is
523
   * required outside the class. If multiple connections are used,
524
   * accepts an optional key name for the connection.
525
   *
526
   * @param string $connection_name Which connection to use
527
   *
528
   * @return \PDO
529
   */
530 243
  public static function get_db($connection_name = self::DEFAULT_CONNECTION)
531
  {
532 243
    static::_setup_db($connection_name); // required in case this is called before Idiorm is instantiated
533 243
    return static::$_db[$connection_name];
534
  }
535
536
  /**
537
   * Executes a raw query as a wrapper for PDOStatement::execute.
538
   * Useful for queries that can't be accomplished through Idiorm,
539
   * particularly those using engine-specific features.
540
   *
541
   * @example raw_execute('SELECT `name`, AVG(`order`) FROM `customer` GROUP BY `name` HAVING AVG(`order`) > 10')
542
   * @example raw_execute('INSERT OR REPLACE INTO `widget` (`id`, `name`) SELECT `id`, `name` FROM `other_table`')
543
   *
544
   * @param string $query           The raw SQL query
545
   * @param array  $parameters      Optional bound parameters
546
   * @param string $connection_name Which connection to use
547
   *
548
   * @return bool Success
549
   */
550 1
  public static function raw_execute($query, $parameters = array(), $connection_name = self::DEFAULT_CONNECTION)
551
  {
552 1
    static::_setup_db($connection_name);
553
554 1
    return static::_execute($query, $parameters, $connection_name);
555
  }
556
557
  /**
558
   * Returns the PDOStatement instance last used by any connection wrapped by the ORM.
559
   * Useful for access to PDOStatement::rowCount() or error information
560
   *
561
   * @return \PDOStatement
562
   */
563 207
  public static function get_last_statement()
564
  {
565 207
    return static::$_last_statement;
566
  }
567
568
  /**
569
   * Internal helper method for executing statements. Logs queries, and
570
   * stores statement object in ::_last_statement, accessible publicly
571
   * through ::get_last_statement()
572
   *
573
   * @param string $query
574
   * @param array  $parameters      An array of parameters to be bound in to the query
575
   * @param string $connection_name Which connection to use
576
   *
577
   * @return bool Response of PDOStatement::execute()
578
   *
579
   * @throws \Exception
580
   */
581 219
  protected static function _execute($query, $parameters = array(), $connection_name = self::DEFAULT_CONNECTION)
582
  {
583 219
    $time = microtime(true);
584
585
    try {
586 219
      $statement = static::get_db($connection_name)->prepare($query);
587 219
      static::$_last_statement = $statement;
588
589 219
      foreach ($parameters as $key => &$param) {
590
591 118
        if (null === $param) {
592
          $type = \PDO::PARAM_NULL;
593 118
        } elseif (is_bool($param)) {
594
          $type = \PDO::PARAM_BOOL;
595 118
        } elseif (is_int($param)) {
596 84
          $type = \PDO::PARAM_INT;
597 84
        } else {
598 80
          $type = \PDO::PARAM_STR;
599
        }
600
601 118
        $statement->bindParam(is_int($key) ? ++$key : $key, $param, $type);
602 219
      }
603 219
      unset($param);
604
605 219
      $q = $statement->execute();
606 219
      static::_log_query($query, $parameters, $connection_name, microtime(true) - $time);
607 219
    } catch (\Exception $ex) {
608
      static::_log_query($query, $parameters, $connection_name, microtime(true) - $time);
609
      throw $ex;
610
    }
611
612 219
    return $q;
613
  }
614
615
  /**
616
   * Add a query to the internal query log. Only works if the
617
   * 'logging' config option is set to true.
618
   *
619
   * This works by manually binding the parameters to the query - the
620
   * query isn't executed like this (PDO normally passes the query and
621
   * parameters to the database which takes care of the binding) but
622
   * doing it this way makes the logged queries more readable.
623
   *
624
   * @param string $query
625
   * @param array  $parameters      An array of parameters to be bound in to the query
626
   * @param string $connection_name Which connection to use
627
   * @param float  $query_time      Query time
628
   *
629
   * @return bool
630
   */
631 219
  protected static function _log_query($query, $parameters, $connection_name, $query_time)
632
  {
633
    // If logging is not enabled, do nothing
634 219
    if (!static::$_config[$connection_name]['logging']) {
635
      return false;
636
    }
637
638 219
    if (!isset(static::$_query_log[$connection_name])) {
639 2
      static::$_query_log[$connection_name] = array();
640 2
    }
641
642
    // Strip out any non-integer indexes from the parameters
643 219
    foreach ($parameters as $key => $value) {
644 118
      if (!is_int($key)) {
645
        unset($parameters[$key]);
646
      }
647 219
    }
648
649 219
    if (count($parameters) > 0) {
650
      // Escape the parameters
651 118
      $parameters = array_map(array(static::get_db($connection_name), 'quote'), $parameters);
652
653
      // Avoid %format collision for vsprintf
654 118
      $query = str_replace('%', '%%', $query);
655
656
      // Replace placeholders in the query for vsprintf
657 118
      if (false !== strpos($query, "'") || false !== strpos($query, '"')) {
658 4
        $query = IdiormString::str_replace_outside_quotes('?', '%s', $query);
659 4
      } else {
660 116
        $query = str_replace('?', '%s', $query);
661
      }
662
663
      // Replace the question marks in the query with the parameters
664 118
      $bound_query = vsprintf($query, $parameters);
665 118
    } else {
666 104
      $bound_query = $query;
667
    }
668
669 219
    static::$_last_query = $bound_query;
670 219
    static::$_query_log[$connection_name][] = $bound_query;
671
672
673 219
    if (is_callable(static::$_config[$connection_name]['logger'])) {
674
      $logger = static::$_config[$connection_name]['logger'];
675
      $logger($bound_query, $query_time);
676
    }
677
678 219
    return true;
679
  }
680
681
  /**
682
   * Get the last query executed. Only works if the
683
   * 'logging' config option is set to true. Otherwise
684
   * this will return null. Returns last query from all connections if
685
   * no connection_name is specified
686
   *
687
   * @param null|string $connection_name Which connection to use
688
   *
689
   * @return string
690
   */
691 208
  public static function get_last_query($connection_name = null)
692
  {
693 208
    if ($connection_name === null) {
694 207
      return static::$_last_query;
695
    }
696 3
    if (!isset(static::$_query_log[$connection_name])) {
697
      return '';
698
    }
699
700 3
    return end(static::$_query_log[$connection_name]);
701
  }
702
703
  /**
704
   * Get an array containing all the queries run on a
705
   * specified connection up to now.
706
   * Only works if the 'logging' config option is
707
   * set to true. Otherwise, returned array will be empty.
708
   *
709
   * @param string $connection_name Which connection to use
710
   *
711
   * @return array
712
   */
713
  public static function get_query_log($connection_name = self::DEFAULT_CONNECTION)
714
  {
715
    if (isset(static::$_query_log[$connection_name])) {
716
      return static::$_query_log[$connection_name];
717
    }
718
719
    return array();
720
  }
721
722
  /**
723
   * Get a list of the available connection names
724
   *
725
   * @return array
726
   */
727
  public static function get_connection_names()
728
  {
729
    return array_keys(static::$_db);
730
  }
731
732
  // ------------------------ //
733
  // --- INSTANCE METHODS --- //
734
  // ------------------------ //
735
736
  /**
737
   * "Private" constructor; shouldn't be called directly.
738
   * Use the ORM::for_table factory method instead.
739
   *
740
   * @param string $table_name
741
   * @param array  $data
742
   * @param string $connection_name
743
   */
744 229
  protected function __construct($table_name, $data = array(), $connection_name = self::DEFAULT_CONNECTION)
745
  {
746 229
    $this->_table_name = $table_name;
747 229
    $this->_data = $data;
748
749 229
    $this->_connection_name = $connection_name;
750 229
    static::_setup_db_config($connection_name);
751 229
  }
752
753
  /**
754
   * Create a new, empty instance of the class. Used
755
   * to add a new row to your database. May optionally
756
   * be passed an associative array of data to populate
757
   * the instance. If so, all fields will be flagged as
758
   * dirty so all will be saved to the database when
759
   * save() is called.
760
   *
761
   * @param null|array $data
762
   *
763
   * @return $this
764
   */
765 20
  public function create($data = null)
766
  {
767 20
    $this->_is_new = true;
768 20
    if (null !== $data) {
769 7
      return $this->hydrate($data)->force_all_dirty();
770
    }
771
772 15
    return $this;
773
  }
774
775
  /**
776
   * Specify the ID column to use for this instance or array of instances only.
777
   * This overrides the id_column and id_column_overrides settings.
778
   *
779
   * This is mostly useful for libraries built on top of Idiorm, and will
780
   * not normally be used in manually built queries. If you don't know why
781
   * you would want to use this, you should probably just ignore it.
782
   *
783
   * @param string $id_column
784
   *
785
   * @return $this
786
   */
787 208
  public function use_id_column($id_column)
788
  {
789 208
    $this->_instance_id_column = $id_column;
790
791 208
    return $this;
792
  }
793
794
  /**
795
   * Create an ORM instance from the given row (an associative
796
   * array of data fetched from the database)
797
   *
798
   * @param array $row
799
   *
800
   * @return $this
801
   */
802 204
  protected function _create_instance_from_row($row)
803
  {
804 204
    $instance = static::for_table($this->_table_name, $this->_connection_name);
805 204
    $instance->use_id_column($this->_instance_id_column);
806 204
    $instance->hydrate($row);
807
808 204
    return $instance;
809
  }
810
811
  /**
812
   * Tell the ORM that you are expecting a single result
813
   * back from your query, and execute it. Will return
814
   * a single instance of the ORM class, or false if no
815
   * rows were returned.
816
   * As a shortcut, you may supply an ID as a parameter
817
   * to this method. This will perform a primary key
818
   * lookup on the table.
819
   *
820
   * @param mixed $id
821
   *
822
   * @return false|ORM false on error
823
   */
824 87
  public function find_one($id = null)
825
  {
826 87
    if (null !== $id) {
827 31
      $this->where_id_is($id);
828 31
    }
829 87
    $this->limit(1);
830 87
    $rows = $this->_run();
831
832 87
    if (empty($rows)) {
833 2
      return false;
834
    }
835
836 86
    return $this->_create_instance_from_row($rows[0]);
837
  }
838
839
  /**
840
   * Tell the ORM that you are expecting multiple results
841
   * from your query, and execute it. Will return an array
842
   * of instances of the ORM class, or an empty array if
843
   * no rows were returned.
844
   *
845
   * @return array|IdiormResultSet
846
   */
847 117
  public function find_many()
848
  {
849 117
    if (static::$_config[$this->_connection_name]['return_result_sets']) {
850 1
      return $this->find_result_set();
851
    }
852
853 117
    return $this->_find_many();
854
  }
855
856
  /**
857
   * Tell the ORM that you are expecting multiple results
858
   * from your query, and execute it. Will return an array
859
   * of instances of the ORM class, or an empty array if
860
   * no rows were returned.
861
   *
862
   * @return array
863
   */
864 119
  protected function _find_many()
865
  {
866 119
    $rows = $this->_run();
867
868 119
    return array_map(array($this, '_create_instance_from_row'), $rows);
869
  }
870
871
  /**
872
   * Tell the ORM that you are expecting multiple results
873
   * from your query, and execute it. Will return a result set object
874
   * containing instances of the ORM class.
875
   *
876
   * @return IdiormResultSet
877
   */
878 3
  public function find_result_set()
879
  {
880 3
    return new IdiormResultSet($this->_find_many());
881
  }
882
883
  /**
884
   * Tell the ORM that you are expecting multiple results
885
   * from your query, and execute it. Will return an array,
886
   * or an empty array if no rows were returned.
887
   *
888
   * @return array
889
   */
890 1
  public function find_array()
891
  {
892 1
    return $this->_run();
893
  }
894
895
  /**
896
   * Tell the ORM that you wish to execute a COUNT query.
897
   * Will return an integer representing the number of
898
   * rows returned.
899
   *
900
   * @param string $column
901
   *
902
   * @return int
903
   */
904 4
  public function count($column = '*')
905
  {
906 4
    return $this->_call_aggregate_db_function(__FUNCTION__, $column);
907
  }
908
909
  /**
910
   * Tell the ORM that you wish to execute a MAX query.
911
   * Will return the max value of the choosen column.
912
   *
913
   * @param string $column
914
   *
915
   * @return int
916
   */
917 2
  public function max($column)
918
  {
919 2
    return $this->_call_aggregate_db_function(__FUNCTION__, $column);
920
  }
921
922
  /**
923
   * Tell the ORM that you wish to execute a MIN query.
924
   * Will return the min value of the choosen column.
925
   *
926
   * @param string $column
927
   *
928
   * @return int|float
929
   */
930 2
  public function min($column)
931
  {
932 2
    return $this->_call_aggregate_db_function(__FUNCTION__, $column);
933
  }
934
935
  /**
936
   * Tell the ORM that you wish to execute a AVG query.
937
   * Will return the average value of the choosen column.
938
   *
939
   * @param string $column
940
   *
941
   * @return float
942
   */
943 2
  public function avg($column)
944
  {
945 2
    return $this->_call_aggregate_db_function(__FUNCTION__, $column);
946
  }
947
948
  /**
949
   * Tell the ORM that you wish to execute a SUM query.
950
   * Will return the sum of the choosen column.
951
   *
952
   * @param string $column
953
   *
954
   * @return int|float
955
   */
956 2
  public function sum($column)
957
  {
958 2
    return $this->_call_aggregate_db_function(__FUNCTION__, $column);
959
  }
960
961
  /**
962
   * Execute an aggregate query on the current connection.
963
   *
964
   * @param string $sql_function The aggregate function to call eg. MIN, COUNT, etc
965
   * @param string $column       The column to execute the aggregate query against
966
   *
967
   * @return float|int|mixed
968
   */
969 12
  protected function _call_aggregate_db_function($sql_function, $column)
970
  {
971 12
    $alias = strtolower($sql_function);
972 12
    $sql_function = strtoupper($sql_function);
973 12
    if ('*' != $column) {
974 8
      $column = $this->_quote_identifier($column);
975 8
    }
976 12
    $result_columns = $this->_result_columns;
977 12
    $this->_result_columns = array();
978 12
    $this->select_expr("$sql_function($column)", $alias);
979 12
    $result = $this->find_one();
980 12
    $this->_result_columns = $result_columns;
981
982 12
    $return_value = 0;
983 12
    if ($result !== false && isset($result->$alias)) {
984
      if (!is_numeric($result->$alias)) {
985
        $return_value = $result->$alias;
986
      } elseif ((int)$result->$alias == (float)$result->$alias) {
987
        $return_value = (int)$result->$alias;
988
      } else {
989
        $return_value = (float)$result->$alias;
990
      }
991
    }
992
993 12
    return $return_value;
994
  }
995
996
  /**
997
   * This method can be called to hydrate (populate) this
998
   * instance of the class from an associative array of data.
999
   * This will usually be called only from inside the class,
1000
   * but it's public in case you need to call it directly.
1001
   *
1002
   * @param array $data
1003
   *
1004
   * @return $this
1005
   */
1006 209
  public function hydrate($data = array())
1007
  {
1008 209
    $this->_data = $data;
1009
1010 209
    return $this;
1011
  }
1012
1013
  /**
1014
   * Force the ORM to flag all the fields in the $data array
1015
   * as "dirty" and therefore update them when save() is called.
1016
   *
1017
   * @return $this
1018
   */
1019 7
  public function force_all_dirty()
1020
  {
1021 7
    $this->_dirty_fields = $this->_data;
1022
1023 7
    return $this;
1024
  }
1025
1026
  /**
1027
   * Perform a raw query. The query can contain placeholders in
1028
   * either named or question mark style. If placeholders are
1029
   * used, the parameters should be an array of values which will
1030
   * be bound to the placeholders in the query. If this method
1031
   * is called, all other query building methods will be ignored.
1032
   *
1033
   * @param string $query
1034
   * @param array  $parameters
1035
   *
1036
   * @return $this
1037
   */
1038 4
  public function raw_query($query, $parameters = array())
1039
  {
1040 4
    $this->_is_raw_query = true;
1041 4
    $this->_raw_query = $query;
1042 4
    $this->_raw_parameters = $parameters;
1043
1044 4
    return $this;
1045
  }
1046
1047
  /**
1048
   * Add an alias for the main table to be used in SELECT queries
1049
   *
1050
   * @param string $alias
1051
   *
1052
   * @return $this
1053
   */
1054 4
  public function table_alias($alias)
1055
  {
1056 4
    $this->_table_alias = $alias;
1057
1058 4
    return $this;
1059
  }
1060
1061
  /**
1062
   * Internal method to add an unquoted expression to the set
1063
   * of columns returned by the SELECT query. The second optional
1064
   * argument is the alias to return the expression as.
1065
   *
1066
   * @param string $expr
1067
   * @param mixed  $alias
1068
   *
1069
   * @return $this
1070
   */
1071 34
  protected function _add_result_column($expr, $alias = null)
1072
  {
1073 34
    if (null !== $alias) {
1074 20
      $expr .= ' AS ' . $this->_quote_identifier($alias);
1075 20
    }
1076
1077 34
    if ($this->_using_default_result_columns) {
1078 34
      $this->_result_columns = array($expr);
1079 34
      $this->_using_default_result_columns = false;
1080 34
    } else {
1081 8
      $this->_result_columns[] = $expr;
1082
    }
1083
1084 34
    return $this;
1085
  }
1086
1087
  /**
1088
   * Counts the number of columns that belong to the primary
1089
   * key and their value is null.
1090
   *
1091
   * @return int
1092
   */
1093 12
  public function count_null_id_columns()
1094
  {
1095 12
    if (is_array($this->_get_id_column_name())) {
1096 3
      return count(array_filter($this->id(), 'is_null'));
1097
    } else {
1098 9
      return (null === $this->id() ? 1 : 0);
1099
    }
1100
  }
1101
1102
  /**
1103
   * Add a column to the list of columns returned by the SELECT
1104
   * query. This defaults to '*'. The second optional argument is
1105
   * the alias to return the column as.
1106
   *
1107
   * @param string $column
1108
   * @param mixed  $alias
1109
   *
1110
   * @return $this
1111
   */
1112 20
  public function select($column, $alias = null)
1113
  {
1114 20
    $column = $this->_quote_identifier($column);
1115
1116 20
    return $this->_add_result_column($column, $alias);
1117
  }
1118
1119
  /**
1120
   * Add an unquoted expression to the list of columns returned
1121
   * by the SELECT query. The second optional argument is
1122
   * the alias to return the column as.
1123
   *
1124
   * @param string $expr
1125
   * @param mixed  $alias
1126
   *
1127
   * @return $this
1128
   */
1129 16
  public function select_expr($expr, $alias = null)
1130
  {
1131 16
    return $this->_add_result_column($expr, $alias);
1132
  }
1133
1134
  /**
1135
   * Add columns to the list of columns returned by the SELECT
1136
   * query. This defaults to '*'. Many columns can be supplied
1137
   * as either an array or as a list of parameters to the method.
1138
   *
1139
   * Note that the alias must not be numeric - if you want a
1140
   * numeric alias then prepend it with some alpha chars. eg. a1
1141
   *
1142
   * @example select_many(array('alias' => 'column', 'column2', 'alias2' => 'column3'), 'column4', 'column5');
1143
   * @example select_many('column', 'column2', 'column3');
1144
   * @example select_many(array('column', 'column2', 'column3'), 'column4', 'column5');
1145
   *
1146
   * @return $this
1147
   */
1148 2 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...
1149
  {
1150 2
    $columns = func_get_args();
1151 2
    if (!empty($columns)) {
1152 2
      $columns = $this->_normalise_select_many_columns($columns);
1153 2
      foreach ($columns as $alias => $column) {
1154 2
        if (is_numeric($alias)) {
1155 2
          $alias = null;
1156 2
        }
1157 2
        $this->select($column, $alias);
1158 2
      }
1159 2
    }
1160
1161 2
    return $this;
1162
  }
1163
1164
  /**
1165
   * Add an unquoted expression to the list of columns returned
1166
   * by the SELECT query. Many columns can be supplied as either
1167
   * an array or as a list of parameters to the method.
1168
   *
1169
   * Note that the alias must not be numeric - if you want a
1170
   * numeric alias then prepend it with some alpha chars. eg. a1
1171
   *
1172
   * @example select_many_expr(array('alias' => 'column', 'column2', 'alias2' => 'column3'), 'column4', 'column5')
1173
   * @example select_many_expr('column', 'column2', 'column3')
1174
   * @example select_many_expr(array('column', 'column2', 'column3'), 'column4', 'column5')
1175
   *
1176
   * @return $this
1177
   */
1178 2 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...
1179
  {
1180 2
    $columns = func_get_args();
1181 2
    if (!empty($columns)) {
1182 2
      $columns = $this->_normalise_select_many_columns($columns);
1183 2
      foreach ($columns as $alias => $column) {
1184 2
        if (is_numeric($alias)) {
1185 2
          $alias = null;
1186 2
        }
1187 2
        $this->select_expr($column, $alias);
1188 2
      }
1189 2
    }
1190
1191 2
    return $this;
1192
  }
1193
1194
  /**
1195
   * Take a column specification for the select many methods and convert it
1196
   * into a normalised array of columns and aliases.
1197
   *
1198
   * It is designed to turn the following styles into a normalised array:
1199
   *
1200
   * array(array('alias' => 'column', 'column2', 'alias2' => 'column3'), 'column4', 'column5'))
1201
   *
1202
   * @param array $columns
1203
   *
1204
   * @return array
1205
   */
1206 4
  protected function _normalise_select_many_columns($columns)
1207
  {
1208 4
    $return = array();
1209 4
    foreach ($columns as $column) {
1210 4
      if (is_array($column)) {
1211 4
        foreach ($column as $key => $value) {
1212 4
          if (!is_numeric($key)) {
1213 4
            $return[$key] = $value;
1214 4
          } else {
1215
            $return[] = $value;
1216
          }
1217 4
        }
1218 4
      } else {
1219 4
        $return[] = $column;
1220
      }
1221 4
    }
1222
1223 4
    return $return;
1224
  }
1225
1226
  /**
1227
   * Add a DISTINCT keyword before the list of columns in the SELECT query
1228
   *
1229
   * @return $this
1230
   */
1231 2
  public function distinct()
1232
  {
1233 2
    $this->_distinct = true;
1234
1235 2
    return $this;
1236
  }
1237
1238
  /**
1239
   * Internal method to add a JOIN source to the query.
1240
   *
1241
   * The join_operator should be one of INNER, LEFT OUTER, CROSS etc - this
1242
   * will be prepended to JOIN.
1243
   *
1244
   * The table should be the name of the table to join to.
1245
   *
1246
   * The constraint may be either a string or an array with three elements. If it
1247
   * is a string, it will be compiled into the query as-is, with no escaping. The
1248
   * recommended way to supply the constraint is as an array with three elements:
1249
   *
1250
   * first_column, operator, second_column
1251
   *
1252
   * Example: array('user.id', '=', 'profile.user_id')
1253
   *
1254
   * will compile to
1255
   *
1256
   * ON `user`.`id` = `profile`.`user_id`
1257
   *
1258
   * The final (optional) argument specifies an alias for the joined table.
1259
   *
1260
   * @param string      $join_operator
1261
   * @param string      $table
1262
   * @param string      $constraint
1263
   * @param string|null $table_alias
1264
   *
1265
   * @return $this
1266
   */
1267 20
  protected function _add_join_source($join_operator, $table, $constraint, $table_alias = null)
1268
  {
1269 20
    $join_operator = trim("{$join_operator} JOIN");
1270
1271 20
    $table = $this->_quote_identifier($table);
1272
1273
    // Add table alias if present
1274 20
    if (null !== $table_alias) {
1275 4
      $table_alias = $this->_quote_identifier($table_alias);
1276 4
      $table .= " {$table_alias}";
1277 4
    }
1278
1279
    // Build the constraint
1280 20 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...
1281 18
      list($first_column, $operator, $second_column) = $constraint;
1282 18
      $first_column = $this->_quote_identifier($first_column);
1283 18
      $second_column = $this->_quote_identifier($second_column);
1284 18
      $constraint = "{$first_column} {$operator} {$second_column}";
1285 18
    }
1286
1287 20
    $this->_join_sources[] = "{$join_operator} {$table} ON {$constraint}";
1288
1289 20
    return $this;
1290
  }
1291
1292
  /**
1293
   * Add a RAW JOIN source to the query
1294
   *
1295
   * @param string $table
1296
   * @param string $constraint
1297
   * @param string $table_alias
1298
   * @param array  $parameters
1299
   *
1300
   * @return $this
1301
   */
1302 6
  public function raw_join($table, $constraint, $table_alias, $parameters = array())
1303
  {
1304
    // Add table alias if present
1305 6
    if (null !== $table_alias) {
1306 6
      $table_alias = $this->_quote_identifier($table_alias);
1307 6
      $table .= " {$table_alias}";
1308 6
    }
1309
1310 6
    $this->_values = array_merge($this->_values, $parameters);
1311
1312
    // Build the constraint
1313 6 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...
1314 6
      list($first_column, $operator, $second_column) = $constraint;
1315 6
      $first_column = $this->_quote_identifier($first_column);
1316 6
      $second_column = $this->_quote_identifier($second_column);
1317 6
      $constraint = "{$first_column} {$operator} {$second_column}";
1318 6
    }
1319
1320 6
    $this->_join_sources[] = "{$table} ON {$constraint}";
1321
1322 6
    return $this;
1323
  }
1324
1325
  /**
1326
   * Add a simple JOIN source to the query
1327
   *
1328
   * @param string      $table
1329
   * @param string      $constraint
1330
   * @param string|null $table_alias
1331
   *
1332
   * @return $this
1333
   */
1334 12
  public function join($table, $constraint, $table_alias = null)
1335
  {
1336 12
    return $this->_add_join_source('', $table, $constraint, $table_alias);
1337
  }
1338
1339
  /**
1340
   * Add an INNER JOIN souce to the query
1341
   */
1342
  /**
1343
   * @param string      $table
1344
   * @param string      $constraint
1345
   * @param null|string $table_alias
1346
   *
1347
   * @return $this
1348
   */
1349 2
  public function inner_join($table, $constraint, $table_alias = null)
1350
  {
1351 2
    return $this->_add_join_source('INNER', $table, $constraint, $table_alias);
1352
  }
1353
1354
  /**
1355
   * Add an LEFT JOIN source to the query
1356
   *
1357
   * @param string $table
1358
   * @param        $constraint
1359
   * @param null   $table_alias
1360
   *
1361
   * @return $this
1362
   */
1363
  public function left_join($table, $constraint, $table_alias = null)
1364
  {
1365
    return $this->_add_join_source('LEFT', $table, $constraint, $table_alias);
1366
  }
1367
1368
  /**
1369
   * Add an RIGHT JOIN source to the query
1370
   *
1371
   * @param string $table
1372
   * @param        $constraint
1373
   * @param null   $table_alias
1374
   *
1375
   * @return $this
1376
   */
1377
  public function right_join($table, $constraint, $table_alias = null)
1378
  {
1379
    return $this->_add_join_source('RIGHT', $table, $constraint, $table_alias);
1380
  }
1381
1382
  /**
1383
   * Add a LEFT OUTER JOIN souce to the query
1384
   *
1385
   * @param string      $table
1386
   * @param string      $constraint
1387
   * @param null|string $table_alias
1388
   *
1389
   * @return $this
1390
   */
1391 2
  public function left_outer_join($table, $constraint, $table_alias = null)
1392
  {
1393 2
    return $this->_add_join_source('LEFT OUTER', $table, $constraint, $table_alias);
1394
  }
1395
1396
  /**
1397
   * Add an RIGHT OUTER JOIN souce to the query
1398
   *
1399
   * @param string      $table
1400
   * @param string      $constraint
1401
   * @param null|string $table_alias
1402
   *
1403
   * @return $this
1404
   */
1405 2
  public function right_outer_join($table, $constraint, $table_alias = null)
1406
  {
1407 2
    return $this->_add_join_source('RIGHT OUTER', $table, $constraint, $table_alias);
1408
  }
1409
1410
  /**
1411
   * Add an FULL OUTER JOIN souce to the query
1412
   */
1413
  /**
1414
   * @param string      $table
1415
   * @param string      $constraint
1416
   * @param null|string $table_alias
1417
   *
1418
   * @return $this
1419
   */
1420 2
  public function full_outer_join($table, $constraint, $table_alias = null)
1421
  {
1422 2
    return $this->_add_join_source('FULL OUTER', $table, $constraint, $table_alias);
1423
  }
1424
1425
  /**
1426
   * Internal method to add a HAVING condition to the query
1427
   */
1428
  /**
1429
   * @param string $fragment
1430
   * @param array  $values
1431
   *
1432
   * @return $this
1433
   */
1434 10
  protected function _add_having($fragment, $values = array())
1435
  {
1436 10
    return $this->_add_condition('having', $fragment, $values);
1437
  }
1438
1439
  /**
1440
   * Internal method to add a HAVING condition to the query
1441
   */
1442
  /**
1443
   * @param string|array $column_name
1444
   * @param string       $separator
1445
   * @param mixed        $value
1446
   *
1447
   * @return $this
1448
   */
1449 14
  protected function _add_simple_having($column_name, $separator, $value)
1450
  {
1451 14
    return $this->_add_simple_condition('having', $column_name, $separator, $value);
1452
  }
1453
1454
  /**
1455
   * Internal method to add a HAVING clause with multiple values (like IN and NOT IN)
1456
   */
1457
  /**
1458
   * @param string|array $column_name
1459
   * @param string       $separator
1460
   * @param mixed        $values
1461
   *
1462
   * @return $this
1463
   */
1464 4 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...
1465
  {
1466 4
    if (!is_array($column_name)) {
1467 4
      $data = array($column_name => $values);
1468 4
    } else {
1469
      $data = $column_name;
1470
    }
1471
1472 4
    $result = $this;
1473 4
    foreach ($data as $key => $val) {
1474 4
      $column = $result->_quote_identifier($key);
1475 4
      $placeholders = $result->_create_placeholders($val);
1476 4
      $result = $result->_add_having("{$column} {$separator} ({$placeholders})", $val);
1477 4
    }
1478
1479 4
    return $result;
1480
  }
1481
1482
  /**
1483
   * Internal method to add a HAVING clause with no parameters(like IS NULL and IS NOT NULL)
1484
   *
1485
   * @param string|array $column_name
1486
   * @param string       $operator
1487
   *
1488
   * @return $this
1489
   */
1490 4 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...
1491
  {
1492 4
    if (is_array($column_name)) {
1493
      $conditions = $column_name;
1494
    } else {
1495 4
      $conditions = array($column_name);
1496
    }
1497
1498 4
    $result = $this;
1499 4
    foreach ($conditions as $column) {
1500 4
      $column = $this->_quote_identifier($column);
1501 4
      $result = $result->_add_having("{$column} {$operator}");
1502 4
    }
1503
1504 4
    return $result;
1505
  }
1506
1507
  /**
1508
   * Internal method to add a WHERE condition to the query
1509
   *
1510
   * @param string $fragment
1511
   * @param array  $values
1512
   *
1513
   * @return $this
1514
   */
1515 29
  protected function _add_where($fragment, $values = array())
1516
  {
1517 29
    return $this->_add_condition('where', $fragment, $values);
1518
  }
1519
1520
  /**
1521
   * Internal method to add a WHERE condition to the query
1522
   *
1523
   * @param string|array $column_name
1524
   * @param string       $separator
1525
   * @param mixed        $value
1526
   *
1527
   * @return $this
1528
   */
1529 65
  protected function _add_simple_where($column_name, $separator, $value)
1530
  {
1531 65
    return $this->_add_simple_condition('where', $column_name, $separator, $value);
1532
  }
1533
1534
  /**
1535
   * Add a WHERE clause with multiple values (like IN and NOT IN)
1536
   *
1537
   * @param string|array $column_name
1538
   * @param string       $separator
1539
   * @param mixed        $values
1540
   *
1541
   * @return $this
1542
   */
1543 5 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...
1544
  {
1545 5
    if (!is_array($column_name)) {
1546 5
      $data = array($column_name => $values);
1547 5
    } else {
1548
      $data = $column_name;
1549
    }
1550
1551 5
    $result = $this;
1552 5
    foreach ($data as $key => $val) {
1553 5
      $column = $result->_quote_identifier($key);
1554 5
      $placeholders = $result->_create_placeholders($val);
1555 5
      $result = $result->_add_where("{$column} {$separator} ({$placeholders})", $val);
1556 5
    }
1557
1558 5
    return $result;
1559
  }
1560
1561
  /**
1562
   * Add a WHERE clause with no parameters(like IS NULL and IS NOT NULL)
1563
   *
1564
   * @param string|array $column_name
1565
   * @param string       $operator
1566
   *
1567
   * @return $this
1568
   */
1569 4 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...
1570
  {
1571 4
    if (is_array($column_name)) {
1572
      $conditions = $column_name;
1573
    } else {
1574 4
      $conditions = array($column_name);
1575
    }
1576
1577 4
    $result = $this;
1578 4
    foreach ($conditions as $column) {
1579 4
      $column = $this->_quote_identifier($column);
1580 4
      $result = $result->_add_where("{$column} {$operator}");
1581 4
    }
1582
1583 4
    return $result;
1584
  }
1585
1586
  /**
1587
   * Internal method to add a HAVING or WHERE condition to the query
1588
   *
1589
   * @param string $type
1590
   * @param string $fragment
1591
   * @param mixed  $values
1592
   *
1593
   * @return $this
1594
   */
1595 119
  protected function _add_condition($type, $fragment, $values = array())
1596
  {
1597 119
    $conditions_class_property_name = "_{$type}_conditions";
1598
1599 119
    if (!is_array($values)) {
1600 84
      $values = array($values);
1601 84
    }
1602
1603 119
    array_push(
1604 119
        $this->$conditions_class_property_name,
1605
        array(
1606 119
            static::CONDITION_FRAGMENT => $fragment,
1607 119
            static::CONDITION_VALUES   => $values,
1608
        )
1609 119
    );
1610
1611 119
    return $this;
1612
  }
1613
1614
  /**
1615
   * Helper method to compile a simple COLUMN SEPARATOR VALUE
1616
   * style HAVING or WHERE condition into a string and value ready to
1617
   * be passed to the _add_condition method. Avoids duplication
1618
   * of the call to _quote_identifier
1619
   *
1620
   * If column_name is an associative array, it will add a condition for each column
1621
   *
1622
   * @param string       $type
1623
   * @param string|array $column_name
1624
   * @param string       $separator
1625
   * @param string|int   $value
1626
   *
1627
   * @return $this
1628
   */
1629 79
  protected function _add_simple_condition($type, $column_name, $separator, $value)
1630
  {
1631 79
    if (is_array($column_name)) {
1632 1
      $multiple = $column_name;
1633 1
    } else {
1634 78
      $multiple = array($column_name => $value);
1635
    }
1636
1637 79
    $result = $this;
1638
1639 79
    foreach ($multiple as $key => $val) {
1640
      // Add the table name in case of ambiguous columns
1641 79 View Code Duplication
      if (count($result->_join_sources) > 0 && strpos($key, '.') === false) {
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...
1642
1643 4
        $table = $result->_table_name;
1644 4
        if (null !== $result->_table_alias) {
1645 2
          $table = $result->_table_alias;
1646 2
        }
1647
1648 4
        $key = "{$table}.{$key}";
1649 4
      }
1650 79
      $key = $result->_quote_identifier($key);
1651 79
      $result = $result->_add_condition($type, "{$key} {$separator} ?", $val);
1652 79
    }
1653
1654 79
    return $result;
1655
  }
1656
1657
  /**
1658
   * Add a WHERE clause width DATE
1659
   *
1660
   * If column_name is an associative array, it will add a condition for each column
1661
   *
1662
   * @param string       $type
1663
   * @param string|array $column_name
1664
   * @param string       $separator
1665
   * @param string|int   $value
1666
   *
1667
   * @return $this
1668
   */
1669 5
  protected function _add_date_condition($type, $column_name, $separator, $value)
1670
  {
1671 5
    $multiple = is_array($column_name) ? $column_name : array($column_name => $value);
1672 5
    $result = $this;
1673
1674 5
    foreach ($multiple as $key => $val) {
1675
      // Add the table name in case of ambiguous columns
1676 5 View Code Duplication
      if (count($result->_join_sources) > 0 && strpos($key, '.') === false) {
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...
1677
1678
        $table = $result->_table_name;
1679
        if (!is_null($result->_table_alias)) {
1680
          $table = $result->_table_alias;
1681
        }
1682
1683
        $key = "{$table}.{$key}";
1684
      }
1685 5
      $key = 'DATE(' . $result->_quote_identifier($key) . ')';
1686 5
      $result = $result->_add_condition($type, "{$key} {$separator} ?", $val);
1687 5
    }
1688
1689 5
    return $result;
1690
  }
1691
1692
  /**
1693
   * Return a string containing the given number of question marks,
1694
   * separated by commas. Eg "?, ?, ?"
1695
   *
1696
   * @param mixed $fields
1697
   *
1698
   * @return string
1699
   */
1700 21
  protected function _create_placeholders($fields)
1701
  {
1702 21
    if (!empty($fields)) {
1703 21
      $db_fields = array();
1704 21
      foreach ($fields as $key => $value) {
1705
        // Process expression fields directly into the query
1706 21
        if (array_key_exists($key, $this->_expr_fields)) {
1707 2
          $db_fields[] = $value;
1708 2
        } else {
1709 21
          $db_fields[] = '?';
1710
        }
1711 21
      }
1712
1713 21
      return implode(', ', $db_fields);
1714
    } else {
1715 1
      return '';
1716
    }
1717
  }
1718
1719
  /**
1720
   * Helper method that filters a column/value array returning only those
1721
   * columns that belong to a compound primary key.
1722
   *
1723
   * If the key contains a column that does not exist in the given array,
1724
   * a null value will be returned for it.
1725
   *
1726
   * @param mixed $value
1727
   *
1728
   * @return array
1729
   */
1730 2
  protected function _get_compound_id_column_values($value)
1731
  {
1732 2
    $filtered = array();
1733 2
    foreach ($this->_get_id_column_name() as $key) {
1734 2
      $filtered[$key] = isset($value[$key]) ? $value[$key] : null;
1735 2
    }
1736
1737 2
    return $filtered;
1738
  }
1739
1740
  /**
1741
   * Helper method that filters an array containing compound column/value
1742
   * arrays.
1743
   *
1744
   * @param array $values
1745
   *
1746
   * @return array
1747
   */
1748 1
  protected function _get_compound_id_column_values_array($values)
1749
  {
1750 1
    $filtered = array();
1751 1
    foreach ($values as $value) {
1752 1
      $filtered[] = $this->_get_compound_id_column_values($value);
1753 1
    }
1754
1755 1
    return $filtered;
1756
  }
1757
1758
  /**
1759
   * Add a WHERE column = value clause to your query. Each time
1760
   * this is called in the chain, an additional WHERE will be
1761
   * added, and these will be ANDed together when the final query
1762
   * is built.
1763
   *
1764
   * If you use an array in $column_name, a new clause will be
1765
   * added for each element. In this case, $value is ignored.
1766
   *
1767
   * @param string $column_name
1768
   * @param mixed  $value
1769
   *
1770
   * @return $this
1771
   */
1772 51
  public function where($column_name, $value = null)
1773
  {
1774 51
    return $this->where_equal($column_name, $value);
1775
  }
1776
1777
  /**
1778
   * More explicitly named version of for the where() method.
1779
   * Can be used if preferred.
1780
   *
1781
   * @param string $column_name
1782
   * @param mixed  $value
1783
   *
1784
   * @return $this
1785
   */
1786 55
  public function where_equal($column_name, $value = null)
1787
  {
1788 55
    return $this->_add_simple_where($column_name, '=', $value);
1789
  }
1790
1791
  /**
1792
   * Add a WHERE column != value clause to your query.
1793
   *
1794
   * @param string $column_name
1795
   * @param mixed  $value
1796
   *
1797
   * @return $this
1798
   */
1799 2
  public function where_not_equal($column_name, $value = null)
1800
  {
1801 2
    return $this->_add_simple_where($column_name, '!=', $value);
1802
  }
1803
1804
  /**
1805
   * Special method to query the table by its primary key
1806
   *
1807
   * If primary key is compound, only the columns that
1808
   * belong to they key will be used for the query
1809
   *
1810
   * @param mixed $id
1811
   *
1812
   * @return $this
1813
   */
1814 33 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...
1815
  {
1816 33
    if (is_array($this->_get_id_column_name())) {
1817 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...
1818
    } else {
1819 32
      return $this->where($this->_get_id_column_name(), $id);
1820
    }
1821
  }
1822
1823
  /**
1824
   * Allows adding a WHERE clause that matches any of the conditions
1825
   * specified in the array. Each element in the associative array will
1826
   * be a different condition, where the key will be the column name.
1827
   *
1828
   * By default, an equal operator will be used against all columns, but
1829
   * it can be overridden for any or every column using the second parameter.
1830
   *
1831
   * Each condition will be ORed together when added to the final query.
1832
   *
1833
   * @param array  $values
1834
   * @param string $operator
1835
   *
1836
   * @return $this
1837
   */
1838 4
  public function where_any_is($values, $operator = '=')
1839
  {
1840 4
    $data = array();
1841 4
    $query = array('((');
1842 4
    $first = true;
1843 4
    foreach ($values as $item) {
1844 4
      if ($first) {
1845 4
        $first = false;
1846 4
      } else {
1847 4
        $query[] = ') OR (';
1848
      }
1849 4
      $firstsub = true;
1850
1851 4
      foreach ($item as $key => $subItem) {
1852
1853 4
        if (is_string($operator)) {
1854 3
          $op = $operator;
1855 3
        } else {
1856 1
          $op = (isset($operator[$key]) ? $operator[$key] : '=');
1857
        }
1858
1859 4
        if ($firstsub) {
1860 4
          $firstsub = false;
1861 4
        } else {
1862 4
          $query[] = 'AND';
1863
        }
1864
1865 4
        $query[] = $this->_quote_identifier($key);
1866 4
        $data[] = $subItem;
1867 4
        $query[] = $op . ' ?';
1868 4
      }
1869 4
    }
1870 4
    $query[] = '))';
1871
1872 4
    return $this->where_raw(implode($query, ' '), $data);
1873
  }
1874
1875
  /**
1876
   * Similar to where_id_is() but allowing multiple primary keys.
1877
   *
1878
   * If primary key is compound, only the columns that
1879
   * belong to they key will be used for the query
1880
   *
1881
   * @param mixed $ids
1882
   *
1883
   * @return $this
1884
   */
1885 2
  public function where_id_in($ids)
1886
  {
1887 2
    if (is_array($this->_get_id_column_name())) {
1888 1
      return $this->where_any_is($this->_get_compound_id_column_values_array($ids));
1889
    } else {
1890 1
      return $this->where_in($this->_get_id_column_name(), $ids);
1891
    }
1892
  }
1893
1894
  /**
1895
   * Add a WHERE ... LIKE clause to your query.
1896
   *
1897
   * @param string $column_name
1898
   * @param mixed  $value
1899
   *
1900
   * @return $this
1901
   */
1902 2
  public function where_like($column_name, $value = null)
1903
  {
1904 2
    return $this->_add_simple_where($column_name, 'LIKE', $value);
1905
  }
1906
1907
  /**
1908
   * Add where WHERE ... NOT LIKE clause to your query.
1909
   *
1910
   * @param string $column_name
1911
   * @param mixed  $value
1912
   *
1913
   * @return $this
1914
   */
1915 2
  public function where_not_like($column_name, $value = null)
1916
  {
1917 2
    return $this->_add_simple_where($column_name, 'NOT LIKE', $value);
1918
  }
1919
1920
  /**
1921
   * Add a WHERE ... > clause to your query
1922
   *
1923
   * @param string $column_name
1924
   * @param mixed  $value
1925
   *
1926
   * @return $this
1927
   */
1928 2
  public function where_gt($column_name, $value = null)
1929
  {
1930 2
    return $this->_add_simple_where($column_name, '>', $value);
1931
  }
1932
1933
  /**
1934
   * Add a WHERE ... < clause to your query
1935
   *
1936
   * @param string $column_name
1937
   * @param mixed  $value
1938
   *
1939
   * @return $this
1940
   */
1941 2
  public function where_lt($column_name, $value = null)
1942
  {
1943 2
    return $this->_add_simple_where($column_name, '<', $value);
1944
  }
1945
1946
  /**
1947
   * Add a WHERE ... >= clause to your query
1948
   *
1949
   * @param string $column_name
1950
   * @param mixed  $value
1951
   *
1952
   * @return $this
1953
   */
1954 2
  public function where_gte($column_name, $value = null)
1955
  {
1956 2
    return $this->_add_simple_where($column_name, '>=', $value);
1957
  }
1958
1959
  /**
1960
   * Add a WHERE ... <= clause to your query
1961
   *
1962
   * @param string $column_name
1963
   * @param mixed  $value
1964
   *
1965
   * @return $this
1966
   */
1967 2
  public function where_lte($column_name, $value = null)
1968
  {
1969 2
    return $this->_add_simple_where($column_name, '<=', $value);
1970
  }
1971
1972
  /**
1973
   * Add a WHERE ... IN clause to your query
1974
   *
1975
   * @param string $column_name
1976
   * @param mixed  $values
1977
   *
1978
   * @return $this
1979
   */
1980 3
  public function where_in($column_name, $values)
1981
  {
1982 3
    return $this->_add_where_placeholder($column_name, 'IN', $values);
1983
  }
1984
1985
  /**
1986
   * Add a WHERE ... NOT IN clause to your query
1987
   *
1988
   * @param string $column_name
1989
   * @param mixed  $values
1990
   *
1991
   * @return $this
1992
   */
1993 2
  public function where_not_in($column_name, $values)
1994
  {
1995 2
    return $this->_add_where_placeholder($column_name, 'NOT IN', $values);
1996
  }
1997
1998
  /**
1999
   * @param string $column_name
2000
   * @param mixed  $value
2001
   *
2002
   * @return $this
2003
   */
2004 1
  public function where_date_eq($column_name, $value = null)
2005
  {
2006 1
    return $this->_add_date_condition('where', $column_name, '=', $value);
2007
  }
2008
2009
2010
  /**
2011
   * @param string $column_name
2012
   * @param mixed  $value
2013
   *
2014
   * @return $this
2015
   */
2016 1
  public function where_date_lt($column_name, $value = null)
2017
  {
2018 1
    return $this->_add_date_condition('where', $column_name, '<', $value);
2019
  }
2020
2021
  /**
2022
   * @param string $column_name
2023
   * @param mixed  $value
2024
   *
2025
   * @return $this
2026
   */
2027 1
  public function where_date_gt($column_name, $value = null)
2028
  {
2029 1
    return $this->_add_date_condition('where', $column_name, '>', $value);
2030
  }
2031
2032
  /**
2033
   * @param string $column_name
2034
   * @param mixed  $value
2035
   *
2036
   * @return $this
2037
   */
2038 1
  public function where_date_le($column_name, $value = null)
2039
  {
2040 1
    return $this->_add_date_condition('where', $column_name, '<=', $value);
2041
  }
2042
2043
  /**
2044
   * @param string $column_name
2045
   * @param mixed  $value
2046
   *
2047
   * @return $this
2048
   */
2049 1
  public function where_date_ge($column_name, $value = null)
2050
  {
2051 1
    return $this->_add_date_condition('where', $column_name, '>=', $value);
2052
  }
2053
2054
  /**
2055
   * Add a WHERE column IS NULL clause to your query.
2056
   *
2057
   * @param string $column_name
2058
   *
2059
   * @return $this
2060
   */
2061 2
  public function where_null($column_name)
2062
  {
2063 2
    return $this->_add_where_no_value($column_name, 'IS NULL');
2064
  }
2065
2066
  /**
2067
   * Add a WHERE column IS NOT NULL clause to your query
2068
   *
2069
   * @param string $column_name
2070
   *
2071
   * @return $this
2072
   */
2073 2
  public function where_not_null($column_name)
2074
  {
2075 2
    return $this->_add_where_no_value($column_name, 'IS NOT NULL');
2076
  }
2077
2078
  /**
2079
   * Add a raw WHERE clause to the query. The clause should
2080
   * contain question mark placeholders, which will be bound
2081
   * to the parameters supplied in the second argument.
2082
   *
2083
   * @param string $clause
2084
   * @param array  $parameters
2085
   *
2086
   * @return $this
2087
   */
2088 20
  public function where_raw($clause, $parameters = array())
2089
  {
2090 20
    return $this->_add_where($clause, $parameters);
2091
  }
2092
2093
  /**
2094
   * Add a LIMIT to the query
2095
   *
2096
   * @param int $limit
2097
   *
2098
   * @return $this
2099
   */
2100 96
  public function limit($limit)
2101
  {
2102 96
    $this->_limit = $limit;
2103
2104 96
    return $this;
2105
  }
2106
2107
  /**
2108
   * Add an OFFSET to the query
2109
   *
2110
   * @param $offset
2111
   *
2112
   * @return $this
2113
   */
2114 4
  public function offset($offset)
2115
  {
2116 4
    $this->_offset = $offset;
2117
2118 4
    return $this;
2119
  }
2120
2121
  /**
2122
   * Add an ORDER BY clause to the query
2123
   *
2124
   * @param string $column_name
2125
   * @param string $ordering
2126
   *
2127
   * @return $this
2128
   */
2129 12
  protected function _add_order_by($column_name, $ordering)
2130
  {
2131 12
    $column_name = $this->_quote_identifier($column_name);
2132 12
    $this->_order_by[] = "{$column_name} {$ordering}";
2133
2134 12
    return $this;
2135
  }
2136
2137
  /**
2138
   * Add an ORDER BY column DESC clause
2139
   *
2140
   * @param string $column_name
2141
   *
2142
   * @return $this
2143
   */
2144 8
  public function order_by_desc($column_name)
2145
  {
2146 8
    return $this->_add_order_by($column_name, 'DESC');
2147
  }
2148
2149
  /**
2150
   * Add an ORDER BY RAND clause
2151
   */
2152 1
  public function order_by_rand()
2153
  {
2154 1
    $this->_order_by[] = 'RAND()';
2155
2156 1
    return $this;
2157
  }
2158
2159
  /**
2160
   * Add an ORDER BY column ASC clause
2161
   *
2162
   * @param string $column_name
2163
   *
2164
   * @return $this
2165
   */
2166 6
  public function order_by_asc($column_name)
2167
  {
2168 6
    return $this->_add_order_by($column_name, 'ASC');
2169
  }
2170
2171
  /**
2172
   * Add an unquoted expression as an ORDER BY clause
2173
   *
2174
   * @param $clause
2175
   *
2176
   * @return $this
2177
   */
2178 2
  public function order_by_expr($clause)
2179
  {
2180 2
    $this->_order_by[] = $clause;
2181
2182 2
    return $this;
2183
  }
2184
2185
  /**
2186
   * Reset the ORDER BY clause
2187
   */
2188 1
  public function reset_order_by()
2189
  {
2190 1
    $this->_order_by = array();
2191
2192 1
    return $this;
2193
  }
2194
2195
  /**
2196
   * Add a column to the list of columns to GROUP BY
2197
   *
2198
   * @param string $column_name
2199
   *
2200
   * @return $this
2201
   */
2202 28
  public function group_by($column_name)
2203
  {
2204 28
    $column_name = $this->_quote_identifier($column_name);
2205 28
    $this->_group_by[] = $column_name;
2206
2207 28
    return $this;
2208
  }
2209
2210
  /**
2211
   * Add an unquoted expression to the list of columns to GROUP BY
2212
   *
2213
   * @param string $expr
2214
   *
2215
   * @return $this
2216
   */
2217 2
  public function group_by_expr($expr)
2218
  {
2219 2
    $this->_group_by[] = $expr;
2220
2221 2
    return $this;
2222
  }
2223
2224
  /**
2225
   * Add a HAVING column = value clause to your query. Each time
2226
   * this is called in the chain, an additional HAVING will be
2227
   * added, and these will be ANDed together when the final query
2228
   * is built.
2229
   *
2230
   * If you use an array in $column_name, a new clause will be
2231
   * added for each element. In this case, $value is ignored.
2232
   *
2233
   * @param string $column_name
2234
   * @param null   $value
2235
   *
2236
   * @return $this
2237
   */
2238 4
  public function having($column_name, $value = null)
2239
  {
2240 4
    return $this->having_equal($column_name, $value);
2241
  }
2242
2243
  /**
2244
   * More explicitly named version of for the having() method.
2245
   * Can be used if preferred.
2246
   *
2247
   * @param string $column_name
2248
   * @param null   $value
2249
   *
2250
   * @return $this
2251
   */
2252 4
  public function having_equal($column_name, $value = null)
2253
  {
2254 4
    return $this->_add_simple_having($column_name, '=', $value);
2255
  }
2256
2257
  /**
2258
   * Add a HAVING column != value clause to your query.
2259
   *
2260
   * @param string $column_name
2261
   * @param null   $value
2262
   *
2263
   * @return $this
2264
   */
2265 2
  public function having_not_equal($column_name, $value = null)
2266
  {
2267 2
    return $this->_add_simple_having($column_name, '!=', $value);
2268
  }
2269
2270
  /**
2271
   * Special method to query the table by its primary key.
2272
   *
2273
   * If primary key is compound, only the columns that
2274
   * belong to they key will be used for the query
2275
   *
2276
   * @param $id
2277
   *
2278
   * @return $this
2279
   */
2280 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...
2281
  {
2282
    if (is_array($this->_get_id_column_name())) {
2283
      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...
2284
    } else {
2285
      return $this->having($this->_get_id_column_name(), $id);
2286
    }
2287
  }
2288
2289
  /**
2290
   * Add a HAVING ... LIKE clause to your query.
2291
   *
2292
   * @param string $column_name
2293
   * @param mixed  $value
2294
   *
2295
   * @return $this
2296
   */
2297 2
  public function having_like($column_name, $value = null)
2298
  {
2299 2
    return $this->_add_simple_having($column_name, 'LIKE', $value);
2300
  }
2301
2302
  /**
2303
   * Add where HAVING ... NOT LIKE clause to your query.
2304
   *
2305
   * @param string $column_name
2306
   * @param mixed  $value
2307
   *
2308
   * @return $this
2309
   */
2310 2
  public function having_not_like($column_name, $value = null)
2311
  {
2312 2
    return $this->_add_simple_having($column_name, 'NOT LIKE', $value);
2313
  }
2314
2315
  /**
2316
   * Add a HAVING ... > clause to your query
2317
   *
2318
   * @param string $column_name
2319
   * @param mixed  $value
2320
   *
2321
   * @return $this
2322
   */
2323 2
  public function having_gt($column_name, $value = null)
2324
  {
2325 2
    return $this->_add_simple_having($column_name, '>', $value);
2326
  }
2327
2328
  /**
2329
   * Add a HAVING ... < clause to your query
2330
   *
2331
   * @param string $column_name
2332
   * @param mixed  $value
2333
   *
2334
   * @return $this
2335
   */
2336 2
  public function having_lt($column_name, $value = null)
2337
  {
2338 2
    return $this->_add_simple_having($column_name, '<', $value);
2339
  }
2340
2341
  /**
2342
   * Add a HAVING ... >= clause to your query
2343
   *
2344
   * @param string $column_name
2345
   * @param mixed  $value
2346
   *
2347
   * @return $this
2348
   */
2349 2
  public function having_gte($column_name, $value = null)
2350
  {
2351 2
    return $this->_add_simple_having($column_name, '>=', $value);
2352
  }
2353
2354
  /**
2355
   * Add a HAVING ... <= clause to your query
2356
   *
2357
   * @param string $column_name
2358
   * @param mixed  $value
2359
   *
2360
   * @return $this
2361
   */
2362 2
  public function having_lte($column_name, $value = null)
2363
  {
2364 2
    return $this->_add_simple_having($column_name, '<=', $value);
2365
  }
2366
2367
  /**
2368
   * Add a HAVING ... IN clause to your query
2369
   *
2370
   * @param string $column_name
2371
   * @param mixed  $values
2372
   *
2373
   * @return $this
2374
   */
2375 2
  public function having_in($column_name, $values = null)
2376
  {
2377 2
    return $this->_add_having_placeholder($column_name, 'IN', $values);
2378
  }
2379
2380
  /**
2381
   * Add a HAVING ... NOT IN clause to your query
2382
   *
2383
   * @param string $column_name
2384
   * @param mixed  $values
2385
   *
2386
   * @return $this
2387
   */
2388 2
  public function having_not_in($column_name, $values = null)
2389
  {
2390 2
    return $this->_add_having_placeholder($column_name, 'NOT IN', $values);
2391
  }
2392
2393
  /**
2394
   * Add a HAVING column IS NULL clause to your query
2395
   *
2396
   * @param string $column_name
2397
   *
2398
   * @return $this
2399
   */
2400 2
  public function having_null($column_name)
2401
  {
2402 2
    return $this->_add_having_no_value($column_name, 'IS NULL');
2403
  }
2404
2405
  /**
2406
   * Add a HAVING column IS NOT NULL clause to your query
2407
   *
2408
   * @param string $column_name
2409
   *
2410
   * @return $this
2411
   */
2412 2
  public function having_not_null($column_name)
2413
  {
2414 2
    return $this->_add_having_no_value($column_name, 'IS NOT NULL');
2415
  }
2416
2417
  /**
2418
   * Add a raw HAVING clause to the query. The clause should
2419
   * contain question mark placeholders, which will be bound
2420
   * to the parameters supplied in the second argument.
2421
   *
2422
   * @param string $clause
2423
   * @param array  $parameters
2424
   *
2425
   * @return $this
2426
   */
2427 2
  public function having_raw($clause, $parameters = array())
2428
  {
2429 2
    return $this->_add_having($clause, $parameters);
2430
  }
2431
2432
  /**
2433
   * Activate cache refreshing for current query
2434
   *
2435
   * @return $this
2436
   */
2437
  public function refreshCache()
2438
  {
2439
    $this->_refresh_cache = true;
2440
2441
    return $this;
2442
  }
2443
2444
  /**
2445
   * Disable caching for current query
2446
   *
2447
   * @return $this
2448
   */
2449
  public function noCaching()
2450
  {
2451
    $this->_no_caching = true;
2452
2453
    return $this;
2454
  }
2455
2456
  /**
2457
   * Build a SELECT statement based on the clauses that have
2458
   * been passed to this instance by chaining method calls.
2459
   */
2460 207
  protected function _build_select()
2461
  {
2462
    // If the query is raw, just set the $this->_values to be
2463
    // the raw query parameters and return the raw query
2464 207
    if ($this->_is_raw_query) {
2465 4
      $this->_values = $this->_raw_parameters;
2466
2467 4
      return $this->_raw_query;
2468
    }
2469
2470
    // Build and return the full SELECT statement by concatenating
2471
    // the results of calling each separate builder method.
2472 203
    return $this->_join_if_not_empty(
2473 203
        ' ',
2474
        array(
2475 203
            $this->_build_select_start(),
2476 203
            $this->_build_join(),
2477 203
            $this->_build_where(),
2478 203
            $this->_build_group_by(),
2479 203
            $this->_build_having(),
2480 203
            $this->_build_order_by(),
2481 203
            $this->_build_limit(),
2482 203
            $this->_build_offset(),
2483
        )
2484 203
    );
2485
  }
2486
2487
  /**
2488
   * Build the start of the SELECT statement
2489
   *
2490
   * @return string
2491
   */
2492 203
  protected function _build_select_start()
2493
  {
2494 203
    $fragment = 'SELECT ';
2495 203
    $result_columns = implode(', ', $this->_result_columns);
2496
2497
    if (
2498 203
        null !== $this->_limit
2499 203
        &&
2500 96
        static::$_config[$this->_connection_name]['limit_clause_style'] === self::LIMIT_STYLE_TOP_N
2501 203
    ) {
2502 2
      $fragment .= "TOP {$this->_limit} ";
2503 2
    }
2504
2505 203
    if ($this->_distinct) {
2506 2
      $result_columns = 'DISTINCT ' . $result_columns;
2507 2
    }
2508
2509 203
    $fragment .= "{$result_columns} FROM " . $this->_quote_identifier($this->_table_name);
2510
2511 203
    if (null !== $this->_table_alias) {
2512 4
      $fragment .= ' ' . $this->_quote_identifier($this->_table_alias);
2513 4
    }
2514
2515 203
    return $fragment;
2516
  }
2517
2518
  /**
2519
   * Build the JOIN sources
2520
   *
2521
   * @return string
2522
   */
2523 203
  protected function _build_join()
2524
  {
2525 203
    if (count($this->_join_sources) === 0) {
2526 177
      return '';
2527
    }
2528
2529 26
    return implode(' ', $this->_join_sources);
2530
  }
2531
2532
  /**
2533
   * Build the WHERE clause(s)
2534
   *
2535
   * @return string
2536
   */
2537 205
  protected function _build_where()
2538
  {
2539 205
    return $this->_build_conditions('where');
2540
  }
2541
2542
  /**
2543
   * Build the HAVING clause(s)
2544
   *
2545
   * @return string
2546
   */
2547 203
  protected function _build_having()
2548
  {
2549 203
    return $this->_build_conditions('having');
2550
  }
2551
2552
  /**
2553
   * Build GROUP BY
2554
   *
2555
   * @return string
2556
   */
2557 203
  protected function _build_group_by()
2558
  {
2559 203
    if (count($this->_group_by) === 0) {
2560 173
      return '';
2561
    }
2562
2563 30
    return 'GROUP BY ' . implode(', ', $this->_group_by);
2564
  }
2565
2566
  /**
2567
   * Build a WHERE or HAVING clause
2568
   *
2569
   * @param string $type
2570
   *
2571
   * @return string
2572
   */
2573 205
  protected function _build_conditions($type)
2574
  {
2575 205
    $conditions_class_property_name = "_{$type}_conditions";
2576
    // If there are no clauses, return empty string
2577 205
    if (count($this->$conditions_class_property_name) === 0) {
2578 203
      return '';
2579
    }
2580
2581 119
    $conditions = array();
2582 119
    foreach ($this->$conditions_class_property_name as $condition) {
2583 119
      $conditions[] = $condition[static::CONDITION_FRAGMENT];
2584
      /** @noinspection SlowArrayOperationsInLoopInspection */
2585 119
      $this->_values = array_merge($this->_values, $condition[static::CONDITION_VALUES]);
2586 119
    }
2587
2588 119
    return strtoupper($type) . ' ' . implode(' AND ', $conditions);
2589
  }
2590
2591
  /**
2592
   * Build ORDER BY
2593
   *
2594
   * @return string
2595
   */
2596 203
  protected function _build_order_by()
2597
  {
2598 203
    if (count($this->_order_by) === 0) {
2599 191
      return '';
2600
    }
2601
2602 14
    $db = static::get_db($this->_connection_name);
2603
2604 14
    return 'ORDER BY ' . trim($db->quote(implode(', ', $this->_order_by)), "'");
2605
  }
2606
2607
  /**
2608
   * Build LIMIT
2609
   *
2610
   * @return string
2611
   */
2612 203
  protected function _build_limit()
2613
  {
2614
    // init
2615 203
    $fragment = '';
2616
2617
    if (
2618 203
        null !== $this->_limit
2619 203
        &&
2620 96
        static::$_config[$this->_connection_name]['limit_clause_style'] == self::LIMIT_STYLE_LIMIT
2621 203
    ) {
2622 94
      if (static::get_db($this->_connection_name)->getAttribute(\PDO::ATTR_DRIVER_NAME) == 'firebird') {
2623
        $fragment = 'ROWS';
2624
      } else {
2625 94
        $fragment = 'LIMIT';
2626
      }
2627
2628 94
      $this->_limit = (int)$this->_limit;
2629
2630 94
      $fragment .= " {$this->_limit}";
2631 94
    }
2632
2633 203
    return $fragment;
2634
  }
2635
2636
  /**
2637
   * Build OFFSET
2638
   *
2639
   * @return string
2640
   */
2641 203
  protected function _build_offset()
2642
  {
2643 203
    if (null !== $this->_offset) {
2644 4
      $clause = 'OFFSET';
2645 4
      if (static::get_db($this->_connection_name)->getAttribute(\PDO::ATTR_DRIVER_NAME) == 'firebird') {
2646
        $clause = 'TO';
2647
      }
2648
2649 4
      $this->_offset = (int)$this->_offset;
2650
2651 4
      return "$clause " . $this->_offset;
2652
    }
2653
2654 199
    return '';
2655
  }
2656
2657
  /**
2658
   * Wrapper around PHP's join function which
2659
   * only adds the pieces if they are not empty.
2660
   *
2661
   * @param string $glue
2662
   * @param array  $pieces
2663
   *
2664
   * @return string
2665
   */
2666 205
  protected function _join_if_not_empty($glue, $pieces)
2667
  {
2668 205
    $filtered_pieces = array();
2669 205
    foreach ($pieces as $piece) {
2670 205
      if (is_string($piece)) {
2671 205
        $piece = trim($piece);
2672 205
      }
2673 205
      if (!empty($piece)) {
2674 205
        $filtered_pieces[] = $piece;
2675 205
      }
2676 205
    }
2677
2678 205
    return implode($glue, $filtered_pieces);
2679
  }
2680
2681
  /**
2682
   * Quote a string that is used as an identifier
2683
   * (table names, column names etc). This method can
2684
   * also deal with dot-separated identifiers eg table.column
2685
   *
2686
   * @param string $identifier
2687
   *
2688
   * @return string
2689
   */
2690 215
  protected function _quote_one_identifier($identifier)
2691
  {
2692 215
    $parts = explode('.', $identifier);
2693 215
    $parts = array_map(array($this, '_quote_identifier_part'), $parts);
2694
2695 215
    return implode('.', $parts);
2696
  }
2697
2698
  /**
2699
   * Quote a string that is used as an identifier
2700
   * (table names, column names etc) or an array containing
2701
   * multiple identifiers. This method can also deal with
2702
   * dot-separated identifiers eg table.column
2703
   *
2704
   * @param array|string $identifier
2705
   *
2706
   * @return string
2707
   */
2708 215
  protected function _quote_identifier($identifier)
2709
  {
2710 215
    if (is_array($identifier)) {
2711 1
      $result = array_map(array($this, '_quote_one_identifier'), $identifier);
2712
2713 1
      return implode(', ', $result);
2714
    } else {
2715 214
      return $this->_quote_one_identifier($identifier);
2716
    }
2717
  }
2718
2719
  /**
2720
   * This method performs the actual quoting of a single
2721
   * part of an identifier, using the identifier quote
2722
   * character specified in the config (or autodetected).
2723
   *
2724
   * @param string $part
2725
   *
2726
   * @return string
2727
   */
2728 215
  protected function _quote_identifier_part($part)
2729
  {
2730 215
    if ($part === '*') {
2731 2
      return $part;
2732
    }
2733
2734 215
    $quote_character = static::$_config[$this->_connection_name]['identifier_quote_character'];
2735
2736
    // double up any identifier quotes to escape them
2737 215
    return $quote_character . str_replace($quote_character, $quote_character . $quote_character, $part) . $quote_character;
2738
  }
2739
2740
  /**
2741
   * Create a cache key for the given query and parameters.
2742
   *
2743
   * @param string      $query
2744
   * @param array       $parameters
2745
   * @param null|string $table_name
2746
   * @param string      $connection_name
2747
   *
2748
   * @return mixed|string
2749
   */
2750 3
  protected static function _create_cache_key($query, $parameters, $table_name = null, $connection_name = self::DEFAULT_CONNECTION)
2751
  {
2752 View Code Duplication
    if (
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...
2753 3
        isset(static::$_config[$connection_name]['create_cache_key']) === true
2754 3
        &&
2755 1
        is_callable(static::$_config[$connection_name]['create_cache_key']) === true
2756 3
    ) {
2757 1
      return call_user_func_array(
2758 1
          static::$_config[$connection_name]['create_cache_key'],
2759
          array(
2760 1
              $query,
2761 1
              $parameters,
2762 1
              $table_name,
2763 1
              $connection_name,
2764
          )
2765 1
      );
2766
    }
2767 2
    $parameter_string = implode(',', $parameters);
2768 2
    $key = $query . ':' . $parameter_string;
2769
2770 2
    return sha1($key);
2771
  }
2772
2773
  /**
2774
   * Check the query cache for the given cache key. If a value
2775
   * is cached for the key, return the value. Otherwise, return false.
2776
   *
2777
   * @param string      $cache_key
2778
   * @param null|string $table_name
2779
   * @param string      $connection_name
2780
   *
2781
   * @return bool|mixed
2782
   */
2783 3
  protected static function _check_query_cache($cache_key, $table_name = null, $connection_name = self::DEFAULT_CONNECTION)
2784
  {
2785
    if (
2786 3
        isset(static::$_config[$connection_name]['check_query_cache']) === true
2787 3
        &&
2788 1
        is_callable(static::$_config[$connection_name]['check_query_cache']) === true
2789 3
    ) {
2790 1
      return call_user_func_array(
2791 1
          static::$_config[$connection_name]['check_query_cache'],
2792
          array(
2793 1
              $cache_key,
2794 1
              $table_name,
2795 1
              $connection_name,
2796
          )
2797 1
      );
2798 2
    } elseif (isset(static::$_query_cache[$connection_name][$cache_key])) {
2799 2
      return static::$_query_cache[$connection_name][$cache_key];
2800
    }
2801
2802 2
    return false;
2803
  }
2804
2805
  /**
2806
   * Clear the query cache
2807
   *
2808
   * @param null|string $table_name
2809
   * @param string      $connection_name
2810
   *
2811
   * @return bool|mixed
2812
   */
2813 1
  public static function clear_cache($table_name = null, $connection_name = self::DEFAULT_CONNECTION)
2814
  {
2815
    // init
2816 1
    static::$_query_cache = array();
2817
2818 View Code Duplication
    if (
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...
2819 1
        isset(static::$_config[$connection_name]['clear_cache']) === true
2820 1
        &&
2821 1
        is_callable(static::$_config[$connection_name]['clear_cache']) === true
2822 1
    ) {
2823 1
      return call_user_func_array(
2824 1
          static::$_config[$connection_name]['clear_cache'],
2825
          array(
2826 1
              $table_name,
2827 1
              $connection_name,
2828
          )
2829 1
      );
2830
    }
2831
2832
    return false;
2833
  }
2834
2835
  /**
2836
   * Add the given value to the query cache.
2837
   *
2838
   * @param string      $cache_key
2839
   * @param string      $value
2840
   * @param null|string $table_name
2841
   * @param string      $connection_name
2842
   *
2843
   * @return bool|mixed
2844
   */
2845 3
  protected static function _cache_query_result($cache_key, $value, $table_name = null, $connection_name = self::DEFAULT_CONNECTION)
2846
  {
2847
    if (
2848 3
        isset(static::$_config[$connection_name]['cache_query_result']) === true
2849 3
        &&
2850 1
        is_callable(static::$_config[$connection_name]['cache_query_result']) === true
2851 3
    ) {
2852
2853 1
      return call_user_func_array(
2854 1
          static::$_config[$connection_name]['cache_query_result'],
2855
          array(
2856 1
              $cache_key,
2857 1
              $value,
2858 1
              $table_name,
2859 1
              $connection_name,
2860
          )
2861 1
      );
2862
2863 2
    } elseif (!isset(static::$_query_cache[$connection_name])) {
2864 2
      static::$_query_cache[$connection_name] = array();
2865 2
    }
2866
2867 2
    static::$_query_cache[$connection_name][$cache_key] = $value;
2868
2869 2
    return true;
2870
  }
2871
2872
  /**
2873
   * Execute the SELECT query that has been built up by chaining methods
2874
   * on this class. Return an array of rows as associative arrays.
2875
   */
2876 207
  protected function _run()
2877
  {
2878
    // init
2879 207
    $cache_key = false;
2880
2881 207
    $query = $this->_build_select();
2882 207
    $caching_enabled = static::$_config[$this->_connection_name]['caching'];
2883
2884 207
    if ($caching_enabled && !$this->_no_caching) {
2885 3
      $cache_key = static::_create_cache_key($query, $this->_values, $this->_table_name, $this->_connection_name);
2886
2887 3
      if (!$this->_refresh_cache) {
2888 3
        $cached_result = static::_check_query_cache($cache_key, $this->_table_name, $this->_connection_name);
2889
2890 3
        if ($cached_result !== false) {
2891 3
          return $cached_result;
2892
        }
2893 3
      }
2894 3
    }
2895
2896 207
    static::_execute($query, $this->_values, $this->_connection_name);
2897 207
    $statement = static::get_last_statement();
2898
2899 207
    $rows = array();
2900
    /** @noinspection PhpAssignmentInConditionInspection */
2901 207
    while ($row = $statement->fetch(\PDO::FETCH_ASSOC)) {
2902 204
      $rows[] = $row;
2903 204
    }
2904
2905 207
    if ($cache_key) {
2906 3
      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...
2907 3
    }
2908
2909
    // reset Idiorm after executing the query
2910 207
    $this->_values = array();
2911 207
    $this->_result_columns = array('*');
2912 207
    $this->_using_default_result_columns = true;
2913
2914 207
    return $rows;
2915
  }
2916
2917
  /**
2918
   * Return the raw data wrapped by this ORM
2919
   * instance as an associative array. Column
2920
   * names may optionally be supplied as arguments,
2921
   * if so, only those keys will be returned.
2922
   */
2923 3
  public function as_array()
2924
  {
2925 3
    if (func_num_args() === 0) {
2926 3
      return $this->_data;
2927
    }
2928 2
    $args = func_get_args();
2929
2930 2
    return array_intersect_key($this->_data, array_flip($args));
2931
  }
2932
2933
  /**
2934
   * Return the raw data wrapped by this ORM
2935
   * instance as an json.
2936
   *
2937
   * @param int $options
2938
   *
2939
   * @return string
2940
   */
2941 1
  public function as_json($options = 0)
2942
  {
2943 1
    return json_encode($this->as_array(), $options);
2944
  }
2945
2946
  /**
2947
   * Return the value of a property of this object (database row)
2948
   * or null if not present.
2949
   *
2950
   * If a column-names array is passed, it will return a associative array
2951
   * with the value of each column or null if it is not present.
2952
   *
2953
   * @param mixed $key
2954
   *
2955
   * @return mixed
2956
   */
2957 35
  public function get($key)
2958
  {
2959 35
    if (is_array($key)) {
2960 4
      $result = array();
2961 4
      foreach ($key as $column) {
2962 4
        $result[$column] = isset($this->_data[$column]) ? $this->_data[$column] : null;
2963 4
      }
2964
2965 4
      return $result;
2966
    } else {
2967 31
      return isset($this->_data[$key]) ? $this->_data[$key] : null;
2968
    }
2969
  }
2970
2971
  /**
2972
   * Return the name of the column in the database table which contains
2973
   * the primary key ID of the row.
2974
   */
2975 50
  protected function _get_id_column_name()
2976
  {
2977 50
    if (null !== $this->_instance_id_column) {
2978 13
      return $this->_instance_id_column;
2979
    }
2980
2981 37
    if (isset(static::$_config[$this->_connection_name]['id_column_overrides'][$this->_table_name])) {
2982 2
      return static::$_config[$this->_connection_name]['id_column_overrides'][$this->_table_name];
2983
    }
2984
2985 35
    return static::$_config[$this->_connection_name]['id_column'];
2986
  }
2987
2988
  /**
2989
   * Get the primary key ID of this object.
2990
   *
2991
   * @param bool $disallow_null
2992
   *
2993
   * @return mixed
2994
   *
2995
   * @throws \Exception
2996
   */
2997 33
  public function id($disallow_null = false)
2998
  {
2999 33
    $id = $this->get($this->_get_id_column_name());
3000
3001 33
    if ($disallow_null) {
3002 23
      if (is_array($id)) {
3003 3
        foreach ($id as $id_part) {
3004 3
          if ($id_part === null) {
3005 1
            throw new \Exception('Primary key ID contains null value(s)');
3006
          }
3007 3
        }
3008 22
      } elseif ($id === null) {
3009 3
        throw new \Exception('Primary key ID missing from row or is null');
3010
      }
3011 19
    }
3012
3013 29
    return $id;
3014
  }
3015
3016
  /**
3017
   * Set a property to a particular value on this object.
3018
   * To set multiple properties at once, pass an associative array
3019
   * as the first parameter and leave out the second parameter.
3020
   * Flags the properties as 'dirty' so they will be saved to the
3021
   * database when save() is called.
3022
   *
3023
   * @param mixed $key
3024
   * @param mixed $value
3025
   *
3026
   * @return $this
3027
   */
3028 27
  public function set($key, $value = null)
3029
  {
3030 27
    return $this->_set_orm_property($key, $value);
3031
  }
3032
3033
  /**
3034
   * Set a property to a particular value on this object.
3035
   * To set multiple properties at once, pass an associative array
3036
   * as the first parameter and leave out the second parameter.
3037
   * Flags the properties as 'dirty' so they will be saved to the
3038
   * database when save() is called.
3039
   *
3040
   * @param string|array $key
3041
   * @param string|null  $value
3042
   *
3043
   * @return $this
3044
   */
3045 10
  public function set_expr($key, $value = null)
3046
  {
3047 10
    return $this->_set_orm_property($key, $value, true);
3048
  }
3049
3050
  /**
3051
   * Set a property on the ORM object.
3052
   *
3053
   * @param  string|array $key
3054
   * @param string|null   $value
3055
   * @param bool          $expr
3056
   *
3057
   * @return $this
3058
   */
3059 29
  protected function _set_orm_property($key, $value = null, $expr = false)
3060
  {
3061 29
    if (!is_array($key)) {
3062 25
      $key = array($key => $value);
3063 25
    }
3064
3065
    /** @noinspection SuspiciousLoopInspection */
3066 29
    foreach ($key as $field => $value) {
3067 29
      $this->_data[$field] = $value;
3068 29
      $this->_dirty_fields[$field] = $value;
3069 29
      if (false === $expr && isset($this->_expr_fields[$field])) {
3070 2
        unset($this->_expr_fields[$field]);
3071 29
      } elseif (true === $expr) {
3072 10
        $this->_expr_fields[$field] = true;
3073 10
      }
3074 29
    }
3075
3076 29
    return $this;
3077
  }
3078
3079
  /**
3080
   * Check whether the given field has been changed since this
3081
   * object was saved.
3082
   *
3083
   * @param string $key
3084
   *
3085
   * @return bool
3086
   */
3087 1
  public function is_dirty($key)
3088
  {
3089 1
    return array_key_exists($key, $this->_dirty_fields);
3090
  }
3091
3092
  /**
3093
   * Check whether the model was the result of a call to create() or not
3094
   *
3095
   * @return bool
3096
   */
3097 2
  public function is_new()
3098
  {
3099 2
    return $this->_is_new;
3100
  }
3101
3102
  /**
3103
   * Save any fields which have been modified on this object
3104
   * to the database.
3105
   *
3106
   * @return bool
3107
   *
3108
   * @throws \Exception
3109
   */
3110 28
  public function save()
3111
  {
3112
    // remove any expression fields as they are already baked into the query
3113 28
    $values = array_values(array_diff_key($this->_dirty_fields, $this->_expr_fields));
3114
3115 28
    if (!$this->_is_new) {
3116
3117
      // UPDATE
3118
3119
      // If there are no dirty values, do nothing
3120
      if (
3121 17
          empty($values)
3122 17
          &&
3123 2
          0 === count($this->_expr_fields)
3124 17
      ) {
3125
        return true;
3126
      }
3127
3128 17
      $query = $this->_build_update();
3129 17
      $id = $this->id(true);
3130
3131 16
      if (is_array($id)) {
3132 1
        $values = array_merge($values, array_values($id));
3133 1
      } else {
3134 15
        $values[] = $id;
3135
      }
3136
3137 16
    } else {
3138
3139
      // INSERT
3140 12
      $query = $this->_build_insert();
3141
    }
3142
3143 27
    $success = static::_execute($query, $values, $this->_connection_name);
3144 27
    $caching_auto_clear_enabled = static::$_config[$this->_connection_name]['caching_auto_clear'];
3145
3146 27
    if ($caching_auto_clear_enabled) {
3147 1
      static::clear_cache($this->_table_name, $this->_connection_name);
3148 1
    }
3149
3150
    // If we've just inserted a new record, set the ID of this object
3151 27
    if ($success && $this->_is_new) {
3152
3153 12
      $this->_is_new = false;
3154 12
      if ($this->count_null_id_columns() != 0) {
3155 9
        $db = static::get_db($this->_connection_name);
3156
3157 9
        if ($db->getAttribute(\PDO::ATTR_DRIVER_NAME) == 'pgsql') {
3158
3159
          // it may return several columns if a compound primary
3160
          // key is used
3161
          $row = static::get_last_statement()->fetch(\PDO::FETCH_ASSOC);
3162
          foreach ($row as $key => $value) {
3163
            $this->_data[$key] = $value;
3164
          }
3165
3166
        } else {
3167 9
          $column = $this->_get_id_column_name();
3168
          // if the primary key is compound, assign the last inserted id
3169
          // to the first column
3170 9
          if (is_array($column)) {
3171
            $column = reset($column);
3172
          }
3173 9
          $this->_data[$column] = $db->lastInsertId();
3174
        }
3175 9
      }
3176 12
    }
3177
3178 27
    $this->_dirty_fields = $this->_expr_fields = array();
3179
3180 27
    return $success;
3181
  }
3182
3183
  /**
3184
   * Add a WHERE clause for every column that belongs to the primary key
3185
   *
3186
   * @param array $query warning: this is a reference
3187
   */
3188 21
  public function _add_id_column_conditions(&$query)
3189
  {
3190 21
    $query[] = 'WHERE';
3191
3192 21
    if (is_array($this->_get_id_column_name())) {
3193 2
      $keys = $this->_get_id_column_name();
3194 2
    } else {
3195 19
      $keys = array($this->_get_id_column_name());
3196
    }
3197
3198 21
    $first = true;
3199 21
    foreach ($keys as $key) {
3200
3201 21
      if ($first) {
3202 21
        $first = false;
3203 21
      } else {
3204 2
        $query[] = 'AND';
3205
      }
3206
3207 21
      $query[] = $this->_quote_identifier($key);
3208 21
      $query[] = '= ?';
3209 21
    }
3210 21
  }
3211
3212
  /**
3213
   * Build an UPDATE query
3214
   *
3215
   * @return string
3216
   */
3217 17
  protected function _build_update()
3218
  {
3219 17
    $query = array();
3220 17
    $query[] = "UPDATE {$this->_quote_identifier($this->_table_name)} SET";
3221
3222 17
    $field_list = array();
3223 17
    foreach ($this->_dirty_fields as $key => $value) {
3224
3225 17
      if (!array_key_exists($key, $this->_expr_fields)) {
3226 15
        $value = '?';
3227 15
      }
3228
3229 17
      $field_list[] = "{$this->_quote_identifier($key)} = $value";
3230 17
    }
3231
3232 17
    $query[] = implode(', ', $field_list);
3233 17
    $this->_add_id_column_conditions($query);
3234
3235 17
    return implode(' ', $query);
3236
  }
3237
3238
  /**
3239
   * Build an INSERT query
3240
   *
3241
   * @return string
3242
   */
3243 12
  protected function _build_insert()
3244
  {
3245 12
    $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...
3246 12
    $query[] = $this->_quote_identifier($this->_table_name);
3247 12
    $field_list = array_map(array($this, '_quote_identifier'), array_keys($this->_dirty_fields));
3248 12
    $query[] = '(' . implode(', ', $field_list) . ')';
3249 12
    $query[] = 'VALUES';
3250
3251 12
    $placeholders = $this->_create_placeholders($this->_dirty_fields);
3252 12
    $query[] = "({$placeholders})";
3253
3254 12
    if (static::get_db($this->_connection_name)->getAttribute(\PDO::ATTR_DRIVER_NAME) == 'pgsql') {
3255
      $query[] = 'RETURNING ' . $this->_quote_identifier($this->_get_id_column_name());
3256
    }
3257
3258 12
    return implode(' ', $query);
3259
  }
3260
3261
  /**
3262
   * Delete this record from the database
3263
   *
3264
   * @return bool
3265
   */
3266 4
  public function delete()
3267
  {
3268
    $query = array(
3269 4
        'DELETE FROM',
3270 4
        $this->_quote_identifier($this->_table_name),
3271 4
    );
3272 4
    $this->_add_id_column_conditions($query);
3273
3274 4
    return static::_execute(
3275 4
        implode(' ', $query), is_array($this->id(true)) ?
3276 3
        array_values($this->id(true)) :
3277 3
        array($this->id(true)), $this->_connection_name
3278 3
    );
3279
  }
3280
3281
  /**
3282
   * Delete many records from the database
3283
   *
3284
   * @return bool
3285
   */
3286 2
  public function delete_many()
3287
  {
3288
    // Build and return the full DELETE statement by concatenating
3289
    // the results of calling each separate builder method.
3290 2
    $query = $this->_join_if_not_empty(
3291 2
        ' ',
3292
        array(
3293 2
            'DELETE FROM',
3294 2
            $this->_quote_identifier($this->_table_name),
3295 2
            $this->_build_where(),
3296
        )
3297 2
    );
3298
3299 2
    return static::_execute($query, $this->_values, $this->_connection_name);
3300
  }
3301
3302
  // --------------------- //
3303
  // ---  ArrayAccess  --- //
3304
  // --------------------- //
3305
3306
  /**
3307
   * @param mixed $key
3308
   *
3309
   * @return bool
3310
   */
3311 14
  public function offsetExists($key)
3312
  {
3313 14
    return array_key_exists($key, $this->_data);
3314
  }
3315
3316
  /**
3317
   * @param mixed $key
3318
   *
3319
   * @return mixed
3320
   */
3321 4
  public function offsetGet($key)
3322
  {
3323 4
    return $this->get($key);
3324
  }
3325
3326
  /**
3327
   * @param mixed $key
3328
   * @param mixed $value
3329
   */
3330 16
  public function offsetSet($key, $value)
3331
  {
3332 16
    if (null === $key) {
3333
      throw new \InvalidArgumentException('You must specify a key/array index.');
3334
    }
3335 16
    $this->set($key, $value);
3336 16
  }
3337
3338
  /**
3339
   * @param mixed $key
3340
   */
3341 1
  public function offsetUnset($key)
3342
  {
3343 1
    unset($this->_data[$key]);
3344 1
    unset($this->_dirty_fields[$key]);
3345 1
  }
3346
3347
  // --------------------- //
3348
  // --- MAGIC METHODS --- //
3349
  // --------------------- //
3350
3351
  /**
3352
   * @param $key
3353
   *
3354
   * @return mixed
3355
   */
3356 1
  public function __get($key)
3357
  {
3358 1
    return $this->offsetGet($key);
3359
  }
3360
3361
  /**
3362
   * @param $key
3363
   * @param $value
3364
   */
3365 13
  public function __set($key, $value)
3366
  {
3367 13
    $this->offsetSet($key, $value);
3368 13
  }
3369
3370
  /**
3371
   * @param $key
3372
   */
3373
  public function __unset($key)
3374
  {
3375
    $this->offsetUnset($key);
3376
  }
3377
3378
  /**
3379
   * @param $key
3380
   *
3381
   * @return bool
3382
   */
3383 13
  public function __isset($key)
3384
  {
3385 13
    return $this->offsetExists($key);
3386
  }
3387
3388
  /**
3389
   * Magic method to capture calls to undefined class methods.
3390
   * In this case we are attempting to convert camel case formatted
3391
   * methods into underscore formatted methods.
3392
   *
3393
   * This allows us to call ORM methods using camel case and remain
3394
   * backwards compatible.
3395
   *
3396
   * @param  string $name
3397
   * @param  array  $arguments
3398
   *
3399
   * @return $this
3400
   *
3401
   * @throws IdiormMethodMissingException
3402
   */
3403 81
  public function __call($name, $arguments)
3404
  {
3405 81
    $method = strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $name));
3406
3407 81 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...
3408 80
      return call_user_func_array(array($this, $method), $arguments);
3409
    } else {
3410 1
      throw new IdiormMethodMissingException("Method $name() does not exist in class " . get_class($this));
3411
    }
3412
  }
3413
3414
  /**
3415
   * Magic method to capture calls to undefined static class methods.
3416
   * In this case we are attempting to convert camel case formatted
3417
   * methods into underscore formatted methods.
3418
   *
3419
   * This allows us to call ORM methods using camel case and remain
3420
   * backwards compatible.
3421
   *
3422
   * @param  string $name
3423
   * @param  array  $arguments
3424
   *
3425
   * @return $this
3426
   */
3427 88
  public static function __callStatic($name, $arguments)
3428
  {
3429 88
    $method = strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $name));
3430
3431 88
    return call_user_func_array(array('idiorm\orm\ORM', $method), $arguments);
3432
  }
3433
3434
}
3435