Yaffs site version 1.1
[yaffs-website] / vendor / gabordemooij / redbean / RedBeanPHP / Driver / RPDO.php
1 <?php
2
3 namespace RedBeanPHP\Driver;
4
5 use RedBeanPHP\Driver as Driver;
6 use RedBeanPHP\Logger as Logger;
7 use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
8 use RedBeanPHP\RedException as RedException;
9 use RedBeanPHP\RedException\SQL as SQL;
10 use RedBeanPHP\Logger\RDefault as RDefault;
11 use RedBeanPHP\PDOCompatible as PDOCompatible;
12 use RedBeanPHP\Cursor\PDOCursor as PDOCursor;
13
14 /**
15  * PDO Driver
16  * This Driver implements the RedBean Driver API.
17  * for RedBeanPHP. This is the standard / default database driver
18  * for RedBeanPHP.
19  *
20  * @file    RedBeanPHP/PDO.php
21  * @author  Gabor de Mooij and the RedBeanPHP Community, Desfrenes
22  * @license BSD/GPLv2
23  *
24  * @copyright
25  * copyright (c) Desfrenes & Gabor de Mooij and the RedBeanPHP community
26  * This source file is subject to the BSD/GPLv2 License that is bundled
27  * with this source code in the file license.txt.
28  */
29 class RPDO implements Driver
30 {
31         /**
32          * @var integer
33          */
34         protected $max;
35
36         /**
37          * @var string
38          */
39         protected $dsn;
40
41         /**
42          * @var boolean
43          */
44         protected $loggingEnabled = FALSE;
45
46         /**
47          * @var Logger
48          */
49         protected $logger = NULL;
50
51         /**
52          * @var PDO
53          */
54         protected $pdo;
55
56         /**
57          * @var integer
58          */
59         protected $affectedRows;
60
61         /**
62          * @var integer
63          */
64         protected $resultArray;
65
66         /**
67          * @var array
68          */
69         protected $connectInfo = array();
70
71         /**
72          * @var boolean
73          */
74         protected $isConnected = FALSE;
75
76         /**
77          * @var bool
78          */
79         protected $flagUseStringOnlyBinding = FALSE;
80
81         /**
82          * @var integer
83          */
84         protected $queryCounter = 0;
85
86         /**
87          * @var string
88          */
89         protected $mysqlEncoding = '';
90
91         /**
92          * Binds parameters. This method binds parameters to a PDOStatement for
93          * Query Execution. This method binds parameters as NULL, INTEGER or STRING
94          * and supports both named keys and question mark keys.
95          *
96          * @param PDOStatement $statement PDO Statement instance
97          * @param array        $bindings  values that need to get bound to the statement
98          *
99          * @return void
100          */
101         protected function bindParams( $statement, $bindings )
102         {
103                 foreach ( $bindings as $key => &$value ) {
104                         if ( is_integer( $key ) ) {
105                                 if ( is_null( $value ) ) {
106                                         $statement->bindValue( $key + 1, NULL, \PDO::PARAM_NULL );
107                                 } elseif ( !$this->flagUseStringOnlyBinding && AQueryWriter::canBeTreatedAsInt( $value ) && abs( $value ) <= $this->max ) {
108                                         $statement->bindParam( $key + 1, $value, \PDO::PARAM_INT );
109                                 } else {
110                                         $statement->bindParam( $key + 1, $value, \PDO::PARAM_STR );
111                                 }
112                         } else {
113                                 if ( is_null( $value ) ) {
114                                         $statement->bindValue( $key, NULL, \PDO::PARAM_NULL );
115                                 } elseif ( !$this->flagUseStringOnlyBinding && AQueryWriter::canBeTreatedAsInt( $value ) && abs( $value ) <= $this->max ) {
116                                         $statement->bindParam( $key, $value, \PDO::PARAM_INT );
117                                 } else {
118                                         $statement->bindParam( $key, $value, \PDO::PARAM_STR );
119                                 }
120                         }
121                 }
122         }
123
124         /**
125          * This method runs the actual SQL query and binds a list of parameters to the query.
126          * slots. The result of the query will be stored in the protected property
127          * $rs (always array). The number of rows affected (result of rowcount, if supported by database)
128          * is stored in protected property $affectedRows. If the debug flag is set
129          * this function will send debugging output to screen buffer.
130          *
131          * @param string $sql      the SQL string to be send to database server
132          * @param array  $bindings the values that need to get bound to the query slots
133          * @param array  $options
134          *
135          * @return mixed
136          * @throws SQL
137          */
138         protected function runQuery( $sql, $bindings, $options = array() )
139         {
140                 $this->connect();
141                 if ( $this->loggingEnabled && $this->logger ) {
142                         $this->logger->log( $sql, $bindings );
143                 }
144                 try {
145                         if ( strpos( 'pgsql', $this->dsn ) === 0 ) {
146                                 if ( defined( '\PDO::PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT' ) ) {
147                                         $statement = $this->pdo->prepare( $sql, array( \PDO::PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT => TRUE ) );
148                                 } else {
149                                         $statement = $this->pdo->prepare( $sql );
150                                 }
151                         } else {
152                                 $statement = $this->pdo->prepare( $sql );
153                         }
154                         $this->bindParams( $statement, $bindings );
155                         $statement->execute();
156                         $this->queryCounter ++;
157                         $this->affectedRows = $statement->rowCount();
158                         if ( $statement->columnCount() ) {
159                                 $fetchStyle = ( isset( $options['fetchStyle'] ) ) ? $options['fetchStyle'] : NULL;
160                                 if ( isset( $options['noFetch'] ) && $options['noFetch'] ) {
161                                         $this->resultArray = array();
162                                         return $statement;
163                                 }
164                                 $this->resultArray = $statement->fetchAll( $fetchStyle );
165                                 if ( $this->loggingEnabled && $this->logger ) {
166                                         $this->logger->log( 'resultset: ' . count( $this->resultArray ) . ' rows' );
167                                 }
168                         } else {
169                                 $this->resultArray = array();
170                         }
171                 } catch ( \PDOException $e ) {
172                         //Unfortunately the code field is supposed to be int by default (php)
173                         //So we need a property to convey the SQL State code.
174                         $err = $e->getMessage();
175                         if ( $this->loggingEnabled && $this->logger ) $this->logger->log( 'An error occurred: ' . $err );
176                         $exception = new SQL( $err, 0 );
177                         $exception->setSQLState( $e->getCode() );
178                         throw $exception;
179                 }
180         }
181
182         /**
183          * Try to fix MySQL character encoding problems.
184          * MySQL < 5.5 does not support proper 4 byte unicode but they
185          * seem to have added it with version 5.5 under a different label: utf8mb4.
186          * We try to select the best possible charset based on your version data.
187          *
188          * @return void
189          */
190         protected function setEncoding()
191         {
192                 $driver = $this->pdo->getAttribute( \PDO::ATTR_DRIVER_NAME );
193                 $version = floatval( $this->pdo->getAttribute( \PDO::ATTR_SERVER_VERSION ) );
194                 if ($driver === 'mysql') {
195                         $encoding = ($version >= 5.5) ? 'utf8mb4' : 'utf8';
196                         $this->pdo->setAttribute(\PDO::MYSQL_ATTR_INIT_COMMAND, 'SET NAMES '.$encoding ); //on every re-connect
197                         $this->pdo->exec(' SET NAMES '. $encoding); //also for current connection
198                         $this->mysqlEncoding = $encoding;
199                 }
200         }
201
202         /**
203          * Constructor. You may either specify dsn, user and password or
204          * just give an existing PDO connection.
205          *
206          * Examples:
207          *    $driver = new RPDO($dsn, $user, $password);
208          *    $driver = new RPDO($existingConnection);
209          *
210          * @param string|object $dsn  database connection string
211          * @param string        $user optional, usename to sign in
212          * @param string        $pass optional, password for connection login
213          *
214          * @return void
215          */
216         public function __construct( $dsn, $user = NULL, $pass = NULL )
217         {
218                 if ( is_object( $dsn ) ) {
219                         $this->pdo = $dsn;
220                         $this->isConnected = TRUE;
221                         $this->setEncoding();
222                         $this->pdo->setAttribute( \PDO::ATTR_ERRMODE,\PDO::ERRMODE_EXCEPTION );
223                         $this->pdo->setAttribute( \PDO::ATTR_DEFAULT_FETCH_MODE,\PDO::FETCH_ASSOC );
224                         // make sure that the dsn at least contains the type
225                         $this->dsn = $this->getDatabaseType();
226                 } else {
227                         $this->dsn = $dsn;
228                         $this->connectInfo = array( 'pass' => $pass, 'user' => $user );
229                 }
230
231                 //PHP 5.3 PDO SQLite has a bug with large numbers:
232                 if ( ( strpos( $this->dsn, 'sqlite' ) === 0 && PHP_MAJOR_VERSION === 5 && PHP_MINOR_VERSION === 3 ) ||  defined('HHVM_VERSION') || $this->dsn === 'test-sqlite-53' ) {
233                         $this->max = 2147483647; //otherwise you get -2147483648 ?! demonstrated in build #603 on Travis.
234                 } elseif ( strpos( $this->dsn, 'cubrid' ) === 0 ) {
235                         $this->max = 2147483647; //bindParam in pdo_cubrid also fails...
236                 } else {
237                         $this->max = PHP_INT_MAX; //the normal value of course (makes it possible to use large numbers in LIMIT clause)
238                 }
239         }
240
241         /**
242          * Returns the best possible encoding for MySQL based on version data.
243          *
244          * @return string
245          */
246         public function getMysqlEncoding()
247         {
248                 return $this->mysqlEncoding;
249         }
250
251         /**
252          * Whether to bind all parameters as strings.
253          * If set to TRUE this will cause all integers to be bound as STRINGS.
254          * This will NOT affect NULL values.
255          *
256          * @param boolean $yesNo pass TRUE to bind all parameters as strings.
257          *
258          * @return void
259          */
260         public function setUseStringOnlyBinding( $yesNo )
261         {
262                 $this->flagUseStringOnlyBinding = (boolean) $yesNo;
263         }
264
265         /**
266          * Sets the maximum value to be bound as integer, normally
267          * this value equals PHP's MAX INT constant, however sometimes
268          * PDO driver bindings cannot bind large integers as integers.
269          * This method allows you to manually set the max integer binding
270          * value to manage portability/compatibility issues among different
271          * PHP builds. This method will return the old value.
272          *
273          * @param integer $max maximum value for integer bindings
274          *
275          * @return integer
276          */
277         public function setMaxIntBind( $max )
278         {
279                 if ( !is_integer( $max ) ) throw new RedException( 'Parameter has to be integer.' );
280                 $oldMax = $this->max;
281                 $this->max = $max;
282                 return $oldMax;
283         }
284
285         /**
286          * Establishes a connection to the database using PHP\PDO
287          * functionality. If a connection has already been established this
288          * method will simply return directly. This method also turns on
289          * UTF8 for the database and PDO-ERRMODE-EXCEPTION as well as
290          * PDO-FETCH-ASSOC.
291          *
292          * @return void
293          */
294         public function connect()
295         {
296                 if ( $this->isConnected ) return;
297                 try {
298                         $user = $this->connectInfo['user'];
299                         $pass = $this->connectInfo['pass'];
300                         $this->pdo = new \PDO(
301                                 $this->dsn,
302                                 $user,
303                                 $pass
304                         );
305                         $this->setEncoding();
306                         $this->pdo->setAttribute( \PDO::ATTR_STRINGIFY_FETCHES, TRUE );
307                         //cant pass these as argument to constructor, CUBRID driver does not understand...
308                         $this->pdo->setAttribute( \PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION );
309                         $this->pdo->setAttribute( \PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC );
310                         $this->isConnected = TRUE;
311                 } catch ( \PDOException $exception ) {
312                         $matches = array();
313                         $dbname  = ( preg_match( '/dbname=(\w+)/', $this->dsn, $matches ) ) ? $matches[1] : '?';
314                         throw new \PDOException( 'Could not connect to database (' . $dbname . ').', $exception->getCode() );
315                 }
316         }
317
318         /**
319          * Directly sets PDO instance into driver.
320          * This method might improve performance, however since the driver does
321          * not configure this instance terrible things may happen... only use
322          * this method if you are an expert on RedBeanPHP, PDO and UTF8 connections and
323          * you know your database server VERY WELL.
324          *
325          * @param PDO $pdo PDO instance
326          *
327          * @return void
328          */
329         public function setPDO( \PDO $pdo ) {
330                 $this->pdo = $pdo;
331         }
332
333         /**
334          * @see Driver::GetAll
335          */
336         public function GetAll( $sql, $bindings = array() )
337         {
338                 $this->runQuery( $sql, $bindings );
339                 return $this->resultArray;
340         }
341
342         /**
343          * @see Driver::GetAssocRow
344          */
345         public function GetAssocRow( $sql, $bindings = array() )
346         {
347                 $this->runQuery( $sql, $bindings, array(
348                                 'fetchStyle' => \PDO::FETCH_ASSOC
349                         )
350                 );
351                 return $this->resultArray;
352         }
353
354         /**
355          * @see Driver::GetCol
356          */
357         public function GetCol( $sql, $bindings = array() )
358         {
359                 $rows = $this->GetAll( $sql, $bindings );
360                 $cols = array();
361                 if ( $rows && is_array( $rows ) && count( $rows ) > 0 ) {
362                         foreach ( $rows as $row ) {
363                                 $cols[] = array_shift( $row );
364                         }
365                 }
366
367                 return $cols;
368         }
369
370         /**
371          * @see Driver::GetOne
372          */
373         public function GetOne( $sql, $bindings = array() )
374         {
375                 $arr = $this->GetAll( $sql, $bindings );
376                 $res = NULL;
377                 if ( !is_array( $arr ) ) return NULL;
378                 if ( count( $arr ) === 0 ) return NULL;
379                 $row1 = array_shift( $arr );
380                 if ( !is_array( $row1 ) ) return NULL;
381                 if ( count( $row1 ) === 0 ) return NULL;
382                 $col1 = array_shift( $row1 );
383                 return $col1;
384         }
385
386         /**
387          * Alias for getOne().
388          * Backward compatibility.
389          *
390          * @param string $sql      SQL
391          * @param array  $bindings bindings
392          *
393          * @return mixed
394          */
395         public function GetCell( $sql, $bindings = array() )
396         {
397                 return $this->GetOne( $sql, $bindings );
398         }
399
400         /**
401          * @see Driver::GetRow
402          */
403         public function GetRow( $sql, $bindings = array() )
404         {
405                 $arr = $this->GetAll( $sql, $bindings );
406                 return array_shift( $arr );
407         }
408
409         /**
410          * @see Driver::Excecute
411          */
412         public function Execute( $sql, $bindings = array() )
413         {
414                 $this->runQuery( $sql, $bindings );
415                 return $this->affectedRows;
416         }
417
418         /**
419          * @see Driver::GetInsertID
420          */
421         public function GetInsertID()
422         {
423                 $this->connect();
424
425                 return (int) $this->pdo->lastInsertId();
426         }
427
428         /**
429          * @see Driver::GetCursor
430          */
431         public function GetCursor( $sql, $bindings = array() )
432         {
433                 $statement = $this->runQuery( $sql, $bindings, array( 'noFetch' => TRUE ) );
434                 $cursor = new PDOCursor( $statement, \PDO::FETCH_ASSOC );
435                 return $cursor;
436         }
437
438         /**
439          * @see Driver::Affected_Rows
440          */
441         public function Affected_Rows()
442         {
443                 $this->connect();
444                 return (int) $this->affectedRows;
445         }
446
447         /**
448          * Toggles debug mode. In debug mode the driver will print all
449          * SQL to the screen together with some information about the
450          * results.
451          *
452          * @param boolean $trueFalse turn on/off
453          * @param Logger  $logger    logger instance
454          *
455          * @return void
456          */
457         public function setDebugMode( $tf, $logger = NULL )
458         {
459                 $this->connect();
460                 $this->loggingEnabled = (bool) $tf;
461                 if ( $this->loggingEnabled and !$logger ) {
462                         $logger = new RDefault();
463                 }
464                 $this->setLogger( $logger );
465         }
466
467         /**
468          * Injects Logger object.
469          * Sets the logger instance you wish to use.
470          *
471          * @param Logger $logger the logger instance to be used for logging
472          *
473          * @return void
474          */
475         public function setLogger( Logger $logger )
476         {
477                 $this->logger = $logger;
478         }
479
480         /**
481          * Gets Logger object.
482          * Returns the currently active Logger instance.
483          *
484          * @return Logger
485          */
486         public function getLogger()
487         {
488                 return $this->logger;
489         }
490
491         /**
492          * @see Driver::StartTrans
493          */
494         public function StartTrans()
495         {
496                 $this->connect();
497                 $this->pdo->beginTransaction();
498         }
499
500         /**
501          * @see Driver::CommitTrans
502          */
503         public function CommitTrans()
504         {
505                 $this->connect();
506                 $this->pdo->commit();
507         }
508
509         /**
510          * @see Driver::FailTrans
511          */
512         public function FailTrans()
513         {
514                 $this->connect();
515                 $this->pdo->rollback();
516         }
517
518         /**
519          * Returns the name of database driver for PDO.
520          * Uses the PDO attribute DRIVER NAME to obtain the name of the
521          * PDO driver.
522          *
523          * @return string
524          */
525         public function getDatabaseType()
526         {
527                 $this->connect();
528
529                 return $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME );
530         }
531
532         /**
533          * Returns the version number of the database.
534          *
535          * @return mixed
536          */
537         public function getDatabaseVersion()
538         {
539                 $this->connect();
540                 return $this->pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION );
541         }
542
543         /**
544          * Returns the underlying PHP PDO instance.
545          *
546          * @return PDO
547          */
548         public function getPDO()
549         {
550                 $this->connect();
551                 return $this->pdo;
552         }
553
554         /**
555          * Closes database connection by destructing PDO.
556          *
557          * @return void
558          */
559         public function close()
560         {
561                 $this->pdo         = NULL;
562                 $this->isConnected = FALSE;
563         }
564
565         /**
566          * Returns TRUE if the current PDO instance is connected.
567          *
568          * @return boolean
569          */
570         public function isConnected()
571         {
572                 return $this->isConnected && $this->pdo;
573         }
574
575         /**
576          * Toggles logging, enables or disables logging.
577          *
578          * @param boolean $enable TRUE to enable logging
579          *
580          * @return self
581          */
582         public function setEnableLogging( $enable )
583         {
584                 $this->loggingEnabled = (boolean) $enable;
585         }
586
587         /**
588          * Resets the internal Query Counter.
589          *
590          * @return self
591          */
592         public function resetCounter()
593         {
594                 $this->queryCounter = 0;
595                 return $this;
596         }
597
598         /**
599          * Returns the number of SQL queries processed.
600          *
601          * @return integer
602          */
603         public function getQueryCount()
604         {
605                 return $this->queryCounter;
606         }
607
608         /**
609          * Returns the maximum value treated as integer parameter
610          * binding.
611          *
612          * This method is mainly for testing purposes but it can help
613          * you solve some issues relating to integer bindings.
614          *
615          * @return integer
616          */
617         public function getIntegerBindingMax()
618         {
619                 return $this->max;
620         }
621 }