Passed
Push — release_2_0 ( 19429a...5c1ce0 )
by Stefan
07:13
created

DBConnection::__construct()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 16
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 12
c 0
b 0
f 0
dl 0
loc 16
rs 8.8333
cc 7
nc 6
nop 1
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 47 and the first side effect is on line 35.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
/*
3
 * *****************************************************************************
4
 * Contributions to this work were made on behalf of the GÉANT project, a 
5
 * project that has received funding from the European Union’s Framework 
6
 * Programme 7 under Grant Agreements No. 238875 (GN3) and No. 605243 (GN3plus),
7
 * Horizon 2020 research and innovation programme under Grant Agreements No. 
8
 * 691567 (GN4-1) and No. 731122 (GN4-2).
9
 * On behalf of the aforementioned projects, GEANT Association is the sole owner
10
 * of the copyright in all material which was developed by a member of the GÉANT
11
 * project. GÉANT Vereniging (Association) is registered with the Chamber of 
12
 * Commerce in Amsterdam with registration number 40535155 and operates in the 
13
 * UK as a branch of GÉANT Vereniging.
14
 * 
15
 * Registered office: Hoekenrode 3, 1102BR Amsterdam, The Netherlands. 
16
 * UK branch address: City House, 126-130 Hills Road, Cambridge CB2 1PQ, UK
17
 *
18
 * License: see the web/copyright.inc.php file in the file structure or
19
 *          <base_url>/copyright.php after deploying the software
20
 */
21
22
/**
23
 * This file contains the DBConnection singleton.
24
 * 
25
 * @author Stefan Winter <[email protected]>
26
 * @author Tomasz Wolniewicz <[email protected]>
27
 * 
28
 * @package Developer
29
 */
30
31
namespace core;
32
33
use \Exception;
34
35
require_once dirname(__DIR__) . "/config/_config.php";
36
37
/**
38
 * This class is a singleton for establishing a connection to the database
39
 *
40
 * @author Stefan Winter <[email protected]>
41
 * @author Tomasz Wolniewicz <[email protected]>
42
 *
43
 * @license see LICENSE file in root directory
44
 *
45
 * @package Developer
46
 */
47
class DBConnection {
48
49
    /**
50
     * This is the actual constructor for the singleton. It creates a database connection if it is not up yet, and returns a handle to the database connection on every call.
51
     * 
52
     * @param string $database the database type to open
53
     * @return DBConnection|array the (only) instance of this class; or all instances of a DB cluster (only for RADIUS auth servers right now)
54
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag in function comment
Loading history...
55
    public static function handle($database) {
56
        $theDb = strtoupper($database);
57
        switch ($theDb) {
58
            case "INST":
59
            case "USER":
60
            case "EXTERNAL":
61
            case "FRONTEND":
62
            case "DIAGNOSTICS":
63
                if (!isset(self::${"instance" . $theDb})) {
64
                    $class = __CLASS__;
65
                    self::${"instance" . $theDb} = new $class($database);
66
                    DBConnection::${"instance" . $theDb}->databaseInstance = $theDb;
67
                }
68
                return self::${"instance" . $theDb};
69
            case "RADIUS":
70
                if (!isset(self::${"instance" . $theDb})) {
71
                    $class = __CLASS__;
72
                    foreach (CONFIG_CONFASSISTANT['DB'] as $name => $oneRadiusAuthDb) {
73
                        $theInstance = new $class($name);
74
                        self::${"instance" . $theDb}[] = $theInstance;
75
                        $theInstance->databaseInstance = $theDb;
76
                    }
77
                }
78
                return self::${"instance" . $theDb};
79
            default:
80
                throw new Exception("This type of database (" . strtoupper($database) . ") is not known!");
81
        }
82
    }
83
84
    /**
85
     * Implemented for safety reasons only. Cloning is forbidden and will tell the user so.
86
     *
87
     * @return void
88
     */
89
    public function __clone() {
90
        trigger_error('Clone is not allowed.', E_USER_ERROR);
91
    }
92
93
    /**
94
     * tells the caller if the database is to be accessed read-only
95
     * @return bool
96
     */
97
    public function isReadOnly() {
98
        return $this->readOnly;
99
    }
100
101
    /**
102
     * executes a query and triggers logging to the SQL audit log if it's not a SELECT
103
     * @param string $querystring  the query to be executed
104
     * @param string $types        for prepared statements, the type list of parameters
105
     * @param mixed  ...$arguments for prepared statements, the parameters
106
     * @return mixed the query result as mysqli_result object; or TRUE on non-return-value statements
107
     * @throws Exception
108
     */
109
    public function exec($querystring, $types = NULL, &...$arguments) {
110
        // log exact query to audit log, if it's not a SELECT
111
        $isMoreThanSelect = FALSE;
112
        if (preg_match("/^SELECT/i", $querystring) == 0 && preg_match("/^DESC/i", $querystring) == 0) {
113
            $isMoreThanSelect = TRUE;
114
            if ($this->readOnly) { // let's not do this.
115
                throw new Exception("This is a read-only DB connection, but this is statement is not a SELECT!");
116
            }
117
        }
118
        // log exact query to debug log, if log level is at 5
119
        $this->loggerInstance->debug(5, "DB ATTEMPT: " . $querystring . "\n");
120
        if ($types !== NULL) {
121
            $this->loggerInstance->debug(5, "Argument type sequence: $types, parameters are: " . print_r($arguments, true));
122
        }
123
124
        if ($this->connection->connect_error) {
125
            throw new Exception("ERROR: Cannot send query to $this->databaseInstance database (no connection, error number" . $this->connection->connect_error . ")!");
126
        }
127
        if ($types === NULL) {
128
            $result = $this->connection->query($querystring);
129
            if ($result === FALSE) {
130
                throw new Exception("DB: Unable to execute simple statement! Error was --> " . $this->connection->error . " <--");
131
            }
132
        } else {
133
            // fancy! prepared statement with dedicated argument list
134
            if (strlen($types) != count($arguments)) {
135
                throw new Exception("DB: Prepared Statement: Number of arguments and the type list length differ!");
136
            }
137
            $statementObject = $this->connection->stmt_init();
138
            if ($statementObject === FALSE) {
139
                throw new Exception("DB: Unable to initialise prepared Statement!");
140
            }
141
            $prepResult = $statementObject->prepare($querystring);
142
            if ($prepResult === FALSE) {
143
                throw new Exception("DB: Unable to prepare statement! Statement was --> $querystring <--, error was --> " . $statementObject->error . " <--.");
144
            }
145
146
            // we have a variable number of arguments packed into the ... array
147
            // but the function needs to be called exactly once, with a series of
148
            // individual arguments, not an array. The voodoo solution is to call
149
            // it via call_user_func_array()
150
151
            $localArray = $arguments;
152
            array_unshift($localArray, $types);
153
            $retval = call_user_func_array([$statementObject, "bind_param"], $localArray);
154
            if ($retval === FALSE) {
155
                throw new Exception("DB: Unable to bind parameters to prepared statement! Argument array was --> " . var_export($localArray, TRUE) . " <--. Error was --> " . $statementObject->error . " <--");
156
            }
157
            $result = $statementObject->execute();
158
            if ($result === FALSE) {
159
                throw new Exception("DB: Unable to execute prepared statement! Error was --> " . $statementObject->error . " <--");
160
            }
161
            $selectResult = $statementObject->get_result();
162
            if ($selectResult !== FALSE) {
163
                $result = $selectResult;
164
            }
165
166
            $statementObject->close();
167
        }
168
169
        // all cases where $result could be FALSE have been caught earlier
170
        if ($this->connection->errno) {
171
            throw new Exception("ERROR: Cannot execute query in $this->databaseInstance database - (hopefully escaped) query was '$querystring', errno was " . $this->connection->errno . "!");
172
        }
173
174
175
        if ($isMoreThanSelect) {
176
            $this->loggerInstance->writeSQLAudit("[DB: " . strtoupper($this->databaseInstance) . "] " . $querystring);
177
            if ($types !== NULL) {
178
                $this->loggerInstance->writeSQLAudit("Argument type sequence: $types, parameters are: " . print_r($arguments, true));
179
            }
180
        }
181
        return $result;
182
    }
183
184
    /**
185
     * Retrieves the last auto-id of an INSERT. Needs to be called immediately after the corresponding exec() call
186
     * @return int the last autoincrement-ID
187
     */
188
    public function lastID() {
189
        return $this->connection->insert_id;
190
    }
191
192
    /**
193
     * Holds the singleton instance reference to USER database
194
     * 
195
     * @var DBConnection 
196
     */
197
    private static $instanceUSER;
198
199
    /**
200
     * Holds the singleton instance reference to INST database
201
     * 
202
     * @var DBConnection 
203
     */
204
    private static $instanceINST;
205
206
    /**
207
     * Holds the singleton instance reference to EXTERNAL database
208
     * 
209
     * @var DBConnection 
210
     */
211
    private static $instanceEXTERNAL;
212
213
    /**
214
     * Holds the singleton instance reference to FRONTEND database
215
     * 
216
     * @var DBConnection 
217
     */
218
    private static $instanceFRONTEND;
219
220
    /**
221
     * Holds the singleton instance reference to DIAGNOSTICS database
222
     * 
223
     * @var DBConnection 
224
     */
225
    private static $instanceDIAGNOSTICS;
226
227
    /**
228
     * Holds an ARRAY of all RADIUS server instances for Silverbullet
229
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @var tag in member variable comment
Loading history...
230
    private static $instanceRADIUS;
231
    
232
    /**
233
     * after instantiation, keep state of which DB *this one* talks to
234
     * 
235
     * @var string which database does this instance talk to
236
     */
237
    private $databaseInstance;
238
239
    /**
240
     * The connection to the DB server
241
     * 
242
     * @var \mysqli
243
     */
244
    private $connection;
245
246
    /**
247
     * @var \core\common\Logging
248
     */
249
    private $loggerInstance;
250
251
    /**
252
     * Keeps state whether we are a readonly DB instance
253
     * @var boolean
254
     */
255
    private $readOnly;
256
257
    /**
258
     * Class constructor. Cannot be called directly; use handle()
259
     * 
260
     * @param string $database the database to open
261
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag in function comment
Loading history...
262
    private function __construct($database) {
263
        $this->loggerInstance = new \core\common\Logging();
264
        $databaseCapitalised = strtoupper($database);
265
        if (isset(CONFIG['DB'][$databaseCapitalised])) {
266
            $this->connection = new \mysqli(CONFIG['DB'][$databaseCapitalised]['host'], CONFIG['DB'][$databaseCapitalised]['user'], CONFIG['DB'][$databaseCapitalised]['pass'], CONFIG['DB'][$databaseCapitalised]['db']);
267
            $this->readOnly = CONFIG['DB'][$databaseCapitalised]['readonly'];
268
        } else { // one of the RADIUS DBs
269
            $this->connection = new \mysqli(CONFIG_CONFASSISTANT['DB'][$databaseCapitalised]['host'], CONFIG_CONFASSISTANT['DB'][$databaseCapitalised]['user'], CONFIG_CONFASSISTANT['DB'][$databaseCapitalised]['pass'], CONFIG_CONFASSISTANT['DB'][$databaseCapitalised]['db']);
270
            $this->readOnly = CONFIG_CONFASSISTANT['DB'][$databaseCapitalised]['readonly'];
271
        }
272
        if ($this->connection->connect_error) {
273
            throw new Exception("ERROR: Unable to connect to $database database! This is a fatal error, giving up (error number " . $this->connection->connect_errno . ").");
274
        }
275
276
        if ($databaseCapitalised == "EXTERNAL" && CONFIG_CONFASSISTANT['CONSORTIUM']['name'] == "eduroam" && isset(CONFIG_CONFASSISTANT['CONSORTIUM']['deployment-voodoo']) && CONFIG_CONFASSISTANT['CONSORTIUM']['deployment-voodoo'] == "Operations Team") {
277
            $this->connection->query("SET NAMES 'latin1'");
278
        }
279
        
280
    }
281
282
}
283