Subversion Repositories PHPX

Rev

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

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