Total Complexity | 87 |
Total Lines | 628 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like Database 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 Database, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
14 | class Database |
||
15 | { |
||
16 | private static EntityManager $em; |
||
17 | private static Connection $connection; |
||
18 | |||
19 | /** |
||
20 | * Setup doctrine only for the installation. |
||
21 | * |
||
22 | * @throws \Doctrine\ORM\ORMException |
||
23 | * @throws \Doctrine\DBAL\Exception |
||
24 | */ |
||
25 | public static function connect(array $params = [], string $entityRootPath = ''): void |
||
26 | { |
||
27 | $config = self::getDoctrineConfig($entityRootPath); |
||
28 | $config->setAutoGenerateProxyClasses(true); |
||
29 | $config->setEntityNamespaces( |
||
30 | [ |
||
31 | 'ChamiloCoreBundle' => 'Chamilo\CoreBundle\Entity', |
||
32 | 'ChamiloCourseBundle' => 'Chamilo\CourseBundle\Entity', |
||
33 | ] |
||
34 | ); |
||
35 | |||
36 | $params['charset'] = 'utf8'; |
||
37 | |||
38 | // standard annotation reader |
||
39 | $annotationReader = new Doctrine\Common\Annotations\AnnotationReader(); |
||
40 | $cachedAnnotationReader = new PsrCachedReader( |
||
41 | $annotationReader, |
||
42 | new ArrayAdapter() |
||
43 | ); |
||
44 | |||
45 | $evm = new EventManager(); |
||
46 | $timestampableListener = new Gedmo\Timestampable\TimestampableListener(); |
||
47 | $timestampableListener->setAnnotationReader($cachedAnnotationReader); |
||
48 | $evm->addEventSubscriber($timestampableListener); |
||
49 | |||
50 | $driverChain = new \Doctrine\Persistence\Mapping\Driver\MappingDriverChain(); |
||
51 | // load superclass metadata mapping only, into driver chain |
||
52 | // also registers Gedmo annotations.NOTE: you can personalize it |
||
53 | Gedmo\DoctrineExtensions::registerAbstractMappingIntoDriverChainORM( |
||
54 | $driverChain, // our metadata driver chain, to hook into |
||
55 | $cachedAnnotationReader // our cached annotation reader |
||
56 | ); |
||
57 | |||
58 | $entityManager = EntityManager::create($params, $config, $evm); |
||
59 | |||
60 | if (false === Type::hasType('uuid')) { |
||
61 | Type::addType('uuid', UuidType::class); |
||
62 | } |
||
63 | |||
64 | $connection = $entityManager->getConnection(); |
||
65 | |||
66 | self::setConnection($connection); |
||
67 | self::setManager($entityManager); |
||
68 | } |
||
69 | |||
70 | public static function setManager(EntityManager $em) |
||
73 | } |
||
74 | |||
75 | public static function setConnection(Connection $connection) |
||
78 | } |
||
79 | |||
80 | public static function getConnection(): Connection |
||
81 | { |
||
82 | return self::$connection; |
||
83 | } |
||
84 | |||
85 | public static function getManager(): EntityManager |
||
86 | { |
||
87 | return self::$em; |
||
88 | } |
||
89 | |||
90 | /** |
||
91 | * Returns the name of the main database. |
||
92 | * |
||
93 | * @throws \Doctrine\DBAL\Exception |
||
94 | */ |
||
95 | public static function get_main_database(): bool|string|null |
||
96 | { |
||
97 | return self::getManager()->getConnection()->getDatabase(); |
||
98 | } |
||
99 | |||
100 | /** |
||
101 | * Get main table. |
||
102 | */ |
||
103 | public static function get_main_table(string $table): string |
||
104 | { |
||
105 | return $table; |
||
106 | } |
||
107 | |||
108 | /** |
||
109 | * Get course table. |
||
110 | */ |
||
111 | public static function get_course_table(string $table): string |
||
112 | { |
||
113 | return DB_COURSE_PREFIX.$table; |
||
114 | } |
||
115 | |||
116 | /** |
||
117 | * Counts the number of rows in a table. |
||
118 | * |
||
119 | * @throws Exception |
||
120 | * @throws \Doctrine\DBAL\Exception |
||
121 | * |
||
122 | * @deprecated |
||
123 | */ |
||
124 | public static function count_rows(string $table): int |
||
125 | { |
||
126 | $result = self::query("SELECT COUNT(*) AS n FROM $table"); |
||
127 | |||
128 | return (int) $result->fetchOne(); |
||
129 | } |
||
130 | |||
131 | /** |
||
132 | * Returns the number of affected rows in the last database operation. |
||
133 | * |
||
134 | * @throws \Doctrine\DBAL\Exception |
||
135 | */ |
||
136 | public static function affected_rows(\Doctrine\DBAL\Result $result): int |
||
139 | } |
||
140 | |||
141 | /** |
||
142 | * Escapes a string to insert into the database as text. |
||
143 | */ |
||
144 | public static function escape_string(mixed $string): string |
||
145 | { |
||
146 | $string = self::getManager()->getConnection()->quote($string); |
||
147 | // The quote method from PDO also adds quotes around the string, which |
||
148 | // is not how the legacy mysql_real_escape_string() was used in |
||
149 | // Chamilo, so we need to remove the quotes around. Using trim will |
||
150 | // remove more than one quote if they are sequenced, generating |
||
151 | // broken queries and SQL injection risks |
||
152 | return substr($string, 1, -1); |
||
153 | } |
||
154 | |||
155 | /** |
||
156 | * Gets the (associative) array from a SQL result (as returned by Database::query). |
||
157 | * |
||
158 | * @throws \Doctrine\DBAL\Exception |
||
159 | */ |
||
160 | public static function fetch_array(\Doctrine\DBAL\Result $result): mixed |
||
161 | { |
||
162 | $data = $result->fetchAssociative(); |
||
163 | |||
164 | if (empty($data)) { |
||
165 | return []; |
||
166 | } |
||
167 | |||
168 | $return = $data; |
||
169 | |||
170 | foreach ($data as $value) { |
||
171 | $return[] = $value; |
||
172 | } |
||
173 | |||
174 | return $return; |
||
175 | } |
||
176 | |||
177 | /** |
||
178 | * Gets an associative array from a SQL result (as returned by Database::query). |
||
179 | * |
||
180 | * @throws \Doctrine\DBAL\Exception |
||
181 | */ |
||
182 | public static function fetch_assoc(\Doctrine\DBAL\Result $result): array|bool |
||
183 | { |
||
184 | return $result->fetchAssociative(); |
||
185 | } |
||
186 | |||
187 | /** |
||
188 | * Gets the next row of the result of the SQL query |
||
189 | * (as returned by Database::query) in an object form. |
||
190 | * |
||
191 | * @throws \Doctrine\DBAL\Exception |
||
192 | */ |
||
193 | public static function fetch_object(\Doctrine\DBAL\Result $result): ?stdClass |
||
194 | { |
||
195 | $data = $result->fetchAssociative(); |
||
196 | |||
197 | if (empty($data)) { |
||
198 | return null; |
||
199 | } |
||
200 | |||
201 | $object = new stdClass(); |
||
202 | |||
203 | foreach ($data as $key => $value) { |
||
204 | $object->$key = $value; |
||
205 | } |
||
206 | |||
207 | return $object; |
||
208 | } |
||
209 | |||
210 | /** |
||
211 | * Gets the array from a SQL result (as returned by Database::query) |
||
212 | * help to achieve database independence. |
||
213 | * |
||
214 | * @throws \Doctrine\DBAL\Exception |
||
215 | */ |
||
216 | public static function fetch_row(\Doctrine\DBAL\Result $result): array |
||
217 | { |
||
218 | $row = $result->fetchNumeric(); |
||
219 | |||
220 | return empty($row) ? [] : $row; |
||
221 | } |
||
222 | |||
223 | /** |
||
224 | * Gets the ID of the last item inserted into the database. |
||
225 | * |
||
226 | * @throws \Doctrine\DBAL\Exception |
||
227 | */ |
||
228 | public static function insert_id(): int |
||
229 | { |
||
230 | return (int) self::getManager()->getConnection()->lastInsertId(); |
||
231 | } |
||
232 | |||
233 | /** |
||
234 | * @throws \Doctrine\DBAL\Exception |
||
235 | */ |
||
236 | public static function num_rows(\Doctrine\DBAL\Result $result): int |
||
239 | } |
||
240 | |||
241 | /** |
||
242 | * Acts as the relative *_result() function of most DB drivers and fetches a |
||
243 | * specific line and a field. |
||
244 | * |
||
245 | * @throws \Doctrine\DBAL\Exception |
||
246 | */ |
||
247 | public static function result(\Doctrine\DBAL\Result $resource, int $row, string $field): mixed |
||
248 | { |
||
249 | if ($resource->rowCount() > 0) { |
||
262 | } |
||
263 | |||
264 | /** |
||
265 | * Wrapper to executes a query on the defined database handler. |
||
266 | * @throws Exception |
||
267 | */ |
||
268 | public static function query(string $query): ?\Doctrine\DBAL\Result |
||
279 | } |
||
280 | |||
281 | /** |
||
282 | * @throws Exception |
||
283 | */ |
||
284 | public static function handleError(Exception $e) |
||
285 | { |
||
286 | $debug = 'test' === api_get_setting('server_type'); |
||
287 | if ($debug) { |
||
288 | throw $e; |
||
289 | } else { |
||
290 | error_log($e->getMessage()); |
||
291 | api_not_allowed(false, get_lang('An error has occurred. Please contact your system administrator.')); |
||
292 | } |
||
293 | } |
||
294 | |||
295 | /** |
||
296 | * Stores a query result into an array. |
||
297 | * |
||
298 | * @throws \Doctrine\DBAL\Exception |
||
299 | */ |
||
300 | public static function store_result(\Doctrine\DBAL\Result $result, $option = 'BOTH'): array |
||
307 | } |
||
308 | |||
309 | /** |
||
310 | * Database insert. |
||
311 | * |
||
312 | * @throws \Doctrine\DBAL\Exception |
||
313 | * @throws Exception |
||
314 | */ |
||
315 | public static function insert(string $table_name, array $attributes, bool $show_query = false, array $options = []): bool|int |
||
316 | { |
||
317 | if (empty($attributes) || empty($table_name)) { |
||
318 | return false; |
||
319 | } |
||
320 | |||
321 | $params = array_keys($attributes); |
||
322 | |||
323 | if (!empty($params)) { |
||
324 | $prefix = (!empty($options['ignore'])) ? 'INSERT IGNORE INTO' : 'INSERT INTO'; |
||
325 | $sql = $prefix.' '.$table_name.' ('.implode(',', $params).') |
||
326 | VALUES (:'.implode(', :', $params).')'; |
||
327 | |||
328 | if ($show_query) { |
||
329 | var_dump($sql); |
||
|
|||
330 | error_log($sql); |
||
331 | } |
||
332 | |||
333 | try { |
||
334 | self::getConnection() |
||
335 | ->prepare($sql) |
||
336 | ->executeQuery($attributes) |
||
337 | ; |
||
338 | } catch (Exception $e) { |
||
339 | self::handleError($e); |
||
340 | |||
341 | return false; |
||
342 | } |
||
343 | |||
344 | return (int) self::getManager()->getConnection()->lastInsertId(); |
||
345 | } |
||
346 | |||
347 | return false; |
||
348 | } |
||
349 | |||
350 | /** |
||
351 | * Wrapper executing an SQL update query based on the given attributes array |
||
352 | * @param string $tableName use Database::get_main_table |
||
353 | * @param array $attributes Values to updates |
||
354 | * Example: $params['name'] = 'Julio'; $params['lastname'] = 'Montoya'; |
||
355 | * @param array $whereConditions where conditions i.e. array('id = ?' =>'4') |
||
356 | * @param bool $showQuery |
||
357 | * |
||
358 | * @throws Exception |
||
359 | * |
||
360 | * @return bool|int |
||
361 | */ |
||
362 | public static function update( |
||
363 | string $tableName, |
||
364 | array $attributes, |
||
365 | array $whereConditions = [], |
||
366 | bool $showQuery = false |
||
367 | ): bool|int { |
||
368 | if (!empty($tableName) && !empty($attributes)) { |
||
369 | $updateSql = ''; |
||
370 | $count = 1; |
||
371 | |||
372 | foreach ($attributes as $key => $value) { |
||
373 | if ($showQuery) { |
||
374 | echo $key.': '.$value.PHP_EOL; |
||
375 | } |
||
376 | $updateSql .= "$key = :$key "; |
||
377 | if ($count < count($attributes)) { |
||
378 | $updateSql .= ', '; |
||
379 | } |
||
380 | $count++; |
||
381 | } |
||
382 | |||
383 | if (!empty($updateSql)) { |
||
384 | // Parsing and cleaning the where conditions |
||
385 | $whereReturn = self::parse_where_conditions($whereConditions); |
||
386 | $sql = "UPDATE $tableName SET $updateSql $whereReturn "; |
||
387 | |||
388 | try { |
||
389 | $statement = self::getManager()->getConnection()->prepare($sql); |
||
390 | $result = $statement->executeQuery($attributes); |
||
391 | } catch (Exception $e) { |
||
392 | self::handleError($e); |
||
393 | |||
394 | return false; |
||
395 | } |
||
396 | |||
397 | if ($showQuery) { |
||
398 | var_dump($sql); |
||
399 | var_dump($attributes); |
||
400 | var_dump($whereConditions); |
||
401 | } |
||
402 | |||
403 | return $result->rowCount(); |
||
404 | } |
||
405 | } |
||
406 | |||
407 | return false; |
||
408 | } |
||
409 | |||
410 | /** |
||
411 | * Experimental useful database finder. |
||
412 | * |
||
413 | * @throws Exception |
||
414 | * |
||
415 | * @todo lot of stuff to do here |
||
416 | * @todo known issues, it doesn't work when using LIKE conditions |
||
417 | * |
||
418 | * @example ['where'=> ['course_code LIKE "?%"']] |
||
419 | * @example ['where'=> ['type = ? AND category = ?' => ['setting', 'Plugins']]] |
||
420 | * @example ['where'=> ['name = "Julio" AND lastname = "montoya"']] |
||
421 | */ |
||
422 | public static function select( |
||
423 | string|array $columns, |
||
424 | string $table_name, |
||
425 | array $conditions = [], |
||
426 | string $type_result = 'all', |
||
427 | string $option = 'ASSOC', |
||
428 | bool $debug = false |
||
429 | ): int|array { |
||
430 | if ('count' === $type_result) { |
||
431 | $conditions['LIMIT'] = null; |
||
432 | $conditions['limit'] = null; |
||
433 | } |
||
434 | $conditions = self::parse_conditions($conditions); |
||
435 | |||
436 | //@todo we could do a describe here to check the columns ... |
||
437 | if (is_array($columns)) { |
||
438 | $clean_columns = implode(',', $columns); |
||
439 | } else { |
||
440 | if ('*' === $columns) { |
||
441 | $clean_columns = '*'; |
||
442 | } else { |
||
443 | $clean_columns = (string) $columns; |
||
444 | } |
||
445 | } |
||
446 | |||
447 | if ('count' === $type_result) { |
||
448 | $clean_columns = ' count(*) count '; |
||
449 | } |
||
450 | $sql = "SELECT $clean_columns FROM $table_name $conditions"; |
||
451 | if ($debug) { |
||
452 | var_dump($sql); |
||
453 | } |
||
454 | $result = self::query($sql); |
||
455 | if ('count' === $type_result) { |
||
456 | $row = self::fetch_array($result); |
||
457 | if ($row) { |
||
458 | return (int) $row['count']; |
||
459 | } |
||
460 | |||
461 | return 0; |
||
462 | } |
||
463 | $array = []; |
||
464 | |||
465 | if ('all' === $type_result) { |
||
466 | while ($row = self::fetch_array($result)) { |
||
467 | if (isset($row['id'])) { |
||
468 | $array[$row['id']] = $row; |
||
469 | } else { |
||
470 | $array[] = $row; |
||
471 | } |
||
472 | } |
||
473 | } else { |
||
474 | $array = self::fetch_array($result); |
||
475 | } |
||
476 | |||
477 | return $array; |
||
478 | } |
||
479 | |||
480 | /** |
||
481 | * Parses WHERE/ORDER conditions i.e. array('where'=>array('id = ?' =>'4'), 'order'=>'id DESC'). |
||
482 | * |
||
483 | * @todo known issues, it doesn't work when using |
||
484 | * LIKE conditions example: array('where'=>array('course_code LIKE "?%"')) |
||
485 | */ |
||
486 | public static function parse_conditions(array $conditions): string |
||
576 | } |
||
577 | |||
578 | public static function parse_where_conditions(array $conditions): string |
||
579 | { |
||
580 | return self::parse_conditions(['where' => $conditions]); |
||
581 | } |
||
582 | |||
583 | /** |
||
584 | * @throws Exception |
||
585 | */ |
||
586 | public static function delete(string $tableName, array $where_conditions, bool $show_query = false): int |
||
587 | { |
||
588 | $where_return = self::parse_where_conditions($where_conditions); |
||
589 | $sql = "DELETE FROM $tableName $where_return "; |
||
590 | if ($show_query) { |
||
591 | echo $sql; |
||
592 | echo '<br />'; |
||
593 | } |
||
594 | $result = self::query($sql); |
||
595 | |||
596 | return self::affected_rows($result); |
||
597 | } |
||
598 | |||
599 | /** |
||
600 | * Get Doctrine configuration. |
||
601 | */ |
||
602 | public static function getDoctrineConfig(string $path): Configuration |
||
603 | { |
||
604 | $cache = null; |
||
605 | $path = !empty($path) ? $path : api_get_path(SYMFONY_SYS_PATH); |
||
606 | |||
607 | $paths = [ |
||
608 | $path.'src/Chamilo/CoreBundle/Entity', |
||
609 | $path.'src/Chamilo/CourseBundle/Entity', |
||
610 | ]; |
||
611 | |||
612 | $proxyDir = $path.'var/cache/'; |
||
613 | |||
614 | return \Doctrine\ORM\Tools\Setup::createAnnotationMetadataConfiguration( |
||
615 | $paths, |
||
616 | true, // Forces doctrine to use ArrayCache instead of apc/xcache/memcache/redis |
||
617 | $proxyDir, |
||
618 | $cache, |
||
619 | false // related to annotations @Entity |
||
620 | ); |
||
621 | } |
||
622 | |||
623 | /** |
||
624 | * @throws \Doctrine\DBAL\Exception |
||
625 | */ |
||
626 | public static function tableExists(string $table): bool |
||
629 | } |
||
630 | |||
631 | /** |
||
632 | * @throws \Doctrine\DBAL\Exception |
||
633 | */ |
||
634 | public static function listTableColumns(string $table): array |
||
637 | } |
||
638 | |||
639 | public static function escapeField($field): string |
||
644 |