1 | <?php |
||
2 | namespace Mezon\PdoCrud; |
||
3 | |||
4 | /** |
||
5 | * Class PdoCrud |
||
6 | * |
||
7 | * @package Mezon |
||
8 | * @subpackage PdoCrud |
||
9 | * @author Dodonov A.A. |
||
10 | * @version v.1.0 (2019/08/16) |
||
11 | * @copyright Copyright (c) 2019, aeon.org |
||
12 | */ |
||
13 | |||
14 | // TODO mark all old methods as deprecated |
||
15 | // TODO use PHPUnit 8.5 in Travis |
||
16 | |||
17 | /** |
||
18 | * Class provides simple CRUD operations |
||
19 | */ |
||
20 | class PdoCrud |
||
21 | { |
||
22 | |||
23 | /** |
||
24 | * PDO object |
||
25 | * |
||
26 | * @var \PDO |
||
27 | */ |
||
28 | private $pdo = null; |
||
29 | |||
30 | /** |
||
31 | * PDO statement |
||
32 | * |
||
33 | * @var \PDOStatement |
||
34 | */ |
||
35 | private $pdoStatement = null; |
||
36 | |||
37 | /** |
||
38 | * Method connects to the database |
||
39 | * |
||
40 | * @param array $connnectionData |
||
41 | * Connection settings |
||
42 | * @codeCoverageIgnore |
||
43 | */ |
||
44 | public function connect(array $connnectionData): void |
||
45 | { |
||
46 | // no need to test this single string. assume that PDO developers did it |
||
47 | $this->pdo = new \PDO($connnectionData['dsn'], $connnectionData['user'], $connnectionData['password']); |
||
48 | |||
49 | $this->query('SET NAMES utf8'); |
||
50 | } |
||
51 | |||
52 | /** |
||
53 | * Method handles request errors |
||
54 | * |
||
55 | * @param mixed $result |
||
56 | * Query result |
||
57 | * @param string $query |
||
58 | * SQL Query |
||
59 | * @codeCoverageIgnore |
||
60 | */ |
||
61 | protected function processQueryError($result, string $query): void |
||
62 | { |
||
63 | if ($result === false) { |
||
64 | $errorInfo = $this->pdo->errorInfo(); |
||
65 | |||
66 | throw (new \Exception($errorInfo[2] . ' in statement ' . $query)); |
||
67 | } |
||
68 | } |
||
69 | |||
70 | /** |
||
71 | * Method sets safe query |
||
72 | * |
||
73 | * @param string $query |
||
74 | * safe query |
||
75 | * @codeCoverageIgnore |
||
76 | */ |
||
77 | public function prepare(string $query): void |
||
78 | { |
||
79 | $this->pdoStatement = $this->pdo->prepare($query, [ |
||
0 ignored issues
–
show
|
|||
80 | \PDO::ATTR_CURSOR => \PDO::CURSOR_FWDONLY |
||
81 | ]); |
||
82 | } |
||
83 | |||
84 | /** |
||
85 | * Method executes select query and fetches results |
||
86 | * |
||
87 | * @param array $data |
||
88 | * query data |
||
89 | * @return array query result as an array of objects |
||
90 | * @codeCoverageIgnore |
||
91 | */ |
||
92 | public function execSelect(array $data): array |
||
93 | { |
||
94 | $this->pdoStatement->execute($data); |
||
95 | |||
96 | return $this->pdoStatement->fetchAll(\PDO::FETCH_OBJ); |
||
97 | } |
||
98 | |||
99 | /** |
||
100 | * Getting records |
||
101 | * |
||
102 | * @param string $fields |
||
103 | * List of fields |
||
104 | * @param string $tableNames |
||
105 | * List of tables |
||
106 | * @param string $where |
||
107 | * Condition |
||
108 | * @param int $from |
||
109 | * First record in query |
||
110 | * @param int $limit |
||
111 | * Count of records |
||
112 | * @return array List of records |
||
113 | * @deprecated since 2020-06-16 |
||
114 | */ |
||
115 | public function select( |
||
116 | string $fields, |
||
117 | string $tableNames, |
||
118 | string $where = '1 = 1', |
||
119 | int $from = 0, |
||
120 | int $limit = 1000000): array |
||
121 | { |
||
122 | $query = "SELECT $fields FROM $tableNames WHERE $where LIMIT " . intval($from) . ' , ' . intval($limit); |
||
123 | |||
124 | $result = $this->query($query); |
||
125 | |||
126 | $this->processQueryError($result, $query); |
||
127 | |||
128 | return $result->fetchAll(\PDO::FETCH_ASSOC); |
||
129 | } |
||
130 | |||
131 | /** |
||
132 | * Method compiles set-query |
||
133 | * |
||
134 | * @param array $record |
||
135 | * Inserting record |
||
136 | * @return string Compiled query string |
||
137 | */ |
||
138 | protected function compileGetQuery(array $record): string |
||
139 | { |
||
140 | $setFieldsStatement = []; |
||
141 | |||
142 | foreach ($record as $field => $value) { |
||
143 | if (is_string($value) && strtoupper($value) === 'INC') { |
||
144 | $setFieldsStatement[] = $field . ' = ' . $field . ' + 1'; |
||
145 | } elseif (is_string($value) && strtoupper($value) !== 'NOW()') { |
||
146 | $setFieldsStatement[] = $field . ' = "' . $value . '"'; |
||
147 | } elseif ($value === null) { |
||
148 | $setFieldsStatement[] = $field . ' = NULL'; |
||
149 | } else { |
||
150 | $setFieldsStatement[] = $field . ' = ' . $value; |
||
151 | } |
||
152 | } |
||
153 | |||
154 | return implode(' , ', $setFieldsStatement); |
||
155 | } |
||
156 | |||
157 | /** |
||
158 | * Method compiles set-multyple-query |
||
159 | * |
||
160 | * @param array $records |
||
161 | * Inserting records |
||
162 | * @return string Compiled query string |
||
163 | */ |
||
164 | protected function setMultypleQuery(array $records): string |
||
165 | { |
||
166 | $query = '( ' . implode(' , ', array_keys($records[0])) . ' ) VALUES '; |
||
167 | |||
168 | $values = []; |
||
169 | |||
170 | foreach ($records as $record) { |
||
171 | $values[] = "( '" . implode("' , '", array_values($record)) . "' )"; |
||
172 | } |
||
173 | |||
174 | return $query . implode(' , ', $values); |
||
175 | } |
||
176 | |||
177 | /** |
||
178 | * Updating records |
||
179 | * |
||
180 | * @param string $tableName |
||
181 | * Table name |
||
182 | * @param array $record |
||
183 | * Updating records |
||
184 | * @param string $where |
||
185 | * Condition |
||
186 | * @param int $limit |
||
187 | * Liti for afffecting records |
||
188 | * @return int Count of updated records |
||
189 | */ |
||
190 | public function update(string $tableName, array $record, string $where, int $limit = 10000000): int |
||
191 | { |
||
192 | $query = 'UPDATE ' . $tableName . ' SET ' . $this->compileGetQuery($record) . ' WHERE ' . $where . ' LIMIT ' . |
||
193 | $limit; |
||
194 | |||
195 | $result = $this->query($query); |
||
196 | |||
197 | $this->processQueryError($result, $query); |
||
198 | |||
199 | return $result->rowCount(); |
||
200 | } |
||
201 | |||
202 | /** |
||
203 | * Deleting records |
||
204 | * |
||
205 | * @param string $tableName |
||
206 | * Table name |
||
207 | * @param string $where |
||
208 | * Condition |
||
209 | * @param int $limit |
||
210 | * Liti for afffecting records |
||
211 | * @return int Count of deleted records |
||
212 | */ |
||
213 | public function delete($tableName, $where, $limit = 10000000): int |
||
214 | { |
||
215 | $query = 'DELETE FROM ' . $tableName . ' WHERE ' . $where . ' LIMIT ' . intval($limit); |
||
216 | |||
217 | $result = $this->query($query); |
||
218 | |||
219 | $this->processQueryError($result, $query); |
||
220 | |||
221 | return $result->rowCount(); |
||
222 | } |
||
223 | |||
224 | /** |
||
225 | * Method compiles lock queries |
||
226 | * |
||
227 | * @param array $tables |
||
228 | * List of tables |
||
229 | * @param array $modes |
||
230 | * List of lock modes |
||
231 | * @return string Query |
||
232 | */ |
||
233 | protected function lockQuery(array $tables, array $modes): string |
||
234 | { |
||
235 | $query = []; |
||
236 | |||
237 | foreach ($tables as $i => $table) { |
||
238 | $query[] = $table . ' ' . $modes[$i]; |
||
239 | } |
||
240 | |||
241 | return 'LOCK TABLES ' . implode(' , ', $query); |
||
242 | } |
||
243 | |||
244 | /** |
||
245 | * Method locks tables |
||
246 | * |
||
247 | * @param array $tables |
||
248 | * List of tables |
||
249 | * @param array $modes |
||
250 | * List of lock modes |
||
251 | */ |
||
252 | public function lock(array $tables, array $modes): void |
||
253 | { |
||
254 | $query = $this->lockQuery($tables, $modes); |
||
255 | |||
256 | $result = $this->query($query); |
||
257 | |||
258 | $this->processQueryError($result, $query); |
||
259 | } |
||
260 | |||
261 | /** |
||
262 | * Method unlocks locked tables |
||
263 | */ |
||
264 | public function unlock(): void |
||
265 | { |
||
266 | $result = $this->query('UNLOCK TABLES'); |
||
267 | |||
268 | $this->processQueryError($result, 'UNLOCK TABLES'); |
||
269 | } |
||
270 | |||
271 | /** |
||
272 | * Method starts transaction |
||
273 | */ |
||
274 | public function startTransaction(): void |
||
275 | { |
||
276 | // setting autocommit off |
||
277 | $result = $this->query('SET AUTOCOMMIT = 0'); |
||
278 | |||
279 | $this->processQueryError($result, 'SET AUTOCOMMIT = 0'); |
||
280 | |||
281 | // starting transaction |
||
282 | $result = $this->query('START TRANSACTION'); |
||
283 | |||
284 | $this->processQueryError($result, 'START TRANSACTION'); |
||
285 | } |
||
286 | |||
287 | /** |
||
288 | * Commiting transaction |
||
289 | */ |
||
290 | public function commit(): void |
||
291 | { |
||
292 | // commit transaction |
||
293 | $result = $this->query('COMMIT'); |
||
294 | |||
295 | $this->processQueryError($result, 'COMMIT'); |
||
296 | |||
297 | // setting autocommit on |
||
298 | $result = $this->query('SET AUTOCOMMIT = 1'); |
||
299 | |||
300 | $this->processQueryError($result, 'SET AUTOCOMMIT = 1'); |
||
301 | } |
||
302 | |||
303 | /** |
||
304 | * Rollback transaction |
||
305 | */ |
||
306 | public function rollback(): void |
||
307 | { |
||
308 | // rollback transaction |
||
309 | $result = $this->query('ROLLBACK'); |
||
310 | |||
311 | $this->processQueryError($result, 'ROLLBACK'); |
||
312 | } |
||
313 | |||
314 | /** |
||
315 | * Method executes query |
||
316 | * |
||
317 | * @param string $query |
||
318 | * Query |
||
319 | * @return mixed Query execution result |
||
320 | */ |
||
321 | public function query(string $query) |
||
322 | { |
||
323 | // @codeCoverageIgnoreStart |
||
324 | return $this->pdo->query($query); |
||
325 | // @codeCoverageIgnoreEnd |
||
326 | } |
||
327 | |||
328 | /** |
||
329 | * Method returns id of the last inserted record |
||
330 | * |
||
331 | * @return int id of the last inserted record |
||
332 | */ |
||
333 | public function lastInsertId(): int |
||
334 | { |
||
335 | // @codeCoverageIgnoreStart |
||
336 | return (int) $this->pdo->lastInsertId(); |
||
337 | // @codeCoverageIgnoreEnd |
||
338 | } |
||
339 | |||
340 | /** |
||
341 | * Method inserts record |
||
342 | * |
||
343 | * @param string $tableName |
||
344 | * Table name |
||
345 | * @param array $record |
||
346 | * Inserting record |
||
347 | * @return int New record's id |
||
348 | */ |
||
349 | public function insert(string $tableName, array $record): int |
||
350 | { |
||
351 | $query = 'INSERT ' . $tableName . ' SET ' . $this->compileGetQuery($record); |
||
352 | |||
353 | $result = $this->query($query); |
||
354 | |||
355 | $this->processQueryError($result, $query); |
||
356 | |||
357 | return $this->lastInsertId(); |
||
358 | } |
||
359 | |||
360 | /** |
||
361 | * Method inserts record |
||
362 | * |
||
363 | * @param string $tableName |
||
364 | * Table name |
||
365 | * @param array $records |
||
366 | * Inserting records |
||
367 | * @return int New record's id |
||
368 | */ |
||
369 | public function insertMultyple(string $tableName, array $records): int |
||
370 | { |
||
371 | $query = 'INSERT INTO ' . $tableName . ' ' . $this->setMultypleQuery($records) . ';'; |
||
372 | |||
373 | $result = $this->query($query); |
||
374 | |||
375 | $this->processQueryError($result, $query); |
||
376 | |||
377 | return 0; |
||
378 | } |
||
379 | |||
380 | /** |
||
381 | * Method destroys connection |
||
382 | */ |
||
383 | public function __destruct() |
||
384 | { |
||
385 | $this->pdo = null; |
||
386 | |||
387 | unset($this->pdo); |
||
388 | } |
||
389 | } |
||
390 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.