diff --git a/lib/Cake/Model/Datasource/Database/Driver/PDODriverTrait.php b/lib/Cake/Model/Datasource/Database/Driver/PDODriverTrait.php index 5234b5e33b2..5e1f53adf92 100644 --- a/lib/Cake/Model/Datasource/Database/Driver/PDODriverTrait.php +++ b/lib/Cake/Model/Datasource/Database/Driver/PDODriverTrait.php @@ -17,7 +17,7 @@ */ namespace Cake\Model\Datasource\Database\Driver; -use Cake\Model\Datasource\Database\Statement; +use Cake\Model\Datasource\Database\Statement\PDOStatement; use PDO; trait PDODriverTrait { @@ -70,7 +70,7 @@ public function disconnect() { */ public function prepare($sql) { $statement = $this->connection()->prepare($sql); - return new Statement($statement, $this); + return new PDOStatement($statement, $this); } /** diff --git a/lib/Cake/Model/Datasource/Database/Driver/Sqlite.php b/lib/Cake/Model/Datasource/Database/Driver/Sqlite.php index efddab4c338..724975eeb45 100644 --- a/lib/Cake/Model/Datasource/Database/Driver/Sqlite.php +++ b/lib/Cake/Model/Datasource/Database/Driver/Sqlite.php @@ -17,6 +17,7 @@ */ namespace Cake\Model\Datasource\Database\Driver; +use Cake\Model\Datasource\Database\Statement\PDOStatement; use Cake\Model\Datasource\Database\Statement\SqliteStatement; use Cake\Model\Datasource\Database\Dialect\SqliteDialectTrait; use PDO; @@ -88,7 +89,7 @@ public function enabled() { */ public function prepare($sql) { $statement = $this->connection()->prepare($sql); - return new SqliteStatement($statement, $this); + return new SqliteStatement(new PDOStatement($statement, $this), $this); } } diff --git a/lib/Cake/Model/Datasource/Database/Statement.php b/lib/Cake/Model/Datasource/Database/Statement.php index b1c232f3dad..b371a4f155d 100644 --- a/lib/Cake/Model/Datasource/Database/Statement.php +++ b/lib/Cake/Model/Datasource/Database/Statement.php @@ -16,14 +16,14 @@ */ namespace Cake\Model\Datasource\Database; -use PDO; - /** * Represents a database statement. Statements contains queries that can be * executed multiple times by binding different values on each call. This class * also helps convert values to their valid representation for the corresponding * types. * + * This class is but a decorator of an actual statement implementation, such as + * PDOStatement. */ class Statement implements \IteratorAggregate, \Countable { @@ -44,16 +44,6 @@ class Statement implements \IteratorAggregate, \Countable { */ protected $_driver; -/** - * Human readable fetch type names to PDO equivalents - * - * @var array - */ - protected $_fetchMap = [ - 'num' => PDO::FETCH_NUM, - 'assoc' => PDO::FETCH_ASSOC - ]; - /** * Constructor * @@ -82,31 +72,20 @@ public function __get($property) { * positional variables you need to start with index one, if using named params then * just use the name in any order. * - * You can pass PDO compatible constants for binding values with a type or optionally - * any type name registered in the Type class. Any value will be converted to the valid type - * representation if needed. - * * It is not allowed to combine positional and named variables in the same statement * * ## Examples: * * `$statement->bindValue(1, 'a title');` - * `$statement->bindValue(2, 5, PDO::INT);` * `$statement->bindValue('active', true, 'boolean');` * `$statement->bindValue(5, new \DateTime(), 'date');` * * @param string|integer $column name or param position to be bound * @param mixed $value the value to bind to variable in query - * @param string|integer $type PDO type or name of configured Type class + * @param string $type name of configured Type class * @return void */ public function bindValue($column, $value, $type = 'string') { - if ($type === null) { - $type = 'string'; - } - if (!ctype_digit($type)) { - list($value, $type) = $this->cast($value, $type); - } $this->_statement->bindValue($column, $value, $type); } @@ -188,15 +167,7 @@ public function execute($params = null) { * are left */ public function fetch($type = 'num') { - if ($this->_statement instanceof self) { - return $this->_statement->fetch($type); - } - switch ($type) { - case 'num': - return $this->_statement->fetch(PDO::FETCH_NUM); - case 'assoc': - return $this->_statement->fetch(PDO::FETCH_ASSOC); - } + return $this->_statement->fetch($type); } /** @@ -214,15 +185,7 @@ public function fetch($type = 'num') { * @return array list of all results from database for this statement */ public function fetchAll($type = 'num') { - if ($this->_statement instanceof self) { - return $this->_statement->fetchAll($type); - } - switch ($type) { - case 'num': - return $this->_statement->fetchAll(PDO::FETCH_NUM); - case 'assoc': - return $this->_statement->fetchAll(PDO::FETCH_ASSOC); - } + return $this->_statement->fetchAll($type); } /** diff --git a/lib/Cake/Model/Datasource/Database/Statement/PDOStatement.php b/lib/Cake/Model/Datasource/Database/Statement/PDOStatement.php new file mode 100644 index 00000000000..db8d4fd0020 --- /dev/null +++ b/lib/Cake/Model/Datasource/Database/Statement/PDOStatement.php @@ -0,0 +1,123 @@ +_statement = $statement; + $this->_driver = $driver; + } + +/** + * Assign a value to an positional or named variable in prepared query. If using + * positional variables you need to start with index one, if using named params then + * just use the name in any order. + * + * You can pass PDO compatible constants for binding values with a type or optionally + * any type name registered in the Type class. Any value will be converted to the valid type + * representation if needed. + * + * It is not allowed to combine positional and named variables in the same statement + * + * ## Examples: + * + * `$statement->bindValue(1, 'a title');` + * `$statement->bindValue(2, 5, PDO::INT);` + * `$statement->bindValue('active', true, 'boolean');` + * `$statement->bindValue(5, new \DateTime(), 'date');` + * + * @param string|integer $column name or param position to be bound + * @param mixed $value the value to bind to variable in query + * @param string|integer $type PDO type or name of configured Type class + * @return void + */ + public function bindValue($column, $value, $type = 'string') { + if ($type === null) { + $type = 'string'; + } + if (!ctype_digit($type)) { + list($value, $type) = $this->cast($value, $type); + } + $this->_statement->bindValue($column, $value, $type); + } + +/** + * Returns the next row for the result set after executing this statement. + * Rows can be fetched to contain columns as names or positions. If no + * rows are left in result set, this method will return false + * + * ## Example: + * + * {{{ + * $statement = $connection->prepare('SELECT id, title from articles'); + * $statement->execute(); + * print_r($statement->fetch('assoc')); // will show array('id' => 1, 'title' => 'a title') + * }}} + * + * @param string $type 'num' for positional columns, assoc for named columns + * @return mixed|boolean result array containing columns and values or false if no results + * are left + */ + public function fetch($type = 'num') { + if ($type === 'num') { + return $this->_statement->fetch(PDO::FETCH_NUM); + } + if ($type === 'assoc') { + return $this->_statement->fetch(PDO::FETCH_ASSOC); + } + return $this->_statement->fetch($type); + } + +/** + * Returns an array with all rows resulting from executing this statement + * + * ## Example: + * + * {{{ + * $statement = $connection->prepare('SELECT id, title from articles'); + * $statement->execute(); + * print_r($statement->fetchAll('assoc')); // will show [0 => ['id' => 1, 'title' => 'a title']] + * }}} + * + * @param string $type num for fetching columns as positional keys or assoc for column names as keys + * @return array list of all results from database for this statement + */ + public function fetchAll($type = 'num') { + if ($type === 'num') { + return $this->_statement->fetchAll(PDO::FETCH_NUM); + } + if ($type === 'assoc') { + return $this->_statement->fetchAll(PDO::FETCH_ASSOC); + } + return $this->_statement->fetchAll($type); + } + +} diff --git a/lib/Cake/Test/TestCase/Model/Datasource/Database/Log/LoggingStatementTest.php b/lib/Cake/Test/TestCase/Model/Datasource/Database/Log/LoggingStatementTest.php index cce96e32361..45e2d351179 100644 --- a/lib/Cake/Test/TestCase/Model/Datasource/Database/Log/LoggingStatementTest.php +++ b/lib/Cake/Test/TestCase/Model/Datasource/Database/Log/LoggingStatementTest.php @@ -100,14 +100,15 @@ public function testExecuteWithBinding() { $this->attributeEqualTo('numRows', 4), $this->attributeEqualTo('params', ['a' => 1, 'b' => '2014-01-01']) )); + $date = new \DateTime('2013-01-01'); $inner->expects($this->at(0))->method('bindValue')->with('a', 1); - $inner->expects($this->at(1))->method('bindValue')->with('b', '2013-01-01'); + $inner->expects($this->at(1))->method('bindValue')->with('b', $date); $driver = $this->getMock('\Cake\Model\Datasource\Database\Driver'); $st = new LoggingStatement($inner, $driver); $st->queryString = 'SELECT bar FROM foo'; $st->logger($logger); $st->bindValue('a', 1); - $st->bindValue('b', new \DateTime('2013-01-01'), 'date'); + $st->bindValue('b', $date, 'date'); $st->execute(); $st->bindValue('b', new \DateTime('2014-01-01'), 'date'); $st->execute();