Subversion Repositories PHPX

Rev

Rev 48 | Rev 52 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
27 PointedEar 1
<?php
2
 
51 PointedEar 3
namespace PointedEars\PHPX\Db;
4
 
29 PointedEar 5
require_once __DIR__ . '/../global.inc';
6
require_once __DIR__ . '/../AbstractModel.php';
27 PointedEar 7
 
8
/**
9
 * Generic database model class using PDO (PHP Data Objects)
10
 *
41 PointedEar 11
 * @property-read PDO $connection
12
 *   Database connection.  Established on read access to this
13
 *   property if not yet established.
27 PointedEar 14
 * @property-read array $lastError
15
 *   Last error information of the database operation.
16
 *   See {@link PDOStatement::errorInfo()}.
17
 * @property-read string $lastInsertId
18
 *   ID of the last inserted row, or the last value from a sequence object,
19
 *   depending on the underlying driver. May not be supported by all databases.
20
 * @property-read array $lastResult
21
 *   Last result of the database operation
22
 * @property-read boolean $lastSuccess
23
 *   Last success value of the database operation
24
 * @author Thomas Lahn
25
 */
51 PointedEar 26
class Database extends \PointedEars\PHPX\AbstractModel
27 PointedEar 27
{
32 PointedEar 28
  /* Access properties */
41 PointedEar 29
 
27 PointedEar 30
  /**
31
   * DSN of the database
32
   * @var string
33
   */
34
  protected $_dsn = '';
41 PointedEar 35
 
27 PointedEar 36
  /**
37
   * Username to access the database
38
   * @var string
39
   */
40
  protected $_username;
41 PointedEar 41
 
27 PointedEar 42
  /**
43
   * Password to access the database
44
   * @var string
45
   */
46
  protected $_password;
41 PointedEar 47
 
27 PointedEar 48
  /**
49
   * PDO driver-specific options
50
   * @var array
51
   */
52
  protected $_options = array();
32 PointedEar 53
 
54
  /**
55
   * Database-specific string to use for quoting a name or value
56
   * left-hand side (for security reasons and to prevent a name
57
   * from being parsed as a keyword).
58
   * @var string
59
   */
60
  protected $_leftQuote = '';
41 PointedEar 61
 
27 PointedEar 62
  /**
32 PointedEar 63
   * Database-specific string to use for quoting a name or value
64
   * left-hand side (for security reasons and to prevent a name
65
   * from being parsed as a keyword).
66
   * @var string
67
   */
68
  protected $_rightQuote = '';
41 PointedEar 69
 
32 PointedEar 70
  /* Status properties */
41 PointedEar 71
 
32 PointedEar 72
  /**
27 PointedEar 73
   * Database connection
74
   * @var PDO
75
   */
76
  protected $_connection;
41 PointedEar 77
 
27 PointedEar 78
  /**
79
   * Last success value of the database operation
80
   * @var boolean
81
   */
82
  protected $_lastSuccess;
83
 
84
  /**
85
   * Last error information of the database operation
86
   * @var array
87
   */
88
  protected $_lastError;
41 PointedEar 89
 
27 PointedEar 90
  /**
91
   * Last result of the database operation
92
   * @var array
93
   */
94
  protected $_lastResult;
95
 
96
  /**
97
  * ID of the last inserted row, or the last value from a sequence object,
98
  * depending on the underlying driver. May not be supported by all databases.
99
  * @var string
100
  */
101
  protected $_lastInsertId = '';
41 PointedEar 102
 
34 PointedEar 103
  /**
104
   * Creates a new <code>Database</code> instance.
105
   *
106
   * Each of the parameters is optional and can also be given
107
   * by a protected property where the parameter name is preceded
108
   * by <code>_</code>.  Parameter values overwrite the default
109
   * property values.  It is recommended to use default property
110
   * values of inheriting classes except for small applications
111
   * and testing purposes.
112
   *
113
   * @param string $dsn
114
   * @param string $username
115
   * @param string $password
44 PointedEar 116
   * @param array $options
117
   * @see PDO::__construct()
34 PointedEar 118
   */
44 PointedEar 119
  public function __construct ($dsn = '', $username = null,
34 PointedEar 120
    $password = null, array $options = array())
27 PointedEar 121
  {
34 PointedEar 122
    if ($dsn !== '')
123
    {
124
      $this->_dsn = $dsn;
125
    }
41 PointedEar 126
 
34 PointedEar 127
    if ($username !== null)
128
    {
129
      $this->_username = $username;
130
    }
41 PointedEar 131
 
34 PointedEar 132
    if ($password !== null)
133
    {
134
      $this->_password = $password;
135
    }
41 PointedEar 136
 
34 PointedEar 137
    if ($options)
138
    {
139
      $this->_options = $options;
140
    }
27 PointedEar 141
  }
41 PointedEar 142
 
27 PointedEar 143
  /**
47 PointedEar 144
   * Reads the connection configuration for this database
145
   * from the configuration file, application/.config
146
   *
147
   * There must be an INI section named "database:" followed
148
   * by the value of the <code>$_dbname</code> property
149
   * containing keys and values for the properties of the
150
   * <code>Database</code> instance.  Except for the key
151
   * <code>dbname</code>, which allows for aliases, all
152
   * keys are ignored if the corresponding properties
153
   * were set.  That is, definitions in the class file
154
   * override those in the configuration file.
155
   *
156
   * @return boolean|array
157
   *   <code>true</code> if the configuration
158
   *   file could be read, the configuration array otherwise.
159
   */
160
  public function readConfig ()
161
  {
162
        $config = parse_ini_file('application/.config', true);
163
        if ($config !== false)
164
        {
165
                $section = 'database:' . $this->_dbname;
166
                if (isset($config[$section]))
167
                {
168
                        $dbconfig = $config[$section];
169
                        foreach (array('host', 'dbname', 'username', 'password', 'charset') as $key)
170
                        {
171
                                $property = "_$key";
172
                                if (isset($dbconfig[$key])
173
                                     && $key == 'dbname'
174
                                                 || (property_exists($this, $property)
175
                                                                  && $this->$property === null))
176
                                {
177
                                        $this->$property = $dbconfig[$key];
178
                                }
179
                        }
180
                }
181
        }
182
 
183
        return $config;
184
  }
185
 
186
  /**
41 PointedEar 187
   * @return PDO
188
   */
189
  public function getConnection ()
190
  {
191
    if ($this->_connection === null)
192
    {
193
      $this->_connection =
51 PointedEar 194
        new \PDO($this->_dsn, $this->_username, $this->_password, $this->_options);
41 PointedEar 195
    }
196
 
197
    return $this->_connection;
198
  }
199
 
200
  /**
44 PointedEar 201
   * Creates a database according to the specified parameters
202
   *
203
   * Should be overwritten and called by inheriting classes.
204
   *
205
   * @param string $dsn
206
   *   Connection DSN (required; must not include the database
207
   *   name).
208
   * @param string $username = null
209
   *   Connection username.  The default is specified by the
210
   *   <code>$_username</code> property.  Note that creating
211
   *   the database usually requires a user with more privileges
212
   *   than the one accessing the database or its tables.
213
   * @param string $password = null
214
   *   Connection password.  The default is specified by the
215
   *   <code>$_password</code> property.
216
   * @param array? $options = null
217
   *   Connection options.  The default is specified by the
218
   *   <code>$_options</code> property.
219
   * @param string $spec = null
220
   *   Additional database specifications, like character encoding
221
   *   and collation.
222
   * @param boolean $force = false
223
   *   If a true-value, the database will be attempted to be
224
   *   created even if there is a database of the name specified
225
   *   by the <code>$_dbname</code> property.
226
   * @return int
227
   *   The number of rows affected by the CREATE DATABASE statement.
228
   * @see PDO::__construct()
229
   * @see PDO::exec()
230
   */
231
  public function create ($dsn, $username = null, $password = null,
232
    array $options = null, $dbspec = null, $force = false)
233
  {
234
    $connection = new PDO($dsn,
235
      $username !== null ? $username : $this->_username,
236
      $password !== null ? $password : $this->_password,
237
      $options !== null ? $options : $this->_options);
238
 
239
    $query = 'CREATE DATABASE'
240
           . (!$force ? ' IF NOT EXISTS' : '')
241
           . ' ' . $this->escapeName($this->_dbname)
242
           . ($dbspec ? ' ' . $dbspec : '');
243
 
244
    return $connection->exec($query);
245
  }
246
 
247
  /**
27 PointedEar 248
   * Initiates a transaction
249
   *
250
   * @return bool
251
   * @see PDO::beginTransaction()
252
   */
253
  public function beginTransaction()
254
  {
41 PointedEar 255
    return $this->connection->beginTransaction();
27 PointedEar 256
  }
41 PointedEar 257
 
27 PointedEar 258
  /**
259
   * Rolls back a transaction
260
   *
261
   * @return bool
262
   * @see PDO::rollBack()
263
   */
264
  public function rollBack()
265
  {
41 PointedEar 266
    return $this->connection->rollBack();
27 PointedEar 267
  }
41 PointedEar 268
 
27 PointedEar 269
  /**
270
   * Commits a transaction
271
   *
272
   * @return bool
273
   * @see PDO::commit()
274
   */
275
  public function commit()
276
  {
41 PointedEar 277
    return $this->connection->commit();
27 PointedEar 278
  }
41 PointedEar 279
 
27 PointedEar 280
  /**
281
   * Prepares a statement for execution with the database
282
   * @param string $query
283
   */
284
  public function prepare($query, array $driver_options = array())
285
  {
41 PointedEar 286
    return $this->connection->prepare($query, $driver_options);
27 PointedEar 287
  }
41 PointedEar 288
 
27 PointedEar 289
  /**
290
   * Returns the ID of the last inserted row, or the last value from
291
   * a sequence object, depending on the underlying driver.
292
   *
293
   * @return int
294
   */
295
  public function getLastInsertId()
296
  {
297
    return $this->_lastInsertId;
298
  }
41 PointedEar 299
 
27 PointedEar 300
  /**
301
   * Escapes a database name so that it can be used in a query.
302
   *
303
   * @param string $name
304
   *   The name to be escaped
305
   * @return string
306
   *   The escaped name
307
   */
308
  public function escapeName($name)
309
  {
32 PointedEar 310
    return $this->_leftQuote . $name . $this->_rightQuote;
27 PointedEar 311
  }
41 PointedEar 312
 
27 PointedEar 313
  /**
314
   * Determines if an array is associative (has not all integer keys).
315
   *
316
   * @author
317
   *   Algorithm courtesy of squirrel, <http://stackoverflow.com/a/5969617/855543>.
318
   * @param array $a
319
   * @return boolean
320
   *   <code>true</code> if <var>$a</var> is associative,
321
   *   <code>false</code> otherwise
322
   */
323
  protected function _isAssociativeArray(array $a)
324
  {
325
    for (reset($a); is_int(key($a)); next($a));
326
    return !is_null(key($a));
327
  }
41 PointedEar 328
 
27 PointedEar 329
  /**
330
   * Escapes an associative array so that its string representation can be used
331
   * as list with table or column aliases in a query.
332
   *
333
   * This method does not actually escape anything; it only inserts the
334
   * 'AS' keyword.  It should be overridden by inheriting methods.
335
   *
336
   * NOTE: This method intentionally does not check whether the array actually
337
   * is associative.
338
   *
339
   * @param array &$array
340
   *   The array to be escaped
341
   * @return array
342
   *   The escaped array
343
   */
344
  protected function _escapeAliasArray(array &$array)
345
  {
346
    foreach ($array as $column => &$value)
347
    {
32 PointedEar 348
      $quotedColumn = $column;
349
      if (strpos($column, $this->_leftQuote) === false
350
         && strpos($column, $this->_rightQuote) === false)
351
      {
352
        $quotedColumn = $this->_leftQuote . $column . $this->_rightQuote;
353
      }
41 PointedEar 354
 
32 PointedEar 355
      $value = $value . ' AS ' . $quotedColumn;
27 PointedEar 356
    }
41 PointedEar 357
 
27 PointedEar 358
    return $array;
359
  }
360
 
361
  /**
362
   * @param array $a
363
   * @param string $prefix
364
   */
365
  private static function _expand(array $a, $prefix)
366
  {
367
    $a2 = array();
41 PointedEar 368
 
27 PointedEar 369
    foreach ($a as $key => $value)
370
    {
371
      $a2[] = ':' . $prefix . ($key + 1);
372
    }
41 PointedEar 373
 
27 PointedEar 374
    return $a2;
375
  }
41 PointedEar 376
 
27 PointedEar 377
  /**
378
   * Escapes an associative array so that its string representation can be used
379
   * as value list in a query.
380
   *
381
   * This method should be overridden by inheriting classes to escape
382
   * column names as fitting for the database schema they support.  It is
383
   * strongly recommended that the overriding methods call this method with
384
   * an appropriate <var>$escape</var> parameter, pass all other parameters
385
   * on unchanged, and return its return value.
386
   *
387
   * NOTE: Intentionally does not check whether the array actually is associative!
388
   *
389
   * @param array &$array
390
   *   The array to be escaped
391
   * @param string $suffix
392
   *   The string to be appended to the column name for the value placeholder.
393
   *   The default is the empty string.
394
   * @param array $escape
395
   *   The strings to use left-hand side (index 0) and right-hand side (index 1)
396
   *   of the column name.  The default is the empty string, respectively.
397
   * @return array
398
   *   The escaped array
399
   */
32 PointedEar 400
  protected function _escapeValueArray(array &$array, $suffix = '')
27 PointedEar 401
  {
402
    $result = array();
41 PointedEar 403
 
27 PointedEar 404
    foreach ($array as $column => $value)
405
    {
406
      $op = '=';
407
      $placeholder = ":{$column}";
41 PointedEar 408
 
27 PointedEar 409
      if (is_array($value) && $this->_isAssociativeArray($value))
410
      {
411
        reset($value);
412
        $op = ' ' . key($value) . ' ';
41 PointedEar 413
 
27 PointedEar 414
        $value = $value[key($value)];
415
      }
41 PointedEar 416
 
27 PointedEar 417
      if (is_array($value))
418
      {
33 PointedEar 419
        $placeholder = '(' . implode(', ', self::_expand($value, $column)) . ')';
27 PointedEar 420
      }
41 PointedEar 421
 
32 PointedEar 422
      $result[] = $this->_leftQuote . $column . $this->_rightQuote . "{$op}{$placeholder}{$suffix}";
27 PointedEar 423
    }
41 PointedEar 424
 
27 PointedEar 425
    return $result;
426
  }
41 PointedEar 427
 
27 PointedEar 428
  /**
429
   * Constructs the WHERE part of a query
430
   *
431
   * @param string|array $where
432
   *   Condition
433
   * @param string $suffix
434
   *   The string to be appended to the column name for the value placeholder,
435
   *   passed on to {@link Database::_escapeValueArray()}.  The default is
436
   *   the empty string.
437
   * @return string
438
   * @see Database::_escapeValueArray()
439
   */
440
  protected function _where($where, $suffix = '')
441
  {
442
    if (!is_null($where))
443
    {
444
      if (is_array($where))
445
      {
446
        if (count($where) < 1)
447
        {
448
          return '';
449
        }
41 PointedEar 450
 
27 PointedEar 451
        if ($this->_isAssociativeArray($where))
452
        {
453
          $where = $this->_escapeValueArray($where, $suffix);
454
        }
41 PointedEar 455
 
27 PointedEar 456
        $where = '(' . implode(') AND (', $where) . ')';
457
      }
41 PointedEar 458
 
27 PointedEar 459
      return ' WHERE ' . $where;
460
    }
41 PointedEar 461
 
27 PointedEar 462
    return '';
463
  }
464
 
465
  /**
466
   * Selects data from one or more tables; the resulting records are stored
467
   * in the <code>result</code> property and returned as an associative array,
468
   * where the keys are the column (alias) names.
469
   *
470
   * @param string|array[string] $tables Table(s) to select from
471
   * @param string|array[string] $columns Column(s) to select from (optional)
472
   * @param string|array $where Condition (optional)
473
   * @param string $order Sort order (optional)
474
   *   If provided, MUST start with ORDER BY or GROUP BY
475
   * @param string $limit Limit (optional)
476
   * @param int $fetch_style
477
   *   The mode that should be used for {@link PDOStatement::fetchAll()}.
478
   *   The default is {@link PDO::FETCH_ASSOC}.
479
   * @return array
480
   * @see Database::prepare()
481
   * @see PDOStatement::fetchAll()
482
   */
483
  public function select($tables, $columns = null, $where = null,
51 PointedEar 484
    $order = null, $limit = null, $fetch_style = \PDO::FETCH_ASSOC)
27 PointedEar 485
  {
486
    if (is_null($columns))
487
    {
488
      $columns = array('*');
489
    }
41 PointedEar 490
 
27 PointedEar 491
    if (is_array($columns))
492
    {
493
      if ($this->_isAssociativeArray($columns))
494
      {
495
        $columns = $this->_escapeAliasArray($columns);
496
      }
497
 
33 PointedEar 498
      $columns = implode(', ', $columns);
27 PointedEar 499
    }
500
 
501
    if (is_array($tables))
502
    {
503
      if ($this->_isAssociativeArray($columns))
504
      {
505
        $columns = $this->_escapeAliasArray($columns);
506
      }
507
 
33 PointedEar 508
      $tables = implode(', ', $tables);
27 PointedEar 509
    }
510
 
511
    $query = "SELECT {$columns} FROM {$tables}" . $this->_where($where);
512
 
513
    if (!is_null($order))
514
    {
515
      if (is_array($order))
516
      {
33 PointedEar 517
        $order = 'ORDER BY ' . implode(', ', $order);
27 PointedEar 518
      }
41 PointedEar 519
 
27 PointedEar 520
      $query .= " $order";
521
    }
522
 
523
    if (!is_null($limit))
524
    {
525
      $query .= " LIMIT $limit";
526
    }
41 PointedEar 527
 
27 PointedEar 528
    $stmt = $this->prepare($query);
529
 
530
    $params = array();
41 PointedEar 531
 
27 PointedEar 532
    if (is_array($where) && $this->_isAssociativeArray($where))
533
    {
34 PointedEar 534
      /* FIXME: Export and reuse this */
27 PointedEar 535
      foreach ($where as $column => $condition)
536
      {
34 PointedEar 537
        /* TODO: Also handle function calls as keys */
27 PointedEar 538
        if (is_array($condition) && $this->_isAssociativeArray($condition))
539
        {
540
          reset($condition);
541
          $condition = $condition[key($condition)];
41 PointedEar 542
 
27 PointedEar 543
          if (is_array($condition))
544
          {
545
            foreach (self::_expand($condition, $column) as $param_index => $param_name)
546
            {
547
              $params[$param_name] = $condition[$param_index];
548
            }
549
          }
550
        }
551
        else
552
        {
553
          $params[":{$column}"] = $condition;
554
        }
555
      }
556
    }
557
 
558
    /* DEBUG */
559
    if (defined('DEBUG') && DEBUG > 1)
560
    {
561
      debug(array(
562
        'query'  => $query,
563
        'params' => $params
564
      ));
565
    }
41 PointedEar 566
 
27 PointedEar 567
    $success =& $this->_lastSuccess;
568
    $success =  $stmt->execute($params);
41 PointedEar 569
 
27 PointedEar 570
    $errorInfo =& $this->_lastError;
571
    $errorInfo =  $stmt->errorInfo();
41 PointedEar 572
 
27 PointedEar 573
    $result =& $this->_lastResult;
574
    $result =  $stmt->fetchAll($fetch_style);
41 PointedEar 575
 
27 PointedEar 576
    if (defined('DEBUG') && DEBUG > 1)
577
    {
578
      debug(array(
579
        '_lastSuccess' => $success,
580
        '_lastError'   => $errorInfo,
581
        '_lastResult'  => $result
582
      ));
583
    }
41 PointedEar 584
 
27 PointedEar 585
    return $result;
586
  }
587
 
588
  /**
589
   * Sets and returns the ID of the last inserted row, or the last value from
590
   * a sequence object, depending on the underlying driver.
591
   *
592
   * @param string $name
593
   *   Name of the sequence object from which the ID should be returned.
594
   * @return string
595
   */
596
  protected function _setLastInsertId($name = null)
597
  {
41 PointedEar 598
    return ($this->_lastInsertId = $this->connection->lastInsertId($name));
27 PointedEar 599
  }
600
 
601
  /**
602
   * Resets the the ID of the last inserted row, or the last value from
603
   * a sequence object, depending on the underlying driver.
604
   *
605
   * @return string
606
   *   The default value
607
   */
608
  protected function _resetLastInsertId()
609
  {
610
    return ($this->_lastInsertId = '');
611
  }
41 PointedEar 612
 
27 PointedEar 613
  /**
614
   * Updates one or more records
615
   *
616
   * @param string|array $tables
617
   *   Table name
48 PointedEar 618
   * @param array $updates
27 PointedEar 619
   *   Associative array of column-value pairs
620
   * @param array|string $where
621
   *   Only the records matching this condition are updated
622
   * @return bool
623
   */
48 PointedEar 624
  public function update($tables, array $updates, $where = null)
27 PointedEar 625
  {
626
    if (!$tables)
627
    {
628
      throw new InvalidArgumentException('No table specified');
629
    }
41 PointedEar 630
 
27 PointedEar 631
    if (is_array($tables))
632
    {
33 PointedEar 633
      $tables = implode(', ', $tables);
27 PointedEar 634
    }
41 PointedEar 635
 
27 PointedEar 636
    if (!$updates)
637
    {
638
      throw new InvalidArgumentException('No values specified');
639
    }
640
 
641
    $params = array();
41 PointedEar 642
 
27 PointedEar 643
    if ($this->_isAssociativeArray($updates))
644
    {
645
      foreach ($updates as $key => $condition)
646
      {
647
        $params[":{$key}"] = $condition;
648
      }
649
    }
41 PointedEar 650
 
33 PointedEar 651
    $updates = implode(', ', $this->_escapeValueArray($updates));
41 PointedEar 652
 
27 PointedEar 653
    /* TODO: Should escape table names with escapeName(), but what about aliases? */
654
    $query = "UPDATE {$tables} SET {$updates}" . $this->_where($where, '2');
41 PointedEar 655
 
27 PointedEar 656
    $stmt = $this->prepare($query);
41 PointedEar 657
 
27 PointedEar 658
    if (is_array($where) && $this->_isAssociativeArray($where))
659
    {
660
      foreach ($where as $column => $condition)
661
      {
662
        if (is_array($condition) && $this->_isAssociativeArray($condition))
663
        {
664
          reset($condition);
665
          $condition = $condition[key($condition)];
41 PointedEar 666
 
27 PointedEar 667
          if (is_array($condition))
668
          {
669
            foreach (self::_expand($condition, $column) as $param_index => $param_name)
670
            {
671
              $params[$param_name] = $condition[$param_index];
672
            }
673
          }
674
        }
675
        else
676
        {
677
          $params[":{$column}2"] = $condition;
678
        }
679
      }
680
    }
681
 
682
    /* DEBUG */
683
    if (defined('DEBUG') && DEBUG > 1)
684
    {
685
      debug(array(
686
        'query'  => $query,
687
        'params' => $params
688
      ));
689
    }
41 PointedEar 690
 
27 PointedEar 691
    $success =& $this->_lastSuccess;
692
    $success =  $stmt->execute($params);
41 PointedEar 693
 
27 PointedEar 694
    $errorInfo =& $this->_lastError;
695
    $errorInfo =  $stmt->errorInfo();
41 PointedEar 696
 
27 PointedEar 697
    $this->_resetLastInsertId();
41 PointedEar 698
 
27 PointedEar 699
    $result =& $this->_lastResult;
700
    $result =  $stmt->fetchAll();
41 PointedEar 701
 
27 PointedEar 702
    if (defined('DEBUG') && DEBUG > 1)
703
    {
704
      debug(array(
705
        '_lastSuccess' => $success,
706
        '_lastError'    => $errorInfo,
707
        '_lastResult'  => $result
708
      ));
709
    }
41 PointedEar 710
 
27 PointedEar 711
    return $success;
712
  }
41 PointedEar 713
 
27 PointedEar 714
  /**
715
   * Inserts a record into a table.<p>The AUTO_INCREMENT value of the inserted
716
   * row, if any (> 0), is stored in the {@link $lastInsertId} property of
717
   * the <code>Database</code> instance.</p>
718
   *
719
   * @param string $table
720
   *   Table name
721
   * @param array|string $values
722
   *   Associative array of column-value pairs, indexed array,
723
   *   or comma-separated list of values.  If <var>$values</var> is not
724
   *   an associative array, <var>$cols</var> must be passed if the
725
   *   values are not in column order (see below).
726
   * @param array|string $cols
727
   *   Indexed array, or comma-separated list of column names.
728
   *   Needs only be passed if <var>$values</var> is not an associative array
729
   *   and the values are not in column order (default: <code>null</code>);
730
   *   is ignored otherwise.  <strong>You SHOULD NOT rely on column order.</strong>
731
   * @return bool
732
   *   <code>true</code> if successful, <code>false</code> otherwise
733
   * @see PDOStatement::execute()
734
   */
735
  public function insert($table, $values, $cols = null)
736
  {
737
    if ($cols != null)
738
    {
739
      $cols = ' ('
740
            . (is_array($cols)
33 PointedEar 741
                ? implode(', ', array_map(array($this, 'escapeName'), $cols))
27 PointedEar 742
                : $cols) . ')';
743
    }
744
    else
745
    {
746
      $cols = '';
747
    }
41 PointedEar 748
 
27 PointedEar 749
    /* DEBUG */
750
    if (defined('DEBUG') && DEBUG > 2)
751
    {
752
      debug(array('values' => $values));
753
    }
41 PointedEar 754
 
27 PointedEar 755
    $params = array();
41 PointedEar 756
 
27 PointedEar 757
    if (is_array($values))
758
    {
759
      if ($this->_isAssociativeArray($values))
760
      {
761
        foreach ($values as $key => $condition)
762
        {
763
          $params[":{$key}"] = $condition;
764
        }
41 PointedEar 765
 
27 PointedEar 766
        $values = $this->_escapeValueArray($values);
41 PointedEar 767
 
27 PointedEar 768
        $cols = '';
769
        $values = 'SET ' . implode(', ', $values);
770
      }
771
      else
772
      {
773
        foreach ($values as &$value)
774
        {
775
          if (is_string($value))
776
          {
777
            $value = "'" . $value . "'";
778
          }
779
        }
41 PointedEar 780
 
27 PointedEar 781
        $values = ' VALUES (' . implode(', ', $values) . ')';
782
      }
783
    }
41 PointedEar 784
 
27 PointedEar 785
    /* TODO: Should escape table names with escapeName(), but what about aliases? */
786
    $query = "INSERT INTO {$table} {$cols} {$values}";
41 PointedEar 787
 
27 PointedEar 788
    $stmt = $this->prepare($query);
41 PointedEar 789
 
27 PointedEar 790
      /* DEBUG */
791
    if (defined('DEBUG') && DEBUG > 1)
792
    {
793
       debug(array(
794
         'query'  => $query,
795
         'params' => $params
796
       ));
797
    }
41 PointedEar 798
 
27 PointedEar 799
    $success =& $this->_lastSuccess;
800
    $success = $stmt->execute($params);
41 PointedEar 801
 
27 PointedEar 802
    $errorInfo =& $this->_lastError;
803
    $errorInfo =  $stmt->errorInfo();
41 PointedEar 804
 
27 PointedEar 805
    $this->_setLastInsertId();
41 PointedEar 806
 
27 PointedEar 807
    $result =& $this->_lastResult;
808
    $result =  $stmt->fetchAll();
809
 
810
    if (defined('DEBUG') && DEBUG > 1)
811
    {
812
      debug(array(
813
        '_lastSuccess'  => $success,
814
        '_lastError'    => $errorInfo,
815
        '_lastInsertId' => $this->_lastInsertId,
816
        '_lastResult'   => $result
817
      ));
818
    }
41 PointedEar 819
 
27 PointedEar 820
    return $success;
821
  }
41 PointedEar 822
 
27 PointedEar 823
  /**
824
   * Retrieves all rows from a table
825
   *
826
   * @param int[optional] $fetch_style
827
   * @param int[optional] $column_index
828
   * @param array[optional] $ctor_args
829
   * @return array
830
   * @see PDOStatement::fetchAll()
831
   */
832
  public function fetchAll($table, $fetch_style = null, $column_index = null, array $ctor_args = null)
833
  {
834
    /* NOTE: Cannot use table name as statement parameter */
835
    $stmt = $this->prepare("SELECT * FROM $table");
836
    $this->_lastSuccess = $stmt->execute();
41 PointedEar 837
 
27 PointedEar 838
    $this->_lastError = $stmt->errorInfo();
41 PointedEar 839
 
27 PointedEar 840
    $result =& $this->_lastResult;
41 PointedEar 841
 
27 PointedEar 842
    if (is_null($fetch_style))
843
    {
51 PointedEar 844
      $fetch_style = \PDO::FETCH_ASSOC;
27 PointedEar 845
    }
41 PointedEar 846
 
27 PointedEar 847
    if (!is_null($ctor_args))
848
    {
849
      $result = $stmt->fetchAll($fetch_style, $column_index, $ctor_args);
850
    }
851
    else if (!is_null($column_index))
852
    {
853
      $result = $stmt->fetchAll($fetch_style, $column_index);
854
    }
855
    else if (!is_null($fetch_style))
856
    {
857
      $result = $stmt->fetchAll($fetch_style);
858
    }
859
    else
860
    {
861
      $result = $stmt->fetchAll();
862
    }
41 PointedEar 863
 
27 PointedEar 864
    return $result;
865
  }
866
 
867
  /**
868
   * Deletes one or more records
869
   *
870
   * @param string|array $tables
871
   *   Table name(s)
872
   * @param array|string $where
873
   *   Only the records matching this condition are deleted
874
   * @return bool
875
   * @see PDOStatement::execute()
876
   */
877
  public function delete($tables, $where = null)
878
  {
879
    if (!$tables)
880
    {
881
      throw new InvalidArgumentException('No table specified');
882
    }
41 PointedEar 883
 
27 PointedEar 884
    if (is_array($tables))
885
    {
33 PointedEar 886
      $tables = implode(', ', $tables);
27 PointedEar 887
    }
41 PointedEar 888
 
27 PointedEar 889
    $params = array();
41 PointedEar 890
 
27 PointedEar 891
    $query = "DELETE FROM {$tables}" . $this->_where($where);
41 PointedEar 892
 
27 PointedEar 893
    $stmt = $this->prepare($query);
41 PointedEar 894
 
27 PointedEar 895
    if ($this->_isAssociativeArray($where))
896
    {
897
      foreach ($where as $column => $condition)
898
      {
899
        if (is_array($condition) && $this->_isAssociativeArray($condition))
900
        {
901
          reset($condition);
902
          $condition = $condition[key($condition)];
41 PointedEar 903
 
27 PointedEar 904
          if (is_array($condition))
905
          {
906
            foreach (self::_expand($condition, $column) as $param_index => $param_name)
907
            {
908
              $params[$param_name] = $condition[$param_index];
909
            }
910
          }
911
        }
912
        else
913
        {
914
          $params[":{$column}"] = $condition;
915
        }
916
      }
917
    }
918
 
919
    /* DEBUG */
920
    if (defined('DEBUG') && DEBUG > 1)
921
    {
922
      debug(array(
923
        'query'  => $query,
924
        'params' => $params
925
      ));
926
    }
41 PointedEar 927
 
27 PointedEar 928
    $success =& $this->_lastSuccess;
929
    $success =  $stmt->execute($params);
41 PointedEar 930
 
27 PointedEar 931
    $result =& $this->_lastResult;
932
    $result =  $stmt->fetchAll();
41 PointedEar 933
 
27 PointedEar 934
    $errorInfo =& $this->_lastError;
935
    $errorInfo =  $stmt->errorInfo();
41 PointedEar 936
 
27 PointedEar 937
    if (defined('DEBUG') && DEBUG > 1)
938
    {
939
      debug(array(
940
        '_lastSuccess' => $success,
941
        '_lastError'   => $errorInfo,
942
        '_lastResult'  => $result
943
      ));
944
    }
41 PointedEar 945
 
27 PointedEar 946
    return $success;
947
  }
948
}