Completed
Push — master ( 909995...dee2c0 )
by Lars
07:23
created

ORM::right_join()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 3
ccs 0
cts 2
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 3
crap 2
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 $key
314
   * @param mixed  $value
315
   * @param string $connection_name Which connection to use
316
   */
317 232
  public static function configure($key, $value = null, $connection_name = self::DEFAULT_CONNECTION)
318
  {
319 232
    static::_setup_db_config($connection_name); //ensures at least default config is set
320
321 232
    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 232
      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 232
      static::$_config[$connection_name][$key] = $value;
335
    }
336 232
  }
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 mixed
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 145
  public static function reset_config()
359
  {
360 145
    static::$_config = array();
361 145
  }
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 ORM
374
   */
375 220
  public static function for_table($table_name, $connection_name = self::DEFAULT_CONNECTION)
376
  {
377 220
    static::_setup_db($connection_name);
378
379 220
    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 231
  protected static function _setup_db($connection_name = self::DEFAULT_CONNECTION)
388
  {
389
    if (
390 231
        !array_key_exists($connection_name, static::$_db)
391 231
        ||
392 231
        !is_object(static::$_db[$connection_name])
393 231
    ) {
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 231
  }
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 232
  protected static function _setup_db_config($connection_name)
411
  {
412 232
    if (!array_key_exists($connection_name, static::$_config)) {
413 145
      static::$_config[$connection_name] = static::$_default_config;
414 145
    }
415 232
  }
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 232
  public static function set_db($db, $connection_name = self::DEFAULT_CONNECTION)
427
  {
428 232
    static::_setup_db_config($connection_name);
429 232
    static::$_db[$connection_name] = $db;
430
431 232
    if (null !== static::$_db[$connection_name]) {
432 232
      static::_setup_identifier_quote_character($connection_name);
433 232
      static::_setup_limit_clause_style($connection_name);
434 232
    }
435 232
  }
436
437
  /**
438
   * Delete all registered PDO objects in _db array.
439
   */
440 145
  public static function reset_db()
441
  {
442 145
    static::$_db = array();
443 145
  }
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 232
  protected static function _setup_identifier_quote_character($connection_name)
454
  {
455 232
    if (null === static::$_config[$connection_name]['identifier_quote_character']) {
456 145
      static::$_config[$connection_name]['identifier_quote_character'] = static::_detect_identifier_quote_character($connection_name);
457 145
    }
458 232
  }
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 232
  public static function _setup_limit_clause_style($connection_name)
468
  {
469 232
    if (null === static::$_config[$connection_name]['limit_clause_style']) {
470 145
      static::$_config[$connection_name]['limit_clause_style'] = static::_detect_limit_clause_style($connection_name);
471 145
    }
472 232
  }
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 145
  protected static function _detect_identifier_quote_character($connection_name)
483
  {
484 145
    switch (static::get_db($connection_name)->getAttribute(\PDO::ATTR_DRIVER_NAME)) {
485 145
      case 'pgsql':
486 145
      case 'sqlsrv':
487 145
      case 'dblib':
488 145
      case 'mssql':
489 145
      case 'sybase':
490 145
      case 'firebird':
491 2
        return '"';
492 143
      case 'mysql':
493 143
      case 'sqlite':
494 143
      case 'sqlite2':
495 143
      default:
496 143
        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 145
  protected static function _detect_limit_clause_style($connection_name)
509
  {
510 145
    switch (static::get_db($connection_name)->getAttribute(\PDO::ATTR_DRIVER_NAME)) {
511 145
      case 'sqlsrv':
512 145
      case 'dblib':
513 145
      case 'mssql':
514 2
        return self::LIMIT_STYLE_TOP_N;
515 143
      default:
516 143
        return self::LIMIT_STYLE_LIMIT;
517 143
    }
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 231
  public static function get_db($connection_name = self::DEFAULT_CONNECTION)
531
  {
532 231
    static::_setup_db($connection_name); // required in case this is called before Idiorm is instantiated
533 231
    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 199
  public static function get_last_statement()
564
  {
565 199
    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 211
  protected static function _execute($query, $parameters = array(), $connection_name = self::DEFAULT_CONNECTION)
582
  {
583 211
    $time = microtime(true);
584
585
    try {
586 211
      $statement = static::get_db($connection_name)->prepare($query);
587 211
      static::$_last_statement = $statement;
588
589 211
      foreach ($parameters as $key => &$param) {
590
591 111
        if (null === $param) {
592
          $type = \PDO::PARAM_NULL;
593 111
        } elseif (is_bool($param)) {
594
          $type = \PDO::PARAM_BOOL;
595 111
        } elseif (is_int($param)) {
596 82
          $type = \PDO::PARAM_INT;
597 82
        } else {
598 73
          $type = \PDO::PARAM_STR;
599
        }
600
601 111
        $statement->bindParam(is_int($key) ? ++$key : $key, $param, $type);
602 211
      }
603 211
      unset($param);
604
605 211
      $q = $statement->execute();
606 211
      static::_log_query($query, $parameters, $connection_name, microtime(true) - $time);
607 211
    } catch (\Exception $ex) {
608
      static::_log_query($query, $parameters, $connection_name, microtime(true) - $time);
609
      throw $ex;
610
    }
611
612 211
    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 211
  protected static function _log_query($query, $parameters, $connection_name, $query_time)
632
  {
633
    // If logging is not enabled, do nothing
634 211
    if (!static::$_config[$connection_name]['logging']) {
635
      return false;
636
    }
637
638 211
    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 211
    foreach ($parameters as $key => $value) {
644 111
      if (!is_int($key)) {
645
        unset($parameters[$key]);
646
      }
647 211
    }
648
649 211
    if (count($parameters) > 0) {
650
      // Escape the parameters
651 111
      $parameters = array_map(array(static::get_db($connection_name), 'quote'), $parameters);
652
653
      // Avoid %format collision for vsprintf
654 111
      $query = str_replace('%', '%%', $query);
655
656
      // Replace placeholders in the query for vsprintf
657 111
      if (false !== strpos($query, "'") || false !== strpos($query, '"')) {
658 4
        $query = IdiormString::str_replace_outside_quotes('?', '%s', $query);
659 4
      } else {
660 109
        $query = str_replace('?', '%s', $query);
661
      }
662
663
      // Replace the question marks in the query with the parameters
664 111
      $bound_query = vsprintf($query, $parameters);
665 111
    } else {
666 103
      $bound_query = $query;
667
    }
668
669 211
    static::$_last_query = $bound_query;
670 211
    static::$_query_log[$connection_name][] = $bound_query;
671
672
673 211
    if (is_callable(static::$_config[$connection_name]['logger'])) {
674
      $logger = static::$_config[$connection_name]['logger'];
675
      $logger($bound_query, $query_time);
676
    }
677
678 211
    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 200
  public static function get_last_query($connection_name = null)
692
  {
693 200
    if ($connection_name === null) {
694 199
      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 220
  protected function __construct($table_name, $data = array(), $connection_name = self::DEFAULT_CONNECTION)
745
  {
746 220
    $this->_table_name = $table_name;
747 220
    $this->_data = $data;
748
749 220
    $this->_connection_name = $connection_name;
750 220
    static::_setup_db_config($connection_name);
751 220
  }
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 mixed $data
762
   *
763
   * @return $this
764
   */
765 19
  public function create($data = null)
766
  {
767 19
    $this->_is_new = true;
768 19
    if (null !== $data) {
769 6
      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 200
  public function use_id_column($id_column)
788
  {
789 200
    $this->_instance_id_column = $id_column;
790
791 200
    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 ORM
801
   */
802 196
  protected function _create_instance_from_row($row)
803
  {
804 196
    $instance = static::for_table($this->_table_name, $this->_connection_name);
805 196
    $instance->use_id_column($this->_instance_id_column);
806 196
    $instance->hydrate($row);
807
808 196
    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 86
  public function find_one($id = null)
825
  {
826 86
    if (null !== $id) {
827 31
      $this->where_id_is($id);
828 31
    }
829 86
    $this->limit(1);
830 86
    $rows = $this->_run();
831
832 86
    if (empty($rows)) {
833 2
      return false;
834
    }
835
836 85
    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 110
  public function find_many()
848
  {
849 110
    if (static::$_config[$this->_connection_name]['return_result_sets']) {
850 1
      return $this->find_result_set();
851
    }
852
853 110
    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 112
  protected function _find_many()
865
  {
866 112
    $rows = $this->_run();
867
868 112
    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
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 int
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
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 int
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 200
  public function hydrate($data = array())
1007
  {
1008 200
    $this->_data = $data;
1009
1010 200
    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 6
  public function force_all_dirty()
1018
  {
1019 6
    $this->_dirty_fields = $this->_data;
1020
1021 6
    return $this;
1022
  }
1023
1024
  /**
1025
   * Perform a raw query. The query can contain placeholders in
1026
   * either named or question mark style. If placeholders are
1027
   * used, the parameters should be an array of values which will
1028
   * be bound to the placeholders in the query. If this method
1029
   * is called, all other query building methods will be ignored.
1030
   *
1031
   * @param string $query
1032
   * @param array  $parameters
1033
   *
1034
   * @return $this
1035
   */
1036 4
  public function raw_query($query, $parameters = array())
1037
  {
1038 4
    $this->_is_raw_query = true;
1039 4
    $this->_raw_query = $query;
1040 4
    $this->_raw_parameters = $parameters;
1041
1042 4
    return $this;
1043
  }
1044
1045
  /**
1046
   * Add an alias for the main table to be used in SELECT queries
1047
   *
1048
   * @param string $alias
1049
   *
1050
   * @return $this
1051
   */
1052 4
  public function table_alias($alias)
1053
  {
1054 4
    $this->_table_alias = $alias;
1055
1056 4
    return $this;
1057
  }
1058
1059
  /**
1060
   * Internal method to add an unquoted expression to the set
1061
   * of columns returned by the SELECT query. The second optional
1062
   * argument is the alias to return the expression as.
1063
   *
1064
   * @param string $expr
1065
   * @param mixed  $alias
1066
   *
1067
   * @return $this
1068
   */
1069 34
  protected function _add_result_column($expr, $alias = null)
1070
  {
1071 34
    if (null !== $alias) {
1072 20
      $expr .= ' AS ' . $this->_quote_identifier($alias);
1073 20
    }
1074
1075 34
    if ($this->_using_default_result_columns) {
1076 34
      $this->_result_columns = array($expr);
1077 34
      $this->_using_default_result_columns = false;
1078 34
    } else {
1079 8
      $this->_result_columns[] = $expr;
1080
    }
1081
1082 34
    return $this;
1083
  }
1084
1085
  /**
1086
   * Counts the number of columns that belong to the primary
1087
   * key and their value is null.
1088
   */
1089 12
  public function count_null_id_columns()
1090
  {
1091 12
    if (is_array($this->_get_id_column_name())) {
1092 3
      return count(array_filter($this->id(), 'is_null'));
1093
    } else {
1094 9
      return (null === $this->id() ? 1 : 0);
1095
    }
1096
  }
1097
1098
  /**
1099
   * Add a column to the list of columns returned by the SELECT
1100
   * query. This defaults to '*'. The second optional argument is
1101
   * the alias to return the column as.
1102
   *
1103
   * @param string $column
1104
   * @param mixed  $alias
1105
   *
1106
   * @return ORM
1107
   */
1108 20
  public function select($column, $alias = null)
1109
  {
1110 20
    $column = $this->_quote_identifier($column);
1111
1112 20
    return $this->_add_result_column($column, $alias);
1113
  }
1114
1115
  /**
1116
   * Add an unquoted expression to the list of columns returned
1117
   * by the SELECT query. The second optional argument is
1118
   * the alias to return the column as.
1119
   *
1120
   * @param string $expr
1121
   * @param mixed  $alias
1122
   *
1123
   * @return ORM
1124
   */
1125 16
  public function select_expr($expr, $alias = null)
1126
  {
1127 16
    return $this->_add_result_column($expr, $alias);
1128
  }
1129
1130
  /**
1131
   * Add columns to the list of columns returned by the SELECT
1132
   * query. This defaults to '*'. Many columns can be supplied
1133
   * as either an array or as a list of parameters to the method.
1134
   *
1135
   * Note that the alias must not be numeric - if you want a
1136
   * numeric alias then prepend it with some alpha chars. eg. a1
1137
   *
1138
   * @example select_many(array('alias' => 'column', 'column2', 'alias2' => 'column3'), 'column4', 'column5');
1139
   * @example select_many('column', 'column2', 'column3');
1140
   * @example select_many(array('column', 'column2', 'column3'), 'column4', 'column5');
1141
   *
1142
   * @return ORM
1143
   */
1144 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...
1145
  {
1146 2
    $columns = func_get_args();
1147 2
    if (!empty($columns)) {
1148 2
      $columns = $this->_normalise_select_many_columns($columns);
1149 2
      foreach ($columns as $alias => $column) {
1150 2
        if (is_numeric($alias)) {
1151 2
          $alias = null;
1152 2
        }
1153 2
        $this->select($column, $alias);
1154 2
      }
1155 2
    }
1156
1157 2
    return $this;
1158
  }
1159
1160
  /**
1161
   * Add an unquoted expression to the list of columns returned
1162
   * by the SELECT query. Many columns can be supplied as either
1163
   * an array or as a list of parameters to the method.
1164
   *
1165
   * Note that the alias must not be numeric - if you want a
1166
   * numeric alias then prepend it with some alpha chars. eg. a1
1167
   *
1168
   * @example select_many_expr(array('alias' => 'column', 'column2', 'alias2' => 'column3'), 'column4', 'column5')
1169
   * @example select_many_expr('column', 'column2', 'column3')
1170
   * @example select_many_expr(array('column', 'column2', 'column3'), 'column4', 'column5')
1171
   *
1172
   * @return ORM
1173
   */
1174 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...
1175
  {
1176 2
    $columns = func_get_args();
1177 2
    if (!empty($columns)) {
1178 2
      $columns = $this->_normalise_select_many_columns($columns);
1179 2
      foreach ($columns as $alias => $column) {
1180 2
        if (is_numeric($alias)) {
1181 2
          $alias = null;
1182 2
        }
1183 2
        $this->select_expr($column, $alias);
1184 2
      }
1185 2
    }
1186
1187 2
    return $this;
1188
  }
1189
1190
  /**
1191
   * Take a column specification for the select many methods and convert it
1192
   * into a normalised array of columns and aliases.
1193
   *
1194
   * It is designed to turn the following styles into a normalised array:
1195
   *
1196
   * array(array('alias' => 'column', 'column2', 'alias2' => 'column3'), 'column4', 'column5'))
1197
   *
1198
   * @param array $columns
1199
   *
1200
   * @return array
1201
   */
1202 4
  protected function _normalise_select_many_columns($columns)
1203
  {
1204 4
    $return = array();
1205 4
    foreach ($columns as $column) {
1206 4
      if (is_array($column)) {
1207 4
        foreach ($column as $key => $value) {
1208 4
          if (!is_numeric($key)) {
1209 4
            $return[$key] = $value;
1210 4
          } else {
1211
            $return[] = $value;
1212
          }
1213 4
        }
1214 4
      } else {
1215 4
        $return[] = $column;
1216
      }
1217 4
    }
1218
1219 4
    return $return;
1220
  }
1221
1222
  /**
1223
   * Add a DISTINCT keyword before the list of columns in the SELECT query
1224
   */
1225 2
  public function distinct()
1226
  {
1227 2
    $this->_distinct = true;
1228
1229 2
    return $this;
1230
  }
1231
1232
  /**
1233
   * Internal method to add a JOIN source to the query.
1234
   *
1235
   * The join_operator should be one of INNER, LEFT OUTER, CROSS etc - this
1236
   * will be prepended to JOIN.
1237
   *
1238
   * The table should be the name of the table to join to.
1239
   *
1240
   * The constraint may be either a string or an array with three elements. If it
1241
   * is a string, it will be compiled into the query as-is, with no escaping. The
1242
   * recommended way to supply the constraint is as an array with three elements:
1243
   *
1244
   * first_column, operator, second_column
1245
   *
1246
   * Example: array('user.id', '=', 'profile.user_id')
1247
   *
1248
   * will compile to
1249
   *
1250
   * ON `user`.`id` = `profile`.`user_id`
1251
   *
1252
   * The final (optional) argument specifies an alias for the joined table.
1253
   *
1254
   * @param string      $join_operator
1255
   * @param string      $table
1256
   * @param string      $constraint
1257
   * @param string|null $table_alias
1258
   *
1259
   * @return $this
1260
   */
1261 20
  protected function _add_join_source($join_operator, $table, $constraint, $table_alias = null)
1262
  {
1263 20
    $join_operator = trim("{$join_operator} JOIN");
1264
1265 20
    $table = $this->_quote_identifier($table);
1266
1267
    // Add table alias if present
1268 20
    if (null !== $table_alias) {
1269 4
      $table_alias = $this->_quote_identifier($table_alias);
1270 4
      $table .= " {$table_alias}";
1271 4
    }
1272
1273
    // Build the constraint
1274 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...
1275 18
      list($first_column, $operator, $second_column) = $constraint;
1276 18
      $first_column = $this->_quote_identifier($first_column);
1277 18
      $second_column = $this->_quote_identifier($second_column);
1278 18
      $constraint = "{$first_column} {$operator} {$second_column}";
1279 18
    }
1280
1281 20
    $this->_join_sources[] = "{$join_operator} {$table} ON {$constraint}";
1282
1283 20
    return $this;
1284
  }
1285
1286
  /**
1287
   * Add a RAW JOIN source to the query
1288
   *
1289
   * @param string $table
1290
   * @param string $constraint
1291
   * @param string $table_alias
1292
   * @param array  $parameters
1293
   *
1294
   * @return $this
1295
   */
1296 6
  public function raw_join($table, $constraint, $table_alias, $parameters = array())
1297
  {
1298
    // Add table alias if present
1299 6
    if (null !== $table_alias) {
1300 6
      $table_alias = $this->_quote_identifier($table_alias);
1301 6
      $table .= " {$table_alias}";
1302 6
    }
1303
1304 6
    $this->_values = array_merge($this->_values, $parameters);
1305
1306
    // Build the constraint
1307 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...
1308 6
      list($first_column, $operator, $second_column) = $constraint;
1309 6
      $first_column = $this->_quote_identifier($first_column);
1310 6
      $second_column = $this->_quote_identifier($second_column);
1311 6
      $constraint = "{$first_column} {$operator} {$second_column}";
1312 6
    }
1313
1314 6
    $this->_join_sources[] = "{$table} ON {$constraint}";
1315
1316 6
    return $this;
1317
  }
1318
1319
  /**
1320
   * Add a simple JOIN source to the query
1321
   *
1322
   * @param string      $table
1323
   * @param string      $constraint
1324
   * @param string|null $table_alias
1325
   *
1326
   * @return ORM
1327
   */
1328 12
  public function join($table, $constraint, $table_alias = null)
1329
  {
1330 12
    return $this->_add_join_source('', $table, $constraint, $table_alias);
1331
  }
1332
1333
  /**
1334
   * Add an INNER JOIN souce to the query
1335
   */
1336
  /**
1337
   * @param string      $table
1338
   * @param string      $constraint
1339
   * @param null|string $table_alias
1340
   *
1341
   * @return ORM
1342
   */
1343 2
  public function inner_join($table, $constraint, $table_alias = null)
1344
  {
1345 2
    return $this->_add_join_source('INNER', $table, $constraint, $table_alias);
1346
  }
1347
  
1348
  /**
1349
   * Add an LEFT JOIN source to the query
1350
   */
1351
  public function left_join($table, $constraint, $table_alias = null) {
1352
  	return $this->_add_join_source("LEFT", $table, $constraint, $table_alias);
1353
  }
1354
        
1355
  /**
1356
   * Add an RIGHT JOIN source to the query
1357
   */
1358
  public function right_join($table, $constraint, $table_alias  = null) {
1359
  	return $this->_add_join_source("RIGHT", $table, $constraint, $table_alias);
1360
  }
1361
1362
  /**
1363
   * Add a LEFT OUTER JOIN souce to the query
1364
   *
1365
   * @param string      $table
1366
   * @param string      $constraint
1367
   * @param null|string $table_alias
1368
   *
1369
   * @return $this|ORM
1370
   */
1371 2
  public function left_outer_join($table, $constraint, $table_alias = null)
1372
  {
1373 2
    return $this->_add_join_source('LEFT OUTER', $table, $constraint, $table_alias);
1374
  }
1375
1376
  /**
1377
   * Add an RIGHT OUTER JOIN souce to the query
1378
   *
1379
   * @param string      $table
1380
   * @param string      $constraint
1381
   * @param null|string $table_alias
1382
   *
1383
   * @return $this|ORM
1384
   */
1385 2
  public function right_outer_join($table, $constraint, $table_alias = null)
1386
  {
1387 2
    return $this->_add_join_source('RIGHT OUTER', $table, $constraint, $table_alias);
1388
  }
1389
1390
  /**
1391
   * Add an FULL OUTER JOIN souce to the query
1392
   */
1393
  /**
1394
   * @param string      $table
1395
   * @param string      $constraint
1396
   * @param null|string $table_alias
1397
   *
1398
   * @return ORM
1399
   */
1400 2
  public function full_outer_join($table, $constraint, $table_alias = null)
1401
  {
1402 2
    return $this->_add_join_source('FULL OUTER', $table, $constraint, $table_alias);
1403
  }
1404
1405
  /**
1406
   * Internal method to add a HAVING condition to the query
1407
   */
1408
  /**
1409
   * @param string $fragment
1410
   * @param array  $values
1411
   *
1412
   * @return ORM
1413
   */
1414 10
  protected function _add_having($fragment, $values = array())
1415
  {
1416 10
    return $this->_add_condition('having', $fragment, $values);
1417
  }
1418
1419
  /**
1420
   * Internal method to add a HAVING condition to the query
1421
   */
1422
  /**
1423
   * @param string|array $column_name
1424
   * @param string       $separator
1425
   * @param mixed        $value
1426
   *
1427
   * @return $this|ORM
1428
   */
1429 14
  protected function _add_simple_having($column_name, $separator, $value)
1430
  {
1431 14
    return $this->_add_simple_condition('having', $column_name, $separator, $value);
1432
  }
1433
1434
  /**
1435
   * Internal method to add a HAVING clause with multiple values (like IN and NOT IN)
1436
   */
1437
  /**
1438
   * @param string|array $column_name
1439
   * @param string       $separator
1440
   * @param mixed        $values
1441
   *
1442
   * @return ORM
1443
   */
1444 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...
1445
  {
1446 4
    if (!is_array($column_name)) {
1447 4
      $data = array($column_name => $values);
1448 4
    } else {
1449
      $data = $column_name;
1450
    }
1451
1452 4
    $result = $this;
1453 4
    foreach ($data as $key => $val) {
1454 4
      $column = $result->_quote_identifier($key);
1455 4
      $placeholders = $result->_create_placeholders($val);
1456 4
      $result = $result->_add_having("{$column} {$separator} ({$placeholders})", $val);
1457 4
    }
1458
1459 4
    return $result;
1460
  }
1461
1462
  /**
1463
   * Internal method to add a HAVING clause with no parameters(like IS NULL and IS NOT NULL)
1464
   *
1465
   * @param string|array $column_name
1466
   * @param string       $operator
1467
   *
1468
   * @return ORM
1469
   */
1470 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...
1471
  {
1472 4
    if (is_array($column_name)) {
1473
      $conditions = $column_name;
1474
    } else {
1475 4
      $conditions = array($column_name);
1476
    }
1477
1478 4
    $result = $this;
1479 4
    foreach ($conditions as $column) {
1480 4
      $column = $this->_quote_identifier($column);
1481 4
      $result = $result->_add_having("{$column} {$operator}");
1482 4
    }
1483
1484 4
    return $result;
1485
  }
1486
1487
  /**
1488
   * Internal method to add a WHERE condition to the query
1489
   *
1490
   * @param string $fragment
1491
   * @param array  $values
1492
   *
1493
   * @return ORM
1494
   */
1495 27
  protected function _add_where($fragment, $values = array())
1496
  {
1497 27
    return $this->_add_condition('where', $fragment, $values);
1498
  }
1499
1500
  /**
1501
   * Internal method to add a WHERE condition to the query
1502
   *
1503
   * @param string|array $column_name
1504
   * @param string       $separator
1505
   * @param mixed        $value
1506
   *
1507
   * @return $this|ORM
1508
   */
1509 63
  protected function _add_simple_where($column_name, $separator, $value)
1510
  {
1511 63
    return $this->_add_simple_condition('where', $column_name, $separator, $value);
1512
  }
1513
1514
  /**
1515
   * Add a WHERE clause with multiple values (like IN and NOT IN)
1516
   *
1517
   * @param string|array $column_name
1518
   * @param string       $separator
1519
   * @param mixed        $values
1520
   *
1521
   * @return ORM
1522
   */
1523 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...
1524
  {
1525 5
    if (!is_array($column_name)) {
1526 5
      $data = array($column_name => $values);
1527 5
    } else {
1528
      $data = $column_name;
1529
    }
1530
1531 5
    $result = $this;
1532 5
    foreach ($data as $key => $val) {
1533 5
      $column = $result->_quote_identifier($key);
1534 5
      $placeholders = $result->_create_placeholders($val);
1535 5
      $result = $result->_add_where("{$column} {$separator} ({$placeholders})", $val);
1536 5
    }
1537
1538 5
    return $result;
1539
  }
1540
1541
  /**
1542
   * Add a WHERE clause with no parameters(like IS NULL and IS NOT NULL)
1543
   *
1544
   * @param string|array $column_name
1545
   * @param string       $operator
1546
   *
1547
   * @return ORM
1548
   */
1549 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...
1550
  {
1551 4
    if (is_array($column_name)) {
1552
      $conditions = $column_name;
1553
    } else {
1554 4
      $conditions = array($column_name);
1555
    }
1556
1557 4
    $result = $this;
1558 4
    foreach ($conditions as $column) {
1559 4
      $column = $this->_quote_identifier($column);
1560 4
      $result = $result->_add_where("{$column} {$operator}");
1561 4
    }
1562
1563 4
    return $result;
1564
  }
1565
1566
  /**
1567
   * Internal method to add a HAVING or WHERE condition to the query
1568
   *
1569
   * @param string $type
1570
   * @param string $fragment
1571
   * @param mixed  $values
1572
   *
1573
   * @return $this
1574
   */
1575 112
  protected function _add_condition($type, $fragment, $values = array())
1576
  {
1577 112
    $conditions_class_property_name = "_{$type}_conditions";
1578
1579 112
    if (!is_array($values)) {
1580 77
      $values = array($values);
1581 77
    }
1582
1583 112
    array_push(
1584 112
        $this->$conditions_class_property_name,
1585
        array(
1586 112
            static::CONDITION_FRAGMENT => $fragment,
1587 112
            static::CONDITION_VALUES   => $values,
1588
        )
1589 112
    );
1590
1591 112
    return $this;
1592
  }
1593
1594
  /**
1595
   * Helper method to compile a simple COLUMN SEPARATOR VALUE
1596
   * style HAVING or WHERE condition into a string and value ready to
1597
   * be passed to the _add_condition method. Avoids duplication
1598
   * of the call to _quote_identifier
1599
   *
1600
   * If column_name is an associative array, it will add a condition for each column
1601
   *
1602
   * @param string       $type
1603
   * @param string|array $column_name
1604
   * @param string       $separator
1605
   * @param string|int   $value
1606
   *
1607
   * @return $this|ORM
1608
   */
1609 77
  protected function _add_simple_condition($type, $column_name, $separator, $value)
1610
  {
1611 77
    if (is_array($column_name)) {
1612 1
      $multiple = $column_name;
1613 1
    } else {
1614 76
      $multiple = array($column_name => $value);
1615
    }
1616
1617 77
    $result = $this;
1618
1619 77
    foreach ($multiple as $key => $val) {
1620
      // Add the table name in case of ambiguous columns
1621 77
      if (count($result->_join_sources) > 0 && strpos($key, '.') === false) {
1622
1623 4
        $table = $result->_table_name;
1624 4
        if (null !== $result->_table_alias) {
1625 2
          $table = $result->_table_alias;
1626 2
        }
1627
1628 4
        $key = "{$table}.{$key}";
1629 4
      }
1630 77
      $key = $result->_quote_identifier($key);
1631 77
      $result = $result->_add_condition($type, "{$key} {$separator} ?", $val);
1632 77
    }
1633
1634 77
    return $result;
1635
  }
1636
1637
  /**
1638
   * Return a string containing the given number of question marks,
1639
   * separated by commas. Eg "?, ?, ?"
1640
   *
1641
   * @param mixed $fields
1642
   *
1643
   * @return string
1644
   */
1645 21
  protected function _create_placeholders($fields)
1646
  {
1647 21
    if (!empty($fields)) {
1648 21
      $db_fields = array();
1649 21
      foreach ($fields as $key => $value) {
1650
        // Process expression fields directly into the query
1651 21
        if (array_key_exists($key, $this->_expr_fields)) {
1652 2
          $db_fields[] = $value;
1653 2
        } else {
1654 21
          $db_fields[] = '?';
1655
        }
1656 21
      }
1657
1658 21
      return implode(', ', $db_fields);
1659
    } else {
1660 1
      return '';
1661
    }
1662
  }
1663
1664
  /**
1665
   * Helper method that filters a column/value array returning only those
1666
   * columns that belong to a compound primary key.
1667
   *
1668
   * If the key contains a column that does not exist in the given array,
1669
   * a null value will be returned for it.
1670
   *
1671
   * @param mixed $value
1672
   *
1673
   * @return array
1674
   */
1675 2
  protected function _get_compound_id_column_values($value)
1676
  {
1677 2
    $filtered = array();
1678 2
    foreach ($this->_get_id_column_name() as $key) {
1679 2
      $filtered[$key] = isset($value[$key]) ? $value[$key] : null;
1680 2
    }
1681
1682 2
    return $filtered;
1683
  }
1684
1685
  /**
1686
   * Helper method that filters an array containing compound column/value
1687
   * arrays.
1688
   *
1689
   * @param array $values
1690
   *
1691
   * @return array
1692
   */
1693 1
  protected function _get_compound_id_column_values_array($values)
1694
  {
1695 1
    $filtered = array();
1696 1
    foreach ($values as $value) {
1697 1
      $filtered[] = $this->_get_compound_id_column_values($value);
1698 1
    }
1699
1700 1
    return $filtered;
1701
  }
1702
1703
  /**
1704
   * Add a WHERE column = value clause to your query. Each time
1705
   * this is called in the chain, an additional WHERE will be
1706
   * added, and these will be ANDed together when the final query
1707
   * is built.
1708
   *
1709
   * If you use an array in $column_name, a new clause will be
1710
   * added for each element. In this case, $value is ignored.
1711
   *
1712
   * @param string $column_name
1713
   * @param mixed  $value
1714
   *
1715
   * @return $this|ORM
1716
   */
1717 49
  public function where($column_name, $value = null)
1718
  {
1719 49
    return $this->where_equal($column_name, $value);
1720
  }
1721
1722
  /**
1723
   * More explicitly named version of for the where() method.
1724
   * Can be used if preferred.
1725
   *
1726
   * @param string $column_name
1727
   * @param mixed  $value
1728
   *
1729
   * @return $this|ORM
1730
   */
1731 53
  public function where_equal($column_name, $value = null)
1732
  {
1733 53
    return $this->_add_simple_where($column_name, '=', $value);
1734
  }
1735
1736
  /**
1737
   * Add a WHERE column != value clause to your query.
1738
   *
1739
   * @param string $column_name
1740
   * @param mixed  $value
1741
   *
1742
   * @return $this|ORM
1743
   */
1744 2
  public function where_not_equal($column_name, $value = null)
1745
  {
1746 2
    return $this->_add_simple_where($column_name, '!=', $value);
1747
  }
1748
1749
  /**
1750
   * Special method to query the table by its primary key
1751
   *
1752
   * If primary key is compound, only the columns that
1753
   * belong to they key will be used for the query
1754
   *
1755
   * @param mixed $id
1756
   *
1757
   * @return $this|ORM
1758
   */
1759 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...
1760
  {
1761 33
    if (is_array($this->_get_id_column_name())) {
1762 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...
1763
    } else {
1764 32
      return $this->where($this->_get_id_column_name(), $id);
1765
    }
1766
  }
1767
1768
  /**
1769
   * Allows adding a WHERE clause that matches any of the conditions
1770
   * specified in the array. Each element in the associative array will
1771
   * be a different condition, where the key will be the column name.
1772
   *
1773
   * By default, an equal operator will be used against all columns, but
1774
   * it can be overridden for any or every column using the second parameter.
1775
   *
1776
   * Each condition will be ORed together when added to the final query.
1777
   *
1778
   * @param array  $values
1779
   * @param string $operator
1780
   *
1781
   * @return $this|ORM
1782
   */
1783 4
  public function where_any_is($values, $operator = '=')
1784
  {
1785 4
    $data = array();
1786 4
    $query = array('((');
1787 4
    $first = true;
1788 4
    foreach ($values as $item) {
1789 4
      if ($first) {
1790 4
        $first = false;
1791 4
      } else {
1792 4
        $query[] = ') OR (';
1793
      }
1794 4
      $firstsub = true;
1795
1796 4
      foreach ($item as $key => $subItem) {
1797
1798 4
        if (is_string($operator)) {
1799 3
          $op = $operator;
1800 3
        } else {
1801 1
          $op = (isset($operator[$key]) ? $operator[$key] : '=');
1802
        }
1803
1804 4
        if ($firstsub) {
1805 4
          $firstsub = false;
1806 4
        } else {
1807 4
          $query[] = 'AND';
1808
        }
1809
1810 4
        $query[] = $this->_quote_identifier($key);
1811 4
        $data[] = $subItem;
1812 4
        $query[] = $op . ' ?';
1813 4
      }
1814 4
    }
1815 4
    $query[] = '))';
1816
1817 4
    return $this->where_raw(implode($query, ' '), $data);
1818
  }
1819
1820
  /**
1821
   * Similar to where_id_is() but allowing multiple primary keys.
1822
   *
1823
   * If primary key is compound, only the columns that
1824
   * belong to they key will be used for the query
1825
   *
1826
   * @param mixed $ids
1827
   *
1828
   * @return $this|ORM
1829
   */
1830 2
  public function where_id_in($ids)
1831
  {
1832 2
    if (is_array($this->_get_id_column_name())) {
1833 1
      return $this->where_any_is($this->_get_compound_id_column_values_array($ids));
1834
    } else {
1835 1
      return $this->where_in($this->_get_id_column_name(), $ids);
1836
    }
1837
  }
1838
1839
  /**
1840
   * Add a WHERE ... LIKE clause to your query.
1841
   *
1842
   * @param string $column_name
1843
   * @param mixed  $value
1844
   *
1845
   * @return $this|ORM
1846
   */
1847 2
  public function where_like($column_name, $value = null)
1848
  {
1849 2
    return $this->_add_simple_where($column_name, 'LIKE', $value);
1850
  }
1851
1852
  /**
1853
   * Add where WHERE ... NOT LIKE clause to your query.
1854
   *
1855
   * @param string $column_name
1856
   * @param mixed  $value
1857
   *
1858
   * @return $this|ORM
1859
   */
1860 2
  public function where_not_like($column_name, $value = null)
1861
  {
1862 2
    return $this->_add_simple_where($column_name, 'NOT LIKE', $value);
1863
  }
1864
1865
  /**
1866
   * Add a WHERE ... > clause to your query
1867
   *
1868
   * @param string $column_name
1869
   * @param mixed  $value
1870
   *
1871
   * @return $this|ORM
1872
   */
1873 2
  public function where_gt($column_name, $value = null)
1874
  {
1875 2
    return $this->_add_simple_where($column_name, '>', $value);
1876
  }
1877
1878
  /**
1879
   * Add a WHERE ... < clause to your query
1880
   *
1881
   * @param string $column_name
1882
   * @param mixed  $value
1883
   *
1884
   * @return $this|ORM
1885
   */
1886 2
  public function where_lt($column_name, $value = null)
1887
  {
1888 2
    return $this->_add_simple_where($column_name, '<', $value);
1889
  }
1890
1891
  /**
1892
   * Add a WHERE ... >= clause to your query
1893
   *
1894
   * @param string $column_name
1895
   * @param mixed  $value
1896
   *
1897
   * @return $this|ORM
1898
   */
1899 2
  public function where_gte($column_name, $value = null)
1900
  {
1901 2
    return $this->_add_simple_where($column_name, '>=', $value);
1902
  }
1903
1904
  /**
1905
   * Add a WHERE ... <= clause to your query
1906
   *
1907
   * @param string $column_name
1908
   * @param mixed  $value
1909
   *
1910
   * @return $this|ORM
1911
   */
1912 2
  public function where_lte($column_name, $value = null)
1913
  {
1914 2
    return $this->_add_simple_where($column_name, '<=', $value);
1915
  }
1916
1917
  /**
1918
   * Add a WHERE ... IN clause to your query
1919
   *
1920
   * @param string $column_name
1921
   * @param mixed  $values
1922
   *
1923
   * @return $this|ORM
1924
   */
1925 3
  public function where_in($column_name, $values)
1926
  {
1927 3
    return $this->_add_where_placeholder($column_name, 'IN', $values);
1928
  }
1929
1930
  /**
1931
   * Add a WHERE ... NOT IN clause to your query
1932
   *
1933
   * @param string $column_name
1934
   * @param mixed  $values
1935
   *
1936
   * @return $this|ORM
1937
   */
1938 2
  public function where_not_in($column_name, $values)
1939
  {
1940 2
    return $this->_add_where_placeholder($column_name, 'NOT IN', $values);
1941
  }
1942
1943
  /**
1944
   * Add a WHERE column IS NULL clause to your query
1945
   *
1946
   * @param string $column_name
1947
   *
1948
   * @return $this|ORM
1949
   */
1950 2
  public function where_null($column_name)
1951
  {
1952 2
    return $this->_add_where_no_value($column_name, 'IS NULL');
1953
  }
1954
1955
  /**
1956
   * Add a WHERE column IS NOT NULL clause to your query
1957
   *
1958
   * @param string $column_name
1959
   *
1960
   * @return $this|ORM
1961
   */
1962 2
  public function where_not_null($column_name)
1963
  {
1964 2
    return $this->_add_where_no_value($column_name, 'IS NOT NULL');
1965
  }
1966
1967
  /**
1968
   * Add a raw WHERE clause to the query. The clause should
1969
   * contain question mark placeholders, which will be bound
1970
   * to the parameters supplied in the second argument.
1971
   *
1972
   * @param string $clause
1973
   * @param array  $parameters
1974
   *
1975
   * @return $this|ORM
1976
   */
1977 18
  public function where_raw($clause, $parameters = array())
1978
  {
1979 18
    return $this->_add_where($clause, $parameters);
1980
  }
1981
1982
  /**
1983
   * Add a LIMIT to the query
1984
   *
1985
   * @param int $limit
1986
   *
1987
   * @return $this
1988
   */
1989 95
  public function limit($limit)
1990
  {
1991 95
    $this->_limit = $limit;
1992
1993 95
    return $this;
1994
  }
1995
1996
  /**
1997
   * Add an OFFSET to the query
1998
   *
1999
   * @param $offset
2000
   *
2001
   * @return $this
2002
   */
2003 4
  public function offset($offset)
2004
  {
2005 4
    $this->_offset = $offset;
2006
2007 4
    return $this;
2008
  }
2009
2010
  /**
2011
   * Add an ORDER BY clause to the query
2012
   *
2013
   * @param string $column_name
2014
   * @param string $ordering
2015
   *
2016
   * @return $this
2017
   */
2018 12
  protected function _add_order_by($column_name, $ordering)
2019
  {
2020 12
    $column_name = $this->_quote_identifier($column_name);
2021 12
    $this->_order_by[] = "{$column_name} {$ordering}";
2022
2023 12
    return $this;
2024
  }
2025
2026
  /**
2027
   * Add an ORDER BY column DESC clause
2028
   *
2029
   * @param string $column_name
2030
   *
2031
   * @return $this|ORM
2032
   */
2033 8
  public function order_by_desc($column_name)
2034
  {
2035 8
    return $this->_add_order_by($column_name, 'DESC');
2036
  }
2037
2038
  /**
2039
   * Add an ORDER BY column ASC clause
2040
   *
2041
   * @param string $column_name
2042
   *
2043
   * @return $this|ORM
2044
   */
2045 6
  public function order_by_asc($column_name)
2046
  {
2047 6
    return $this->_add_order_by($column_name, 'ASC');
2048
  }
2049
2050
  /**
2051
   * Add an unquoted expression as an ORDER BY clause
2052
   *
2053
   * @param $clause
2054
   *
2055
   * @return $this
2056
   */
2057 2
  public function order_by_expr($clause)
2058
  {
2059 2
    $this->_order_by[] = $clause;
2060
2061 2
    return $this;
2062
  }
2063
2064
  /**
2065
   * Reset the ORDER BY clause
2066
   */
2067 1
  public function reset_order_by()
2068
  {
2069 1
    $this->_order_by = array();
2070
2071 1
    return $this;
2072
  }
2073
2074
  /**
2075
   * Add a column to the list of columns to GROUP BY
2076
   *
2077
   * @param string $column_name
2078
   *
2079
   * @return $this
2080
   */
2081 28
  public function group_by($column_name)
2082
  {
2083 28
    $column_name = $this->_quote_identifier($column_name);
2084 28
    $this->_group_by[] = $column_name;
2085
2086 28
    return $this;
2087
  }
2088
2089
  /**
2090
   * Add an unquoted expression to the list of columns to GROUP BY
2091
   *
2092
   * @param string $expr
2093
   *
2094
   * @return $this
2095
   */
2096 2
  public function group_by_expr($expr)
2097
  {
2098 2
    $this->_group_by[] = $expr;
2099
2100 2
    return $this;
2101
  }
2102
2103
  /**
2104
   * Add a HAVING column = value clause to your query. Each time
2105
   * this is called in the chain, an additional HAVING will be
2106
   * added, and these will be ANDed together when the final query
2107
   * is built.
2108
   *
2109
   * If you use an array in $column_name, a new clause will be
2110
   * added for each element. In this case, $value is ignored.
2111
   *
2112
   * @param string $column_name
2113
   * @param null   $value
2114
   *
2115
   * @return $this|ORM
2116
   */
2117 4
  public function having($column_name, $value = null)
2118
  {
2119 4
    return $this->having_equal($column_name, $value);
2120
  }
2121
2122
  /**
2123
   * More explicitly named version of for the having() method.
2124
   * Can be used if preferred.
2125
   *
2126
   * @param string $column_name
2127
   * @param null   $value
2128
   *
2129
   * @return $this|ORM
2130
   */
2131 4
  public function having_equal($column_name, $value = null)
2132
  {
2133 4
    return $this->_add_simple_having($column_name, '=', $value);
2134
  }
2135
2136
  /**
2137
   * Add a HAVING column != value clause to your query.
2138
   *
2139
   * @param string $column_name
2140
   * @param null   $value
2141
   *
2142
   * @return $this|ORM
2143
   */
2144 2
  public function having_not_equal($column_name, $value = null)
2145
  {
2146 2
    return $this->_add_simple_having($column_name, '!=', $value);
2147
  }
2148
2149
  /**
2150
   * Special method to query the table by its primary key.
2151
   *
2152
   * If primary key is compound, only the columns that
2153
   * belong to they key will be used for the query
2154
   *
2155
   * @param $id
2156
   *
2157
   * @return $this|ORM
2158
   */
2159 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...
2160
  {
2161
    if (is_array($this->_get_id_column_name())) {
2162
      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...
2163
    } else {
2164
      return $this->having($this->_get_id_column_name(), $id);
2165
    }
2166
  }
2167
2168
  /**
2169
   * Add a HAVING ... LIKE clause to your query.
2170
   *
2171
   * @param string $column_name
2172
   * @param mixed  $value
2173
   *
2174
   * @return $this|ORM
2175
   */
2176 2
  public function having_like($column_name, $value = null)
2177
  {
2178 2
    return $this->_add_simple_having($column_name, 'LIKE', $value);
2179
  }
2180
2181
  /**
2182
   * Add where HAVING ... NOT LIKE clause to your query.
2183
   *
2184
   * @param string $column_name
2185
   * @param mixed  $value
2186
   *
2187
   * @return $this|ORM
2188
   */
2189 2
  public function having_not_like($column_name, $value = null)
2190
  {
2191 2
    return $this->_add_simple_having($column_name, 'NOT LIKE', $value);
2192
  }
2193
2194
  /**
2195
   * Add a HAVING ... > clause to your query
2196
   *
2197
   * @param string $column_name
2198
   * @param mixed  $value
2199
   *
2200
   * @return $this|ORM
2201
   */
2202 2
  public function having_gt($column_name, $value = null)
2203
  {
2204 2
    return $this->_add_simple_having($column_name, '>', $value);
2205
  }
2206
2207
  /**
2208
   * Add a HAVING ... < clause to your query
2209
   *
2210
   * @param string $column_name
2211
   * @param mixed  $value
2212
   *
2213
   * @return $this|ORM
2214
   */
2215 2
  public function having_lt($column_name, $value = null)
2216
  {
2217 2
    return $this->_add_simple_having($column_name, '<', $value);
2218
  }
2219
2220
  /**
2221
   * Add a HAVING ... >= clause to your query
2222
   *
2223
   * @param string $column_name
2224
   * @param mixed  $value
2225
   *
2226
   * @return $this|ORM
2227
   */
2228 2
  public function having_gte($column_name, $value = null)
2229
  {
2230 2
    return $this->_add_simple_having($column_name, '>=', $value);
2231
  }
2232
2233
  /**
2234
   * Add a HAVING ... <= clause to your query
2235
   *
2236
   * @param string $column_name
2237
   * @param mixed  $value
2238
   *
2239
   * @return $this|ORM
2240
   */
2241 2
  public function having_lte($column_name, $value = null)
2242
  {
2243 2
    return $this->_add_simple_having($column_name, '<=', $value);
2244
  }
2245
2246
  /**
2247
   * Add a HAVING ... IN clause to your query
2248
   *
2249
   * @param string $column_name
2250
   * @param mixed  $values
2251
   *
2252
   * @return $this|ORM
2253
   */
2254 2
  public function having_in($column_name, $values = null)
2255
  {
2256 2
    return $this->_add_having_placeholder($column_name, 'IN', $values);
2257
  }
2258
2259
  /**
2260
   * Add a HAVING ... NOT IN clause to your query
2261
   *
2262
   * @param string $column_name
2263
   * @param mixed  $values
2264
   *
2265
   * @return $this|ORM
2266
   */
2267 2
  public function having_not_in($column_name, $values = null)
2268
  {
2269 2
    return $this->_add_having_placeholder($column_name, 'NOT IN', $values);
2270
  }
2271
2272
  /**
2273
   * Add a HAVING column IS NULL clause to your query
2274
   *
2275
   * @param string $column_name
2276
   *
2277
   * @return $this|ORM
2278
   */
2279 2
  public function having_null($column_name)
2280
  {
2281 2
    return $this->_add_having_no_value($column_name, 'IS NULL');
2282
  }
2283
2284
  /**
2285
   * Add a HAVING column IS NOT NULL clause to your query
2286
   *
2287
   * @param string $column_name
2288
   *
2289
   * @return $this|ORM
2290
   */
2291 2
  public function having_not_null($column_name)
2292
  {
2293 2
    return $this->_add_having_no_value($column_name, 'IS NOT NULL');
2294
  }
2295
2296
  /**
2297
   * Add a raw HAVING clause to the query. The clause should
2298
   * contain question mark placeholders, which will be bound
2299
   * to the parameters supplied in the second argument.
2300
   *
2301
   * @param string $clause
2302
   * @param array  $parameters
2303
   *
2304
   * @return $this|ORM
2305
   */
2306 2
  public function having_raw($clause, $parameters = array())
2307
  {
2308 2
    return $this->_add_having($clause, $parameters);
2309
  }
2310
2311
  /**
2312
   * Activate cache refreshing for current query
2313
   *
2314
   * @return ORM
2315
   */
2316
  public function refreshCache()
2317
  {
2318
    $this->_refresh_cache = true;
2319
2320
    return $this;
2321
  }
2322
2323
  /**
2324
   * Disable caching for current query
2325
   *
2326
   * @return ORM
2327
   */
2328
  public function noCaching()
2329
  {
2330
    $this->_no_caching = true;
2331
2332
    return $this;
2333
  }
2334
2335
  /**
2336
   * Build a SELECT statement based on the clauses that have
2337
   * been passed to this instance by chaining method calls.
2338
   */
2339 199
  protected function _build_select()
2340
  {
2341
    // If the query is raw, just set the $this->_values to be
2342
    // the raw query parameters and return the raw query
2343 199
    if ($this->_is_raw_query) {
2344 4
      $this->_values = $this->_raw_parameters;
2345
2346 4
      return $this->_raw_query;
2347
    }
2348
2349
    // Build and return the full SELECT statement by concatenating
2350
    // the results of calling each separate builder method.
2351 195
    return $this->_join_if_not_empty(
2352 195
        ' ',
2353
        array(
2354 195
            $this->_build_select_start(),
2355 195
            $this->_build_join(),
2356 195
            $this->_build_where(),
2357 195
            $this->_build_group_by(),
2358 195
            $this->_build_having(),
2359 195
            $this->_build_order_by(),
2360 195
            $this->_build_limit(),
2361 195
            $this->_build_offset(),
2362
        )
2363 195
    );
2364
  }
2365
2366
  /**
2367
   * Build the start of the SELECT statement
2368
   */
2369 195
  protected function _build_select_start()
2370
  {
2371 195
    $fragment = 'SELECT ';
2372 195
    $result_columns = implode(', ', $this->_result_columns);
2373
2374
    if (
2375 195
        null !== $this->_limit
2376 195
        &&
2377 95
        static::$_config[$this->_connection_name]['limit_clause_style'] === self::LIMIT_STYLE_TOP_N
2378 195
    ) {
2379 2
      $fragment .= "TOP {$this->_limit} ";
2380 2
    }
2381
2382 195
    if ($this->_distinct) {
2383 2
      $result_columns = 'DISTINCT ' . $result_columns;
2384 2
    }
2385
2386 195
    $fragment .= "{$result_columns} FROM " . $this->_quote_identifier($this->_table_name);
2387
2388 195
    if (null !== $this->_table_alias) {
2389 4
      $fragment .= ' ' . $this->_quote_identifier($this->_table_alias);
2390 4
    }
2391
2392 195
    return $fragment;
2393
  }
2394
2395
  /**
2396
   * Build the JOIN sources
2397
   */
2398 195
  protected function _build_join()
2399
  {
2400 195
    if (count($this->_join_sources) === 0) {
2401 169
      return '';
2402
    }
2403
2404 26
    return implode(' ', $this->_join_sources);
2405
  }
2406
2407
  /**
2408
   * Build the WHERE clause(s)
2409
   */
2410 197
  protected function _build_where()
2411
  {
2412 197
    return $this->_build_conditions('where');
2413
  }
2414
2415
  /**
2416
   * Build the HAVING clause(s)
2417
   */
2418 195
  protected function _build_having()
2419
  {
2420 195
    return $this->_build_conditions('having');
2421
  }
2422
2423
  /**
2424
   * Build GROUP BY
2425
   */
2426 195
  protected function _build_group_by()
2427
  {
2428 195
    if (count($this->_group_by) === 0) {
2429 165
      return '';
2430
    }
2431
2432 30
    return 'GROUP BY ' . implode(', ', $this->_group_by);
2433
  }
2434
2435
  /**
2436
   * Build a WHERE or HAVING clause
2437
   *
2438
   * @param string $type
2439
   *
2440
   * @return string
2441
   */
2442 197
  protected function _build_conditions($type)
2443
  {
2444 197
    $conditions_class_property_name = "_{$type}_conditions";
2445
    // If there are no clauses, return empty string
2446 197
    if (count($this->$conditions_class_property_name) === 0) {
2447 195
      return '';
2448
    }
2449
2450 112
    $conditions = array();
2451 112
    foreach ($this->$conditions_class_property_name as $condition) {
2452 112
      $conditions[] = $condition[static::CONDITION_FRAGMENT];
2453
      /** @noinspection SlowArrayOperationsInLoopInspection */
2454 112
      $this->_values = array_merge($this->_values, $condition[static::CONDITION_VALUES]);
2455 112
    }
2456
2457 112
    return strtoupper($type) . ' ' . implode(' AND ', $conditions);
2458
  }
2459
2460
  /**
2461
   * Build ORDER BY
2462
   */
2463 195
  protected function _build_order_by()
2464
  {
2465 195
    if (count($this->_order_by) === 0) {
2466 184
      return '';
2467
    }
2468
2469 13
    $db = static::get_db(self::DEFAULT_CONNECTION);
2470
2471 13
    return 'ORDER BY ' . trim($db->quote(implode(', ', $this->_order_by)), "'");
2472
  }
2473
2474
  /**
2475
   * Build LIMIT
2476
   */
2477 195
  protected function _build_limit()
2478
  {
2479
    // init
2480 195
    $fragment = '';
2481
2482
    if (
2483 195
        null !== $this->_limit
2484 195
        &&
2485 95
        static::$_config[$this->_connection_name]['limit_clause_style'] == self::LIMIT_STYLE_LIMIT
2486 195
    ) {
2487 93
      if (static::get_db($this->_connection_name)->getAttribute(\PDO::ATTR_DRIVER_NAME) == 'firebird') {
2488
        $fragment = 'ROWS';
2489
      } else {
2490 93
        $fragment = 'LIMIT';
2491
      }
2492
2493 93
      $this->_limit = (int)$this->_limit;
2494
2495 93
      $fragment .= " {$this->_limit}";
2496 93
    }
2497
2498 195
    return $fragment;
2499
  }
2500
2501
  /**
2502
   * Build OFFSET
2503
   */
2504 195
  protected function _build_offset()
2505
  {
2506 195
    if (null !== $this->_offset) {
2507 4
      $clause = 'OFFSET';
2508 4
      if (static::get_db($this->_connection_name)->getAttribute(\PDO::ATTR_DRIVER_NAME) == 'firebird') {
2509
        $clause = 'TO';
2510
      }
2511
2512 4
      $this->_offset = (int)$this->_offset;
2513
2514 4
      return "$clause " . $this->_offset;
2515
    }
2516
2517 191
    return '';
2518
  }
2519
2520
  /**
2521
   * Wrapper around PHP's join function which
2522
   * only adds the pieces if they are not empty.
2523
   *
2524
   * @param string $glue
2525
   * @param array  $pieces
2526
   *
2527
   * @return string
2528
   */
2529 197
  protected function _join_if_not_empty($glue, $pieces)
2530
  {
2531 197
    $filtered_pieces = array();
2532 197
    foreach ($pieces as $piece) {
2533 197
      if (is_string($piece)) {
2534 197
        $piece = trim($piece);
2535 197
      }
2536 197
      if (!empty($piece)) {
2537 197
        $filtered_pieces[] = $piece;
2538 197
      }
2539 197
    }
2540
2541 197
    return implode($glue, $filtered_pieces);
2542
  }
2543
2544
  /**
2545
   * Quote a string that is used as an identifier
2546
   * (table names, column names etc). This method can
2547
   * also deal with dot-separated identifiers eg table.column
2548
   *
2549
   * @param string $identifier
2550
   *
2551
   * @return string
2552
   */
2553 207
  protected function _quote_one_identifier($identifier)
2554
  {
2555 207
    $parts = explode('.', $identifier);
2556 207
    $parts = array_map(array($this, '_quote_identifier_part'), $parts);
2557
2558 207
    return implode('.', $parts);
2559
  }
2560
2561
  /**
2562
   * Quote a string that is used as an identifier
2563
   * (table names, column names etc) or an array containing
2564
   * multiple identifiers. This method can also deal with
2565
   * dot-separated identifiers eg table.column
2566
   *
2567
   * @param array|string $identifier
2568
   *
2569
   * @return string
2570
   */
2571 207
  protected function _quote_identifier($identifier)
2572
  {
2573 207
    if (is_array($identifier)) {
2574 1
      $result = array_map(array($this, '_quote_one_identifier'), $identifier);
2575
2576 1
      return implode(', ', $result);
2577
    } else {
2578 206
      return $this->_quote_one_identifier($identifier);
2579
    }
2580
  }
2581
2582
  /**
2583
   * This method performs the actual quoting of a single
2584
   * part of an identifier, using the identifier quote
2585
   * character specified in the config (or autodetected).
2586
   *
2587
   * @param string $part
2588
   *
2589
   * @return string
2590
   */
2591 207
  protected function _quote_identifier_part($part)
2592
  {
2593 207
    if ($part === '*') {
2594 2
      return $part;
2595
    }
2596
2597 207
    $quote_character = static::$_config[$this->_connection_name]['identifier_quote_character'];
2598
2599
    // double up any identifier quotes to escape them
2600 207
    return $quote_character . str_replace($quote_character, $quote_character . $quote_character, $part) . $quote_character;
2601
  }
2602
2603
  /**
2604
   * Create a cache key for the given query and parameters.
2605
   *
2606
   * @param string      $query
2607
   * @param array       $parameters
2608
   * @param null|string $table_name
2609
   * @param string      $connection_name
2610
   *
2611
   * @return mixed|string
2612
   */
2613 3
  protected static function _create_cache_key($query, $parameters, $table_name = null, $connection_name = self::DEFAULT_CONNECTION)
2614
  {
2615 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...
2616 3
        isset(static::$_config[$connection_name]['create_cache_key']) === true
2617 3
        &&
2618 1
        is_callable(static::$_config[$connection_name]['create_cache_key']) === true
2619 3
    ) {
2620 1
      return call_user_func_array(
2621 1
          static::$_config[$connection_name]['create_cache_key'],
2622
          array(
2623 1
              $query,
2624 1
              $parameters,
2625 1
              $table_name,
2626 1
              $connection_name,
2627
          )
2628 1
      );
2629
    }
2630 2
    $parameter_string = implode(',', $parameters);
2631 2
    $key = $query . ':' . $parameter_string;
2632
2633 2
    return sha1($key);
2634
  }
2635
2636
  /**
2637
   * Check the query cache for the given cache key. If a value
2638
   * is cached for the key, return the value. Otherwise, return false.
2639
   *
2640
   * @param string      $cache_key
2641
   * @param null|string $table_name
2642
   * @param string      $connection_name
2643
   *
2644
   * @return bool|mixed
2645
   */
2646 3
  protected static function _check_query_cache($cache_key, $table_name = null, $connection_name = self::DEFAULT_CONNECTION)
2647
  {
2648
    if (
2649 3
        isset(static::$_config[$connection_name]['check_query_cache']) === true
2650 3
        &&
2651 1
        is_callable(static::$_config[$connection_name]['check_query_cache']) === true
2652 3
    ) {
2653 1
      return call_user_func_array(
2654 1
          static::$_config[$connection_name]['check_query_cache'],
2655
          array(
2656 1
              $cache_key,
2657 1
              $table_name,
2658 1
              $connection_name,
2659
          )
2660 1
      );
2661 2
    } elseif (isset(static::$_query_cache[$connection_name][$cache_key])) {
2662 2
      return static::$_query_cache[$connection_name][$cache_key];
2663
    }
2664
2665 2
    return false;
2666
  }
2667
2668
  /**
2669
   * Clear the query cache
2670
   *
2671
   * @param null|string $table_name
2672
   * @param string      $connection_name
2673
   *
2674
   * @return bool|mixed
2675
   */
2676 1
  public static function clear_cache($table_name = null, $connection_name = self::DEFAULT_CONNECTION)
2677
  {
2678
    // init
2679 1
    static::$_query_cache = array();
2680
2681 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...
2682 1
        isset(static::$_config[$connection_name]['clear_cache']) === true
2683 1
        &&
2684 1
        is_callable(static::$_config[$connection_name]['clear_cache']) === true
2685 1
    ) {
2686 1
      return call_user_func_array(
2687 1
          static::$_config[$connection_name]['clear_cache'],
2688
          array(
2689 1
              $table_name,
2690 1
              $connection_name,
2691
          )
2692 1
      );
2693
    }
2694
2695
    return false;
2696
  }
2697
2698
  /**
2699
   * Add the given value to the query cache.
2700
   *
2701
   * @param string      $cache_key
2702
   * @param string      $value
2703
   * @param null|string $table_name
2704
   * @param string      $connection_name
2705
   *
2706
   * @return bool|mixed
2707
   */
2708 3
  protected static function _cache_query_result($cache_key, $value, $table_name = null, $connection_name = self::DEFAULT_CONNECTION)
2709
  {
2710
    if (
2711 3
        isset(static::$_config[$connection_name]['cache_query_result']) === true
2712 3
        &&
2713 1
        is_callable(static::$_config[$connection_name]['cache_query_result']) === true
2714 3
    ) {
2715
2716 1
      return call_user_func_array(
2717 1
          static::$_config[$connection_name]['cache_query_result'],
2718
          array(
2719 1
              $cache_key,
2720 1
              $value,
2721 1
              $table_name,
2722 1
              $connection_name,
2723
          )
2724 1
      );
2725
2726 2
    } elseif (!isset(static::$_query_cache[$connection_name])) {
2727 2
      static::$_query_cache[$connection_name] = array();
2728 2
    }
2729
2730 2
    static::$_query_cache[$connection_name][$cache_key] = $value;
2731
2732 2
    return true;
2733
  }
2734
2735
  /**
2736
   * Execute the SELECT query that has been built up by chaining methods
2737
   * on this class. Return an array of rows as associative arrays.
2738
   */
2739 199
  protected function _run()
2740
  {
2741
    // init
2742 199
    $cache_key = false;
2743
2744 199
    $query = $this->_build_select();
2745 199
    $caching_enabled = static::$_config[$this->_connection_name]['caching'];
2746
2747 199
    if ($caching_enabled && !$this->_no_caching) {
2748 3
      $cache_key = static::_create_cache_key($query, $this->_values, $this->_table_name, $this->_connection_name);
2749
2750 3
      if (!$this->_refresh_cache) {
2751 3
        $cached_result = static::_check_query_cache($cache_key, $this->_table_name, $this->_connection_name);
2752
2753 3
        if ($cached_result !== false) {
2754 3
          return $cached_result;
2755
        }
2756 3
      }
2757 3
    }
2758
2759 199
    static::_execute($query, $this->_values, $this->_connection_name);
2760 199
    $statement = static::get_last_statement();
2761
2762 199
    $rows = array();
2763 199
    while ($row = $statement->fetch(\PDO::FETCH_ASSOC)) {
2764 196
      $rows[] = $row;
2765 196
    }
2766
2767 199
    if ($cache_key) {
2768 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...
2769 3
    }
2770
2771
    // reset Idiorm after executing the query
2772 199
    $this->_values = array();
2773 199
    $this->_result_columns = array('*');
2774 199
    $this->_using_default_result_columns = true;
2775
2776 199
    return $rows;
2777
  }
2778
2779
  /**
2780
   * Return the raw data wrapped by this ORM
2781
   * instance as an associative array. Column
2782
   * names may optionally be supplied as arguments,
2783
   * if so, only those keys will be returned.
2784
   */
2785 2
  public function as_array()
2786
  {
2787 2
    if (func_num_args() === 0) {
2788 2
      return $this->_data;
2789
    }
2790 2
    $args = func_get_args();
2791
2792 2
    return array_intersect_key($this->_data, array_flip($args));
2793
  }
2794
2795
  /**
2796
   * Return the value of a property of this object (database row)
2797
   * or null if not present.
2798
   *
2799
   * If a column-names array is passed, it will return a associative array
2800
   * with the value of each column or null if it is not present.
2801
   *
2802
   * @param mixed $key
2803
   *
2804
   * @return mixed
2805
   */
2806 35
  public function get($key)
2807
  {
2808 35
    if (is_array($key)) {
2809 4
      $result = array();
2810 4
      foreach ($key as $column) {
2811 4
        $result[$column] = isset($this->_data[$column]) ? $this->_data[$column] : null;
2812 4
      }
2813
2814 4
      return $result;
2815
    } else {
2816 31
      return isset($this->_data[$key]) ? $this->_data[$key] : null;
2817
    }
2818
  }
2819
2820
  /**
2821
   * Return the name of the column in the database table which contains
2822
   * the primary key ID of the row.
2823
   */
2824 50
  protected function _get_id_column_name()
2825
  {
2826 50
    if (null !== $this->_instance_id_column) {
2827 13
      return $this->_instance_id_column;
2828
    }
2829
2830 37
    if (isset(static::$_config[$this->_connection_name]['id_column_overrides'][$this->_table_name])) {
2831 2
      return static::$_config[$this->_connection_name]['id_column_overrides'][$this->_table_name];
2832
    }
2833
2834 35
    return static::$_config[$this->_connection_name]['id_column'];
2835
  }
2836
2837
  /**
2838
   * Get the primary key ID of this object.
2839
   *
2840
   * @param bool $disallow_null
2841
   *
2842
   * @return mixed
2843
   *
2844
   * @throws \Exception
2845
   */
2846 33
  public function id($disallow_null = false)
2847
  {
2848 33
    $id = $this->get($this->_get_id_column_name());
2849
2850 33
    if ($disallow_null) {
2851 23
      if (is_array($id)) {
2852 3
        foreach ($id as $id_part) {
2853 3
          if ($id_part === null) {
2854 1
            throw new \Exception('Primary key ID contains null value(s)');
2855
          }
2856 3
        }
2857 22
      } elseif ($id === null) {
2858 3
        throw new \Exception('Primary key ID missing from row or is null');
2859
      }
2860 19
    }
2861
2862 29
    return $id;
2863
  }
2864
2865
  /**
2866
   * Set a property to a particular value on this object.
2867
   * To set multiple properties at once, pass an associative array
2868
   * as the first parameter and leave out the second parameter.
2869
   * Flags the properties as 'dirty' so they will be saved to the
2870
   * database when save() is called.
2871
   *
2872
   * @param mixed $key
2873
   * @param mixed $value
2874
   *
2875
   * @return $this|ORM
2876
   */
2877 27
  public function set($key, $value = null)
2878
  {
2879 27
    return $this->_set_orm_property($key, $value);
2880
  }
2881
2882
  /**
2883
   * Set a property to a particular value on this object.
2884
   * To set multiple properties at once, pass an associative array
2885
   * as the first parameter and leave out the second parameter.
2886
   * Flags the properties as 'dirty' so they will be saved to the
2887
   * database when save() is called.
2888
   *
2889
   * @param string|array $key
2890
   * @param string|null  $value
2891
   *
2892
   * @return ORM
2893
   */
2894 10
  public function set_expr($key, $value = null)
2895
  {
2896 10
    return $this->_set_orm_property($key, $value, true);
2897
  }
2898
2899
  /**
2900
   * Set a property on the ORM object.
2901
   *
2902
   * @param  string|array $key
2903
   * @param string|null   $value
2904
   * @param bool          $expr
2905
   *
2906
   * @return $this
2907
   */
2908 29
  protected function _set_orm_property($key, $value = null, $expr = false)
2909
  {
2910 29
    if (!is_array($key)) {
2911 25
      $key = array($key => $value);
2912 25
    }
2913
2914
    /** @noinspection SuspiciousLoopInspection */
2915 29
    foreach ($key as $field => $value) {
2916 29
      $this->_data[$field] = $value;
2917 29
      $this->_dirty_fields[$field] = $value;
2918 29
      if (false === $expr && isset($this->_expr_fields[$field])) {
2919 2
        unset($this->_expr_fields[$field]);
2920 29
      } elseif (true === $expr) {
2921 10
        $this->_expr_fields[$field] = true;
2922 10
      }
2923 29
    }
2924
2925 29
    return $this;
2926
  }
2927
2928
  /**
2929
   * Check whether the given field has been changed since this
2930
   * object was saved.
2931
   *
2932
   * @param string $key
2933
   *
2934
   * @return bool
2935
   */
2936 1
  public function is_dirty($key)
2937
  {
2938 1
    return array_key_exists($key, $this->_dirty_fields);
2939
  }
2940
2941
  /**
2942
   * Check whether the model was the result of a call to create() or not
2943
   *
2944
   * @return bool
2945
   */
2946 2
  public function is_new()
2947
  {
2948 2
    return $this->_is_new;
2949
  }
2950
2951
  /**
2952
   * Save any fields which have been modified on this object
2953
   * to the database.
2954
   *
2955
   * @return bool
2956
   *
2957
   * @throws \Exception
2958
   */
2959 28
  public function save()
2960
  {
2961
    // remove any expression fields as they are already baked into the query
2962 28
    $values = array_values(array_diff_key($this->_dirty_fields, $this->_expr_fields));
2963
2964 28
    if (!$this->_is_new) {
2965
2966
      // UPDATE
2967
2968
      // If there are no dirty values, do nothing
2969
      if (
2970 17
          empty($values)
2971 17
          &&
2972 2
          0 === count($this->_expr_fields)
2973 17
      ) {
2974
        return true;
2975
      }
2976
2977 17
      $query = $this->_build_update();
2978 17
      $id = $this->id(true);
2979
2980 16
      if (is_array($id)) {
2981 1
        $values = array_merge($values, array_values($id));
2982 1
      } else {
2983 15
        $values[] = $id;
2984
      }
2985
2986 16
    } else {
2987
2988
      // INSERT
2989 12
      $query = $this->_build_insert();
2990
    }
2991
2992 27
    $success = static::_execute($query, $values, $this->_connection_name);
2993 27
    $caching_auto_clear_enabled = static::$_config[$this->_connection_name]['caching_auto_clear'];
2994
2995 27
    if ($caching_auto_clear_enabled) {
2996 1
      static::clear_cache($this->_table_name, $this->_connection_name);
2997 1
    }
2998
2999
    // If we've just inserted a new record, set the ID of this object
3000 27
    if ($success && $this->_is_new) {
3001
3002 12
      $this->_is_new = false;
3003 12
      if ($this->count_null_id_columns() != 0) {
3004 9
        $db = static::get_db($this->_connection_name);
3005
3006 9
        if ($db->getAttribute(\PDO::ATTR_DRIVER_NAME) == 'pgsql') {
3007
3008
          // it may return several columns if a compound primary
3009
          // key is used
3010
          $row = static::get_last_statement()->fetch(\PDO::FETCH_ASSOC);
3011
          foreach ($row as $key => $value) {
3012
            $this->_data[$key] = $value;
3013
          }
3014
3015
        } else {
3016 9
          $column = $this->_get_id_column_name();
3017
          // if the primary key is compound, assign the last inserted id
3018
          // to the first column
3019 9
          if (is_array($column)) {
3020
            $column = reset($column);
3021
          }
3022 9
          $this->_data[$column] = $db->lastInsertId();
3023
        }
3024 9
      }
3025 12
    }
3026
3027 27
    $this->_dirty_fields = $this->_expr_fields = array();
3028
3029 27
    return $success;
3030
  }
3031
3032
  /**
3033
   * Add a WHERE clause for every column that belongs to the primary key
3034
   *
3035
   * @param array $query warning: this is a reference
3036
   */
3037 21
  public function _add_id_column_conditions(&$query)
3038
  {
3039 21
    $query[] = 'WHERE';
3040
3041 21
    if (is_array($this->_get_id_column_name())) {
3042 2
      $keys = $this->_get_id_column_name();
3043 2
    } else {
3044 19
      $keys = array($this->_get_id_column_name());
3045
    }
3046
3047 21
    $first = true;
3048 21
    foreach ($keys as $key) {
3049
3050 21
      if ($first) {
3051 21
        $first = false;
3052 21
      } else {
3053 2
        $query[] = 'AND';
3054
      }
3055
3056 21
      $query[] = $this->_quote_identifier($key);
3057 21
      $query[] = '= ?';
3058 21
    }
3059 21
  }
3060
3061
  /**
3062
   * Build an UPDATE query
3063
   */
3064 17
  protected function _build_update()
3065
  {
3066 17
    $query = array();
3067 17
    $query[] = "UPDATE {$this->_quote_identifier($this->_table_name)} SET";
3068
3069 17
    $field_list = array();
3070 17
    foreach ($this->_dirty_fields as $key => $value) {
3071
3072 17
      if (!array_key_exists($key, $this->_expr_fields)) {
3073 15
        $value = '?';
3074 15
      }
3075
3076 17
      $field_list[] = "{$this->_quote_identifier($key)} = $value";
3077 17
    }
3078
3079 17
    $query[] = implode(', ', $field_list);
3080 17
    $this->_add_id_column_conditions($query);
3081
3082 17
    return implode(' ', $query);
3083
  }
3084
3085
  /**
3086
   * Build an INSERT query
3087
   */
3088 12
  protected function _build_insert()
3089
  {
3090 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...
3091 12
    $query[] = $this->_quote_identifier($this->_table_name);
3092 12
    $field_list = array_map(array($this, '_quote_identifier'), array_keys($this->_dirty_fields));
3093 12
    $query[] = '(' . implode(', ', $field_list) . ')';
3094 12
    $query[] = 'VALUES';
3095
3096 12
    $placeholders = $this->_create_placeholders($this->_dirty_fields);
3097 12
    $query[] = "({$placeholders})";
3098
3099 12
    if (static::get_db($this->_connection_name)->getAttribute(\PDO::ATTR_DRIVER_NAME) == 'pgsql') {
3100
      $query[] = 'RETURNING ' . $this->_quote_identifier($this->_get_id_column_name());
3101
    }
3102
3103 12
    return implode(' ', $query);
3104
  }
3105
3106
  /**
3107
   * Delete this record from the database
3108
   */
3109 4
  public function delete()
3110
  {
3111
    $query = array(
3112 4
        'DELETE FROM',
3113 4
        $this->_quote_identifier($this->_table_name),
3114 4
    );
3115 4
    $this->_add_id_column_conditions($query);
3116
3117 4
    return static::_execute(
3118 4
        implode(' ', $query), is_array($this->id(true)) ?
3119 3
        array_values($this->id(true)) :
3120 3
        array($this->id(true)), $this->_connection_name
3121 3
    );
3122
  }
3123
3124
  /**
3125
   * Delete many records from the database
3126
   */
3127 2
  public function delete_many()
3128
  {
3129
    // Build and return the full DELETE statement by concatenating
3130
    // the results of calling each separate builder method.
3131 2
    $query = $this->_join_if_not_empty(
3132 2
        ' ',
3133
        array(
3134 2
            'DELETE FROM',
3135 2
            $this->_quote_identifier($this->_table_name),
3136 2
            $this->_build_where(),
3137
        )
3138 2
    );
3139
3140 2
    return static::_execute($query, $this->_values, $this->_connection_name);
3141
  }
3142
3143
  // --------------------- //
3144
  // ---  ArrayAccess  --- //
3145
  // --------------------- //
3146
3147 14
  public function offsetExists($key)
3148
  {
3149 14
    return array_key_exists($key, $this->_data);
3150
  }
3151
3152 4
  public function offsetGet($key)
3153
  {
3154 4
    return $this->get($key);
3155
  }
3156
3157 16
  public function offsetSet($key, $value)
3158
  {
3159 16
    if (null === $key) {
3160
      throw new \InvalidArgumentException('You must specify a key/array index.');
3161
    }
3162 16
    $this->set($key, $value);
3163 16
  }
3164
3165 1
  public function offsetUnset($key)
3166
  {
3167 1
    unset($this->_data[$key]);
3168 1
    unset($this->_dirty_fields[$key]);
3169 1
  }
3170
3171
  // --------------------- //
3172
  // --- MAGIC METHODS --- //
3173
  // --------------------- //
3174 1
  public function __get($key)
3175
  {
3176 1
    return $this->offsetGet($key);
3177
  }
3178
3179 13
  public function __set($key, $value)
3180
  {
3181 13
    $this->offsetSet($key, $value);
3182 13
  }
3183
3184
  public function __unset($key)
3185
  {
3186
    $this->offsetUnset($key);
3187
  }
3188
3189 13
  public function __isset($key)
3190
  {
3191 13
    return $this->offsetExists($key);
3192
  }
3193
3194
  /**
3195
   * Magic method to capture calls to undefined class methods.
3196
   * In this case we are attempting to convert camel case formatted
3197
   * methods into underscore formatted methods.
3198
   *
3199
   * This allows us to call ORM methods using camel case and remain
3200
   * backwards compatible.
3201
   *
3202
   * @param  string $name
3203
   * @param  array  $arguments
3204
   *
3205
   * @return ORM
3206
   *
3207
   * @throws IdiormMethodMissingException
3208
   */
3209 80
  public function __call($name, $arguments)
3210
  {
3211 80
    $method = strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $name));
3212
3213 80 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...
3214 79
      return call_user_func_array(array($this, $method), $arguments);
3215
    } else {
3216 1
      throw new IdiormMethodMissingException("Method $name() does not exist in class " . get_class($this));
3217
    }
3218
  }
3219
3220
  /**
3221
   * Magic method to capture calls to undefined static class methods.
3222
   * In this case we are attempting to convert camel case formatted
3223
   * methods into underscore formatted methods.
3224
   *
3225
   * This allows us to call ORM methods using camel case and remain
3226
   * backwards compatible.
3227
   *
3228
   * @param  string $name
3229
   * @param  array  $arguments
3230
   *
3231
   * @return ORM
3232
   */
3233 86
  public static function __callStatic($name, $arguments)
3234
  {
3235 86
    $method = strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $name));
3236
3237 86
    return call_user_func_array(array('idiorm\orm\ORM', $method), $arguments);
3238
  }
3239
3240
}
3241