Total Complexity | 114 |
Total Lines | 901 |
Duplicated Lines | 4.88 % |
Changes | 0 |
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like DatabaseSource often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use DatabaseSource, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
27 | class DatabaseSource extends AbstractSource implements |
||
28 | DatabaseSourceInterface |
||
29 | { |
||
30 | const DEFAULT_DB_HOSTNAME = 'localhost'; |
||
31 | const DEFAULT_TABLE_ALIAS = 'objTable'; |
||
32 | const MYSQL_DRIVER_NAME = 'mysql'; |
||
33 | const SQLITE_DRIVER_NAME = 'sqlite'; |
||
34 | |||
35 | /** |
||
36 | * The database connector. |
||
37 | * |
||
38 | * @var PDO |
||
39 | */ |
||
40 | private $pdo; |
||
41 | |||
42 | /** |
||
43 | * The {@see self::$model}'s table name. |
||
44 | * |
||
45 | * @var string |
||
46 | */ |
||
47 | private $table; |
||
48 | |||
49 | /** |
||
50 | * Create a new database handler. |
||
51 | * |
||
52 | * @param array $data Class dependencies. |
||
53 | */ |
||
54 | public function __construct(array $data) |
||
59 | } |
||
60 | |||
61 | /** |
||
62 | * Retrieve the database connector. |
||
63 | * |
||
64 | * @throws RuntimeException If the datahase was not set. |
||
65 | * @return PDO |
||
66 | */ |
||
67 | public function db() |
||
68 | { |
||
69 | if ($this->pdo === null) { |
||
70 | throw new RuntimeException( |
||
71 | 'Database Connector was not set.' |
||
72 | ); |
||
73 | } |
||
74 | return $this->pdo; |
||
75 | } |
||
76 | |||
77 | /** |
||
78 | * Set the database's table to use. |
||
79 | * |
||
80 | * @param string $table The source table. |
||
81 | * @throws InvalidArgumentException If argument is not a string or alphanumeric/underscore. |
||
82 | * @return self |
||
83 | */ |
||
84 | View Code Duplication | public function setTable($table) |
|
|
|||
85 | { |
||
86 | if (!is_string($table)) { |
||
87 | throw new InvalidArgumentException(sprintf( |
||
88 | 'DatabaseSource::setTable() expects a string as table. (%s given). [%s]', |
||
89 | gettype($table), |
||
90 | get_class($this->model()) |
||
91 | )); |
||
92 | } |
||
93 | |||
94 | /** |
||
95 | * For security reason, only alphanumeric characters (+ underscores) |
||
96 | * are valid table names; Although SQL can support more, |
||
97 | * there's really no reason to. |
||
98 | */ |
||
99 | if (!preg_match('/[A-Za-z0-9_]/', $table)) { |
||
100 | throw new InvalidArgumentException(sprintf( |
||
101 | 'Table name "%s" is invalid: must be alphanumeric / underscore.', |
||
102 | $table |
||
103 | )); |
||
104 | } |
||
105 | |||
106 | $this->table = $table; |
||
107 | return $this; |
||
108 | } |
||
109 | |||
110 | /** |
||
111 | * Determine if a table is assigned. |
||
112 | * |
||
113 | * @return boolean |
||
114 | */ |
||
115 | public function hasTable() |
||
116 | { |
||
117 | return !empty($this->table); |
||
118 | } |
||
119 | |||
120 | /** |
||
121 | * Get the database's current table. |
||
122 | * |
||
123 | * @throws RuntimeException If the table was not set. |
||
124 | * @return string |
||
125 | */ |
||
126 | public function table() |
||
127 | { |
||
128 | if ($this->table === null) { |
||
129 | throw new RuntimeException( |
||
130 | 'Table was not set.' |
||
131 | ); |
||
132 | } |
||
133 | return $this->table; |
||
134 | } |
||
135 | |||
136 | /** |
||
137 | * Create a table from a model's metadata. |
||
138 | * |
||
139 | * @return boolean TRUE if the table was created, otherwise FALSE. |
||
140 | */ |
||
141 | public function createTable() |
||
142 | { |
||
143 | if ($this->tableExists() === true) { |
||
144 | return true; |
||
145 | } |
||
146 | |||
147 | $dbh = $this->db(); |
||
148 | $driver = $dbh->getAttribute(PDO::ATTR_DRIVER_NAME); |
||
149 | $model = $this->model(); |
||
150 | $metadata = $model->metadata(); |
||
151 | |||
152 | $table = $this->table(); |
||
153 | $fields = $this->getModelFields($model); |
||
154 | $columns = []; |
||
155 | foreach ($fields as $field) { |
||
156 | $columns[] = $field->sql(); |
||
157 | } |
||
158 | |||
159 | $query = 'CREATE TABLE `'.$table.'` ('."\n"; |
||
160 | $query .= implode(',', $columns); |
||
161 | |||
162 | $key = $model->key(); |
||
163 | if ($key) { |
||
164 | $query .= ', PRIMARY KEY (`'.$key.'`) '."\n"; |
||
165 | } |
||
166 | |||
167 | /** @todo Add indexes for all defined list constraints (yea... tough job...) */ |
||
168 | if ($driver === self::MYSQL_DRIVER_NAME) { |
||
169 | $engine = 'InnoDB'; |
||
170 | $query .= ') ENGINE='.$engine.' DEFAULT CHARSET=utf8 COMMENT="'.addslashes($metadata['name']).'";'; |
||
171 | } else { |
||
172 | $query .= ');'; |
||
173 | } |
||
174 | |||
175 | $this->logger->debug($query); |
||
176 | $dbh->query($query); |
||
177 | |||
178 | return true; |
||
179 | } |
||
180 | |||
181 | /** |
||
182 | * Alter an existing table to match the model's metadata. |
||
183 | * |
||
184 | * @return boolean TRUE if the table was altered, otherwise FALSE. |
||
185 | */ |
||
186 | public function alterTable() |
||
187 | { |
||
188 | if ($this->tableExists() === false) { |
||
189 | return false; |
||
190 | } |
||
191 | |||
192 | $dbh = $this->db(); |
||
193 | $table = $this->table(); |
||
194 | $fields = $this->getModelFields($this->model()); |
||
195 | $cols = $this->tableStructure(); |
||
196 | foreach ($fields as $field) { |
||
197 | $ident = $field->ident(); |
||
198 | |||
199 | if (!array_key_exists($ident, $cols)) { |
||
200 | // The key does not exist at all. |
||
201 | $query = 'ALTER TABLE `'.$table.'` ADD '.$field->sql(); |
||
202 | $this->logger->debug($query); |
||
203 | $dbh->query($query); |
||
204 | } else { |
||
205 | // The key exists. Validate. |
||
206 | $col = $cols[$ident]; |
||
207 | $alter = true; |
||
208 | if (strtolower($col['Type']) !== strtolower($field->sqlType())) { |
||
209 | $alter = true; |
||
210 | } |
||
211 | |||
212 | if ((strtolower($col['Null']) === 'no') && !$field->allowNull()) { |
||
213 | $alter = true; |
||
214 | } |
||
215 | |||
216 | if ((strtolower($col['Null']) !== 'no') && $field->allowNull()) { |
||
217 | $alter = true; |
||
218 | } |
||
219 | |||
220 | if ($col['Default'] !== $field->defaultVal()) { |
||
221 | $alter = true; |
||
222 | } |
||
223 | |||
224 | if ($alter === true) { |
||
225 | $query = 'ALTER TABLE `'.$table.'` CHANGE `'.$ident.'` '.$field->sql(); |
||
226 | $this->logger->debug($query); |
||
227 | $dbh->query($query); |
||
228 | } |
||
229 | } |
||
230 | } |
||
231 | |||
232 | return true; |
||
233 | } |
||
234 | |||
235 | /** |
||
236 | * Determine if the source table exists. |
||
237 | * |
||
238 | * @return boolean TRUE if the table exists, otherwise FALSE. |
||
239 | */ |
||
240 | public function tableExists() |
||
241 | { |
||
242 | $dbh = $this->db(); |
||
243 | $table = $this->table(); |
||
244 | $driver = $dbh->getAttribute(PDO::ATTR_DRIVER_NAME); |
||
245 | View Code Duplication | if ($driver === self::SQLITE_DRIVER_NAME) { |
|
246 | $query = 'SELECT name FROM sqlite_master WHERE type = "table" AND name = "'.$table.'";'; |
||
247 | } else { |
||
248 | $query = 'SHOW TABLES LIKE "'.$table.'"'; |
||
249 | } |
||
250 | |||
251 | $this->logger->debug($query); |
||
252 | $sth = $dbh->query($query); |
||
253 | $exists = $sth->fetchColumn(0); |
||
254 | |||
255 | return !!$exists; |
||
256 | } |
||
257 | |||
258 | /** |
||
259 | * Get the table columns information. |
||
260 | * |
||
261 | * @return array An associative array. |
||
262 | */ |
||
263 | public function tableStructure() |
||
264 | { |
||
265 | $dbh = $this->db(); |
||
266 | $table = $this->table(); |
||
267 | $driver = $dbh->getAttribute(PDO::ATTR_DRIVER_NAME); |
||
268 | View Code Duplication | if ($driver === self::SQLITE_DRIVER_NAME) { |
|
269 | $query = 'PRAGMA table_info("'.$table.'") '; |
||
270 | } else { |
||
271 | $query = 'SHOW COLUMNS FROM `'.$table.'`'; |
||
272 | } |
||
273 | |||
274 | $this->logger->debug($query); |
||
275 | $sth = $dbh->query($query); |
||
276 | |||
277 | $cols = $sth->fetchAll((PDO::FETCH_GROUP | PDO::FETCH_UNIQUE | PDO::FETCH_ASSOC)); |
||
278 | if ($driver === self::SQLITE_DRIVER_NAME) { |
||
279 | $struct = []; |
||
280 | foreach ($cols as $col) { |
||
281 | // Normalize SQLite's result (PRAGMA) with mysql's (SHOW COLUMNS) |
||
282 | $struct[$col['name']] = [ |
||
283 | 'Type' => $col['type'], |
||
284 | 'Null' => !!$col['notnull'] ? 'NO' : 'YES', |
||
285 | 'Default' => $col['dflt_value'], |
||
286 | 'Key' => !!$col['pk'] ? 'PRI' : '', |
||
287 | 'Extra' => '' |
||
288 | ]; |
||
289 | } |
||
290 | return $struct; |
||
291 | } else { |
||
292 | return $cols; |
||
293 | } |
||
294 | } |
||
295 | |||
296 | /** |
||
297 | * Determine if the source table is empty or not. |
||
298 | * |
||
299 | * @return boolean TRUE if the table has no data, otherwise FALSE. |
||
300 | */ |
||
301 | public function tableIsEmpty() |
||
302 | { |
||
303 | $table = $this->table(); |
||
304 | $query = 'SELECT NULL FROM `'.$table.'` LIMIT 1'; |
||
305 | $this->logger->debug($query); |
||
306 | $sth = $this->db()->query($query); |
||
307 | return ($sth->rowCount() === 0); |
||
308 | } |
||
309 | |||
310 | /** |
||
311 | * Retrieve all fields from a model. |
||
312 | * |
||
313 | * @todo Move this method in StorableTrait or AbstractModel |
||
314 | * @param ModelInterface $model The model to get fields from. |
||
315 | * @param array|null $properties Optional list of properties to get. |
||
316 | * If NULL, retrieve all (from metadata). |
||
317 | * @return PropertyField[] |
||
318 | */ |
||
319 | private function getModelFields(ModelInterface $model, $properties = null) |
||
320 | { |
||
321 | if ($properties === null) { |
||
322 | // No custom properties; use all (from model metadata) |
||
323 | $properties = array_keys($model->metadata()->properties()); |
||
324 | } else { |
||
325 | // Ensure the key is always in the required fields. |
||
326 | $properties = array_unique(array_merge([ $model->key() ], $properties)); |
||
327 | } |
||
328 | |||
329 | $fields = []; |
||
330 | foreach ($properties as $propertyIdent) { |
||
331 | $prop = $model->property($propertyIdent); |
||
332 | if (!$prop || !$prop->active() || !$prop->storable()) { |
||
333 | continue; |
||
334 | } |
||
335 | |||
336 | $val = $model->propertyValue($propertyIdent); |
||
337 | foreach ($prop->fields($val) as $fieldIdent => $field) { |
||
338 | $fields[$field->ident()] = $field; |
||
339 | } |
||
340 | } |
||
341 | |||
342 | return $fields; |
||
343 | } |
||
344 | |||
345 | /** |
||
346 | * Load item by the primary column. |
||
347 | * |
||
348 | * @param mixed $ident Ident can be any scalar value. |
||
349 | * @param StorableInterface $item Optional item to load into. |
||
350 | * @return StorableInterface |
||
351 | */ |
||
352 | public function loadItem($ident, StorableInterface $item = null) |
||
353 | { |
||
354 | $key = $this->model()->key(); |
||
355 | |||
356 | return $this->loadItemFromKey($key, $ident, $item); |
||
357 | } |
||
358 | |||
359 | /** |
||
360 | * Load item by the given column. |
||
361 | * |
||
362 | * @param string $key Column name. |
||
363 | * @param mixed $ident Value of said column. |
||
364 | * @param StorableInterface|null $item Optional. Item (storable object) to load into. |
||
365 | * @throws Exception If the query fails. |
||
366 | * @return StorableInterface |
||
367 | */ |
||
368 | public function loadItemFromKey($key, $ident, StorableInterface $item = null) |
||
369 | { |
||
370 | View Code Duplication | if ($item !== null) { |
|
371 | $this->setModel($item); |
||
372 | } else { |
||
373 | $class = get_class($this->model()); |
||
374 | $item = new $class; |
||
375 | } |
||
376 | |||
377 | // Missing parameters |
||
378 | if (!$key || !$ident) { |
||
379 | return $item; |
||
380 | } |
||
381 | |||
382 | $table = $this->table(); |
||
383 | $query = ' |
||
384 | SELECT |
||
385 | * |
||
386 | FROM |
||
387 | `'.$table.'` |
||
388 | WHERE |
||
389 | `'.$key.'` = :ident |
||
390 | LIMIT |
||
391 | 1'; |
||
392 | |||
393 | $binds = [ |
||
394 | 'ident' => $ident |
||
395 | ]; |
||
396 | |||
397 | return $this->loadItemFromQuery($query, $binds, $item); |
||
398 | } |
||
399 | |||
400 | /** |
||
401 | * Load item by the given query statement. |
||
402 | * |
||
403 | * @param string $query The SQL SELECT statement. |
||
404 | * @param array $binds Optional. The query parameters. |
||
405 | * @param StorableInterface $item Optional. Item (storable object) to load into. |
||
406 | * @throws PDOException If there is a query error. |
||
407 | * @return StorableInterface |
||
408 | */ |
||
409 | public function loadItemFromQuery($query, array $binds = [], StorableInterface $item = null) |
||
410 | { |
||
411 | View Code Duplication | if ($item !== null) { |
|
412 | $this->setModel($item); |
||
413 | } else { |
||
414 | $class = get_class($this->model()); |
||
415 | $item = new $class; |
||
416 | } |
||
417 | |||
418 | // Missing parameters |
||
419 | if (!$query) { |
||
420 | return $item; |
||
421 | } |
||
422 | |||
423 | $sth = $this->dbQuery($query, $binds); |
||
424 | if ($sth === false) { |
||
425 | throw new PDOException('Could not load item.'); |
||
426 | } |
||
427 | |||
428 | $data = $sth->fetch(PDO::FETCH_ASSOC); |
||
429 | if ($data) { |
||
430 | $item->setFlatData($data); |
||
431 | } |
||
432 | |||
433 | return $item; |
||
434 | } |
||
435 | |||
436 | /** |
||
437 | * Load items for the given model. |
||
438 | * |
||
439 | * @param StorableInterface|null $item Optional model. |
||
440 | * @return StorableInterface[] |
||
441 | */ |
||
442 | public function loadItems(StorableInterface $item = null) |
||
443 | { |
||
444 | if ($item !== null) { |
||
445 | $this->setModel($item); |
||
446 | } |
||
447 | |||
448 | $query = $this->sqlLoad(); |
||
449 | return $this->loadItemsFromQuery($query, [], $item); |
||
450 | } |
||
451 | |||
452 | /** |
||
453 | * Load items for the given query statement. |
||
454 | * |
||
455 | * @param string $query The SQL SELECT statement. |
||
456 | * @param array $binds This has to be done. |
||
457 | * @param StorableInterface|null $item Model Item. |
||
458 | * @return StorableInterface[] |
||
459 | */ |
||
460 | public function loadItemsFromQuery($query, array $binds = [], StorableInterface $item = null) |
||
461 | { |
||
462 | if ($item !== null) { |
||
463 | $this->setModel($item); |
||
464 | } |
||
465 | |||
466 | $items = []; |
||
467 | |||
468 | $model = $this->model(); |
||
469 | $dbh = $this->db(); |
||
470 | |||
471 | $this->logger->debug($query); |
||
472 | $sth = $dbh->prepare($query); |
||
473 | |||
474 | // @todo Binds |
||
475 | if (!empty($binds)) { |
||
476 | unset($binds); |
||
477 | } |
||
478 | |||
479 | $sth->execute(); |
||
480 | $sth->setFetchMode(PDO::FETCH_ASSOC); |
||
481 | |||
482 | $className = get_class($model); |
||
483 | while ($objData = $sth->fetch()) { |
||
484 | $obj = new $className; |
||
485 | $obj->setFlatData($objData); |
||
486 | $items[] = $obj; |
||
487 | } |
||
488 | |||
489 | return $items; |
||
490 | } |
||
491 | |||
492 | /** |
||
493 | * Save an item (create a new row) in storage. |
||
494 | * |
||
495 | * @param StorableInterface $item The object to save. |
||
496 | * @throws PDOException If a database error occurs. |
||
497 | * @return mixed The created item ID, otherwise FALSE. |
||
498 | */ |
||
499 | public function saveItem(StorableInterface $item) |
||
500 | { |
||
501 | if ($this->tableExists() === false) { |
||
502 | /** @todo Optionnally turn off for some models */ |
||
503 | $this->createTable(); |
||
504 | } |
||
505 | |||
506 | if ($item !== null) { |
||
507 | $this->setModel($item); |
||
508 | } |
||
509 | $model = $this->model(); |
||
510 | $table = $this->table(); |
||
511 | $struct = array_keys($this->tableStructure()); |
||
512 | $fields = $this->getModelFields($model); |
||
513 | |||
514 | $keys = []; |
||
515 | $values = []; |
||
516 | $binds = []; |
||
517 | $types = []; |
||
518 | foreach ($fields as $field) { |
||
519 | $key = $field->ident(); |
||
520 | if (in_array($key, $struct)) { |
||
521 | $keys[] = '`'.$key.'`'; |
||
522 | $values[] = ':'.$key.''; |
||
523 | $binds[$key] = $field->val(); |
||
524 | $types[$key] = $field->sqlPdoType(); |
||
525 | } |
||
526 | } |
||
527 | |||
528 | $query = ' |
||
529 | INSERT |
||
530 | INTO |
||
531 | `'.$table.'` |
||
532 | ('.implode(', ', $keys).') |
||
533 | VALUES |
||
534 | ('.implode(', ', $values).')'; |
||
535 | |||
536 | $result = $this->dbQuery($query, $binds, $types); |
||
537 | |||
538 | if ($result === false) { |
||
539 | throw new PDOException('Could not save item.'); |
||
540 | } else { |
||
541 | if ($model->id()) { |
||
542 | return $model->id(); |
||
543 | } else { |
||
544 | return $this->db()->lastInsertId(); |
||
545 | } |
||
546 | } |
||
547 | } |
||
548 | |||
549 | /** |
||
550 | * Update an item in storage. |
||
551 | * |
||
552 | * @param StorableInterface $item The object to update. |
||
553 | * @param array $properties The list of properties to update, if not all. |
||
554 | * @return boolean TRUE if the item was updated, otherwise FALSE. |
||
555 | */ |
||
556 | public function updateItem(StorableInterface $item, array $properties = null) |
||
557 | { |
||
558 | if ($item !== null) { |
||
559 | $this->setModel($item); |
||
560 | } |
||
561 | $model = $this->model(); |
||
562 | $table = $this->table(); |
||
563 | $struct = array_keys($this->tableStructure()); |
||
564 | $fields = $this->getModelFields($model, $properties); |
||
565 | |||
566 | $updates = []; |
||
567 | $binds = []; |
||
568 | $types = []; |
||
569 | foreach ($fields as $field) { |
||
570 | $key = $field->ident(); |
||
571 | if (in_array($key, $struct)) { |
||
572 | if ($key !== $model->key()) { |
||
573 | $param = ':'.$key; |
||
574 | $updates[] = '`'.$key.'` = '.$param; |
||
575 | } |
||
576 | $binds[$key] = $field->val(); |
||
577 | $types[$key] = $field->sqlPdoType(); |
||
578 | } else { |
||
579 | $this->logger->debug( |
||
580 | sprintf('Field "%s" not in table structure', $key) |
||
581 | ); |
||
582 | } |
||
583 | } |
||
584 | if (empty($updates)) { |
||
585 | $this->logger->warning( |
||
586 | 'Could not update items. No valid fields were set / available in database table.', |
||
587 | [ |
||
588 | 'properties' => $properties, |
||
589 | 'structure' => $struct |
||
590 | ] |
||
591 | ); |
||
592 | return false; |
||
593 | } |
||
594 | |||
595 | $binds[$model->key()] = $model->id(); |
||
596 | $types[$model->key()] = PDO::PARAM_STR; |
||
597 | |||
598 | $query = ' |
||
599 | UPDATE |
||
600 | `'.$table.'` |
||
601 | SET |
||
602 | '.implode(", \n\t", $updates).' |
||
603 | WHERE |
||
604 | `'.$model->key().'`=:'.$model->key().''; |
||
605 | |||
606 | $driver = $this->db()->getAttribute(PDO::ATTR_DRIVER_NAME); |
||
607 | if ($driver == self::MYSQL_DRIVER_NAME) { |
||
608 | $query .= "\n".'LIMIT 1'; |
||
609 | } |
||
610 | |||
611 | $result = $this->dbQuery($query, $binds, $types); |
||
612 | |||
613 | if ($result === false) { |
||
614 | return false; |
||
615 | } else { |
||
616 | return true; |
||
617 | } |
||
618 | } |
||
619 | |||
620 | /** |
||
621 | * Delete an item from storage. |
||
622 | * |
||
623 | * @param StorableInterface $item Optional item to delete. If none, the current model object will be used. |
||
624 | * @throws UnexpectedValueException If the item does not have an ID. |
||
625 | * @return boolean TRUE if the item was deleted, otherwise FALSE. |
||
626 | */ |
||
627 | public function deleteItem(StorableInterface $item = null) |
||
628 | { |
||
629 | if ($item !== null) { |
||
630 | $this->setModel($item); |
||
631 | } |
||
632 | |||
633 | $model = $this->model(); |
||
634 | |||
635 | if (!$model->id()) { |
||
636 | throw new UnexpectedValueException( |
||
637 | sprintf('Can not delete "%s" item. No ID.', get_class($this)) |
||
638 | ); |
||
639 | } |
||
640 | |||
641 | $key = $model->key(); |
||
642 | $table = $this->table(); |
||
643 | $query = ' |
||
644 | DELETE FROM |
||
645 | `'.$table.'` |
||
646 | WHERE |
||
647 | `'.$key.'` = :id'; |
||
648 | |||
649 | $driver = $this->db()->getAttribute(PDO::ATTR_DRIVER_NAME); |
||
650 | if ($driver == self::MYSQL_DRIVER_NAME) { |
||
651 | $query .= "\n".'LIMIT 1'; |
||
652 | } |
||
653 | |||
654 | $binds = [ |
||
655 | 'id' => $model->id() |
||
656 | ]; |
||
657 | |||
658 | $result = $this->dbQuery($query, $binds); |
||
659 | |||
660 | if ($result === false) { |
||
661 | return false; |
||
662 | } else { |
||
663 | return true; |
||
664 | } |
||
665 | } |
||
666 | |||
667 | /** |
||
668 | * Execute a SQL query, with PDO, and returns the PDOStatement. |
||
669 | * |
||
670 | * If the query fails, this method will return false. |
||
671 | * |
||
672 | * @param string $query The SQL query to executed. |
||
673 | * @param array $binds Optional. Query parameter binds. |
||
674 | * @param array $types Optional. Types of parameter bindings. |
||
675 | * @return PDOStatement|false The PDOStatement, otherwise FALSE. |
||
676 | */ |
||
677 | public function dbQuery($query, array $binds = [], array $types = []) |
||
678 | { |
||
679 | $this->logger->debug($query, $binds); |
||
680 | $sth = $this->db()->prepare($query); |
||
681 | if (!$sth) { |
||
682 | return false; |
||
683 | } |
||
684 | |||
685 | if (!empty($binds)) { |
||
686 | foreach ($binds as $key => $val) { |
||
687 | if ($binds[$key] === null) { |
||
688 | $types[$key] = PDO::PARAM_NULL; |
||
689 | } elseif (!is_scalar($binds[$key])) { |
||
690 | $binds[$key] = json_encode($binds[$key]); |
||
691 | } |
||
692 | $type = (isset($types[$key]) ? $types[$key] : PDO::PARAM_STR); |
||
693 | $param = ':'.$key; |
||
694 | $sth->bindParam($param, $binds[$key], $type); |
||
695 | } |
||
696 | } |
||
697 | |||
698 | $result = $sth->execute(); |
||
699 | if ($result === false) { |
||
700 | return false; |
||
701 | } |
||
702 | |||
703 | return $sth; |
||
704 | } |
||
705 | |||
706 | /** |
||
707 | * Compile the SELECT statement for fetching one or more objects. |
||
708 | * |
||
709 | * @throws UnexpectedValueException If the source does not have a table defined. |
||
710 | * @return string |
||
711 | */ |
||
712 | public function sqlLoad() |
||
728 | } |
||
729 | |||
730 | /** |
||
731 | * Compile the SELECT statement for fetching the number of objects. |
||
732 | * |
||
733 | * @throws UnexpectedValueException If the source does not have a table defined. |
||
734 | * @return string |
||
735 | */ |
||
736 | public function sqlLoadCount() |
||
749 | } |
||
750 | |||
751 | /** |
||
752 | * Compile the SELECT clause. |
||
753 | * |
||
754 | * @throws UnexpectedValueException If the clause has no selectable fields. |
||
755 | * @return string |
||
756 | */ |
||
757 | public function sqlSelect() |
||
758 | { |
||
759 | $properties = $this->properties(); |
||
760 | if (empty($properties)) { |
||
761 | return self::DEFAULT_TABLE_ALIAS.'.*'; |
||
762 | } |
||
763 | |||
764 | $parts = []; |
||
765 | foreach ($properties as $key) { |
||
766 | $parts[] = Expression::quoteIdentifier($key, self::DEFAULT_TABLE_ALIAS); |
||
767 | } |
||
768 | |||
769 | if (empty($parts)) { |
||
770 | throw new UnexpectedValueException( |
||
771 | 'Can not get SQL SELECT clause. No valid properties.' |
||
772 | ); |
||
773 | } |
||
774 | |||
775 | $clause = implode(', ', $parts); |
||
776 | |||
777 | return $clause; |
||
778 | } |
||
779 | |||
780 | /** |
||
781 | * Compile the FROM clause. |
||
782 | * |
||
783 | * @throws UnexpectedValueException If the source does not have a table defined. |
||
784 | * @return string |
||
785 | */ |
||
786 | public function sqlFrom() |
||
787 | { |
||
788 | if (!$this->hasTable()) { |
||
789 | throw new UnexpectedValueException( |
||
790 | 'Can not get SQL FROM clause. No table defined.' |
||
791 | ); |
||
792 | } |
||
793 | |||
794 | $table = $this->table(); |
||
795 | return '`'.$table.'` AS `'.self::DEFAULT_TABLE_ALIAS.'`'; |
||
796 | } |
||
797 | |||
798 | /** |
||
799 | * Compile the WHERE clause. |
||
800 | * |
||
801 | * @todo [2016-02-19] Use bindings for filters value |
||
802 | * @return string |
||
803 | */ |
||
804 | public function sqlFilters() |
||
805 | { |
||
806 | if (!$this->hasFilters()) { |
||
807 | return ''; |
||
808 | } |
||
809 | |||
810 | $criteria = $this->createFilter([ |
||
811 | 'filters' => $this->filters() |
||
812 | ]); |
||
813 | |||
814 | $sql = $criteria->sql(); |
||
815 | if (strlen($sql) > 0) { |
||
816 | $sql = ' WHERE '.$sql; |
||
817 | } |
||
818 | |||
819 | return $sql; |
||
820 | } |
||
821 | |||
822 | /** |
||
823 | * Compile the ORDER BY clause. |
||
824 | * |
||
825 | * @return string |
||
826 | */ |
||
827 | public function sqlOrders() |
||
828 | { |
||
829 | if (!$this->hasOrders()) { |
||
830 | return ''; |
||
831 | } |
||
832 | |||
833 | $parts = []; |
||
834 | foreach ($this->orders() as $order) { |
||
835 | if (!$order instanceof DatabaseOrder) { |
||
836 | $order = $this->createOrder($order->data()); |
||
837 | } |
||
838 | |||
839 | $sql = $order->sql(); |
||
840 | if (strlen($sql) > 0) { |
||
841 | $parts[] = $sql; |
||
842 | } |
||
843 | } |
||
844 | |||
845 | if (empty($parts)) { |
||
846 | return ''; |
||
847 | } |
||
848 | |||
849 | return ' ORDER BY '.implode(', ', $parts); |
||
850 | } |
||
851 | |||
852 | /** |
||
853 | * Compile the LIMIT clause. |
||
854 | * |
||
855 | * @return string |
||
856 | */ |
||
857 | public function sqlPagination() |
||
858 | { |
||
859 | $pager = $this->pagination(); |
||
860 | if (!$pager instanceof DatabasePagination) { |
||
861 | $pager = $this->createPagination($pager->data()); |
||
862 | } |
||
863 | |||
864 | $sql = $pager->sql(); |
||
865 | if (strlen($sql) > 0) { |
||
866 | $sql = ' '.$sql; |
||
867 | } |
||
868 | |||
869 | return $sql; |
||
870 | } |
||
871 | |||
872 | /** |
||
873 | * Create a new filter expression. |
||
874 | * |
||
875 | * @param array $data Optional expression data. |
||
876 | * @return DatabaseFilter |
||
877 | */ |
||
878 | protected function createFilter(array $data = null) |
||
885 | } |
||
886 | |||
887 | /** |
||
888 | * Create a new order expression. |
||
889 | * |
||
890 | * @param array $data Optional expression data. |
||
891 | * @return DatabaseOrder |
||
892 | */ |
||
893 | protected function createOrder(array $data = null) |
||
894 | { |
||
895 | $order = new DatabaseOrder(); |
||
896 | if ($data !== null) { |
||
897 | $order->setData($data); |
||
898 | } |
||
899 | return $order; |
||
900 | } |
||
901 | |||
902 | /** |
||
903 | * Create a new pagination clause. |
||
904 | * |
||
905 | * @param array $data Optional clause data. |
||
906 | * @return DatabasePagination |
||
907 | */ |
||
908 | protected function createPagination(array $data = null) |
||
909 | { |
||
910 | $pagination = new DatabasePagination(); |
||
911 | if ($data !== null) { |
||
912 | $pagination->setData($data); |
||
913 | } |
||
914 | return $pagination; |
||
915 | } |
||
916 | |||
917 | /** |
||
918 | * Create a new database source config. |
||
919 | * |
||
920 | * @see \Charcoal\Config\ConfigurableTrait |
||
921 | * @param array $data Optional data. |
||
922 | * @return DatabaseSourceConfig |
||
923 | */ |
||
924 | public function createConfig(array $data = null) |
||
928 | } |
||
929 | } |
||
930 |
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.