Completed
Push — master ( be31cd...ab5f6a )
by Ron
03:10
created

RunnableSelect::fetchRow()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 9
Bugs 0 Features 4
Metric Value
c 9
b 0
f 4
dl 0
loc 3
rs 10
cc 1
eloc 2
nc 1
nop 1
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
	public function fetchRows(Closure $callback = null) {
66
		return $this->fetchAll($callback, PDO::FETCH_ASSOC);
0 ignored issues
show
Bug introduced by
It seems like $callback defined by parameter $callback on line 65 can be null; however, Kir\MySQL\Builder\RunnableSelect::fetchAll() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
67
	}
68
69
	/**
70
	 * @param Closure $callback
71
	 * @return array[]|\Generator
72
	 */
73
	public function fetchRowsLazy(Closure $callback = null) {
74
		return $this->fetchLazy($callback, PDO::FETCH_ASSOC);
0 ignored issues
show
Bug introduced by
It seems like $callback defined by parameter $callback on line 73 can be null; however, Kir\MySQL\Builder\RunnableSelect::fetchLazy() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
75
	}
76
77
	/**
78
	 * @param Closure|null $callback
79
	 * @return mixed[]
80
	 * @throws \Exception
81
	 */
82
	public function fetchRow(Closure $callback = null) {
83
		return $this->fetch($callback, PDO::FETCH_ASSOC);
0 ignored issues
show
Bug introduced by
It seems like $callback defined by parameter $callback on line 82 can be null; however, Kir\MySQL\Builder\RunnableSelect::fetch() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
84
	}
85
86
	/**
87
	 * @param string $className
88
	 * @param Closure $callback
89
	 * @return object[]
90
	 * @throws \Exception
91
	 */
92
	public function fetchObjects($className, Closure $callback = null) {
93
		return $this->fetchAll($callback, PDO::FETCH_CLASS, $className);
0 ignored issues
show
Bug introduced by
It seems like $callback defined by parameter $callback on line 92 can be null; however, Kir\MySQL\Builder\RunnableSelect::fetchAll() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
94
	}
95
96
	/**
97
	 * @param string $className
98
	 * @param Closure $callback
99
	 * @return object[]|Generator
100
	 */
101
	public function fetchObjectsLazy($className, Closure $callback = null) {
102
		return $this->fetchLazy($callback, PDO::FETCH_CLASS, $className);
0 ignored issues
show
Bug introduced by
It seems like $callback defined by parameter $callback on line 101 can be null; however, Kir\MySQL\Builder\RunnableSelect::fetchLazy() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
103
	}
104
105
	/**
106
	 * @param string $className
107
	 * @param Closure|null $callback
108
	 * @return object[]
109
	 * @throws \Exception
110
	 */
111
	public function fetchObject($className, Closure $callback = null) {
112
		return $this->fetch($callback, PDO::FETCH_CLASS, $className);
0 ignored issues
show
Bug introduced by
It seems like $callback defined by parameter $callback on line 111 can be null; however, Kir\MySQL\Builder\RunnableSelect::fetch() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
113
	}
114
115
	/**
116
	 * @param bool $treatValueAsArray
117
	 * @return mixed[]
118
	 */
119
	public function fetchKeyValue($treatValueAsArray = false) {
120
		return $this->createTempStatement(function (QueryStatement $statement) use ($treatValueAsArray) {
121
			if($treatValueAsArray) {
122
				$rows = $statement->fetchAll(\PDO::FETCH_ASSOC);
123
				$result = array();
124
				foreach($rows as $row) {
125
					list($key) = array_values($row);
126
					$result[$key] = $row;
127
				}
128
				return $result;
129
			}
130
			return $statement->fetchAll(\PDO::FETCH_KEY_PAIR);
131
		});
132
	}
133
134
	/**
135
	 * @param array $fields
136
	 * @return array
137
	 */
138
	public function fetchGroups(array $fields) {
139
		$rows = $this->fetchRows();
140
		$result = array();
141
		foreach($rows as $row) {
142
			$tmp = &$result;
143
			foreach($fields as $field) {
144
				$value = $row[$field];
145
				if(!array_key_exists($value, $tmp)) {
146
					$tmp[$value] = [];
147
				}
148
				$tmp = &$tmp[$value];
149
			}
150
			$tmp[] = $row;
151
		}
152
		return $result;
153
	}
154
155
	/**
156
	 * @return string[]
157
	 */
158
	public function fetchArray() {
159
		return $this->createTempStatement(function (QueryStatement $stmt) {
160
			return $stmt->fetchAll(\PDO::FETCH_COLUMN);
161
		});
162
	}
163
164
	/**
165
	 * @param mixed $default
166
	 * @return null|bool|string|int|float
167
	 */
168
	public function fetchValue($default = null) {
169
		return $this->createTempStatement(function (QueryStatement $stmt) use ($default) {
170
			$result = $stmt->fetch(\PDO::FETCH_NUM);
171
			if($result !== false) {
172
				return $result[0];
173
			}
174
			return $default;
175
		});
176
	}
177
178
	/**
179
	 * @return bool
180
	 */
181
	public function getFoundRows() {
182
		return $this->foundRows;
183
	}
184
185
	/**
186
	 * @param callback $fn
187
	 * @return mixed
188
	 * @throws \Exception
189
	 */
190
	private function createTempStatement($fn) {
191
		$stmt = $this->createStatement();
192
		$res = null;
193
		try {
194
			$res = call_user_func($fn, $stmt);
195
		} catch (\Exception $e) { // PHP 5.4 compatibility
196
			$stmt->closeCursor();
197
			throw $e;
198
		}
199
		$stmt->closeCursor();
200
		return $res;
201
	}
202
203
	/**
204
	 * @return QueryStatement
205
	 */
206
	private function createStatement() {
207
		$db = $this->db();
208
		$query = $this->__toString();
209
		$statement = $db->prepare($query);
210
		$statement->execute($this->values);
211
		if($this->getCalcFoundRows()) {
212
			$this->foundRows = (int) $db->query('SELECT FOUND_ROWS()')->fetchColumn();
213
		}
214
		return $statement;
215
	}
216
217
	/**
218
	 * @return Traversable|array[]|\Generator
219
	 */
220
	public function getIterator() {
221
		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 221 which is incompatible with the return type declared by the interface IteratorAggregate::getIterator of type Traversable.
Loading history...
222
	}
223
224
	/**
225
	 * @param callable $callback
226
	 * @param int $mode
227
	 * @param mixed $arg0
228
	 * @return mixed
229
	 * @throws \Exception
230
	 */
231
	private function fetchAll($callback, $mode, $arg0 = null) {
232
		return $this->createTempStatement(function (QueryStatement $statement) use ($callback, $mode, $arg0) {
233
			$statement->setFetchMode($mode, $arg0);
234
			$data = $statement->fetchAll();
235
			if($this->preserveTypes) {
236
				$columnDefinitions = FieldTypeProvider::getFieldTypes($statement);
237
				foreach($data as &$row) {
238
					$row = FieldValueConverter::convertValues($row, $columnDefinitions);
239
				}
240
			}
241
			if($callback !== null) {
242
				return call_user_func(function ($resultData = []) use ($data, $callback) {
243
					foreach($data as $row) {
244
						$result = $callback($row);
245
						if($result !== null && !($result instanceof DBIgnoreRow)) {
246
							$resultData[] = $result;
247
						} else {
248
							$resultData[] = $row;
249
						}
250
					}
251
					return $resultData;
252
				});
253
			}
254
			return $data;
255
		});
256
	}
257
258
	/**
259
	 * @param callable $callback
260
	 * @param int $mode
261
	 * @param mixed $arg0
262
	 * @return Generator|YieldPolyfillIterator|mixed[]
263
	 */
264
	private function fetchLazy($callback, $mode, $arg0 = null) {
265
		if(version_compare(PHP_VERSION, '5.5', '<')) {
266
			return new YieldPolyfillIterator($callback, $this->preserveTypes, function () use ($mode, $arg0) {
0 ignored issues
show
Documentation introduced by
$callback is of type callable, but the function expects a null|object<Closure>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
267
				$statement = $this->createStatement();
268
				$statement->setFetchMode($mode, $arg0);
269
				return $statement;
270
			});
271
		}
272
		$statement = $this->createStatement();
273
		$statement->setFetchMode($mode, $arg0);
274
		$generator = new LazyRowGenerator($this->preserveTypes);
275
		return $generator->generate($statement, $callback);
0 ignored issues
show
Documentation introduced by
$callback is of type callable, but the function expects a null|object<Closure>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
276
	}
277
278
	/**
279
	 * @param callable $callback
280
	 * @param int $mode
281
	 * @param mixed $arg0
282
	 * @return mixed
283
	 * @throws \Exception
284
	 */
285
	private function fetch($callback, $mode, $arg0 = null) {
286
		return $this->createTempStatement(function (QueryStatement $statement) use ($callback, $mode, $arg0) {
287
			$statement->setFetchMode($mode, $arg0);
288
			$row = $statement->fetch();
289
			if(!is_array($row)) {
290
				return [];
291
			}
292
			if($this->preserveTypes) {
293
				$columnDefinitions = FieldTypeProvider::getFieldTypes($statement);
294
				$row = FieldValueConverter::convertValues($row, $columnDefinitions);
295
			}
296
			if($callback !== null) {
297
				$result = $callback($row);
298
				if($result !== null) {
299
					$row = $result;
300
				}
301
			}
302
			return $row;
303
		});
304
	}
305
}
306