Completed
Push — 1.10.x ( a47b7a...621fef )
by
unknown
89:08 queued 37:34
created

Database::fetch_object()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use Doctrine\Common\Annotations\AnnotationRegistry;
5
use Doctrine\DBAL\Connection;
6
use Doctrine\DBAL\Driver\Statement;
7
use Doctrine\DBAL\Types\Type;
8
use Doctrine\ORM\EntityManager;
9
10
/**
11
 * Class Database
12
 */
13
class Database
14
{
15
    /**
16
     * @var EntityManager
17
     */
18
    private static $em;
19
    private static $connection;
20
    public static $utcDateTimeClass;
21
22
    /**
23
     * @param EntityManager $em
24
     */
25
    public function setManager($em)
0 ignored issues
show
Bug introduced by
You have injected the EntityManager via parameter $em. This is generally not recommended as it might get closed and become unusable. Instead, it is recommended to inject the ManagerRegistry and retrieve the EntityManager via getManager() each time you need it.

The EntityManager might become unusable for example if a transaction is rolled back and it gets closed. Let’s assume that somewhere in your application, or in a third-party library, there is code such as the following:

function someFunction(ManagerRegistry $registry) {
    $em = $registry->getManager();
    $em->getConnection()->beginTransaction();
    try {
        // Do something.
        $em->getConnection()->commit();
    } catch (\Exception $ex) {
        $em->getConnection()->rollback();
        $em->close();

        throw $ex;
    }
}

If that code throws an exception and the EntityManager is closed. Any other code which depends on the same instance of the EntityManager during this request will fail.

On the other hand, if you instead inject the ManagerRegistry, the getManager() method guarantees that you will always get a usable manager instance.

Loading history...
26
    {
27
        self::$em = $em;
28
    }
29
30
    /**
31
     * @param Connection $connection
32
     */
33
    public function setConnection(Connection $connection)
34
    {
35
        self::$connection = $connection;
36
    }
37
38
    /**
39
     * @return Connection
40
     */
41
    public function getConnection()
42
    {
43
        return self::$connection;
44
    }
45
46
    /**
47
     * @return EntityManager
48
     */
49
    public static function getManager()
50
    {
51
        return self::$em;
52
    }
53
54
    /**
55
     * Returns the name of the main database.
56
     *
57
     * @return string
58
     */
59
    public static function get_main_database()
60
    {
61
        return self::getManager()->getConnection()->getDatabase();
62
    }
63
64
    /**
65
     * Get main table
66
     *
67
     * @param string $table
68
     *
69
     * @return mixed
70
     */
71
    public static function get_main_table($table)
72
    {
73
        return $table;
74
    }
75
76
    /**
77
     * Get course table
78
     *
79
     * @param string $table
80
     *
81
     * @return string
82
     */
83
    public static function get_course_table($table)
84
    {
85
        return DB_COURSE_PREFIX.$table;
86
    }
87
88
    /**
89
     * Counts the number of rows in a table
90
     * @param string $table The table of which the rows should be counted
91
     *
92
     * @return int The number of rows in the given table.
93
     * @deprecated
94
     */
95
    public static function count_rows($table)
96
    {
97
        $obj = self::fetch_object(self::query("SELECT COUNT(*) AS n FROM $table"));
0 ignored issues
show
Bug introduced by
It seems like self::query("SELECT COUNT(*) AS n FROM {$table}") can be null; however, fetch_object() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
98
99
        return $obj->n;
100
    }
101
102
    /**
103
     * Returns the number of affected rows in the last database operation.
104
     * @param Statement $result
105
     *
106
     * @return int
107
     */
108
    public static function affected_rows(Statement $result)
109
    {
110
        return $result->rowCount();
111
    }
112
113
    /**
114
     * @return string
115
     */
116
    public static function getUTCDateTimeTypeClass()
117
    {
118
        return isset(self::$utcDateTimeClass) ? self::$utcDateTimeClass :
119
        'Application\DoctrineExtensions\DBAL\Types\UTCDateTimeType';
120
    }
121
122
    /**
123
     * Connect to the database sets the entity manager.
124
     *
125
     * @param array  $params
126
     * @param string $sysPath
127
     * @param string $entityRootPath
128
     *
129
     * @throws \Doctrine\ORM\ORMException
130
     */
131
    public function connect($params = array(), $sysPath = '', $entityRootPath = '')
132
    {
133
        $config = self::getDoctrineConfig($entityRootPath);
134
        $config->setAutoGenerateProxyClasses(true);
135
136
        $config->setEntityNamespaces(
137
            array(
138
                'ChamiloUserBundle' => 'Chamilo\UserBundle\Entity',
139
                'ChamiloCoreBundle' => 'Chamilo\CoreBundle\Entity',
140
                'ChamiloCourseBundle' => 'Chamilo\CourseBundle\Entity'
141
            )
142
        );
143
144
        $params['charset'] = 'utf8';
145
        $entityManager = EntityManager::create($params, $config);
146
        $sysPath = !empty($sysPath) ? $sysPath : api_get_path(SYS_PATH);
147
148
        // Registering Constraints
149
        AnnotationRegistry::registerAutoloadNamespace(
150
            'Symfony\Component\Validator\Constraint',
151
            $sysPath."vendor/symfony/validator"
152
        );
153
154
        AnnotationRegistry::registerFile(
155
            $sysPath."vendor/symfony/doctrine-bridge/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php"
156
        );
157
158
        // Registering gedmo extensions
159
        AnnotationRegistry::registerAutoloadNamespace(
160
            'Gedmo\Mapping\Annotation',
161
            $sysPath."vendor/gedmo/doctrine-extensions/lib"
162
        );
163
164
        Type::overrideType(
165
            Type::DATETIME,
166
            self::getUTCDateTimeTypeClass()
167
        );
168
169
        $listener = new \Gedmo\Timestampable\TimestampableListener();
170
        $entityManager->getEventManager()->addEventSubscriber($listener);
171
172
        $listener = new \Gedmo\Tree\TreeListener();
173
        $entityManager->getEventManager()->addEventSubscriber($listener);
174
175
        $listener = new \Gedmo\Sortable\SortableListener();
176
        $entityManager->getEventManager()->addEventSubscriber($listener);
177
        $connection = $entityManager->getConnection();
178
$connection->executeQuery('set sql_mode=""');
179
        $this->setConnection($connection);
180
        $this->setManager($entityManager);
181
    }
182
183
    /**
184
     * Escape MySQL wildchars _ and % in LIKE search
185
     * @param string $text            The string to escape
186
     *
187
     * @return string           The escaped string
188
     */
189
    public static function escape_sql_wildcards($text)
190
    {
191
        $text = api_preg_replace("/_/", "\_", $text);
192
        $text = api_preg_replace("/%/", "\%", $text);
193
194
        return $text;
195
    }
196
197
    /**
198
     * Escapes a string to insert into the database as text
199
     *
200
     * @param string $string
201
     *
202
     * @return string
203
     */
204
    public static function escape_string($string)
205
    {
206
        $string = self::getManager()->getConnection()->quote($string);
207
208
        return trim($string, "'");
209
    }
210
211
    /**
212
     * Gets the array from a SQL result (as returned by Database::query)
213
     *
214
     * @param Statement $result
215
     * @param string    $option Optional: "ASSOC","NUM" or "BOTH"
216
     *
217
     * @return array|mixed
218
     */
219
    public static function fetch_array(Statement $result, $option = 'BOTH')
220
    {
221
        if ($result === false) {
222
            return array();
223
        }
224
225
        return $result->fetch(self::customOptionToDoctrineOption($option));
226
    }
227
228
    /**
229
     * Gets an associative array from a SQL result (as returned by Database::query).
230
     *
231
     * @param Statement $result
232
     *
233
     * @return array
234
     */
235
    public static function fetch_assoc(Statement $result)
236
    {
237
        return $result->fetch(PDO::FETCH_ASSOC);
238
    }
239
240
    /**
241
     * Gets the next row of the result of the SQL query
242
     * (as returned by Database::query) in an object form
243
     *
244
     * @param Statement $result
245
     *
246
     * @return mixed
247
     */
248
    public static function fetch_object(Statement $result)
249
    {
250
        return $result->fetch(PDO::FETCH_OBJ);
251
    }
252
253
    /**
254
     * Gets the array from a SQL result (as returned by Database::query)
255
     * help achieving database independence
256
     *
257
     * @param Statement $result
258
     *
259
     * @return mixed
260
     */
261
    public static function fetch_row(Statement $result)
262
    {
263
        return $result->fetch(PDO::FETCH_NUM);
264
    }
265
266
    /**
267
     * Frees all the memory associated with the provided result identifier.
268
     * @return bool     Returns TRUE on success or FALSE on failure.
269
     * Notes: Use this method if you are concerned about how much memory is being used for queries that return large result sets.
270
     * Anyway, all associated result memory is automatically freed at the end of the script's execution.
271
     */
272
    public static function free_result(Statement $result)
273
    {
274
        $result->closeCursor();
275
    }
276
277
    /**
278
     * Gets the ID of the last item inserted into the database
279
     *
280
     * @return string
281
     */
282
    public static function insert_id()
283
    {
284
        return self::getManager()->getConnection()->lastInsertId();
285
    }
286
287
    /**
288
     * @param Statement $result
289
     *
290
     * @return int
291
     */
292
    public static function num_rows(Statement $result)
293
    {
294
        return $result->rowCount();
295
    }
296
297
    /**
298
     * Acts as the relative *_result() function of most DB drivers and fetches a
299
     * specific line and a field
300
     *
301
     * @param Statement $resource
302
     * @param int       $row
303
     * @param string    $field
304
     *
305
     * @return mixed
306
     */
307
    public static function result(Statement $resource, $row, $field = '')
308
    {
309
        if ($resource->rowCount() > 0) {
310
            $result = $resource->fetchAll(PDO::FETCH_BOTH);
311
312
            return $result[$row][$field];
313
        }
314
    }
315
316
    /**
317
     * @param string $query
318
     *
319
     * @return Statement
320
     *
321
     * @throws \Doctrine\DBAL\DBALException
322
     */
323
    public static function query($query)
324
    {
325
        $connection = self::getManager()->getConnection();
326
327
        if (api_get_setting('server_type') == 'test') {
328
            $result = $connection->executeQuery($query);
329
        } else {
330
            try {
331
                $result = $connection->executeQuery($query);
332
            } catch (Exception $e) {
333
                error_log($e->getMessage());
334
                api_not_allowed(false, get_lang('GeneralError'));
335
336
                exit;
337
            }
338
        }
339
340
        return $result;
341
    }
342
343
    /**
344
     * @param string $option
345
     *
346
     * @return int
347
     */
348
    public static function customOptionToDoctrineOption($option)
349
    {
350
        switch ($option) {
351
            case 'ASSOC':
352
                return PDO::FETCH_ASSOC;
353
                break;
354
            case 'NUM':
355
                return PDO::FETCH_NUM;
356
                break;
357
            case 'BOTH':
358
            default:
359
                return PDO::FETCH_BOTH;
360
                break;
361
        }
362
    }
363
364
    /**
365
     * Stores a query result into an array.
366
     *
367
     * @author Olivier Brouckaert
368
     * @param  Statement $result - the return value of the query
369
     * @param  string $option BOTH, ASSOC, or NUM
370
     *
371
     * @return array - the value returned by the query
372
     */
373
    public static function store_result(Statement $result, $option = 'BOTH')
374
    {
375
        return $result->fetchAll(self::customOptionToDoctrineOption($option));
376
    }
377
378
    /**
379
     * Database insert
380
     * @param string    $table_name
381
     * @param array     $attributes
382
     * @param bool      $show_query
383
     *
384
     * @return bool|int
385
     */
386
    public static function insert($table_name, $attributes, $show_query = false)
387
    {
388
        if (empty($attributes) || empty($table_name)) {
389
            return false;
390
        }
391
392
        $params = array_keys($attributes);
393
394
        if (!empty($params)) {
395
            $sql = 'INSERT INTO '.$table_name.' ('.implode(',', $params).')
396
                    VALUES (:'.implode(', :' ,$params).')';
397
398
            $statement = self::getManager()->getConnection()->prepare($sql);
399
            $result = $statement->execute($attributes);
400
401
            if ($show_query) {
402
                var_dump($sql);
1 ignored issue
show
Security Debugging Code introduced by
var_dump($sql); looks like debug code. Are you sure you do not want to remove it? This might expose sensitive data.
Loading history...
403
                error_log($sql);
404
            }
405
406
            if ($result) {
407
                return self::getManager()->getConnection()->lastInsertId();
408
            }
409
        }
410
411
        return false;
412
    }
413
414
    /**
415
     * @param string $table_name use Database::get_main_table
416
     * @param array $attributes Values to updates
417
     * Example: $params['name'] = 'Julio'; $params['lastname'] = 'Montoya';
418
     * @param array $where_conditions where conditions i.e array('id = ?' =>'4')
419
     * @param bool $show_query
420
     *
421
     * @return bool|int
422
     */
423
    public static function update(
424
        $table_name,
425
        $attributes,
426
        $where_conditions = array(),
427
        $show_query = false
428
    ) {
429
        if (!empty($table_name) && !empty($attributes)) {
430
            $update_sql = '';
431
            //Cleaning attributes
432
            $count = 1;
433
434
            foreach ($attributes as $key => $value) {
435
                $update_sql .= "$key = :$key ";
436
                if ($count < count($attributes)) {
437
                    $update_sql.=', ';
438
                }
439
                $count++;
440
            }
441
442
            if (!empty($update_sql)) {
443
                //Parsing and cleaning the where conditions
444
                $where_return = self::parse_where_conditions($where_conditions);
445
446
                $sql = "UPDATE $table_name SET $update_sql $where_return ";
447
448
                $statement = self::getManager()->getConnection()->prepare($sql);
449
                $result = $statement->execute($attributes);
450
451
                if ($show_query) {
452
                    var_dump($sql);
1 ignored issue
show
Security Debugging Code introduced by
var_dump($sql); looks like debug code. Are you sure you do not want to remove it? This might expose sensitive data.
Loading history...
453
                }
454
455
                if ($result) {
456
457
                    return $statement->rowCount();
458
                }
459
            }
460
        }
461
462
        return false;
463
    }
464
465
    /**
466
     * Experimental useful database finder
467
     * @todo lot of stuff to do here
468
     * @todo known issues, it doesn't work when using LIKE conditions
469
     * @example array('where'=> array('course_code LIKE "?%"'))
470
     * @example array('where'=> array('type = ? AND category = ?' => array('setting', 'Plugins'))
471
     * @example array('where'=> array('name = "Julio" AND lastname = "montoya"'))
472
     */
473
    public static function select($columns, $table_name, $conditions = array(), $type_result = 'all', $option = 'ASSOC')
474
    {
475
        $conditions = self::parse_conditions($conditions);
476
477
        //@todo we could do a describe here to check the columns ...
478
        if (is_array($columns)) {
479
            $clean_columns = implode(',', $columns);
480
        } else {
481
            if ($columns == '*') {
482
                $clean_columns = '*';
483
            } else {
484
                $clean_columns = (string)$columns;
485
            }
486
        }
487
488
        $sql    = "SELECT $clean_columns FROM $table_name $conditions";
489
        $result = self::query($sql);
490
        $array = array();
491
492
        if ($type_result == 'all') {
493
            while ($row = self::fetch_array($result, $option)) {
494
                if (isset($row['id'])) {
495
                    $array[$row['id']] = $row;
496
                } else {
497
                    $array[] = $row;
498
                }
499
            }
500
        } else {
501
            $array = self::fetch_array($result, $option);
502
        }
503
504
        return $array;
505
    }
506
507
    /**
508
     * Parses WHERE/ORDER conditions i.e array('where'=>array('id = ?' =>'4'), 'order'=>'id DESC'))
509
     * @todo known issues, it doesn't work when using
510
     * LIKE conditions example: array('where'=>array('course_code LIKE "?%"'))
511
     * @param   array $conditions
512
     */
513
    public static function parse_conditions($conditions)
514
    {
515
        if (empty($conditions)) {
516
            return '';
517
        }
518
        $return_value = $where_return = '';
519
        foreach ($conditions as $type_condition => $condition_data) {
520
            if ($condition_data == false) {
521
                continue;
522
            }
523
            $type_condition = strtolower($type_condition);
524
            switch ($type_condition) {
525
                case 'where':
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
526
527
                    foreach ($condition_data as $condition => $value_array) {
528
                        if (is_array($value_array)) {
529
                            $clean_values = array();
530
                            foreach($value_array as $item) {
531
                                $item = Database::escape_string($item);
532
                                $clean_values[]= $item;
533
                            }
534
                        } else {
535
                            $value_array = Database::escape_string($value_array);
536
                            $clean_values = $value_array;
537
                        }
538
539
                        if (!empty($condition) && $clean_values != '') {
540
                            $condition = str_replace('%',"'@percentage@'", $condition); //replace "%"
541
                            $condition = str_replace("'?'","%s", $condition);
542
                            $condition = str_replace("?","%s", $condition);
543
544
                            $condition = str_replace("@%s@","@-@", $condition);
545
                            $condition = str_replace("%s","'%s'", $condition);
546
                            $condition = str_replace("@-@","@%s@", $condition);
547
548
                            // Treat conditions as string
549
                            $condition = vsprintf($condition, $clean_values);
550
                            $condition = str_replace('@percentage@','%', $condition); //replace "%"
551
                            $where_return .= $condition;
552
                        }
553
                    }
554
555
                    if (!empty($where_return)) {
556
                        $return_value = " WHERE $where_return" ;
557
                    }
558
                    break;
559
                case 'order':
560
                    $order_array = $condition_data;
561
562
                    if (!empty($order_array)) {
563
                        // 'order' => 'id desc, name desc'
564
                        $order_array = self::escape_string($order_array, null, false);
565
                        $new_order_array = explode(',', $order_array);
566
                        $temp_value = array();
567
568
                        foreach($new_order_array as $element) {
569
                            $element = explode(' ', $element);
570
                            $element = array_filter($element);
571
                            $element = array_values($element);
572
573
                            if (!empty($element[1])) {
574
                                $element[1] = strtolower($element[1]);
575
                                $order = 'DESC';
576
                                if (in_array($element[1], array('desc', 'asc'))) {
577
                                    $order = $element[1];
578
                                }
579
                                $temp_value[]= $element[0].' '.$order.' ';
580
                            } else {
581
                                //by default DESC
582
                                $temp_value[]= $element[0].' DESC ';
583
                            }
584
                        }
585
                        if (!empty($temp_value)) {
586
                            $return_value .= ' ORDER BY '.implode(', ', $temp_value);
587
                        } else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
588
                            //$return_value .= '';
589
                        }
590
                    }
591
                    break;
592
                case 'limit':
593
                    $limit_array = explode(',', $condition_data);
594
                    if (!empty($limit_array)) {
595
                        if (count($limit_array) > 1) {
596
                            $return_value .= ' LIMIT '.intval($limit_array[0]).' , '.intval($limit_array[1]);
597
                        }  else {
598
                            $return_value .= ' LIMIT '.intval($limit_array[0]);
599
                        }
600
                    }
601
                    break;
602
            }
603
        }
604
605
        return $return_value;
606
    }
607
608
    /**
609
     * @param array $conditions
610
     *
611
     * @return string
612
     */
613
    public static function parse_where_conditions($conditions)
614
    {
615
        return self::parse_conditions(array('where' => $conditions));
616
    }
617
618
    /**
619
     * @param string $table_name
620
     * @param array  $where_conditions
621
     * @param bool   $show_query
622
     *
623
     * @return int
624
     */
625
    public static function delete($table_name, $where_conditions, $show_query = false)
626
    {
627
        $where_return = self::parse_where_conditions($where_conditions);
628
        $sql    = "DELETE FROM $table_name $where_return ";
629
        if ($show_query) { echo $sql; echo '<br />'; }
630
        $result = self::query($sql);
631
        $affected_rows = self::affected_rows($result);
632
        //@todo should return affected_rows for
633
        return $affected_rows;
634
    }
635
636
    /**
637
     * Get Doctrine configuration
638
     * @param string $path
639
     *
640
     * @return \Doctrine\ORM\Configuration
641
     */
642
    public static function getDoctrineConfig($path)
643
    {
644
        $isDevMode = false;
645
        $isSimpleMode = false; // related to annotations @Entity
646
        $cache = null;
647
        $path = !empty($path) ? $path : api_get_path(SYS_PATH);
648
649
        $paths = array(
650
            $path.'src/Chamilo/CoreBundle/Entity',
651
            $path.'src/Chamilo/UserBundle/Entity',
652
            $path.'src/Chamilo/CourseBundle/Entity'
653
        );
654
655
        $proxyDir = $path.'app/cache/';
656
657
        return \Doctrine\ORM\Tools\Setup::createAnnotationMetadataConfiguration(
658
            $paths,
659
            $isDevMode,
660
            $proxyDir,
661
            $cache,
662
            $isSimpleMode
663
        );
664
    }
665
666
    /**
667
     * @param string $table
668
     *
669
     * @return bool
670
     */
671
    public static function tableExists($table)
672
    {
673
        return self::getManager()->getConnection()->getSchemaManager()->tablesExist($table);
674
    }
675
676
    /**
677
     * @param $table
678
     * @return \Doctrine\DBAL\Schema\Column[]
679
     */
680
    public static function listTableColumns($table) 
681
    {
682
        return self::getManager()->getConnection()->getSchemaManager()->listTableColumns($table);
683
    }
684
}
685