1 | <?php |
||||
2 | |||||
3 | /** |
||||
4 | * Created by PhpStorm. |
||||
5 | * User: Goldoni |
||||
6 | * Date: 31/10/2018 |
||||
7 | * Time: 00:53. |
||||
8 | */ |
||||
9 | |||||
10 | namespace Goldoni\Builder; |
||||
11 | |||||
12 | use Pagerfanta\Pagerfanta; |
||||
13 | use PDO; |
||||
14 | |||||
15 | /** |
||||
16 | * Class Builder. |
||||
17 | */ |
||||
18 | class Query implements \IteratorAggregate |
||||
19 | { |
||||
20 | /** |
||||
21 | * @var array |
||||
22 | */ |
||||
23 | private $select; |
||||
24 | /** |
||||
25 | * @var array |
||||
26 | */ |
||||
27 | private $insert; |
||||
28 | /** |
||||
29 | * @var array |
||||
30 | */ |
||||
31 | private $update; |
||||
32 | /** |
||||
33 | * @var array |
||||
34 | */ |
||||
35 | private $delete; |
||||
36 | /** |
||||
37 | * @var array |
||||
38 | */ |
||||
39 | private $values; |
||||
40 | /** |
||||
41 | * @var array |
||||
42 | */ |
||||
43 | private $set; |
||||
44 | /** |
||||
45 | * @var array |
||||
46 | */ |
||||
47 | private $from; |
||||
48 | /** |
||||
49 | * @var array |
||||
50 | */ |
||||
51 | private $where = []; |
||||
52 | /** |
||||
53 | * @var array |
||||
54 | */ |
||||
55 | private $joins; |
||||
56 | /** |
||||
57 | * @var string |
||||
58 | */ |
||||
59 | private $entity; |
||||
60 | /** |
||||
61 | * @var string |
||||
62 | */ |
||||
63 | private $group; |
||||
64 | /** |
||||
65 | * @var array |
||||
66 | */ |
||||
67 | private $order; |
||||
68 | /** |
||||
69 | * @var int |
||||
70 | */ |
||||
71 | private $limit; |
||||
72 | |||||
73 | /** |
||||
74 | * @var \PDO |
||||
75 | */ |
||||
76 | private $pdo; |
||||
77 | |||||
78 | /** |
||||
79 | * @var array |
||||
80 | */ |
||||
81 | private $params = []; |
||||
82 | |||||
83 | /** |
||||
84 | * Query constructor. |
||||
85 | * |
||||
86 | * @param null|\PDO $pdo |
||||
87 | */ |
||||
88 | public function __construct(?PDO $pdo = null) |
||||
89 | { |
||||
90 | $this->pdo = $pdo; |
||||
91 | } |
||||
92 | |||||
93 | /** |
||||
94 | * @param string $table |
||||
95 | * @param string $alias |
||||
96 | * |
||||
97 | * @return \Goldoni\Builder\Query |
||||
98 | */ |
||||
99 | public function from(string $table, ?string $alias = null): self |
||||
100 | { |
||||
101 | if ($alias) { |
||||
102 | $this->from[$table] = $alias; |
||||
103 | } else { |
||||
104 | $this->from[] = $table; |
||||
105 | } |
||||
106 | |||||
107 | return $this; |
||||
108 | } |
||||
109 | |||||
110 | /** |
||||
111 | * @param string ...$fields |
||||
112 | * |
||||
113 | * @return \Goldoni\Builder\Query |
||||
114 | */ |
||||
115 | public function select(string ...$fields): self |
||||
116 | { |
||||
117 | $this->select = $fields; |
||||
118 | |||||
119 | return $this; |
||||
120 | } |
||||
121 | |||||
122 | /** |
||||
123 | * @param string $table |
||||
124 | * @param array $attributes |
||||
125 | * |
||||
126 | * @return \Goldoni\Builder\Query |
||||
127 | */ |
||||
128 | public function insert(string $table, ?array $attributes = null): self |
||||
129 | { |
||||
130 | $this->insert = $table; |
||||
0 ignored issues
–
show
|
|||||
131 | |||||
132 | if ($attributes) { |
||||
133 | $this->values = $attributes; |
||||
134 | } |
||||
135 | |||||
136 | return $this; |
||||
137 | } |
||||
138 | |||||
139 | public function value(array $attributes): self |
||||
140 | { |
||||
141 | $this->values = $attributes; |
||||
142 | |||||
143 | return $this; |
||||
144 | } |
||||
145 | |||||
146 | /** |
||||
147 | * @param string $table |
||||
148 | * @param array $attributes |
||||
149 | * @param int $id |
||||
150 | * |
||||
151 | * @return \Goldoni\Builder\Query |
||||
152 | */ |
||||
153 | public function update(string $table, ?array $attributes = null, ?int $id = null): self |
||||
154 | { |
||||
155 | $this->update = $table; |
||||
0 ignored issues
–
show
It seems like
$table of type string is incompatible with the declared type array of property $update .
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property. Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.. ![]() |
|||||
156 | |||||
157 | if ($id) { |
||||
0 ignored issues
–
show
The expression
$id of type integer|null is loosely compared to true ; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.
In PHP, under loose comparison (like For 0 == false // true
0 == null // true
123 == false // false
123 == null // false
// It is often better to use strict comparison
0 === false // false
0 === null // false
![]() |
|||||
158 | $this->where('id = :id'); |
||||
159 | $this->params(['id' => $id]); |
||||
160 | } |
||||
161 | |||||
162 | if ($attributes) { |
||||
163 | $this->set = $attributes; |
||||
164 | } |
||||
165 | |||||
166 | return $this; |
||||
167 | } |
||||
168 | |||||
169 | public function set(array $attributes): self |
||||
170 | { |
||||
171 | $this->set = $attributes; |
||||
172 | |||||
173 | return $this; |
||||
174 | } |
||||
175 | |||||
176 | public function delete(string $table, ?int $id = null): self |
||||
177 | { |
||||
178 | $this->delete = $table; |
||||
0 ignored issues
–
show
It seems like
$table of type string is incompatible with the declared type array of property $delete .
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property. Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.. ![]() |
|||||
179 | |||||
180 | if ($id) { |
||||
0 ignored issues
–
show
The expression
$id of type integer|null is loosely compared to true ; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.
In PHP, under loose comparison (like For 0 == false // true
0 == null // true
123 == false // false
123 == null // false
// It is often better to use strict comparison
0 === false // false
0 === null // false
![]() |
|||||
181 | $this->where('id = :id'); |
||||
182 | $this->params(['id' => $id]); |
||||
183 | } |
||||
184 | |||||
185 | return $this; |
||||
186 | } |
||||
187 | |||||
188 | /** |
||||
189 | * @param string ...$conditions |
||||
190 | * |
||||
191 | * @return \Goldoni\Builder\Query |
||||
192 | */ |
||||
193 | public function where(string ...$conditions): self |
||||
194 | { |
||||
195 | $this->where = array_merge($this->where, $conditions); |
||||
196 | |||||
197 | return $this; |
||||
198 | } |
||||
199 | |||||
200 | /** |
||||
201 | * @param string $table |
||||
202 | * @param string $condition |
||||
203 | * @param string $type |
||||
204 | * |
||||
205 | * @return \Goldoni\Builder\Query |
||||
206 | */ |
||||
207 | public function join(string $table, string $condition, string $type = 'left'): self |
||||
208 | { |
||||
209 | $this->joins[$type][] = [$table, $condition]; |
||||
210 | |||||
211 | return $this; |
||||
212 | } |
||||
213 | |||||
214 | /** |
||||
215 | * @throws \Exception |
||||
216 | * |
||||
217 | * @return int |
||||
218 | */ |
||||
219 | public function count(): int |
||||
220 | { |
||||
221 | $query = clone $this; |
||||
222 | $table = current($this->from); |
||||
223 | |||||
224 | return $query->select("COUNT({$table}.id)")->execute()->fetchColumn(); |
||||
225 | } |
||||
226 | |||||
227 | /** |
||||
228 | * @param string $column |
||||
229 | * @param null|string $direction |
||||
230 | * |
||||
231 | * @return \Goldoni\Builder\Query |
||||
232 | */ |
||||
233 | public function orderBy(string $column, ?string $direction = 'ASC'): self |
||||
234 | { |
||||
235 | $this->order[$column] = $direction; |
||||
236 | |||||
237 | return $this; |
||||
238 | } |
||||
239 | |||||
240 | /** |
||||
241 | * @param string $column |
||||
242 | * |
||||
243 | * @return \Goldoni\Builder\Query |
||||
244 | */ |
||||
245 | public function groupBy(string $column): self |
||||
246 | { |
||||
247 | $this->group = $column; |
||||
248 | |||||
249 | return $this; |
||||
250 | } |
||||
251 | |||||
252 | /** |
||||
253 | * @param int $limit |
||||
254 | * @param int $offset |
||||
255 | * |
||||
256 | * @return \Goldoni\Builder\Query |
||||
257 | */ |
||||
258 | public function limit(int $limit, int $offset = 0): self |
||||
259 | { |
||||
260 | $this->limit = "$offset, $limit"; |
||||
0 ignored issues
–
show
The property
$limit was declared of type integer , but $offset.', '.$limit is of type string . Maybe add a type cast?
This check looks for assignments to scalar types that may be of the wrong type. To ensure the code behaves as expected, it may be a good idea to add an explicit type cast. $answer = 42;
$correct = false;
$correct = (bool) $answer;
![]() |
|||||
261 | |||||
262 | return $this; |
||||
263 | } |
||||
264 | |||||
265 | /** |
||||
266 | * @param string $entity |
||||
267 | * |
||||
268 | * @return \Goldoni\Builder\Query |
||||
269 | */ |
||||
270 | public function into(string $entity): self |
||||
271 | { |
||||
272 | $this->entity = $entity; |
||||
273 | |||||
274 | return $this; |
||||
275 | } |
||||
276 | |||||
277 | public function fetchAll(): QueryResult |
||||
278 | { |
||||
279 | return new QueryResult($this->execute()->fetchAll(\PDO::FETCH_ASSOC), $this->entity); |
||||
280 | } |
||||
281 | |||||
282 | public function paginate(int $perPage, int $currentPage = 1) |
||||
283 | { |
||||
284 | $paginator = new Paginator($this, $perPage, $currentPage); |
||||
0 ignored issues
–
show
The call to
Goldoni\Builder\Paginator::__construct() has too many arguments starting with $perPage .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above. ![]() |
|||||
285 | |||||
286 | return (new Pagerfanta($paginator)) |
||||
287 | ->setMaxPerPage($perPage) |
||||
288 | ->setCurrentPage($currentPage); |
||||
289 | } |
||||
290 | |||||
291 | public function fetch() |
||||
292 | { |
||||
293 | $record = $this->execute()->fetch(\PDO::FETCH_ASSOC); |
||||
294 | |||||
295 | if (false === $record) { |
||||
296 | return false; |
||||
297 | } |
||||
298 | |||||
299 | if ($this->entity) { |
||||
300 | return Builder::hydrate($record, $this->entity); |
||||
301 | } |
||||
302 | |||||
303 | return $record; |
||||
304 | } |
||||
305 | |||||
306 | /** |
||||
307 | * @throws \Exception |
||||
308 | */ |
||||
309 | public function fetchOrFail() |
||||
310 | { |
||||
311 | $record = $this->fetch(); |
||||
312 | |||||
313 | if (false === $record) { |
||||
314 | throw new \Exception('No query results for model'); |
||||
315 | } |
||||
316 | |||||
317 | return $record; |
||||
318 | } |
||||
319 | |||||
320 | /** |
||||
321 | * @param array $params |
||||
322 | * @param bool $merge |
||||
323 | * |
||||
324 | * @return \Goldoni\Builder\Query |
||||
325 | */ |
||||
326 | public function params(array $params, bool $merge = true): self |
||||
327 | { |
||||
328 | if ($merge) { |
||||
329 | $this->params = array_merge($this->params, $params); |
||||
330 | } else { |
||||
331 | $this->params = $params; |
||||
332 | } |
||||
333 | |||||
334 | return $this; |
||||
335 | } |
||||
336 | |||||
337 | /** |
||||
338 | * @return string |
||||
339 | */ |
||||
340 | public function __toString() |
||||
341 | { |
||||
342 | $parts = ['SELECT']; |
||||
343 | |||||
344 | if ($this->select) { |
||||
0 ignored issues
–
show
The expression
$this->select of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using ![]() |
|||||
345 | $parts[] = implode(', ', $this->select); |
||||
346 | } else { |
||||
347 | $parts[] = '*'; |
||||
348 | } |
||||
349 | |||||
350 | if ($this->insert) { |
||||
0 ignored issues
–
show
The expression
$this->insert of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using ![]() |
|||||
351 | $parts = ['INSERT INTO ' . $this->insert]; |
||||
0 ignored issues
–
show
Are you sure
$this->insert of type array can be used in concatenation ?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
352 | } |
||||
353 | |||||
354 | if ($this->values) { |
||||
0 ignored issues
–
show
The expression
$this->values of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using ![]() |
|||||
355 | $parts[] = '(' . implode(', ', array_keys($this->values)) . ')'; |
||||
356 | $parts[] = 'VALUES'; |
||||
357 | $parts[] = '(' . implode(', ', array_values($this->values)) . ')'; |
||||
358 | } |
||||
359 | |||||
360 | if ($this->update) { |
||||
0 ignored issues
–
show
The expression
$this->update of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using ![]() |
|||||
361 | $parts = ['UPDATE ' . $this->update . ' SET']; |
||||
0 ignored issues
–
show
Are you sure
$this->update of type array can be used in concatenation ?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
362 | } |
||||
363 | |||||
364 | if ($this->set) { |
||||
0 ignored issues
–
show
The expression
$this->set of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using ![]() |
|||||
365 | $sets = []; |
||||
366 | |||||
367 | foreach ($this->set as $key => $value) { |
||||
368 | $sets[] = "$key = $value"; |
||||
369 | } |
||||
370 | $parts[] = implode(', ', $sets); |
||||
371 | } |
||||
372 | |||||
373 | if ($this->delete) { |
||||
0 ignored issues
–
show
The expression
$this->delete of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using ![]() |
|||||
374 | $parts = ['DELETE FROM ' . $this->delete]; |
||||
0 ignored issues
–
show
Are you sure
$this->delete of type array can be used in concatenation ?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
375 | } |
||||
376 | |||||
377 | if ($this->from) { |
||||
0 ignored issues
–
show
The expression
$this->from of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using ![]() |
|||||
378 | $parts[] = 'FROM'; |
||||
379 | $parts[] = $this->buildFrom(); |
||||
380 | } |
||||
381 | |||||
382 | if (!empty($this->where)) { |
||||
383 | $parts[] = 'WHERE'; |
||||
384 | $parts[] = '(' . implode(') AND (', $this->where) . ')'; |
||||
385 | } |
||||
386 | |||||
387 | if (!empty($this->joins)) { |
||||
388 | foreach ($this->joins as $type => $joins) { |
||||
389 | foreach ($joins as [$table, $condition]) { |
||||
390 | $parts[] = mb_strtoupper($type) . " JOIN $table ON $condition"; |
||||
391 | } |
||||
392 | } |
||||
393 | } |
||||
394 | |||||
395 | if ($this->order) { |
||||
0 ignored issues
–
show
The expression
$this->order of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using ![]() |
|||||
396 | foreach ($this->order as $key => $value) { |
||||
397 | $parts[] = "ORDER BY $key $value"; |
||||
398 | } |
||||
399 | } |
||||
400 | |||||
401 | if ($this->group) { |
||||
402 | $parts[] = 'GROUP BY ' . $this->group; |
||||
403 | } |
||||
404 | |||||
405 | if ($this->limit) { |
||||
406 | $parts[] = 'LIMIT ' . $this->limit; |
||||
407 | } |
||||
408 | |||||
409 | return implode(' ', $parts); |
||||
410 | } |
||||
411 | |||||
412 | private function buildFrom(): string |
||||
413 | { |
||||
414 | $from = []; |
||||
415 | |||||
416 | foreach ($this->from as $key => $value) { |
||||
417 | if (\is_string($key)) { |
||||
418 | $from[] = "$key as $value"; |
||||
419 | } else { |
||||
420 | $from[] = $value; |
||||
421 | } |
||||
422 | } |
||||
423 | |||||
424 | return implode(', ', $from); |
||||
425 | } |
||||
426 | |||||
427 | public function execute() |
||||
428 | { |
||||
429 | if (!empty($this->params)) { |
||||
430 | $statement = $this->pdo->prepare($this->__toString()); |
||||
431 | |||||
432 | if (!$statement->execute($this->params)) { |
||||
433 | throw new \Exception("Sql Error by execute query: {$this->__toString()}"); |
||||
434 | } |
||||
435 | |||||
436 | return $statement; |
||||
437 | } |
||||
438 | |||||
439 | return $this->pdo->query($this->__toString()); |
||||
440 | } |
||||
441 | |||||
442 | /** |
||||
443 | * @return \Goldoni\Builder\QueryResult|\Traversable |
||||
444 | */ |
||||
445 | public function getIterator() |
||||
446 | { |
||||
447 | return $this->fetchAll(); |
||||
448 | } |
||||
449 | } |
||||
450 |
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.
Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..