1: <?php
2: /**
3: * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
4: * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
5: *
6: * Licensed under The MIT License
7: * For full copyright and license information, please see the LICENSE.txt
8: * Redistributions of files must retain the above copyright notice.
9: *
10: * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
11: * @link https://cakephp.org CakePHP(tm) Project
12: * @since 3.0.0
13: * @license https://opensource.org/licenses/mit-license.php MIT License
14: */
15: namespace Cake\Database\Statement;
16:
17: use Cake\Database\StatementInterface;
18: use Cake\Database\TypeConverterTrait;
19: use Countable;
20: use IteratorAggregate;
21:
22: /**
23: * Represents a database statement. Statements contains queries that can be
24: * executed multiple times by binding different values on each call. This class
25: * also helps convert values to their valid representation for the corresponding
26: * types.
27: *
28: * This class is but a decorator of an actual statement implementation, such as
29: * PDOStatement.
30: *
31: * @property-read string $queryString
32: */
33: class StatementDecorator implements StatementInterface, Countable, IteratorAggregate
34: {
35: use TypeConverterTrait;
36:
37: /**
38: * Statement instance implementation, such as PDOStatement
39: * or any other custom implementation.
40: *
41: * @var \Cake\Database\StatementInterface|\PDOStatement|null
42: */
43: protected $_statement;
44:
45: /**
46: * Reference to the driver object associated to this statement.
47: *
48: * @var \Cake\Database\Driver|null
49: */
50: protected $_driver;
51:
52: /**
53: * Whether or not this statement has already been executed
54: *
55: * @var bool
56: */
57: protected $_hasExecuted = false;
58:
59: /**
60: * Constructor
61: *
62: * @param \Cake\Database\StatementInterface|\PDOStatement|null $statement Statement implementation such as PDOStatement
63: * @param \Cake\Database\Driver|null $driver Driver instance
64: */
65: public function __construct($statement = null, $driver = null)
66: {
67: $this->_statement = $statement;
68: $this->_driver = $driver;
69: }
70:
71: /**
72: * Magic getter to return $queryString as read-only.
73: *
74: * @param string $property internal property to get
75: * @return mixed
76: */
77: public function __get($property)
78: {
79: if ($property === 'queryString') {
80: return $this->_statement->queryString;
81: }
82: }
83:
84: /**
85: * Assign a value to a positional or named variable in prepared query. If using
86: * positional variables you need to start with index one, if using named params then
87: * just use the name in any order.
88: *
89: * It is not allowed to combine positional and named variables in the same statement.
90: *
91: * ### Examples:
92: *
93: * ```
94: * $statement->bindValue(1, 'a title');
95: * $statement->bindValue('active', true, 'boolean');
96: * $statement->bindValue(5, new \DateTime(), 'date');
97: * ```
98: *
99: * @param string|int $column name or param position to be bound
100: * @param mixed $value The value to bind to variable in query
101: * @param string $type name of configured Type class
102: * @return void
103: */
104: public function bindValue($column, $value, $type = 'string')
105: {
106: $this->_statement->bindValue($column, $value, $type);
107: }
108:
109: /**
110: * Closes a cursor in the database, freeing up any resources and memory
111: * allocated to it. In most cases you don't need to call this method, as it is
112: * automatically called after fetching all results from the result set.
113: *
114: * @return void
115: */
116: public function closeCursor()
117: {
118: $this->_statement->closeCursor();
119: }
120:
121: /**
122: * Returns the number of columns this statement's results will contain.
123: *
124: * ### Example:
125: *
126: * ```
127: * $statement = $connection->prepare('SELECT id, title from articles');
128: * $statement->execute();
129: * echo $statement->columnCount(); // outputs 2
130: * ```
131: *
132: * @return int
133: */
134: public function columnCount()
135: {
136: return $this->_statement->columnCount();
137: }
138:
139: /**
140: * Returns the error code for the last error that occurred when executing this statement.
141: *
142: * @return int|string
143: */
144: public function errorCode()
145: {
146: return $this->_statement->errorCode();
147: }
148:
149: /**
150: * Returns the error information for the last error that occurred when executing
151: * this statement.
152: *
153: * @return array
154: */
155: public function errorInfo()
156: {
157: return $this->_statement->errorInfo();
158: }
159:
160: /**
161: * Executes the statement by sending the SQL query to the database. It can optionally
162: * take an array or arguments to be bound to the query variables. Please note
163: * that binding parameters from this method will not perform any custom type conversion
164: * as it would normally happen when calling `bindValue`.
165: *
166: * @param array|null $params list of values to be bound to query
167: * @return bool true on success, false otherwise
168: */
169: public function execute($params = null)
170: {
171: $this->_hasExecuted = true;
172:
173: return $this->_statement->execute($params);
174: }
175:
176: /**
177: * Returns the next row for the result set after executing this statement.
178: * Rows can be fetched to contain columns as names or positions. If no
179: * rows are left in result set, this method will return false.
180: *
181: * ### Example:
182: *
183: * ```
184: * $statement = $connection->prepare('SELECT id, title from articles');
185: * $statement->execute();
186: * print_r($statement->fetch('assoc')); // will show ['id' => 1, 'title' => 'a title']
187: * ```
188: *
189: * @param string $type 'num' for positional columns, assoc for named columns
190: * @return array|false Result array containing columns and values or false if no results
191: * are left
192: */
193: public function fetch($type = self::FETCH_TYPE_NUM)
194: {
195: return $this->_statement->fetch($type);
196: }
197:
198: /**
199: * Returns the next row in a result set as an associative array. Calling this function is the same as calling
200: * $statement->fetch(StatementDecorator::FETCH_TYPE_ASSOC). If no results are found false is returned.
201: *
202: * @return array Result array containing columns and values an an associative array or an empty array if no results
203: */
204: public function fetchAssoc()
205: {
206: $result = $this->fetch(static::FETCH_TYPE_ASSOC);
207:
208: return $result ?: [];
209: }
210:
211: /**
212: * Returns the value of the result at position.
213: *
214: * @param int $position The numeric position of the column to retrieve in the result
215: * @return mixed|false Returns the specific value of the column designated at $position
216: */
217: public function fetchColumn($position)
218: {
219: $result = $this->fetch(static::FETCH_TYPE_NUM);
220: if (isset($result[$position])) {
221: return $result[$position];
222: }
223:
224: return false;
225: }
226:
227: /**
228: * Returns an array with all rows resulting from executing this statement.
229: *
230: * ### Example:
231: *
232: * ```
233: * $statement = $connection->prepare('SELECT id, title from articles');
234: * $statement->execute();
235: * print_r($statement->fetchAll('assoc')); // will show [0 => ['id' => 1, 'title' => 'a title']]
236: * ```
237: *
238: * @param string $type num for fetching columns as positional keys or assoc for column names as keys
239: * @return array List of all results from database for this statement
240: */
241: public function fetchAll($type = self::FETCH_TYPE_NUM)
242: {
243: return $this->_statement->fetchAll($type);
244: }
245:
246: /**
247: * Returns the number of rows affected by this SQL statement.
248: *
249: * ### Example:
250: *
251: * ```
252: * $statement = $connection->prepare('SELECT id, title from articles');
253: * $statement->execute();
254: * print_r($statement->rowCount()); // will show 1
255: * ```
256: *
257: * @return int
258: */
259: public function rowCount()
260: {
261: return $this->_statement->rowCount();
262: }
263:
264: /**
265: * Statements are iterable as arrays, this method will return
266: * the iterator object for traversing all items in the result.
267: *
268: * ### Example:
269: *
270: * ```
271: * $statement = $connection->prepare('SELECT id, title from articles');
272: * foreach ($statement as $row) {
273: * //do stuff
274: * }
275: * ```
276: *
277: * @return \Cake\Database\StatementInterface|\PDOStatement
278: */
279: public function getIterator()
280: {
281: if (!$this->_hasExecuted) {
282: $this->execute();
283: }
284:
285: return $this->_statement;
286: }
287:
288: /**
289: * Statements can be passed as argument for count() to return the number
290: * for affected rows from last execution.
291: *
292: * @return int
293: */
294: public function count()
295: {
296: return $this->rowCount();
297: }
298:
299: /**
300: * Binds a set of values to statement object with corresponding type.
301: *
302: * @param array $params list of values to be bound
303: * @param array $types list of types to be used, keys should match those in $params
304: * @return void
305: */
306: public function bind($params, $types)
307: {
308: if (empty($params)) {
309: return;
310: }
311:
312: $anonymousParams = is_int(key($params)) ? true : false;
313: $offset = 1;
314: foreach ($params as $index => $value) {
315: $type = null;
316: if (isset($types[$index])) {
317: $type = $types[$index];
318: }
319: if ($anonymousParams) {
320: $index += $offset;
321: }
322: $this->bindValue($index, $value, $type);
323: }
324: }
325:
326: /**
327: * Returns the latest primary inserted using this statement.
328: *
329: * @param string|null $table table name or sequence to get last insert value from
330: * @param string|null $column the name of the column representing the primary key
331: * @return string|int
332: */
333: public function lastInsertId($table = null, $column = null)
334: {
335: $row = null;
336: if ($column && $this->columnCount()) {
337: $row = $this->fetch(static::FETCH_TYPE_ASSOC);
338: }
339: if (isset($row[$column])) {
340: return $row[$column];
341: }
342:
343: return $this->_driver->lastInsertId($table, $column);
344: }
345:
346: /**
347: * Returns the statement object that was decorated by this class.
348: *
349: * @return \Cake\Database\StatementInterface|\PDOStatement
350: */
351: public function getInnerStatement()
352: {
353: return $this->_statement;
354: }
355: }
356: