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