GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — integration (#2604)
by Brendan
05:28
created

Database::fetch()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 25
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 6
eloc 13
c 2
b 0
f 0
nc 6
nop 3
dl 0
loc 25
rs 8.439
1
<?php
2
3
/**
4
 * @package toolkit
5
 */
6
Class Database {
7
8
    /**
9
     * Constant to indicate whether the query is a write operation.
10
     *
11
     * @var integer
12
     */
13
    const __WRITE_OPERATION__ = 0;
14
15
    /**
16
     * Constant to indicate whether the query is a write operation
17
     *
18
     * @var integer
19
     */
20
    const __READ_OPERATION__ = 1;
21
22
    /**
23
     * An instance of the current PDO object
24
     * @var PDO
25
     */
26
    public $conn = null;
27
28
    /**
29
     * Sets the current `$_log` to be an empty array
30
     *
31
     * @var array
32
     */
33
    public $log = array();
34
35
    /**
36
     * The number of queries this class has executed, defaults to 0.
37
     *
38
     * @var integer
39
     */
40
    protected $_query_count = 0;
41
42
    /**
43
     * The table prefix for this connection. Queries to be written using
44
     * a `tbl_table_name` syntax, where `tbl_` will be replaced by this
45
     * variable. By default it is `sym_` but it configured in the configuration
46
     *
47
     * @var string
48
     */
49
    protected $_prefix = 'sym_';
50
51
    /**
52
     * Whether query caching is enabled or not. By default this set
53
     * to true which will use SQL_CACHE to cache the results of queries
54
     *
55
     * @var boolean
56
     */
57
    protected $_cache = true;
58
59
    /**
60
     * Whether to log this query in the internal `$log`.
61
     * Defaults to true
62
     *
63
     * @var boolean
64
     */
65
    protected $_logging = true;
66
67
    /**
68
     * @var PDOStatement
69
     */
70
    protected $_result = null;
71
72
    /**
73
     * @var array
74
     */
75
    protected $_lastResult = array();
76
77
    /**
78
     * @var string
79
     */
80
    protected $_lastQuery = null;
81
82
    /**
83
     * @var string
84
     */
85
    protected $_lastQueryHash = null;
86
87
    /**
88
     * Creates a new Database object given an associative array of configuration
89
     * parameters in `$config`. If `$config` contains a key, `pdo` then this
90
     * `Database` instance will use that PDO connection. Otherwise, `$config`
91
     * should include `driver`, `host`, `port`, `user`, `password` and an optional
92
     * array of PDO options in `options`.
93
     *
94
     * @param array $config
95
     */
96
    public function __construct(array $config = array())
97
    {
98
        // If we have an existing PDO object
99
        if(isset($config['pdo'])) {
100
            $this->conn = $config['pdo'];
101
        }
102
        // Otherwise create a PDO object from parameters
103
        else {
104
            $this->connect(sprintf('%s:dbname=%s;host=%s;port=%d;charset=%s', $config['driver'], $config['db'], $config['host'], $config['port'], $config['charset']),
105
                $config['user'],
106
                $config['password'],
107
                $config['options']
108
            );
109
        }
110
    }
111
112
    /**
113
     * Magic function that will flush the MySQL log and close the MySQL
114
     * connection when the MySQL class is removed or destroyed.
115
     *
116
     * @link http://php.net/manual/en/language.oop5.decon.php
117
     */
118
    public function __destruct()
119
    {
120
        unset($this->conn);
121
        $this->flush();
122
    }
123
124
    /**
125
     * Resets the `result`, `lastResult`, `lastQuery` and lastQueryHash properties to `null`.
126
     * Called on each query and when the class is destroyed.
127
     */
128
    public function flush()
129
    {
130
        $this->_result = null;
131
        $this->_lastResult = array();
132
        $this->_lastQuery = null;
133
        $this->_lastQueryHash = null;
134
    }
135
136
    /**
137
     * Creates a PDO connection to the desired database given the parameters.
138
     * This will also set the error mode to be exceptions (handled by this class)
139
     *
140
     * @link http://www.php.net/manual/en/pdo.drivers.php
141
     * @param string $dsn
142
     * @param string $username
143
     * @param string $password
144
     * @param array $options
145
     * @return boolean
146
     */
147
    public function connect($dsn = null, $username = null, $password = null, array $options = array())
148
    {
149
        try {
150
            $this->conn = new PDO($dsn, $username, $password, $options);
151
            $this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
152
        }
153
        catch (PDOException $ex) {
154
            $this->error($ex);
155
156
            return false;
157
        }
158
159
        return true;
160
    }
161
162
    /**
163
     * Returns the number of queries that has been executed
164
     *
165
     * @return integer
166
     */
167
    public function queryCount()
168
    {
169
        return $this->_query_count;
170
    }
171
172
    /**
173
     * Sets query caching to true, this will prepend all READ_OPERATION
174
     * queries with SQL_CACHE. Symphony be default enables caching. It
175
     * can be turned off by setting the query_cache parameter to 'off' in the
176
     * Symphony config file.
177
     *
178
     * @link http://dev.mysql.com/doc/refman/5.1/en/query-cache.html
179
     */
180
    public function enableCaching()
181
    {
182
        $this->_cache = true;
183
    }
184
185
    /**
186
     * Sets query caching to false, this will prepend all READ_OPERATION
187
     * queries will SQL_NO_CACHE.
188
     */
189
    public function disableCaching()
190
    {
191
        $this->_cache = false;
192
    }
193
194
    /**
195
     * Returns boolean if query caching is enabled or not
196
     *
197
     * @return boolean
198
     */
199
    public function isCachingEnabled()
200
    {
201
        return $this->_cache;
202
    }
203
204
    /**
205
     * Enables the logging of queries
206
     */
207
    public function enableLogging()
208
    {
209
        $this->_logging = true;
210
    }
211
212
    /**
213
     * Sets logging to false
214
     */
215
    public function disableLogging()
216
    {
217
        $this->_logging = false;
218
    }
219
220
    /**
221
     * Returns boolean if logging is enabled or not
222
     *
223
     * @return boolean
224
     */
225
    public function isLoggingEnabled()
226
    {
227
        return $this->_logging;
228
    }
229
230
    /**
231
     * Symphony uses a prefix for all it's database tables so it can live peacefully
232
     * on the same database as other applications. By default this is sym_, but it
233
     * can be changed when Symphony is installed.
234
     *
235
     * @param string $prefix
236
     *  The table prefix for Symphony, by default this is sym_
237
     */
238
    public function setPrefix($prefix)
239
    {
240
        $this->_prefix = $prefix;
241
    }
242
243
    /**
244
     * Returns the prefix used by Symphony for this Database instance.
245
     *
246
     * @since Symphony 2.4
247
     * @return string
248
     */
249
    public function getPrefix()
250
    {
251
        return $this->_prefix;
252
    }
253
254
    /**
255
     * Given a string, replace the default table prefixes with the
256
     * table prefix for this database instance.
257
     *
258
     * @param string $query
259
     * @return string
260
     */
261
    public function replaceTablePrefix($query)
262
    {
263
        if($this->_prefix !== 'tbl_'){
264
            $query = preg_replace('/tbl_(\S+?)([\s\.,]|$)/', $this->_prefix .'\\1\\2', $query);
265
        }
266
267
        return $query;
268
    }
269
270
    /**
271
     * Function looks over a query to determine if it's a READ or WRITE operation.
272
     * WRITE operations are any query that starts with: SET, CREATE, INSERT, REPLACE
273
     * ALTER, DELETE, UPDATE, OPTIMIZE, TRUNCATE or DROP. All other queries are considered
274
     * READ operations
275
     *
276
     * @param string $query
277
     * @return integer
278
     */
279
    public function determineQueryType($query)
280
    {
281
        return (preg_match('/^(set|create|insert|replace|alter|delete|update|optimize|truncate|drop)/i', $query)
282
            ? self::__WRITE_OPERATION__
283
            : self::__READ_OPERATION__);
284
    }
285
286
    /**
287
     * @param array $values
288
     * @return string
289
     */
290
    public static function addPlaceholders(array $values = array())
291
    {
292
        $placeholders = null;
293
        if(!empty($values)) {
294
            $placeholders = str_repeat('?,', count($values) - 1) . '?';
295
        }
296
297
        return $placeholders;
298
    }
299
300
    /**
301
     * Given a query that has been prepared and an array of values to subsitute
302
     * into the query, the function will return the result.
303
     *
304
     * @param string $query
305
     * @param array $values
306
     * @return PDOStatement
307
     */
308
    public function insert($query, array $values)
309
    {
310
        $result = $this->q($query, $values);
311
312
        return $result;
313
    }
314
315
    /**
316
     * Returns the last insert ID from the previous query. This is
317
     * the value from an auto_increment field.
318
     *
319
     * @return integer
320
     *  The last interested row's ID
321
     */
322
    public function getInsertID()
323
    {
324
        return $this->conn->lastInsertId();
325
    }
326
327
    /**
328
     * Given a query that has been prepared and an array of values to subsitute
329
     * into the query, the function will return the result.
330
     *
331
     * @param string $query
332
     * @param array $values
333
     * @return PDOStatement
334
     */
335
    public function update($query, array $values)
336
    {
337
        $result = $this->q($query, $values);
338
339
        return $result;
340
    }
341
342
    /**
343
     * Given a query that has been prepared and an array of values to subsitute
344
     * into the query, the function will return the result.
345
     *
346
     * @param string $query
347
     * @param array $values
348
     * @return PDOStatement
349
     */
350
    public function delete($query, array $values)
351
    {
352
        $result = $this->q($query, $values);
353
354
        return $result;
355
    }
356
357
    /**
358
     * Given a query that has been prepared and an array of optional
359
     * parameters, this function will return the results of a query
360
     * as an array.
361
     *
362
     * @param string $query
363
     * @param array $params
364
     *   - `fetch-type` = 'ASSOC'/'OBJECT'
365
     *          Return result as array or an object
366
     *   - `index` = 'column_name'
367
     *          The name of a column in the table to use it's value to index
368
     *          the result by. If this is omitted (and it is by default), an
369
     *          array of associative arrays is returned, with the key being the
370
     *          column names
371
     *      `offset` = `0`
372
     *          An integer representing the row to return
373
     * @param array $values
374
     * @return array
375
     */
376
    public function fetch($query = null, array $params = array(), array $values = array())
377
    {
378
        if(!is_null($query)) {
379
            $params['fetch-type'] = 'ASSOC';
380
            $this->query($query, $params, $values);
381
        }
382
383
        if(empty($this->_lastResult)) {
384
            return array();
385
        }
386
387
        $result = $this->_lastResult;
388
389
        if(isset($params['index']) && isset($result[0][$params['index']])){
390
            $n = array();
391
392
            foreach($result as $ii) {
393
                $n[$ii[$params['index']]] = $ii;
394
            }
395
396
            $result = $n;
397
        }
398
399
        return $result;
400
    }
401
402
    /**
403
     * Takes an SQL string and creates a prepared statement.
404
     *
405
     * @link http://php.net/manual/en/pdo.prepare.php
406
     * @param string $query
407
     * @param array $driver_options
408
     *  This array holds one or more key=>value pairs to set attribute values
409
     *  for the DatabaseStatement object that this method returns.
410
     * @return DatabaseStatement
411
     */
412
    public function prepare($query, array $driver_options = array())
413
    {
414
        $query = $this->replaceTablePrefix($query);
415
416
        return new DatabaseStatement($this, $this->conn->prepare($query, $driver_options));
417
    }
418
419
    /**
420
     * Create a transaction.
421
     *
422
     * @return DatabaseTransaction
423
     */
424
    public function transaction()
425
    {
426
        return new DatabaseTransaction($this->conn);
427
    }
428
429
    /**
430
     * Given a query that has been prepared and an array of values to subsitute
431
     * into the query, the function will return the result. Unlike `insert` and
432
     * `update`, this function is a bit of a catch all and will be able to populate
433
     * `$this->_lastResult` with an array of data. This function is usually used
434
     * via `fetch()`.
435
     *
436
     * @see fetch()
437
     * @param string $query
438
     * @param array $params
439
     *  Supports `fetch-type` and `offset` parameters for the moment
440
     * @param array $values
441
     *  If the `$query` has placeholders, this parameter will include the data
442
     *  to subsitute into the placeholders
443
     * @return boolean
444
     */
445
    public function query($query, array $params = array(), array $values = array())
446
    {
447
        if(empty($query)) return false;
448
449
        $query_type = $this->determineQueryType(trim($query));
450
451
        if($query_type === self::__READ_OPERATION__ && !preg_match('/^\s*SELECT\s+SQL(_NO)?_CACHE/i', $query)){
452
            if($this->isCachingEnabled()) {
453
                $query = preg_replace('/^\s*SELECT\s+/i', 'SELECT SQL_CACHE ', $query);
454
            }
455
            else {
456
                $query = preg_replace('/^\s*SELECT\s+/i', 'SELECT SQL_NO_CACHE ', $query);
457
            }
458
        }
459
460
        $this->q($query, $values, false);
461
462
        if($this->_result instanceof PDOStatement && $query_type === self::__READ_OPERATION__) {
463
            if($params['fetch-type'] === "ASSOC") {
464
                if(isset($params['offset'])) {
465
                    while ($row = $this->_result->fetch(PDO::FETCH_ASSOC, PDO::FETCH_ORI_ABS, $params['offset'])) {
466
                        $this->_lastResult = $row;
0 ignored issues
show
Documentation Bug introduced by
It seems like $row of type * is incompatible with the declared type array of property $_lastResult.

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..

Loading history...
467
                    }
468
                }
469
                else {
470
                    while ($row = $this->_result->fetch(PDO::FETCH_ASSOC)) {
471
                        $this->_lastResult[] = $row;
472
                    }
473
                }
474
            }
475
            else if($params['fetch-type'] === 'OBJECT') {
476
                while ($row = $this->_result->fetchObject()) {
477
                    $this->_lastResult[] = $row;
478
                }
479
            }
480
        }
481
482
        return true;
483
    }
484
485
    /**
486
     * This function is actually responsible for subsituting the values into
487
     * the query and logging the query for basic profiling/debugging.
488
     *
489
     * @param string $query
490
     * @param array $values
491
     * @param boolean $close
492
     *  If true, once the query is executed, the cursor will be closed,
493
     *  otherwise it'll be left open for further manipulation (as done by
494
     *  `query()`). Defaults to `true`
495
     * @return PDOStatement
496
     */
497
    private function q($query, $values, $close = true)
498
    {
499
        if(empty($query)) return false;
500
501
        // Default query preparation
502
        $query = $this->replaceTablePrefix(trim($query));
503
504
        if($this->_logging) {
505
            $start = precision_timer();
506
        }
507
508
        // Cleanup from last time, set some logging parameters
509
        $this->flush();
510
        $this->_lastQuery = $query;
511
        $this->_lastQueryHash = md5($query.$start);
0 ignored issues
show
Bug introduced by
The variable $start does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
512
513
        // Execute
514
        try {
515
            $this->_result = $this->conn->prepare($query);
516
            $this->_result->execute($values);
517
            $this->_query_count++;
518
        }
519
        catch (PDOException $ex) {
520
            $this->error($ex);
521
        }
522
523
        if($this->conn->errorCode() !== PDO::ERR_NONE) {
524
            $this->error();
525
526
            return false;
527
        }
528
        else if($this->_result instanceof PDOStatement) {
529
            $this->_lastQuery = $this->_result->queryString;
530
531
            if($close) {
532
                $this->_result->closeCursor();
533
            }
534
        }
535
536
        if($this->_logging) {
537
            $this->logQuery($query, $this->_lastQueryHash, precision_timer('stop', $start));
538
        }
539
540
        return $this->_result;
541
    }
542
543
    /**
544
     * Given an Exception, or called when an error occurs, this function will
545
     * fire the `QueryExecutionError` delegate and then raise a `DatabaseException`
546
     *
547
     * @uses QueryExecutionError
548
     * @throws DatabaseException
549
     * @param Exception $ex
550
     *  The exception thrown while doing something with the Database
551
     * @return void
552
     */
553
    public function error(Exception $ex = null)
554
    {
555
        if(isset($ex)) {
556
            $msg = $ex->getMessage();
557
            $errornum = $ex->getCode();
558
        }
559
        else {
560
            $error = $this->conn->errorInfo();
561
            $msg = $error[2];
562
            $errornum = $error[0];
563
        }
564
565
        /**
566
         * After a query execution has failed this delegate will provide the query,
567
         * query hash, error message and the error number.
568
         *
569
         * Note that this function only starts logging once the `ExtensionManager`
570
         * is available, which means it will not fire for the first couple of
571
         * queries that set the character set.
572
         *
573
         * @since Symphony 2.3
574
         * @delegate QueryExecutionError
575
         * @param string $context
576
         * '/frontend/' or '/backend/'
577
         * @param string $query
578
         *  The query that has just been executed
579
         * @param string $query_hash
580
         *  The hash used by Symphony to uniquely identify this query
581
         * @param string $msg
582
         *  The error message provided by MySQL which includes information on why the execution failed
583
         * @param integer $num
584
         *  The error number that corresponds with the MySQL error message
585
         */
586
        if(Symphony::ExtensionManager() instanceof ExtensionManager) {
587
            Symphony::ExtensionManager()->notifyMembers('QueryExecutionError', class_exists('Administration') ? '/backend/' : '/frontend/', array(
588
                'query' => $this->_lastQuery,
589
                'query_hash' => $this->_lastQueryHash,
590
                'msg' => $msg,
591
                'num' => $errornum
592
            ));
593
        }
594
595
        throw new DatabaseException(__('Database Error (%1$s): %2$s in query: %3$s', array($errornum, $msg, $this->_lastQuery)), array(
596
            'msg' => $msg,
597
            'num' => $errornum,
598
            'query' => $this->_lastQuery
599
        ), $ex);
600
    }
601
602
    /**
603
     * Throw a new DatabaseException when given an original exception and a query.
604
     *
605
     * @param Exception $error
606
     * @param string $query
607
     * @param string $query_hash
608
     */
609
    public function throwError(Exception $error, $query, $query_hash)
610
    {
611
        $this->_lastQuery = $query;
612
        $this->_lastQueryHash = $query_hash;
613
614
        $this->error($error);
615
    }
616
617
    /**
618
     * Function is called everytime a query is executed to log it for
619
     * basic profiling/debugging purposes
620
     *
621
     * @uses PostQueryExecution
622
     * @param string $query
623
     * @param string $query_hash
624
     * @param integer $stop
625
     */
626
    public function logQuery($query, $query_hash, $stop)
627
    {
628
        /**
629
         * After a query has successfully executed, that is it was considered
630
         * valid SQL, this delegate will provide the query, the query_hash and
631
         * the execution time of the query.
632
         *
633
         * Note that this function only starts logging once the ExtensionManager
634
         * is available, which means it will not fire for the first couple of
635
         * queries that set the character set.
636
         *
637
         * @since Symphony 2.3
638
         * @delegate PostQueryExecution
639
         * @param string $context
640
         * '/frontend/' or '/backend/'
641
         * @param string $query
642
         *  The query that has just been executed
643
         * @param string $query_hash
644
         *  The hash used by Symphony to uniquely identify this query
645
         * @param float $execution_time
646
         *  The time that it took to run `$query`
647
         */
648
        if(Symphony::ExtensionManager() instanceof ExtensionManager) {
649
            Symphony::ExtensionManager()->notifyMembers('PostQueryExecution', class_exists('Administration') ? '/backend/' : '/frontend/', array(
650
                'query' => $query,
651
                'query_hash' => $query_hash,
652
                'execution_time' => $stop
653
            ));
654
655
            // If the ExceptionHandler is enabled, then the user is authenticated
656
            // or we have a serious issue, so log the query.
657
            if(GenericExceptionHandler::$enabled) {
658
                $this->_log[$query_hash] = array(
0 ignored issues
show
Bug introduced by
The property _log does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
659
                    'query' => $query,
660
                    'query_hash' => $query_hash,
661
                    'execution_time' => $stop
662
                );
663
            }
664
        }
665
666
        // Symphony isn't ready yet. Log internally
667
        else {
668
            $this->_log[$query_hash] = array(
669
                'query' => $query,
670
                'query_hash' => $query_hash,
671
                'execution_time' => $stop
672
            );
673
        }
674
    }
675
676
    /**
677
     * Returns all the log entries by type. There are two valid types,
678
     * error and debug. If no type is given, the entire log is returned,
679
     * otherwise only log messages for that type are returned
680
     *
681
     * @param string $type
682
     * @return array
683
     * An array of associative array's. Log entries of the error type
684
     * return the query the error occurred on and the error number and
685
     * message from MySQL. Log entries of the debug type return the
686
     * the query and the start/stop time to indicate how long it took
687
     * to run
688
     */
689
    public function debug($type = null)
690
    {
691
        if(!$type) return $this->_log;
0 ignored issues
show
Bug Best Practice introduced by
The expression $type of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
692
693
        return ($type === 'error' ? $this->_log['error'] : $this->_log['query']);
694
    }
695
696
    /**
697
     * Returns some basic statistics from the MySQL class about the
698
     * number of queries, the time it took to query and any slow queries.
699
     * A slow query is defined as one that took longer than 0.0999 seconds
700
     * This function is used by the Profile devkit
701
     *
702
     * @return array
703
     *  An associative array with the number of queries, an array of slow
704
     *  queries and the total query time.
705
     */
706
    public function getStatistics()
707
    {
708
        $query_timer = 0.0;
709
        $slow_queries = array();
710
711
        foreach($this->_log as $key => $val) {
712
            $query_timer += $val['execution_time'];
713
            if($val['execution_time'] > 0.0999) $slow_queries[] = $val;
714
        }
715
716
        return array(
717
            'queries' => $this->queryCount(),
718
            'slow-queries' => $slow_queries,
719
            'total-query-time' => number_format($query_timer, 4, '.', '')
720
        );
721
    }
722
}
723