Completed
Push — master ( 345dfa...b1aa1a )
by Lars
02:54
created

ORM::as_json()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
ccs 0
cts 0
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 1
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|array $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 string|array
345
   */
346 3
  public static function get_config($key = null, $connection_name = self::DEFAULT_CONNECTION)
347
  {
348 3
    if ($key) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $key of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
349 2
      return static::$_config[$connection_name][$key];
350
    } else {
351 1
      return static::$_config[$connection_name];
352
    }
353
  }
354
355
  /**
356
   * Delete all configs in _config array.
357
   */
358 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 static
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 null|array $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|float
929
   */
930 2
  public function min($column)
931
  {
932 2
    return $this->_call_aggregate_db_function(__FUNCTION__, $column);
933
  }
934
935
  /**
936
   * Tell the ORM that you wish to execute a AVG query.
937
   * Will return the average value of the choosen column.
938
   *
939
   * @param string $column
940
   *
941
   * @return float
942
   */
943 2
  public function avg($column)
944
  {
945 2
    return $this->_call_aggregate_db_function(__FUNCTION__, $column);
946
  }
947
948
  /**
949
   * Tell the ORM that you wish to execute a SUM query.
950
   * Will return the sum of the choosen column.
951
   *
952
   * @param string $column
953
   *
954
   * @return int|float
955
   */
956 2
  public function sum($column)
957
  {
958 2
    return $this->_call_aggregate_db_function(__FUNCTION__, $column);
959
  }
960
961
  /**
962
   * Execute an aggregate query on the current connection.
963
   *
964
   * @param string $sql_function The aggregate function to call eg. MIN, COUNT, etc
965
   * @param string $column       The column to execute the aggregate query against
966
   *
967
   * @return float|int|mixed
968
   */
969 12
  protected function _call_aggregate_db_function($sql_function, $column)
970
  {
971 12
    $alias = strtolower($sql_function);
972 12
    $sql_function = strtoupper($sql_function);
973 12
    if ('*' != $column) {
974 8
      $column = $this->_quote_identifier($column);
975 8
    }
976 12
    $result_columns = $this->_result_columns;
977 12
    $this->_result_columns = array();
978 12
    $this->select_expr("$sql_function($column)", $alias);
979 12
    $result = $this->find_one();
980 12
    $this->_result_columns = $result_columns;
981
982 12
    $return_value = 0;
983 12
    if ($result !== false && isset($result->$alias)) {
984
      if (!is_numeric($result->$alias)) {
985
        $return_value = $result->$alias;
986
      } elseif ((int)$result->$alias == (float)$result->$alias) {
987
        $return_value = (int)$result->$alias;
988
      } else {
989
        $return_value = (float)$result->$alias;
990
      }
991
    }
992
993 12
    return $return_value;
994
  }
995
996
  /**
997
   * This method can be called to hydrate (populate) this
998
   * instance of the class from an associative array of data.
999
   * This will usually be called only from inside the class,
1000
   * but it's public in case you need to call it directly.
1001
   *
1002
   * @param array $data
1003
   *
1004
   * @return $this
1005
   */
1006 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
   * @return $this
1018
   */
1019 6
  public function force_all_dirty()
1020
  {
1021 6
    $this->_dirty_fields = $this->_data;
1022
1023
    return $this;
1024
  }
1025
1026
  /**
1027
   * Perform a raw query. The query can contain placeholders in
1028
   * either named or question mark style. If placeholders are
1029
   * used, the parameters should be an array of values which will
1030
   * be bound to the placeholders in the query. If this method
1031
   * is called, all other query building methods will be ignored.
1032
   *
1033
   * @param string $query
1034
   * @param array  $parameters
1035
   *
1036 4
   * @return $this
1037
   */
1038 4
  public function raw_query($query, $parameters = array())
1039 4
  {
1040 4
    $this->_is_raw_query = true;
1041
    $this->_raw_query = $query;
1042 4
    $this->_raw_parameters = $parameters;
1043
1044
    return $this;
1045
  }
1046
1047
  /**
1048
   * Add an alias for the main table to be used in SELECT queries
1049
   *
1050
   * @param string $alias
1051
   *
1052 4
   * @return $this
1053
   */
1054 4
  public function table_alias($alias)
1055
  {
1056 4
    $this->_table_alias = $alias;
1057
1058
    return $this;
1059
  }
1060
1061
  /**
1062
   * Internal method to add an unquoted expression to the set
1063
   * of columns returned by the SELECT query. The second optional
1064
   * argument is the alias to return the expression as.
1065
   *
1066
   * @param string $expr
1067
   * @param mixed  $alias
1068
   *
1069 34
   * @return $this
1070
   */
1071 34
  protected function _add_result_column($expr, $alias = null)
1072 20
  {
1073 20
    if (null !== $alias) {
1074
      $expr .= ' AS ' . $this->_quote_identifier($alias);
1075 34
    }
1076 34
1077 34
    if ($this->_using_default_result_columns) {
1078 34
      $this->_result_columns = array($expr);
1079 8
      $this->_using_default_result_columns = false;
1080
    } else {
1081
      $this->_result_columns[] = $expr;
1082 34
    }
1083
1084
    return $this;
1085
  }
1086
1087
  /**
1088
   * Counts the number of columns that belong to the primary
1089 12
   * key and their value is null.
1090
   *
1091 12
   * @return int
1092 3
   */
1093
  public function count_null_id_columns()
1094 9
  {
1095
    if (is_array($this->_get_id_column_name())) {
1096
      return count(array_filter($this->id(), 'is_null'));
1097
    } else {
1098
      return (null === $this->id() ? 1 : 0);
1099
    }
1100
  }
1101
1102
  /**
1103
   * Add a column to the list of columns returned by the SELECT
1104
   * query. This defaults to '*'. The second optional argument is
1105
   * the alias to return the column as.
1106
   *
1107
   * @param string $column
1108 20
   * @param mixed  $alias
1109
   *
1110 20
   * @return ORM
1111
   */
1112 20
  public function select($column, $alias = null)
1113
  {
1114
    $column = $this->_quote_identifier($column);
1115
1116
    return $this->_add_result_column($column, $alias);
1117
  }
1118
1119
  /**
1120
   * Add an unquoted expression to the list of columns returned
1121
   * by the SELECT query. The second optional argument is
1122
   * the alias to return the column as.
1123
   *
1124
   * @param string $expr
1125 16
   * @param mixed  $alias
1126
   *
1127 16
   * @return ORM
1128
   */
1129
  public function select_expr($expr, $alias = null)
1130
  {
1131
    return $this->_add_result_column($expr, $alias);
1132
  }
1133
1134
  /**
1135
   * Add columns to the list of columns returned by the SELECT
1136
   * query. This defaults to '*'. Many columns can be supplied
1137
   * as either an array or as a list of parameters to the method.
1138
   *
1139
   * Note that the alias must not be numeric - if you want a
1140
   * numeric alias then prepend it with some alpha chars. eg. a1
1141
   *
1142
   * @example select_many(array('alias' => 'column', 'column2', 'alias2' => 'column3'), 'column4', 'column5');
1143
   * @example select_many('column', 'column2', 'column3');
1144 2
   * @example select_many(array('column', 'column2', 'column3'), 'column4', 'column5');
1145
   *
1146 2
   * @return ORM
1147 2
   */
1148 2 View Code Duplication
  public function select_many()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

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

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

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

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

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

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

Loading history...
1281 20
      list($first_column, $operator, $second_column) = $constraint;
1282
      $first_column = $this->_quote_identifier($first_column);
1283 20
      $second_column = $this->_quote_identifier($second_column);
1284
      $constraint = "{$first_column} {$operator} {$second_column}";
1285
    }
1286
1287
    $this->_join_sources[] = "{$join_operator} {$table} ON {$constraint}";
1288
1289
    return $this;
1290
  }
1291
1292
  /**
1293
   * Add a RAW JOIN source to the query
1294
   *
1295
   * @param string $table
1296 6
   * @param string $constraint
1297
   * @param string $table_alias
1298
   * @param array  $parameters
1299 6
   *
1300 6
   * @return $this
1301 6
   */
1302 6
  public function raw_join($table, $constraint, $table_alias, $parameters = array())
1303
  {
1304 6
    // Add table alias if present
1305
    if (null !== $table_alias) {
1306
      $table_alias = $this->_quote_identifier($table_alias);
1307 6
      $table .= " {$table_alias}";
1308 6
    }
1309 6
1310 6
    $this->_values = array_merge($this->_values, $parameters);
1311 6
1312 6
    // Build the constraint
1313 View Code Duplication
    if (is_array($constraint)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1314 6
      list($first_column, $operator, $second_column) = $constraint;
1315
      $first_column = $this->_quote_identifier($first_column);
1316 6
      $second_column = $this->_quote_identifier($second_column);
1317
      $constraint = "{$first_column} {$operator} {$second_column}";
1318
    }
1319
1320
    $this->_join_sources[] = "{$table} ON {$constraint}";
1321
1322
    return $this;
1323
  }
1324
1325
  /**
1326
   * Add a simple JOIN source to the query
1327
   *
1328 12
   * @param string      $table
1329
   * @param string      $constraint
1330 12
   * @param string|null $table_alias
1331
   *
1332
   * @return ORM
1333
   */
1334
  public function join($table, $constraint, $table_alias = null)
1335
  {
1336
    return $this->_add_join_source('', $table, $constraint, $table_alias);
1337
  }
1338
1339
  /**
1340
   * Add an INNER JOIN souce to the query
1341
   */
1342
  /**
1343 2
   * @param string      $table
1344
   * @param string      $constraint
1345 2
   * @param null|string $table_alias
1346
   *
1347
   * @return ORM
1348
   */
1349
  public function inner_join($table, $constraint, $table_alias = null)
1350
  {
1351
    return $this->_add_join_source('INNER', $table, $constraint, $table_alias);
1352
  }
1353
1354
  /**
1355
   * Add an LEFT JOIN source to the query
1356
   *
1357
   * @param string $table
1358
   * @param        $constraint
1359
   * @param null   $table_alias
1360
   *
1361
   * @return ORM
1362
   */
1363
  public function left_join($table, $constraint, $table_alias = null)
1364
  {
1365
    return $this->_add_join_source('LEFT', $table, $constraint, $table_alias);
1366
  }
1367
1368
  /**
1369
   * Add an RIGHT JOIN source to the query
1370
   *
1371 2
   * @param string $table
1372
   * @param        $constraint
1373 2
   * @param null   $table_alias
1374
   *
1375
   * @return ORM
1376
   */
1377
  public function right_join($table, $constraint, $table_alias = null)
1378
  {
1379
    return $this->_add_join_source('RIGHT', $table, $constraint, $table_alias);
1380
  }
1381
1382
  /**
1383
   * Add a LEFT OUTER JOIN souce to the query
1384
   *
1385 2
   * @param string      $table
1386
   * @param string      $constraint
1387 2
   * @param null|string $table_alias
1388
   *
1389
   * @return ORM
1390
   */
1391
  public function left_outer_join($table, $constraint, $table_alias = null)
1392
  {
1393
    return $this->_add_join_source('LEFT OUTER', $table, $constraint, $table_alias);
1394
  }
1395
1396
  /**
1397
   * Add an RIGHT OUTER JOIN souce to the query
1398
   *
1399
   * @param string      $table
1400 2
   * @param string      $constraint
1401
   * @param null|string $table_alias
1402 2
   *
1403
   * @return ORM
1404
   */
1405
  public function right_outer_join($table, $constraint, $table_alias = null)
1406
  {
1407
    return $this->_add_join_source('RIGHT OUTER', $table, $constraint, $table_alias);
1408
  }
1409
1410
  /**
1411
   * Add an FULL OUTER JOIN souce to the query
1412
   */
1413
  /**
1414 10
   * @param string      $table
1415
   * @param string      $constraint
1416 10
   * @param null|string $table_alias
1417
   *
1418
   * @return ORM
1419
   */
1420
  public function full_outer_join($table, $constraint, $table_alias = null)
1421
  {
1422
    return $this->_add_join_source('FULL OUTER', $table, $constraint, $table_alias);
1423
  }
1424
1425
  /**
1426
   * Internal method to add a HAVING condition to the query
1427
   */
1428
  /**
1429 14
   * @param string $fragment
1430
   * @param array  $values
1431 14
   *
1432
   * @return ORM
1433
   */
1434
  protected function _add_having($fragment, $values = array())
1435
  {
1436
    return $this->_add_condition('having', $fragment, $values);
1437
  }
1438
1439
  /**
1440
   * Internal method to add a HAVING condition to the query
1441
   */
1442
  /**
1443
   * @param string|array $column_name
1444 4
   * @param string       $separator
1445
   * @param mixed        $value
1446 4
   *
1447 4
   * @return ORM
1448 4
   */
1449
  protected function _add_simple_having($column_name, $separator, $value)
1450
  {
1451
    return $this->_add_simple_condition('having', $column_name, $separator, $value);
1452 4
  }
1453 4
1454 4
  /**
1455 4
   * Internal method to add a HAVING clause with multiple values (like IN and NOT IN)
1456 4
   */
1457 4
  /**
1458
   * @param string|array $column_name
1459 4
   * @param string       $separator
1460
   * @param mixed        $values
1461
   *
1462
   * @return ORM
1463
   */
1464 View Code Duplication
  public function _add_having_placeholder($column_name, $separator, $values)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1465
  {
1466
    if (!is_array($column_name)) {
1467
      $data = array($column_name => $values);
1468
    } else {
1469
      $data = $column_name;
1470 4
    }
1471
1472 4
    $result = $this;
1473
    foreach ($data as $key => $val) {
1474
      $column = $result->_quote_identifier($key);
1475 4
      $placeholders = $result->_create_placeholders($val);
1476
      $result = $result->_add_having("{$column} {$separator} ({$placeholders})", $val);
1477
    }
1478 4
1479 4
    return $result;
1480 4
  }
1481 4
1482 4
  /**
1483
   * Internal method to add a HAVING clause with no parameters(like IS NULL and IS NOT NULL)
1484 4
   *
1485
   * @param string|array $column_name
1486
   * @param string       $operator
1487
   *
1488
   * @return ORM
1489
   */
1490 View Code Duplication
  public function _add_having_no_value($column_name, $operator)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1491
  {
1492
    if (is_array($column_name)) {
1493
      $conditions = $column_name;
1494
    } else {
1495 27
      $conditions = array($column_name);
1496
    }
1497 27
1498
    $result = $this;
1499
    foreach ($conditions as $column) {
1500
      $column = $this->_quote_identifier($column);
1501
      $result = $result->_add_having("{$column} {$operator}");
1502
    }
1503
1504
    return $result;
1505
  }
1506
1507
  /**
1508
   * Internal method to add a WHERE condition to the query
1509 63
   *
1510
   * @param string $fragment
1511 63
   * @param array  $values
1512
   *
1513
   * @return ORM
1514
   */
1515
  protected function _add_where($fragment, $values = array())
1516
  {
1517
    return $this->_add_condition('where', $fragment, $values);
1518
  }
1519
1520
  /**
1521
   * Internal method to add a WHERE condition to the query
1522
   *
1523 5
   * @param string|array $column_name
1524
   * @param string       $separator
1525 5
   * @param mixed        $value
1526 5
   *
1527 5
   * @return ORM
1528
   */
1529
  protected function _add_simple_where($column_name, $separator, $value)
1530
  {
1531 5
    return $this->_add_simple_condition('where', $column_name, $separator, $value);
1532 5
  }
1533 5
1534 5
  /**
1535 5
   * Add a WHERE clause with multiple values (like IN and NOT IN)
1536 5
   *
1537
   * @param string|array $column_name
1538 5
   * @param string       $separator
1539
   * @param mixed        $values
1540
   *
1541
   * @return ORM
1542
   */
1543 View Code Duplication
  public function _add_where_placeholder($column_name, $separator, $values)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1544
  {
1545
    if (!is_array($column_name)) {
1546
      $data = array($column_name => $values);
1547
    } else {
1548
      $data = $column_name;
1549 4
    }
1550
1551 4
    $result = $this;
1552
    foreach ($data as $key => $val) {
1553
      $column = $result->_quote_identifier($key);
1554 4
      $placeholders = $result->_create_placeholders($val);
1555
      $result = $result->_add_where("{$column} {$separator} ({$placeholders})", $val);
1556
    }
1557 4
1558 4
    return $result;
1559 4
  }
1560 4
1561 4
  /**
1562
   * Add a WHERE clause with no parameters(like IS NULL and IS NOT NULL)
1563 4
   *
1564
   * @param string|array $column_name
1565
   * @param string       $operator
1566
   *
1567
   * @return ORM
1568
   */
1569 View Code Duplication
  public function _add_where_no_value($column_name, $operator)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1570
  {
1571
    if (is_array($column_name)) {
1572
      $conditions = $column_name;
1573
    } else {
1574
      $conditions = array($column_name);
1575 112
    }
1576
1577 112
    $result = $this;
1578
    foreach ($conditions as $column) {
1579 112
      $column = $this->_quote_identifier($column);
1580 77
      $result = $result->_add_where("{$column} {$operator}");
1581 77
    }
1582
1583 112
    return $result;
1584 112
  }
1585
1586 112
  /**
1587 112
   * Internal method to add a HAVING or WHERE condition to the query
1588
   *
1589 112
   * @param string $type
1590
   * @param string $fragment
1591 112
   * @param mixed  $values
1592
   *
1593
   * @return $this
1594
   */
1595
  protected function _add_condition($type, $fragment, $values = array())
1596
  {
1597
    $conditions_class_property_name = "_{$type}_conditions";
1598
1599
    if (!is_array($values)) {
1600
      $values = array($values);
1601
    }
1602
1603
    array_push(
1604
        $this->$conditions_class_property_name,
1605
        array(
1606
            static::CONDITION_FRAGMENT => $fragment,
1607
            static::CONDITION_VALUES   => $values,
1608
        )
1609 77
    );
1610
1611 77
    return $this;
1612 1
  }
1613 1
1614 76
  /**
1615
   * Helper method to compile a simple COLUMN SEPARATOR VALUE
1616
   * style HAVING or WHERE condition into a string and value ready to
1617 77
   * be passed to the _add_condition method. Avoids duplication
1618
   * of the call to _quote_identifier
1619 77
   *
1620
   * If column_name is an associative array, it will add a condition for each column
1621 77
   *
1622
   * @param string       $type
1623 4
   * @param string|array $column_name
1624 4
   * @param string       $separator
1625 2
   * @param string|int   $value
1626 2
   *
1627
   * @return ORM
1628 4
   */
1629 4
  protected function _add_simple_condition($type, $column_name, $separator, $value)
1630 77
  {
1631 77
    if (is_array($column_name)) {
1632 77
      $multiple = $column_name;
1633
    } else {
1634 77
      $multiple = array($column_name => $value);
1635
    }
1636
1637
    $result = $this;
1638
1639
    foreach ($multiple as $key => $val) {
1640
      // Add the table name in case of ambiguous columns
1641
      if (count($result->_join_sources) > 0 && strpos($key, '.') === false) {
1642
1643
        $table = $result->_table_name;
1644
        if (null !== $result->_table_alias) {
1645 21
          $table = $result->_table_alias;
1646
        }
1647 21
1648 21
        $key = "{$table}.{$key}";
1649 21
      }
1650
      $key = $result->_quote_identifier($key);
1651 21
      $result = $result->_add_condition($type, "{$key} {$separator} ?", $val);
1652 2
    }
1653 2
1654 21
    return $result;
1655
  }
1656 21
1657
  /**
1658 21
   * Return a string containing the given number of question marks,
1659
   * separated by commas. Eg "?, ?, ?"
1660 1
   *
1661
   * @param mixed $fields
1662
   *
1663
   * @return string
1664
   */
1665
  protected function _create_placeholders($fields)
1666
  {
1667
    if (!empty($fields)) {
1668
      $db_fields = array();
1669
      foreach ($fields as $key => $value) {
1670
        // Process expression fields directly into the query
1671
        if (array_key_exists($key, $this->_expr_fields)) {
1672
          $db_fields[] = $value;
1673
        } else {
1674
          $db_fields[] = '?';
1675 2
        }
1676
      }
1677 2
1678 2
      return implode(', ', $db_fields);
1679 2
    } else {
1680 2
      return '';
1681
    }
1682 2
  }
1683
1684
  /**
1685
   * Helper method that filters a column/value array returning only those
1686
   * columns that belong to a compound primary key.
1687
   *
1688
   * If the key contains a column that does not exist in the given array,
1689
   * a null value will be returned for it.
1690
   *
1691
   * @param mixed $value
1692
   *
1693 1
   * @return array
1694
   */
1695 1
  protected function _get_compound_id_column_values($value)
1696 1
  {
1697 1
    $filtered = array();
1698 1
    foreach ($this->_get_id_column_name() as $key) {
1699
      $filtered[$key] = isset($value[$key]) ? $value[$key] : null;
1700 1
    }
1701
1702
    return $filtered;
1703
  }
1704
1705
  /**
1706
   * Helper method that filters an array containing compound column/value
1707
   * arrays.
1708
   *
1709
   * @param array $values
1710
   *
1711
   * @return array
1712
   */
1713
  protected function _get_compound_id_column_values_array($values)
1714
  {
1715
    $filtered = array();
1716
    foreach ($values as $value) {
1717 49
      $filtered[] = $this->_get_compound_id_column_values($value);
1718
    }
1719 49
1720
    return $filtered;
1721
  }
1722
1723
  /**
1724
   * Add a WHERE column = value clause to your query. Each time
1725
   * this is called in the chain, an additional WHERE will be
1726
   * added, and these will be ANDed together when the final query
1727
   * is built.
1728
   *
1729
   * If you use an array in $column_name, a new clause will be
1730
   * added for each element. In this case, $value is ignored.
1731 53
   *
1732
   * @param string $column_name
1733 53
   * @param mixed  $value
1734
   *
1735
   * @return ORM
1736
   */
1737
  public function where($column_name, $value = null)
1738
  {
1739
    return $this->where_equal($column_name, $value);
1740
  }
1741
1742
  /**
1743
   * More explicitly named version of for the where() method.
1744 2
   * Can be used if preferred.
1745
   *
1746 2
   * @param string $column_name
1747
   * @param mixed  $value
1748
   *
1749
   * @return ORM
1750
   */
1751
  public function where_equal($column_name, $value = null)
1752
  {
1753
    return $this->_add_simple_where($column_name, '=', $value);
1754
  }
1755
1756
  /**
1757
   * Add a WHERE column != value clause to your query.
1758
   *
1759 33
   * @param string $column_name
1760
   * @param mixed  $value
1761 33
   *
1762 1
   * @return ORM
1763
   */
1764 32
  public function where_not_equal($column_name, $value = null)
1765
  {
1766
    return $this->_add_simple_where($column_name, '!=', $value);
1767
  }
1768
1769
  /**
1770
   * Special method to query the table by its primary key
1771
   *
1772
   * If primary key is compound, only the columns that
1773
   * belong to they key will be used for the query
1774
   *
1775
   * @param mixed $id
1776
   *
1777
   * @return ORM
1778
   */
1779 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...
1780
  {
1781
    if (is_array($this->_get_id_column_name())) {
1782
      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...
1783 4
    } else {
1784
      return $this->where($this->_get_id_column_name(), $id);
1785 4
    }
1786 4
  }
1787 4
1788 4
  /**
1789 4
   * Allows adding a WHERE clause that matches any of the conditions
1790 4
   * specified in the array. Each element in the associative array will
1791 4
   * be a different condition, where the key will be the column name.
1792 4
   *
1793
   * By default, an equal operator will be used against all columns, but
1794 4
   * it can be overridden for any or every column using the second parameter.
1795
   *
1796 4
   * Each condition will be ORed together when added to the final query.
1797
   *
1798 4
   * @param array  $values
1799 3
   * @param string $operator
1800 3
   *
1801 1
   * @return ORM
1802
   */
1803
  public function where_any_is($values, $operator = '=')
1804 4
  {
1805 4
    $data = array();
1806 4
    $query = array('((');
1807 4
    $first = true;
1808
    foreach ($values as $item) {
1809
      if ($first) {
1810 4
        $first = false;
1811 4
      } else {
1812 4
        $query[] = ') OR (';
1813 4
      }
1814 4
      $firstsub = true;
1815 4
1816
      foreach ($item as $key => $subItem) {
1817 4
1818
        if (is_string($operator)) {
1819
          $op = $operator;
1820
        } else {
1821
          $op = (isset($operator[$key]) ? $operator[$key] : '=');
1822
        }
1823
1824
        if ($firstsub) {
1825
          $firstsub = false;
1826
        } else {
1827
          $query[] = 'AND';
1828
        }
1829
1830 2
        $query[] = $this->_quote_identifier($key);
1831
        $data[] = $subItem;
1832 2
        $query[] = $op . ' ?';
1833 1
      }
1834
    }
1835 1
    $query[] = '))';
1836
1837
    return $this->where_raw(implode($query, ' '), $data);
1838
  }
1839
1840
  /**
1841
   * Similar to where_id_is() but allowing multiple primary keys.
1842
   *
1843
   * If primary key is compound, only the columns that
1844
   * belong to they key will be used for the query
1845
   *
1846
   * @param mixed $ids
1847 2
   *
1848
   * @return ORM
1849 2
   */
1850
  public function where_id_in($ids)
1851
  {
1852
    if (is_array($this->_get_id_column_name())) {
1853
      return $this->where_any_is($this->_get_compound_id_column_values_array($ids));
1854
    } else {
1855
      return $this->where_in($this->_get_id_column_name(), $ids);
1856
    }
1857
  }
1858
1859
  /**
1860 2
   * Add a WHERE ... LIKE clause to your query.
1861
   *
1862 2
   * @param string $column_name
1863
   * @param mixed  $value
1864
   *
1865
   * @return ORM
1866
   */
1867
  public function where_like($column_name, $value = null)
1868
  {
1869
    return $this->_add_simple_where($column_name, 'LIKE', $value);
1870
  }
1871
1872
  /**
1873 2
   * Add where WHERE ... NOT LIKE clause to your query.
1874
   *
1875 2
   * @param string $column_name
1876
   * @param mixed  $value
1877
   *
1878
   * @return ORM
1879
   */
1880
  public function where_not_like($column_name, $value = null)
1881
  {
1882
    return $this->_add_simple_where($column_name, 'NOT LIKE', $value);
1883
  }
1884
1885
  /**
1886 2
   * Add a WHERE ... > clause to your query
1887
   *
1888 2
   * @param string $column_name
1889
   * @param mixed  $value
1890
   *
1891
   * @return ORM
1892
   */
1893
  public function where_gt($column_name, $value = null)
1894
  {
1895
    return $this->_add_simple_where($column_name, '>', $value);
1896
  }
1897
1898
  /**
1899 2
   * Add a WHERE ... < clause to your query
1900
   *
1901 2
   * @param string $column_name
1902
   * @param mixed  $value
1903
   *
1904
   * @return ORM
1905
   */
1906
  public function where_lt($column_name, $value = null)
1907
  {
1908
    return $this->_add_simple_where($column_name, '<', $value);
1909
  }
1910
1911
  /**
1912 2
   * Add a WHERE ... >= clause to your query
1913
   *
1914 2
   * @param string $column_name
1915
   * @param mixed  $value
1916
   *
1917
   * @return ORM
1918
   */
1919
  public function where_gte($column_name, $value = null)
1920
  {
1921
    return $this->_add_simple_where($column_name, '>=', $value);
1922
  }
1923
1924
  /**
1925 3
   * Add a WHERE ... <= clause to your query
1926
   *
1927 3
   * @param string $column_name
1928
   * @param mixed  $value
1929
   *
1930
   * @return ORM
1931
   */
1932
  public function where_lte($column_name, $value = null)
1933
  {
1934
    return $this->_add_simple_where($column_name, '<=', $value);
1935
  }
1936
1937
  /**
1938 2
   * Add a WHERE ... IN clause to your query
1939
   *
1940 2
   * @param string $column_name
1941
   * @param mixed  $values
1942
   *
1943
   * @return ORM
1944
   */
1945
  public function where_in($column_name, $values)
1946
  {
1947
    return $this->_add_where_placeholder($column_name, 'IN', $values);
1948
  }
1949
1950 2
  /**
1951
   * Add a WHERE ... NOT IN clause to your query
1952 2
   *
1953
   * @param string $column_name
1954
   * @param mixed  $values
1955
   *
1956
   * @return ORM
1957
   */
1958
  public function where_not_in($column_name, $values)
1959
  {
1960
    return $this->_add_where_placeholder($column_name, 'NOT IN', $values);
1961
  }
1962 2
1963
  /**
1964 2
   * Add a WHERE column IS NULL clause to your query
1965
   *
1966
   * @param string $column_name
1967
   *
1968
   * @return ORM
1969
   */
1970
  public function where_null($column_name)
1971
  {
1972
    return $this->_add_where_no_value($column_name, 'IS NULL');
1973
  }
1974
1975
  /**
1976
   * Add a WHERE column IS NOT NULL clause to your query
1977 18
   *
1978
   * @param string $column_name
1979 18
   *
1980
   * @return ORM
1981
   */
1982
  public function where_not_null($column_name)
1983
  {
1984
    return $this->_add_where_no_value($column_name, 'IS NOT NULL');
1985
  }
1986
1987
  /**
1988
   * Add a raw WHERE clause to the query. The clause should
1989 95
   * contain question mark placeholders, which will be bound
1990
   * to the parameters supplied in the second argument.
1991 95
   *
1992
   * @param string $clause
1993 95
   * @param array  $parameters
1994
   *
1995
   * @return ORM
1996
   */
1997
  public function where_raw($clause, $parameters = array())
1998
  {
1999
    return $this->_add_where($clause, $parameters);
2000
  }
2001
2002
  /**
2003 4
   * Add a LIMIT to the query
2004
   *
2005 4
   * @param int $limit
2006
   *
2007 4
   * @return $this
2008
   */
2009
  public function limit($limit)
2010
  {
2011
    $this->_limit = $limit;
2012
2013
    return $this;
2014
  }
2015
2016
  /**
2017
   * Add an OFFSET to the query
2018 12
   *
2019
   * @param $offset
2020 12
   *
2021 12
   * @return $this
2022
   */
2023 12
  public function offset($offset)
2024
  {
2025
    $this->_offset = $offset;
2026
2027
    return $this;
2028
  }
2029
2030
  /**
2031
   * Add an ORDER BY clause to the query
2032
   *
2033 8
   * @param string $column_name
2034
   * @param string $ordering
2035 8
   *
2036
   * @return $this
2037
   */
2038
  protected function _add_order_by($column_name, $ordering)
2039
  {
2040
    $column_name = $this->_quote_identifier($column_name);
2041
    $this->_order_by[] = "{$column_name} {$ordering}";
2042
2043
    return $this;
2044
  }
2045 6
2046
  /**
2047 6
   * Add an ORDER BY column DESC clause
2048
   *
2049
   * @param string $column_name
2050
   *
2051
   * @return ORM
2052
   */
2053
  public function order_by_desc($column_name)
2054
  {
2055
    return $this->_add_order_by($column_name, 'DESC');
2056
  }
2057 2
2058
  /**
2059 2
   * Add an ORDER BY column ASC clause
2060
   *
2061 2
   * @param string $column_name
2062
   *
2063
   * @return ORM
2064
   */
2065
  public function order_by_asc($column_name)
2066
  {
2067 1
    return $this->_add_order_by($column_name, 'ASC');
2068
  }
2069 1
2070
  /**
2071 1
   * Add an unquoted expression as an ORDER BY clause
2072
   *
2073
   * @param $clause
2074
   *
2075
   * @return $this
2076
   */
2077
  public function order_by_expr($clause)
2078
  {
2079
    $this->_order_by[] = $clause;
2080
2081 28
    return $this;
2082
  }
2083 28
2084 28
  /**
2085
   * Reset the ORDER BY clause
2086 28
   */
2087
  public function reset_order_by()
2088
  {
2089
    $this->_order_by = array();
2090
2091
    return $this;
2092
  }
2093
2094
  /**
2095
   * Add a column to the list of columns to GROUP BY
2096 2
   *
2097
   * @param string $column_name
2098 2
   *
2099
   * @return $this
2100 2
   */
2101
  public function group_by($column_name)
2102
  {
2103
    $column_name = $this->_quote_identifier($column_name);
2104
    $this->_group_by[] = $column_name;
2105
2106
    return $this;
2107
  }
2108
2109
  /**
2110
   * Add an unquoted expression to the list of columns to GROUP BY
2111
   *
2112
   * @param string $expr
2113
   *
2114
   * @return $this
2115
   */
2116
  public function group_by_expr($expr)
2117 4
  {
2118
    $this->_group_by[] = $expr;
2119 4
2120
    return $this;
2121
  }
2122
2123
  /**
2124
   * Add a HAVING column = value clause to your query. Each time
2125
   * this is called in the chain, an additional HAVING will be
2126
   * added, and these will be ANDed together when the final query
2127
   * is built.
2128
   *
2129
   * If you use an array in $column_name, a new clause will be
2130
   * added for each element. In this case, $value is ignored.
2131 4
   *
2132
   * @param string $column_name
2133 4
   * @param null   $value
2134
   *
2135
   * @return ORM
2136
   */
2137
  public function having($column_name, $value = null)
2138
  {
2139
    return $this->having_equal($column_name, $value);
2140
  }
2141
2142
  /**
2143
   * More explicitly named version of for the having() method.
2144 2
   * Can be used if preferred.
2145
   *
2146 2
   * @param string $column_name
2147
   * @param null   $value
2148
   *
2149
   * @return ORM
2150
   */
2151
  public function having_equal($column_name, $value = null)
2152
  {
2153
    return $this->_add_simple_having($column_name, '=', $value);
2154
  }
2155
2156
  /**
2157
   * Add a HAVING column != value clause to your query.
2158
   *
2159
   * @param string $column_name
2160
   * @param null   $value
2161
   *
2162
   * @return ORM
2163
   */
2164
  public function having_not_equal($column_name, $value = null)
2165
  {
2166
    return $this->_add_simple_having($column_name, '!=', $value);
2167
  }
2168
2169
  /**
2170
   * Special method to query the table by its primary key.
2171
   *
2172
   * If primary key is compound, only the columns that
2173
   * belong to they key will be used for the query
2174
   *
2175
   * @param $id
2176 2
   *
2177
   * @return ORM
2178 2
   */
2179 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...
2180
  {
2181
    if (is_array($this->_get_id_column_name())) {
2182
      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...
2183
    } else {
2184
      return $this->having($this->_get_id_column_name(), $id);
2185
    }
2186
  }
2187
2188
  /**
2189 2
   * Add a HAVING ... LIKE clause to your query.
2190
   *
2191 2
   * @param string $column_name
2192
   * @param mixed  $value
2193
   *
2194
   * @return ORM
2195
   */
2196
  public function having_like($column_name, $value = null)
2197
  {
2198
    return $this->_add_simple_having($column_name, 'LIKE', $value);
2199
  }
2200
2201
  /**
2202 2
   * Add where HAVING ... NOT LIKE clause to your query.
2203
   *
2204 2
   * @param string $column_name
2205
   * @param mixed  $value
2206
   *
2207
   * @return ORM
2208
   */
2209
  public function having_not_like($column_name, $value = null)
2210
  {
2211
    return $this->_add_simple_having($column_name, 'NOT LIKE', $value);
2212
  }
2213
2214
  /**
2215 2
   * Add a HAVING ... > clause to your query
2216
   *
2217 2
   * @param string $column_name
2218
   * @param mixed  $value
2219
   *
2220
   * @return ORM
2221
   */
2222
  public function having_gt($column_name, $value = null)
2223
  {
2224
    return $this->_add_simple_having($column_name, '>', $value);
2225
  }
2226
2227
  /**
2228 2
   * Add a HAVING ... < clause to your query
2229
   *
2230 2
   * @param string $column_name
2231
   * @param mixed  $value
2232
   *
2233
   * @return ORM
2234
   */
2235
  public function having_lt($column_name, $value = null)
2236
  {
2237
    return $this->_add_simple_having($column_name, '<', $value);
2238
  }
2239
2240
  /**
2241 2
   * Add a HAVING ... >= clause to your query
2242
   *
2243 2
   * @param string $column_name
2244
   * @param mixed  $value
2245
   *
2246
   * @return ORM
2247
   */
2248
  public function having_gte($column_name, $value = null)
2249
  {
2250
    return $this->_add_simple_having($column_name, '>=', $value);
2251
  }
2252
2253
  /**
2254 2
   * Add a HAVING ... <= clause to your query
2255
   *
2256 2
   * @param string $column_name
2257
   * @param mixed  $value
2258
   *
2259
   * @return ORM
2260
   */
2261
  public function having_lte($column_name, $value = null)
2262
  {
2263
    return $this->_add_simple_having($column_name, '<=', $value);
2264
  }
2265
2266
  /**
2267 2
   * Add a HAVING ... IN clause to your query
2268
   *
2269 2
   * @param string $column_name
2270
   * @param mixed  $values
2271
   *
2272
   * @return ORM
2273
   */
2274
  public function having_in($column_name, $values = null)
2275
  {
2276
    return $this->_add_having_placeholder($column_name, 'IN', $values);
2277
  }
2278
2279 2
  /**
2280
   * Add a HAVING ... NOT IN clause to your query
2281 2
   *
2282
   * @param string $column_name
2283
   * @param mixed  $values
2284
   *
2285
   * @return ORM
2286
   */
2287
  public function having_not_in($column_name, $values = null)
2288
  {
2289
    return $this->_add_having_placeholder($column_name, 'NOT IN', $values);
2290
  }
2291 2
2292
  /**
2293 2
   * Add a HAVING column IS NULL clause to your query
2294
   *
2295
   * @param string $column_name
2296
   *
2297
   * @return ORM
2298
   */
2299
  public function having_null($column_name)
2300
  {
2301
    return $this->_add_having_no_value($column_name, 'IS NULL');
2302
  }
2303
2304
  /**
2305
   * Add a HAVING column IS NOT NULL clause to your query
2306 2
   *
2307
   * @param string $column_name
2308 2
   *
2309
   * @return ORM
2310
   */
2311
  public function having_not_null($column_name)
2312
  {
2313
    return $this->_add_having_no_value($column_name, 'IS NOT NULL');
2314
  }
2315
2316
  /**
2317
   * Add a raw HAVING clause to the query. The clause should
2318
   * contain question mark placeholders, which will be bound
2319
   * to the parameters supplied in the second argument.
2320
   *
2321
   * @param string $clause
2322
   * @param array  $parameters
2323
   *
2324
   * @return ORM
2325
   */
2326
  public function having_raw($clause, $parameters = array())
2327
  {
2328
    return $this->_add_having($clause, $parameters);
2329
  }
2330
2331
  /**
2332
   * Activate cache refreshing for current query
2333
   *
2334
   * @return ORM
2335
   */
2336
  public function refreshCache()
2337
  {
2338
    $this->_refresh_cache = true;
2339 199
2340
    return $this;
2341
  }
2342
2343 199
  /**
2344 4
   * Disable caching for current query
2345
   *
2346 4
   * @return ORM
2347
   */
2348
  public function noCaching()
2349
  {
2350
    $this->_no_caching = true;
2351 195
2352 195
    return $this;
2353
  }
2354 195
2355 195
  /**
2356 195
   * Build a SELECT statement based on the clauses that have
2357 195
   * been passed to this instance by chaining method calls.
2358 195
   */
2359 195
  protected function _build_select()
2360 195
  {
2361 195
    // If the query is raw, just set the $this->_values to be
2362
    // the raw query parameters and return the raw query
2363 195
    if ($this->_is_raw_query) {
2364
      $this->_values = $this->_raw_parameters;
2365
2366
      return $this->_raw_query;
2367
    }
2368
2369 195
    // Build and return the full SELECT statement by concatenating
2370
    // the results of calling each separate builder method.
2371 195
    return $this->_join_if_not_empty(
2372 195
        ' ',
2373
        array(
2374
            $this->_build_select_start(),
2375 195
            $this->_build_join(),
2376 195
            $this->_build_where(),
2377 95
            $this->_build_group_by(),
2378 195
            $this->_build_having(),
2379 2
            $this->_build_order_by(),
2380 2
            $this->_build_limit(),
2381
            $this->_build_offset(),
2382 195
        )
2383 2
    );
2384 2
  }
2385
2386 195
  /**
2387
   * Build the start of the SELECT statement
2388 195
   *
2389 4
   * @return string
2390 4
   */
2391
  protected function _build_select_start()
2392 195
  {
2393
    $fragment = 'SELECT ';
2394
    $result_columns = implode(', ', $this->_result_columns);
2395
2396
    if (
2397
        null !== $this->_limit
2398 195
        &&
2399
        static::$_config[$this->_connection_name]['limit_clause_style'] === self::LIMIT_STYLE_TOP_N
2400 195
    ) {
2401 169
      $fragment .= "TOP {$this->_limit} ";
2402
    }
2403
2404 26
    if ($this->_distinct) {
2405
      $result_columns = 'DISTINCT ' . $result_columns;
2406
    }
2407
2408
    $fragment .= "{$result_columns} FROM " . $this->_quote_identifier($this->_table_name);
2409
2410 197
    if (null !== $this->_table_alias) {
2411
      $fragment .= ' ' . $this->_quote_identifier($this->_table_alias);
2412 197
    }
2413
2414
    return $fragment;
2415
  }
2416
2417
  /**
2418 195
   * Build the JOIN sources
2419
   *
2420 195
   * @return string
2421
   */
2422
  protected function _build_join()
2423
  {
2424
    if (count($this->_join_sources) === 0) {
2425
      return '';
2426 195
    }
2427
2428 195
    return implode(' ', $this->_join_sources);
2429 165
  }
2430
2431
  /**
2432 30
   * Build the WHERE clause(s)
2433
   *
2434
   * @return string
2435
   */
2436
  protected function _build_where()
2437
  {
2438
    return $this->_build_conditions('where');
2439
  }
2440
2441
  /**
2442 197
   * Build the HAVING clause(s)
2443
   *
2444 197
   * @return string
2445
   */
2446 197
  protected function _build_having()
2447 195
  {
2448
    return $this->_build_conditions('having');
2449
  }
2450 112
2451 112
  /**
2452 112
   * Build GROUP BY
2453
   *
2454 112
   * @return string
2455 112
   */
2456
  protected function _build_group_by()
2457 112
  {
2458
    if (count($this->_group_by) === 0) {
2459
      return '';
2460
    }
2461
2462
    return 'GROUP BY ' . implode(', ', $this->_group_by);
2463 195
  }
2464
2465 195
  /**
2466 184
   * Build a WHERE or HAVING clause
2467
   *
2468
   * @param string $type
2469 13
   *
2470
   * @return string
2471 13
   */
2472
  protected function _build_conditions($type)
2473
  {
2474
    $conditions_class_property_name = "_{$type}_conditions";
2475
    // If there are no clauses, return empty string
2476
    if (count($this->$conditions_class_property_name) === 0) {
2477 195
      return '';
2478
    }
2479
2480 195
    $conditions = array();
2481
    foreach ($this->$conditions_class_property_name as $condition) {
2482
      $conditions[] = $condition[static::CONDITION_FRAGMENT];
2483 195
      /** @noinspection SlowArrayOperationsInLoopInspection */
2484 195
      $this->_values = array_merge($this->_values, $condition[static::CONDITION_VALUES]);
2485 95
    }
2486 195
2487 93
    return strtoupper($type) . ' ' . implode(' AND ', $conditions);
2488
  }
2489
2490 93
  /**
2491
   * Build ORDER BY
2492
   *
2493 93
   * @return string
2494
   */
2495 93
  protected function _build_order_by()
2496 93
  {
2497
    if (count($this->_order_by) === 0) {
2498 195
      return '';
2499
    }
2500
2501
    $db = static::get_db(self::DEFAULT_CONNECTION);
2502
2503
    return 'ORDER BY ' . trim($db->quote(implode(', ', $this->_order_by)), "'");
2504 195
  }
2505
2506 195
  /**
2507 4
   * Build LIMIT
2508 4
   *
2509
   * @return string
2510
   */
2511
  protected function _build_limit()
2512 4
  {
2513
    // init
2514 4
    $fragment = '';
2515
2516
    if (
2517 191
        null !== $this->_limit
2518
        &&
2519
        static::$_config[$this->_connection_name]['limit_clause_style'] == self::LIMIT_STYLE_LIMIT
2520
    ) {
2521
      if (static::get_db($this->_connection_name)->getAttribute(\PDO::ATTR_DRIVER_NAME) == 'firebird') {
2522
        $fragment = 'ROWS';
2523
      } else {
2524
        $fragment = 'LIMIT';
2525
      }
2526
2527
      $this->_limit = (int)$this->_limit;
2528
2529 197
      $fragment .= " {$this->_limit}";
2530
    }
2531 197
2532 197
    return $fragment;
2533 197
  }
2534 197
2535 197
  /**
2536 197
   * Build OFFSET
2537 197
   *
2538 197
   * @return string
2539 197
   */
2540
  protected function _build_offset()
2541 197
  {
2542
    if (null !== $this->_offset) {
2543
      $clause = 'OFFSET';
2544
      if (static::get_db($this->_connection_name)->getAttribute(\PDO::ATTR_DRIVER_NAME) == 'firebird') {
2545
        $clause = 'TO';
2546
      }
2547
2548
      $this->_offset = (int)$this->_offset;
2549
2550
      return "$clause " . $this->_offset;
2551
    }
2552
2553 207
    return '';
2554
  }
2555 207
2556 207
  /**
2557
   * Wrapper around PHP's join function which
2558 207
   * only adds the pieces if they are not empty.
2559
   *
2560
   * @param string $glue
2561
   * @param array  $pieces
2562
   *
2563
   * @return string
2564
   */
2565
  protected function _join_if_not_empty($glue, $pieces)
2566
  {
2567
    $filtered_pieces = array();
2568
    foreach ($pieces as $piece) {
2569
      if (is_string($piece)) {
2570
        $piece = trim($piece);
2571 207
      }
2572
      if (!empty($piece)) {
2573 207
        $filtered_pieces[] = $piece;
2574 1
      }
2575
    }
2576 1
2577
    return implode($glue, $filtered_pieces);
2578 206
  }
2579
2580
  /**
2581
   * Quote a string that is used as an identifier
2582
   * (table names, column names etc). This method can
2583
   * also deal with dot-separated identifiers eg table.column
2584
   *
2585
   * @param string $identifier
2586
   *
2587
   * @return string
2588
   */
2589
  protected function _quote_one_identifier($identifier)
2590
  {
2591 207
    $parts = explode('.', $identifier);
2592
    $parts = array_map(array($this, '_quote_identifier_part'), $parts);
2593 207
2594 2
    return implode('.', $parts);
2595
  }
2596
2597 207
  /**
2598
   * Quote a string that is used as an identifier
2599
   * (table names, column names etc) or an array containing
2600 207
   * multiple identifiers. This method can also deal with
2601
   * dot-separated identifiers eg table.column
2602
   *
2603
   * @param array|string $identifier
2604
   *
2605
   * @return string
2606
   */
2607
  protected function _quote_identifier($identifier)
2608
  {
2609
    if (is_array($identifier)) {
2610
      $result = array_map(array($this, '_quote_one_identifier'), $identifier);
2611
2612
      return implode(', ', $result);
2613 3
    } else {
2614
      return $this->_quote_one_identifier($identifier);
2615
    }
2616 3
  }
2617 3
2618 1
  /**
2619 3
   * This method performs the actual quoting of a single
2620 1
   * part of an identifier, using the identifier quote
2621 1
   * character specified in the config (or autodetected).
2622
   *
2623 1
   * @param string $part
2624 1
   *
2625 1
   * @return string
2626 1
   */
2627
  protected function _quote_identifier_part($part)
2628 1
  {
2629
    if ($part === '*') {
2630 2
      return $part;
2631 2
    }
2632
2633 2
    $quote_character = static::$_config[$this->_connection_name]['identifier_quote_character'];
2634
2635
    // double up any identifier quotes to escape them
2636
    return $quote_character . str_replace($quote_character, $quote_character . $quote_character, $part) . $quote_character;
2637
  }
2638
2639
  /**
2640
   * Create a cache key for the given query and parameters.
2641
   *
2642
   * @param string      $query
2643
   * @param array       $parameters
2644
   * @param null|string $table_name
2645
   * @param string      $connection_name
2646 3
   *
2647
   * @return mixed|string
2648
   */
2649 3
  protected static function _create_cache_key($query, $parameters, $table_name = null, $connection_name = self::DEFAULT_CONNECTION)
2650 3
  {
2651 1 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...
2652 3
        isset(static::$_config[$connection_name]['create_cache_key']) === true
2653 1
        &&
2654 1
        is_callable(static::$_config[$connection_name]['create_cache_key']) === true
2655
    ) {
2656 1
      return call_user_func_array(
2657 1
          static::$_config[$connection_name]['create_cache_key'],
2658 1
          array(
2659
              $query,
2660 1
              $parameters,
2661 2
              $table_name,
2662 2
              $connection_name,
2663
          )
2664
      );
2665 2
    }
2666
    $parameter_string = implode(',', $parameters);
2667
    $key = $query . ':' . $parameter_string;
2668
2669
    return sha1($key);
2670
  }
2671
2672
  /**
2673
   * Check the query cache for the given cache key. If a value
2674
   * is cached for the key, return the value. Otherwise, return false.
2675
   *
2676 1
   * @param string      $cache_key
2677
   * @param null|string $table_name
2678
   * @param string      $connection_name
2679 1
   *
2680
   * @return bool|mixed
2681
   */
2682 1
  protected static function _check_query_cache($cache_key, $table_name = null, $connection_name = self::DEFAULT_CONNECTION)
2683 1
  {
2684 1
    if (
2685 1
        isset(static::$_config[$connection_name]['check_query_cache']) === true
2686 1
        &&
2687 1
        is_callable(static::$_config[$connection_name]['check_query_cache']) === true
2688
    ) {
2689 1
      return call_user_func_array(
2690 1
          static::$_config[$connection_name]['check_query_cache'],
2691
          array(
2692 1
              $cache_key,
2693
              $table_name,
2694
              $connection_name,
2695
          )
2696
      );
2697
    } elseif (isset(static::$_query_cache[$connection_name][$cache_key])) {
2698
      return static::$_query_cache[$connection_name][$cache_key];
2699
    }
2700
2701
    return false;
2702
  }
2703
2704
  /**
2705
   * Clear the query cache
2706
   *
2707
   * @param null|string $table_name
2708 3
   * @param string      $connection_name
2709
   *
2710
   * @return bool|mixed
2711 3
   */
2712 3
  public static function clear_cache($table_name = null, $connection_name = self::DEFAULT_CONNECTION)
2713 1
  {
2714 3
    // init
2715
    static::$_query_cache = array();
2716 1
2717 1 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...
2718
        isset(static::$_config[$connection_name]['clear_cache']) === true
2719 1
        &&
2720 1
        is_callable(static::$_config[$connection_name]['clear_cache']) === true
2721 1
    ) {
2722 1
      return call_user_func_array(
2723
          static::$_config[$connection_name]['clear_cache'],
2724 1
          array(
2725
              $table_name,
2726 2
              $connection_name,
2727 2
          )
2728 2
      );
2729
    }
2730 2
2731
    return false;
2732 2
  }
2733
2734
  /**
2735
   * Add the given value to the query cache.
2736
   *
2737
   * @param string      $cache_key
2738
   * @param string      $value
2739 199
   * @param null|string $table_name
2740
   * @param string      $connection_name
2741
   *
2742 199
   * @return bool|mixed
2743
   */
2744 199
  protected static function _cache_query_result($cache_key, $value, $table_name = null, $connection_name = self::DEFAULT_CONNECTION)
2745 199
  {
2746
    if (
2747 199
        isset(static::$_config[$connection_name]['cache_query_result']) === true
2748 3
        &&
2749
        is_callable(static::$_config[$connection_name]['cache_query_result']) === true
2750 3
    ) {
2751 3
2752
      return call_user_func_array(
2753 3
          static::$_config[$connection_name]['cache_query_result'],
2754 3
          array(
2755
              $cache_key,
2756 3
              $value,
2757 3
              $table_name,
2758
              $connection_name,
2759 199
          )
2760 199
      );
2761
2762 199
    } elseif (!isset(static::$_query_cache[$connection_name])) {
2763 199
      static::$_query_cache[$connection_name] = array();
2764 196
    }
2765 196
2766
    static::$_query_cache[$connection_name][$cache_key] = $value;
2767 199
2768 3
    return true;
2769 3
  }
2770
2771
  /**
2772 199
   * Execute the SELECT query that has been built up by chaining methods
2773 199
   * on this class. Return an array of rows as associative arrays.
2774 199
   */
2775
  protected function _run()
2776 199
  {
2777
    // init
2778
    $cache_key = false;
2779
2780
    $query = $this->_build_select();
2781
    $caching_enabled = static::$_config[$this->_connection_name]['caching'];
2782
2783
    if ($caching_enabled && !$this->_no_caching) {
2784
      $cache_key = static::_create_cache_key($query, $this->_values, $this->_table_name, $this->_connection_name);
2785 2
2786
      if (!$this->_refresh_cache) {
2787 2
        $cached_result = static::_check_query_cache($cache_key, $this->_table_name, $this->_connection_name);
2788 2
2789
        if ($cached_result !== false) {
2790 2
          return $cached_result;
2791
        }
2792 2
      }
2793
    }
2794
2795
    static::_execute($query, $this->_values, $this->_connection_name);
2796
    $statement = static::get_last_statement();
2797
2798
    $rows = array();
2799
    while ($row = $statement->fetch(\PDO::FETCH_ASSOC)) {
2800
      $rows[] = $row;
2801
    }
2802
2803
    if ($cache_key) {
2804
      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...
2805
    }
2806 35
2807
    // reset Idiorm after executing the query
2808 35
    $this->_values = array();
2809 4
    $this->_result_columns = array('*');
2810 4
    $this->_using_default_result_columns = true;
2811 4
2812 4
    return $rows;
2813
  }
2814 4
2815
  /**
2816 31
   * Return the raw data wrapped by this ORM
2817
   * instance as an associative array. Column
2818
   * names may optionally be supplied as arguments,
2819
   * if so, only those keys will be returned.
2820
   */
2821
  public function as_array()
2822
  {
2823
    if (func_num_args() === 0) {
2824 50
      return $this->_data;
2825
    }
2826 50
    $args = func_get_args();
2827 13
2828
    return array_intersect_key($this->_data, array_flip($args));
2829
  }
2830 37
2831 2
  /**
2832
   * Return the raw data wrapped by this ORM
2833
   * instance as an json.
2834 35
   *
2835
   * @param int $options
2836
   *
2837
   * @return string
2838
   */
2839
  public function as_json($options = 0)
2840
  {
2841
    return json_encode($this->as_array(), $options);
2842
  }
2843
2844
  /**
2845
   * Return the value of a property of this object (database row)
2846 33
   * or null if not present.
2847
   *
2848 33
   * If a column-names array is passed, it will return a associative array
2849
   * with the value of each column or null if it is not present.
2850 33
   *
2851 23
   * @param mixed $key
2852 3
   *
2853 3
   * @return mixed
2854 1
   */
2855
  public function get($key)
2856 3
  {
2857 22
    if (is_array($key)) {
2858 3
      $result = array();
2859
      foreach ($key as $column) {
2860 19
        $result[$column] = isset($this->_data[$column]) ? $this->_data[$column] : null;
2861
      }
2862 29
2863
      return $result;
2864
    } else {
2865
      return isset($this->_data[$key]) ? $this->_data[$key] : null;
2866
    }
2867
  }
2868
2869
  /**
2870
   * Return the name of the column in the database table which contains
2871
   * the primary key ID of the row.
2872
   */
2873
  protected function _get_id_column_name()
2874
  {
2875
    if (null !== $this->_instance_id_column) {
2876
      return $this->_instance_id_column;
2877 27
    }
2878
2879 27
    if (isset(static::$_config[$this->_connection_name]['id_column_overrides'][$this->_table_name])) {
2880
      return static::$_config[$this->_connection_name]['id_column_overrides'][$this->_table_name];
2881
    }
2882
2883
    return static::$_config[$this->_connection_name]['id_column'];
2884
  }
2885
2886
  /**
2887
   * Get the primary key ID of this object.
2888
   *
2889
   * @param bool $disallow_null
2890
   *
2891
   * @return mixed
2892
   *
2893
   * @throws \Exception
2894 10
   */
2895
  public function id($disallow_null = false)
2896 10
  {
2897
    $id = $this->get($this->_get_id_column_name());
2898
2899
    if ($disallow_null) {
2900
      if (is_array($id)) {
2901
        foreach ($id as $id_part) {
2902
          if ($id_part === null) {
2903
            throw new \Exception('Primary key ID contains null value(s)');
2904
          }
2905
        }
2906
      } elseif ($id === null) {
2907
        throw new \Exception('Primary key ID missing from row or is null');
2908 29
      }
2909
    }
2910 29
2911 25
    return $id;
2912 25
  }
2913
2914
  /**
2915 29
   * Set a property to a particular value on this object.
2916 29
   * To set multiple properties at once, pass an associative array
2917 29
   * as the first parameter and leave out the second parameter.
2918 29
   * Flags the properties as 'dirty' so they will be saved to the
2919 2
   * database when save() is called.
2920 29
   *
2921 10
   * @param mixed $key
2922 10
   * @param mixed $value
2923 29
   *
2924
   * @return ORM
2925 29
   */
2926
  public function set($key, $value = null)
2927
  {
2928
    return $this->_set_orm_property($key, $value);
2929
  }
2930
2931
  /**
2932
   * Set a property to a particular value on this object.
2933
   * To set multiple properties at once, pass an associative array
2934
   * as the first parameter and leave out the second parameter.
2935
   * Flags the properties as 'dirty' so they will be saved to the
2936 1
   * database when save() is called.
2937
   *
2938 1
   * @param string|array $key
2939
   * @param string|null  $value
2940
   *
2941
   * @return ORM
2942
   */
2943
  public function set_expr($key, $value = null)
2944
  {
2945
    return $this->_set_orm_property($key, $value, true);
2946 2
  }
2947
2948 2
  /**
2949
   * Set a property on the ORM object.
2950
   *
2951
   * @param  string|array $key
2952
   * @param string|null   $value
2953
   * @param bool          $expr
2954
   *
2955
   * @return $this
2956
   */
2957
  protected function _set_orm_property($key, $value = null, $expr = false)
2958
  {
2959 28
    if (!is_array($key)) {
2960
      $key = array($key => $value);
2961
    }
2962 28
2963
    /** @noinspection SuspiciousLoopInspection */
2964 28
    foreach ($key as $field => $value) {
2965
      $this->_data[$field] = $value;
2966
      $this->_dirty_fields[$field] = $value;
2967
      if (false === $expr && isset($this->_expr_fields[$field])) {
2968
        unset($this->_expr_fields[$field]);
2969
      } elseif (true === $expr) {
2970 17
        $this->_expr_fields[$field] = true;
2971 17
      }
2972 2
    }
2973 17
2974
    return $this;
2975
  }
2976
2977 17
  /**
2978 17
   * Check whether the given field has been changed since this
2979
   * object was saved.
2980 16
   *
2981 1
   * @param string $key
2982 1
   *
2983 15
   * @return bool
2984
   */
2985
  public function is_dirty($key)
2986 16
  {
2987
    return array_key_exists($key, $this->_dirty_fields);
2988
  }
2989 12
2990
  /**
2991
   * Check whether the model was the result of a call to create() or not
2992 27
   *
2993 27
   * @return bool
2994
   */
2995 27
  public function is_new()
2996 1
  {
2997 1
    return $this->_is_new;
2998
  }
2999
3000 27
  /**
3001
   * Save any fields which have been modified on this object
3002 12
   * to the database.
3003 12
   *
3004 9
   * @return bool
3005
   *
3006 9
   * @throws \Exception
3007
   */
3008
  public function save()
3009
  {
3010
    // remove any expression fields as they are already baked into the query
3011
    $values = array_values(array_diff_key($this->_dirty_fields, $this->_expr_fields));
3012
3013
    if (!$this->_is_new) {
3014
3015
      // UPDATE
3016 9
3017
      // If there are no dirty values, do nothing
3018
      if (
3019 9
          empty($values)
3020
          &&
3021
          0 === count($this->_expr_fields)
3022 9
      ) {
3023
        return true;
3024 9
      }
3025 12
3026
      $query = $this->_build_update();
3027 27
      $id = $this->id(true);
3028
3029 27
      if (is_array($id)) {
3030
        $values = array_merge($values, array_values($id));
3031
      } else {
3032
        $values[] = $id;
3033
      }
3034
3035
    } else {
3036
3037 21
      // INSERT
3038
      $query = $this->_build_insert();
3039 21
    }
3040
3041 21
    $success = static::_execute($query, $values, $this->_connection_name);
3042 2
    $caching_auto_clear_enabled = static::$_config[$this->_connection_name]['caching_auto_clear'];
3043 2
3044 19
    if ($caching_auto_clear_enabled) {
3045
      static::clear_cache($this->_table_name, $this->_connection_name);
3046
    }
3047 21
3048 21
    // If we've just inserted a new record, set the ID of this object
3049
    if ($success && $this->_is_new) {
3050 21
3051 21
      $this->_is_new = false;
3052 21
      if ($this->count_null_id_columns() != 0) {
3053 2
        $db = static::get_db($this->_connection_name);
3054
3055
        if ($db->getAttribute(\PDO::ATTR_DRIVER_NAME) == 'pgsql') {
3056 21
3057 21
          // it may return several columns if a compound primary
3058 21
          // key is used
3059 21
          $row = static::get_last_statement()->fetch(\PDO::FETCH_ASSOC);
3060
          foreach ($row as $key => $value) {
3061
            $this->_data[$key] = $value;
3062
          }
3063
3064 17
        } else {
3065
          $column = $this->_get_id_column_name();
3066 17
          // if the primary key is compound, assign the last inserted id
3067 17
          // to the first column
3068
          if (is_array($column)) {
3069 17
            $column = reset($column);
3070 17
          }
3071
          $this->_data[$column] = $db->lastInsertId();
3072 17
        }
3073 15
      }
3074 15
    }
3075
3076 17
    $this->_dirty_fields = $this->_expr_fields = array();
3077 17
3078
    return $success;
3079 17
  }
3080 17
3081
  /**
3082 17
   * Add a WHERE clause for every column that belongs to the primary key
3083
   *
3084
   * @param array $query warning: this is a reference
3085
   */
3086
  public function _add_id_column_conditions(&$query)
3087
  {
3088 12
    $query[] = 'WHERE';
3089
3090 12
    if (is_array($this->_get_id_column_name())) {
3091 12
      $keys = $this->_get_id_column_name();
3092 12
    } else {
3093 12
      $keys = array($this->_get_id_column_name());
3094 12
    }
3095
3096 12
    $first = true;
3097 12
    foreach ($keys as $key) {
3098
3099 12
      if ($first) {
3100
        $first = false;
3101
      } else {
3102
        $query[] = 'AND';
3103 12
      }
3104
3105
      $query[] = $this->_quote_identifier($key);
3106
      $query[] = '= ?';
3107
    }
3108
  }
3109 4
3110
  /**
3111
   * Build an UPDATE query
3112 4
   *
3113 4
   * @return string
3114 4
   */
3115 4
  protected function _build_update()
3116
  {
3117 4
    $query = array();
3118 4
    $query[] = "UPDATE {$this->_quote_identifier($this->_table_name)} SET";
3119 3
3120 3
    $field_list = array();
3121 3
    foreach ($this->_dirty_fields as $key => $value) {
3122
3123
      if (!array_key_exists($key, $this->_expr_fields)) {
3124
        $value = '?';
3125
      }
3126
3127 2
      $field_list[] = "{$this->_quote_identifier($key)} = $value";
3128
    }
3129
3130
    $query[] = implode(', ', $field_list);
3131 2
    $this->_add_id_column_conditions($query);
3132 2
3133
    return implode(' ', $query);
3134 2
  }
3135 2
3136 2
  /**
3137
   * Build an INSERT query
3138 2
   *
3139
   * @return string
3140 2
   */
3141
  protected function _build_insert()
3142
  {
3143
    $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...
3144
    $query[] = $this->_quote_identifier($this->_table_name);
3145
    $field_list = array_map(array($this, '_quote_identifier'), array_keys($this->_dirty_fields));
3146
    $query[] = '(' . implode(', ', $field_list) . ')';
3147 14
    $query[] = 'VALUES';
3148
3149 14
    $placeholders = $this->_create_placeholders($this->_dirty_fields);
3150
    $query[] = "({$placeholders})";
3151
3152 4
    if (static::get_db($this->_connection_name)->getAttribute(\PDO::ATTR_DRIVER_NAME) == 'pgsql') {
3153
      $query[] = 'RETURNING ' . $this->_quote_identifier($this->_get_id_column_name());
3154 4
    }
3155
3156
    return implode(' ', $query);
3157 16
  }
3158
3159 16
  /**
3160
   * Delete this record from the database
3161
   *
3162 16
   * @return bool
3163 16
   */
3164
  public function delete()
3165 1
  {
3166
    $query = array(
3167 1
        'DELETE FROM',
3168 1
        $this->_quote_identifier($this->_table_name),
3169 1
    );
3170
    $this->_add_id_column_conditions($query);
3171
3172
    return static::_execute(
3173
        implode(' ', $query), is_array($this->id(true)) ?
3174 1
        array_values($this->id(true)) :
3175
        array($this->id(true)), $this->_connection_name
3176 1
    );
3177
  }
3178
3179 13
  /**
3180
   * Delete many records from the database
3181 13
   *
3182 13
   * @return bool
3183
   */
3184
  public function delete_many()
3185
  {
3186
    // Build and return the full DELETE statement by concatenating
3187
    // the results of calling each separate builder method.
3188
    $query = $this->_join_if_not_empty(
3189 13
        ' ',
3190
        array(
3191 13
            'DELETE FROM',
3192
            $this->_quote_identifier($this->_table_name),
3193
            $this->_build_where(),
3194
        )
3195
    );
3196
3197
    return static::_execute($query, $this->_values, $this->_connection_name);
3198
  }
3199
3200
  // --------------------- //
3201
  // ---  ArrayAccess  --- //
3202
  // --------------------- //
3203
3204
  /**
3205
   * @param mixed $key
3206
   *
3207
   * @return bool
3208
   */
3209 81
  public function offsetExists($key)
3210
  {
3211 81
    return array_key_exists($key, $this->_data);
3212
  }
3213 81
3214 80
  /**
3215
   * @param mixed $key
3216 1
   *
3217
   * @return mixed
3218
   */
3219
  public function offsetGet($key)
3220
  {
3221
    return $this->get($key);
3222
  }
3223
3224
  /**
3225
   * @param mixed $key
3226
   * @param mixed $value
3227
   */
3228
  public function offsetSet($key, $value)
3229
  {
3230
    if (null === $key) {
3231
      throw new \InvalidArgumentException('You must specify a key/array index.');
3232
    }
3233 87
    $this->set($key, $value);
3234
  }
3235 87
3236
  /**
3237 87
   * @param mixed $key
3238
   */
3239
  public function offsetUnset($key)
3240
  {
3241
    unset($this->_data[$key]);
3242
    unset($this->_dirty_fields[$key]);
3243
  }
3244
3245
  // --------------------- //
3246
  // --- MAGIC METHODS --- //
3247
  // --------------------- //
3248
3249
  /**
3250
   * @param $key
3251
   *
3252
   * @return mixed
3253
   */
3254
  public function __get($key)
3255
  {
3256
    return $this->offsetGet($key);
3257
  }
3258
3259
  /**
3260
   * @param $key
3261
   * @param $value
3262
   */
3263
  public function __set($key, $value)
3264
  {
3265
    $this->offsetSet($key, $value);
3266
  }
3267
3268
  /**
3269
   * @param $key
3270
   */
3271
  public function __unset($key)
3272
  {
3273
    $this->offsetUnset($key);
3274
  }
3275
3276
  /**
3277
   * @param $key
3278
   *
3279
   * @return bool
3280
   */
3281
  public function __isset($key)
3282
  {
3283
    return $this->offsetExists($key);
3284
  }
3285
3286
  /**
3287
   * Magic method to capture calls to undefined class methods.
3288
   * In this case we are attempting to convert camel case formatted
3289
   * methods into underscore formatted methods.
3290
   *
3291
   * This allows us to call ORM methods using camel case and remain
3292
   * backwards compatible.
3293
   *
3294
   * @param  string $name
3295
   * @param  array  $arguments
3296
   *
3297
   * @return ORM
3298
   *
3299
   * @throws IdiormMethodMissingException
3300
   */
3301
  public function __call($name, $arguments)
3302
  {
3303
    $method = strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $name));
3304
3305 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...
3306
      return call_user_func_array(array($this, $method), $arguments);
3307
    } else {
3308
      throw new IdiormMethodMissingException("Method $name() does not exist in class " . get_class($this));
3309
    }
3310
  }
3311
3312
  /**
3313
   * Magic method to capture calls to undefined static class methods.
3314
   * In this case we are attempting to convert camel case formatted
3315
   * methods into underscore formatted methods.
3316
   *
3317
   * This allows us to call ORM methods using camel case and remain
3318
   * backwards compatible.
3319
   *
3320
   * @param  string $name
3321
   * @param  array  $arguments
3322
   *
3323
   * @return ORM
3324
   */
3325
  public static function __callStatic($name, $arguments)
3326
  {
3327
    $method = strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $name));
3328
3329
    return call_user_func_array(array('idiorm\orm\ORM', $method), $arguments);
3330
  }
3331
3332
}
3333