Subversion Repositories PHPX

Rev

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

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