Completed
Push — master ( 353d0e...6c7828 )
by Ron
01:57
created

RunnableSelect::__construct()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 4
nc 4
nop 2
1
<?php
2
namespace Kir\MySQL\Builder;
3
4
use Closure;
5
use Generator;
6
use IteratorAggregate;
7
use Kir\MySQL\Builder\Helpers\DBIgnoreRow;
8
use Kir\MySQL\Builder\Helpers\FieldTypeProvider;
9
use Kir\MySQL\Builder\Helpers\FieldValueConverter;
10
use Kir\MySQL\Builder\Helpers\LazyRowGenerator;
11
use Kir\MySQL\Builder\Helpers\YieldPolyfillIterator;
12
use Kir\MySQL\Databases\MySQL;
13
use PDO;
14
use Traversable;
15
16
/**
17
 */
18
class RunnableSelect extends Select implements IteratorAggregate {
19
	/** @var array */
20
	private $values = array();
21
	/** @var bool */
22
	private $preserveTypes = false;
23
	/** @var string */
24
	private $defaultClassName = false;
25
	/** @var int */
26
	private $foundRows = 0;
27
	
28
	/**
29
	 * @param MySQL $db
30
	 * @param array $options
31
	 */
32
	public function __construct(MySQL $db, array $options = []) {
33
		parent::__construct($db);
34
		$this->preserveTypes = array_key_exists('preserve-types-default', $options) ? $options['preserve-types-default'] : false;
35
		$this->defaultClassName = array_key_exists('fetch-object-class-default', $options) ? $options['fetch-object-class-default'] : 'stdClass';
36
	}
37
	
38
	/**
39
	 * @param array $values
40
	 * @return $this
41
	 */
42
	public function bindValues(array $values) {
43
		$this->values = array_merge($this->values, $values);
44
		return $this;
45
	}
46
47
	/**
48
	 * @param string $key
49
	 * @param string|int|bool|float|null $value
50
	 * @return $this
51
	 */
52
	public function bindValue($key, $value) {
53
		$this->values[$key] = $value;
54
		return $this;
55
	}
56
57
	/**
58
	 * @return $this
59
	 */
60
	public function clearValues() {
61
		$this->values = array();
62
		return $this;
63
	}
64
65
	/**
66
	 * @param bool $preserveTypes
67
	 * @return $this
68
	 */
69
	public function setPreserveTypes($preserveTypes = true) {
70
		$this->preserveTypes = $preserveTypes;
71
		return $this;
72
	}
73
74
	/**
75
	 * @param Closure $callback
76
	 * @return array[]
77
	 */
78
	public function fetchRows(Closure $callback = null) {
79
		return $this->fetchAll($callback, PDO::FETCH_ASSOC);
80
	}
81
82
	/**
83
	 * @param Closure $callback
84
	 * @return array[]|\Generator
85
	 */
86
	public function fetchRowsLazy(Closure $callback = null) {
87
		return $this->fetchLazy($callback, PDO::FETCH_ASSOC);
88
	}
89
90
	/**
91
	 * @param Closure|null $callback
92
	 * @return mixed[]
93
	 * @throws \Exception
94
	 */
95
	public function fetchRow(Closure $callback = null) {
96
		return $this->fetch($callback, PDO::FETCH_ASSOC, null, function ($row) {
97
			return ['valid' => is_array($row), 'default' => []];
98
		});
99
	}
100
101
	/**
102
	 * @param string $className
103
	 * @param Closure $callback
104
	 * @return object[]
105
	 * @throws \Exception
106
	 */
107
	public function fetchObjects($className = null, Closure $callback = null) {
108
		return $this->fetchAll($callback, PDO::FETCH_CLASS, $className ?: $this->defaultClassName);
109
	}
110
111
	/**
112
	 * @param string $className
113
	 * @param Closure $callback
114
	 * @return object[]|Generator
115
	 */
116
	public function fetchObjectsLazy($className = null, Closure $callback = null) {
117
		return $this->fetchLazy($callback, PDO::FETCH_CLASS, $className ?: $this->defaultClassName);
118
	}
119
120
	/**
121
	 * @param string $className
122
	 * @param Closure|null $callback
123
	 * @return object[]
124
	 * @throws \Exception
125
	 */
126
	public function fetchObject($className = null, Closure $callback = null) {
127
		return $this->fetch($callback, PDO::FETCH_CLASS, $className ?: $this->defaultClassName, function ($row) {
128
			return ['valid' => is_object($row), 'default' => null];
129
		});
130
	}
131
132
	/**
133
	 * @param bool $treatValueAsArray
134
	 * @return mixed[]
135
	 */
136
	public function fetchKeyValue($treatValueAsArray = false) {
137
		return $this->createTempStatement(function (QueryStatement $statement) use ($treatValueAsArray) {
138
			if($treatValueAsArray) {
139
				$rows = $statement->fetchAll(\PDO::FETCH_ASSOC);
140
				$result = array();
141
				foreach($rows as $row) {
142
					list($key) = array_values($row);
143
					$result[$key] = $row;
144
				}
145
				return $result;
146
			}
147
			return $statement->fetchAll(\PDO::FETCH_KEY_PAIR);
148
		});
149
	}
150
151
	/**
152
	 * @param array $fields
153
	 * @return array
154
	 */
155
	public function fetchGroups(array $fields) {
156
		$rows = $this->fetchRows();
157
		$result = array();
158
		foreach($rows as $row) {
159
			$tmp = &$result;
160
			foreach($fields as $field) {
161
				$value = $row[$field];
162
				if(!array_key_exists($value, $tmp)) {
163
					$tmp[$value] = [];
164
				}
165
				$tmp = &$tmp[$value];
166
			}
167
			$tmp[] = $row;
168
		}
169
		return $result;
170
	}
171
172
	/**
173
	 * @return string[]
174
	 */
175
	public function fetchArray() {
176
		return $this->createTempStatement(function (QueryStatement $stmt) {
177
			return $stmt->fetchAll(\PDO::FETCH_COLUMN);
178
		});
179
	}
180
181
	/**
182
	 * @param mixed $default
183
	 * @return null|bool|string|int|float
184
	 */
185
	public function fetchValue($default = null) {
186
		return $this->createTempStatement(function (QueryStatement $stmt) use ($default) {
187
			$result = $stmt->fetch(\PDO::FETCH_NUM);
188
			if($result !== false) {
189
				return $result[0];
190
			}
191
			return $default;
192
		});
193
	}
194
195
	/**
196
	 * @return bool
197
	 */
198
	public function getFoundRows() {
199
		return $this->foundRows;
200
	}
201
202
	/**
203
	 * @param callback $fn
204
	 * @return mixed
205
	 * @throws \Exception
206
	 */
207
	private function createTempStatement($fn) {
208
		$stmt = $this->createStatement();
209
		$res = null;
210
		try {
211
			$res = call_user_func($fn, $stmt);
212
		} catch (\Exception $e) { // PHP 5.4 compatibility
213
			$stmt->closeCursor();
214
			throw $e;
215
		}
216
		$stmt->closeCursor();
217
		return $res;
218
	}
219
220
	/**
221
	 * @return QueryStatement
222
	 */
223
	private function createStatement() {
224
		$db = $this->db();
225
		$query = $this->__toString();
226
		$statement = $db->prepare($query);
227
		$statement->execute($this->values);
228
		if($this->getCalcFoundRows()) {
229
			$this->foundRows = (int) $db->query('SELECT FOUND_ROWS()')->fetchColumn();
230
		}
231
		return $statement;
232
	}
233
234
	/**
235
	 * @return Traversable|array[]|\Generator
236
	 */
237
	public function getIterator() {
238
		return $this->fetchRowsLazy();
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->fetchRowsLazy(); of type Kir\MySQL\Builder\Helper...yfillIterator|Generator adds the type Generator to the return on line 238 which is incompatible with the return type declared by the interface IteratorAggregate::getIterator of type Traversable.
Loading history...
239
	}
240
241
	/**
242
	 * @param Closure $callback
243
	 * @param int $mode
244
	 * @param mixed $arg0
245
	 * @return mixed
246
	 * @throws \Exception
247
	 */
248
	private function fetchAll(Closure $callback = null, $mode, $arg0 = null) {
249
		return $this->createTempStatement(function (QueryStatement $statement) use ($callback, $mode, $arg0) {
250
			$statement->setFetchMode($mode, $arg0);
251
			$data = $statement->fetchAll();
252
			if($this->preserveTypes) {
253
				$columnDefinitions = FieldTypeProvider::getFieldTypes($statement);
254
				foreach($data as &$row) {
255
					$row = FieldValueConverter::convertValues($row, $columnDefinitions);
256
				}
257
			}
258
			if($callback !== null) {
259
				return call_user_func(function ($resultData = []) use ($data, $callback) {
260
					foreach($data as $row) {
261
						$result = $callback($row);
262
						if($result !== null && !($result instanceof DBIgnoreRow)) {
263
							$resultData[] = $result;
264
						} else {
265
							$resultData[] = $row;
266
						}
267
					}
268
					return $resultData;
269
				});
270
			}
271
			return $data;
272
		});
273
	}
274
275
	/**
276
	 * @param Closure $callback
277
	 * @param int $mode
278
	 * @param mixed $arg0
279
	 * @return Generator|YieldPolyfillIterator|mixed[]
280
	 */
281
	private function fetchLazy(Closure $callback = null, $mode, $arg0 = null) {
282
		if(version_compare(PHP_VERSION, '5.5', '<')) {
283
			return new YieldPolyfillIterator($callback, $this->preserveTypes, function () use ($mode, $arg0) {
284
				$statement = $this->createStatement();
285
				$statement->setFetchMode($mode, $arg0);
286
				return $statement;
287
			});
288
		}
289
		$statement = $this->createStatement();
290
		$statement->setFetchMode($mode, $arg0);
291
		$generator = new LazyRowGenerator($this->preserveTypes);
292
		return $generator->generate($statement, $callback);
293
	}
294
295
	/**
296
	 * @param Closure $callback
297
	 * @param int $mode
298
	 * @param mixed $arg0
299
	 * @param Closure $resultValidator
300
	 * @return mixed
301
	 * @throws \Exception
302
	 */
303
	private function fetch(Closure $callback = null, $mode, $arg0 = null, Closure $resultValidator = null) {
304
		return $this->createTempStatement(function (QueryStatement $statement) use ($callback, $mode, $arg0, $resultValidator) {
305
			$statement->setFetchMode($mode, $arg0);
306
			$row = $statement->fetch();
307
			$result = $resultValidator($row);
308
			if(!$result['valid']) {
309
				return $result['default'];
310
			}
311
			if($this->preserveTypes) {
312
				$columnDefinitions = FieldTypeProvider::getFieldTypes($statement);
313
				$row = FieldValueConverter::convertValues($row, $columnDefinitions);
314
			}
315
			if($callback !== null) {
316
				$result = $callback($row);
317
				if($result !== null) {
318
					$row = $result;
319
				}
320
			}
321
			return $row;
322
		});
323
	}
324
}
325