Subversion Repositories PHPX

Rev

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

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