Subversion Repositories PHPX

Rev

Rev 77 | Go to most recent revision | Only display areas with differences | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

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