allejo /
bzion
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | /** |
||
| 3 | * This file contains functionality related to interacting with the database this CMS uses |
||
| 4 | * |
||
| 5 | * @package BZiON |
||
| 6 | * @license https://github.com/allejo/bzion/blob/master/LICENSE.md GNU General Public License Version 3 |
||
| 7 | */ |
||
| 8 | |||
| 9 | use BZIon\Debug\DatabaseQuery; |
||
| 10 | use Monolog\Logger; |
||
| 11 | |||
| 12 | /** |
||
| 13 | * Database interface class |
||
| 14 | */ |
||
| 15 | class Database |
||
| 16 | { |
||
| 17 | /** |
||
| 18 | * The global database connection object |
||
| 19 | * |
||
| 20 | * @var Database |
||
| 21 | */ |
||
| 22 | private static $Database; |
||
| 23 | |||
| 24 | /** |
||
| 25 | * The database object used inside this class |
||
| 26 | * @var MySQLi |
||
| 27 | */ |
||
| 28 | private $dbc; |
||
| 29 | |||
| 30 | /** |
||
| 31 | * An instance of the logger |
||
| 32 | * @var Logger |
||
| 33 | */ |
||
| 34 | private $logger; |
||
| 35 | |||
| 36 | /** |
||
| 37 | * The id of the last row entered |
||
| 38 | * @var int |
||
| 39 | */ |
||
| 40 | private $last_id; |
||
| 41 | |||
| 42 | /** |
||
| 43 | * Create a new connection to the database |
||
| 44 | * |
||
| 45 | * @param string $host The MySQL host |
||
| 46 | * @param string $user The MySQL user |
||
| 47 | * @param string $password The MySQL password for the user |
||
| 48 | * @param string $dbName The MySQL database name |
||
| 49 | * |
||
| 50 | * @return Database A database object to interact with the database |
||
| 51 | */ |
||
| 52 | 1 | public function __construct($host, $user, $password, $dbName) |
|
| 53 | { |
||
| 54 | 1 | if (Service::getContainer()) { |
|
| 55 | 1 | if ($logger = Service::getContainer()->get('monolog.logger.mysql')) { |
|
| 56 | 1 | $this->logger = $logger; |
|
| 57 | } |
||
| 58 | } |
||
| 59 | |||
| 60 | 1 | $this->dbc = new mysqli($host, $user, $password, $dbName); |
|
| 61 | |||
| 62 | 1 | if ($this->dbc->connect_errno) { |
|
| 63 | $this->logger->addAlert($this->dbc->connect_error); |
||
| 64 | throw new Exception($this->dbc->connect_error, $this->dbc->connect_errno); |
||
| 65 | } |
||
| 66 | |||
| 67 | 1 | $this->dbc->set_charset("utf8"); |
|
| 68 | 1 | } |
|
| 69 | |||
| 70 | /** |
||
| 71 | * Destroy this connection to the database |
||
| 72 | */ |
||
| 73 | public function __destruct() |
||
| 74 | { |
||
| 75 | $this->closeConnection(); |
||
| 76 | } |
||
| 77 | |||
| 78 | /** |
||
| 79 | * Get an instance of the Database object |
||
| 80 | * |
||
| 81 | * This should be the main way to acquire access to the database |
||
| 82 | * |
||
| 83 | * @return Database The Database object |
||
| 84 | */ |
||
| 85 | 39 | public static function getInstance() |
|
| 86 | { |
||
| 87 | 39 | if (!self::$Database) { |
|
| 88 | 1 | if (Service::getEnvironment() == 'test') { |
|
| 89 | 1 | if (!Service::getParameter('bzion.testing.enabled')) { |
|
| 90 | throw new Exception('You have to specify a MySQL database for testing in the bzion.testing section of your configuration file.'); |
||
| 91 | } |
||
| 92 | |||
| 93 | 1 | self::$Database = new self( |
|
| 94 | 1 | Service::getParameter('bzion.testing.host'), |
|
| 95 | 1 | Service::getParameter('bzion.testing.username'), |
|
| 96 | 1 | Service::getParameter('bzion.testing.password'), |
|
| 97 | 1 | Service::getParameter('bzion.testing.database') |
|
| 98 | ); |
||
| 99 | } else { |
||
| 100 | self::$Database = new self( |
||
| 101 | Service::getParameter('bzion.mysql.host'), |
||
| 102 | Service::getParameter('bzion.mysql.username'), |
||
| 103 | Service::getParameter('bzion.mysql.password'), |
||
| 104 | Service::getParameter('bzion.mysql.database') |
||
| 105 | ); |
||
| 106 | } |
||
| 107 | } |
||
| 108 | |||
| 109 | 39 | return self::$Database; |
|
| 110 | } |
||
| 111 | |||
| 112 | /** |
||
| 113 | * Close the current connection to the MySQL database |
||
| 114 | */ |
||
| 115 | public function closeConnection() |
||
| 116 | { |
||
| 117 | @mysqli_close($this->dbc); |
||
| 118 | } |
||
| 119 | |||
| 120 | /** |
||
| 121 | * Tests whether or not the connection to the database is still active |
||
| 122 | * @return bool True if the connection is active |
||
| 123 | */ |
||
| 124 | public function isConnected() |
||
| 125 | { |
||
| 126 | return $this->dbc->ping(); |
||
| 127 | } |
||
| 128 | |||
| 129 | /** |
||
| 130 | * Get the unique row ID of the last row that was inserted |
||
| 131 | * @return int The ID of the row |
||
| 132 | */ |
||
| 133 | 39 | public function getInsertId() |
|
| 134 | { |
||
| 135 | 39 | return $this->last_id; |
|
| 136 | } |
||
| 137 | |||
| 138 | /** |
||
| 139 | * Prepares and executes a MySQL prepared statement. <em>Second two parameters are optional when using this function to execute a query with no placeholders.</em> |
||
| 140 | * |
||
| 141 | * <code> |
||
| 142 | * //the appropriate letters to show what type of variable will be passed |
||
| 143 | * //i - integer |
||
| 144 | * //d - double |
||
| 145 | * //s - string |
||
| 146 | * //b - blob |
||
| 147 | * |
||
| 148 | * $database = new Database(); //create a new database object |
||
| 149 | * |
||
| 150 | * $query = "SELECT * FROM table WHERE id = ?"; //write the prepared statement where ? are placeholders |
||
| 151 | * $params = array("1"); //all the parameters to be binded, in order |
||
| 152 | * $results = $database->query($query, "i", $params); //execute the prepared query |
||
| 153 | * </code> |
||
| 154 | * |
||
| 155 | * @param string $queryText The prepared SQL statement that will be executed |
||
| 156 | * @param bool|string $typeDef (Optional) The types of values that will be passed through the prepared statement. One letter per parameter |
||
| 157 | * @param mixed|array $params (Optional) The array of values that will be binded to the prepared statement |
||
| 158 | * @return mixed Returns an array of the values received from the query or returns false on empty |
||
| 159 | */ |
||
| 160 | 39 | public function query($queryText, $typeDef = false, $params = false) |
|
| 161 | { |
||
| 162 | 39 | if (!is_array($params)) { |
|
| 163 | 1 | $params = array($params); |
|
| 164 | } |
||
| 165 | |||
| 166 | 39 | $debug = new DatabaseQuery($queryText, $typeDef, $params); |
|
|
0 ignored issues
–
show
|
|||
| 167 | |||
| 168 | 39 | $return = $this->doQuery($queryText, $typeDef, $params); |
|
| 169 | |||
| 170 | 39 | $debug->finish($return); |
|
| 171 | |||
| 172 | 39 | return $return; |
|
| 173 | } |
||
| 174 | |||
| 175 | /** |
||
| 176 | * Perform a query |
||
| 177 | * @param string $queryText The prepared SQL statement that will be executed |
||
| 178 | * @param bool|string $typeDef (Optional) The types of values that will be passed through the prepared statement. One letter per parameter |
||
| 179 | * @param bool|array $params (Optional) The array of values that will be binded to the prepared statement |
||
| 180 | * @return mixed Returns an array of the values received from the query or returns false on empty |
||
| 181 | */ |
||
| 182 | 39 | private function doQuery($queryText, $typeDef = false, $params = false) |
|
| 183 | { |
||
| 184 | 39 | $multiQuery = true; |
|
| 185 | 39 | if ($stmt = $this->dbc->prepare($queryText)) { |
|
| 186 | 39 | if (count($params) == count($params, 1)) { |
|
| 187 | 39 | $params = array($params); |
|
| 188 | 39 | $multiQuery = false; |
|
| 189 | } |
||
| 190 | |||
| 191 | 39 | if ($typeDef) { |
|
| 192 | 39 | $bindParams = array(); |
|
| 193 | 39 | $bindParamsReferences = array(); |
|
| 194 | 39 | $bindParams = array_pad($bindParams, (count($params, 1) - count($params)) / count($params), ""); |
|
| 195 | |||
| 196 | 39 | foreach ($bindParams as $key => $value) { |
|
| 197 | 39 | $bindParamsReferences[$key] = &$bindParams[$key]; |
|
| 198 | } |
||
| 199 | |||
| 200 | 39 | array_unshift($bindParamsReferences, $typeDef); |
|
| 201 | 39 | $bindParamsMethod = new ReflectionMethod('mysqli_stmt', 'bind_param'); |
|
| 202 | 39 | $bindParamsMethod->invokeArgs($stmt, $bindParamsReferences); |
|
| 203 | } |
||
| 204 | |||
| 205 | 39 | $result = array(); |
|
| 206 | 39 | foreach ($params as $queryKey => $query) { |
|
| 207 | 39 | if ($typeDef) { |
|
| 208 | 39 | foreach ($bindParams as $paramKey => $value) { |
|
| 209 | 39 | $bindParams[$paramKey] = $query[$paramKey]; |
|
| 210 | } |
||
| 211 | } |
||
| 212 | |||
| 213 | 39 | $queryResult = array(); |
|
| 214 | 39 | if ($stmt->execute()) { |
|
| 215 | 39 | $resultMetaData = $stmt->result_metadata(); |
|
| 216 | 39 | $this->last_id = $stmt->insert_id; |
|
| 217 | |||
| 218 | 39 | if ($resultMetaData) { |
|
| 219 | 39 | $stmtRow = array(); |
|
| 220 | 39 | $rowReferences = array(); |
|
| 221 | |||
| 222 | 39 | while ($field = $resultMetaData->fetch_field()) { |
|
| 223 | 39 | $rowReferences[] = &$stmtRow[$field->name]; |
|
| 224 | } |
||
| 225 | |||
| 226 | 39 | mysqli_free_result($resultMetaData); |
|
| 227 | 39 | $bindResultMethod = new ReflectionMethod('mysqli_stmt', 'bind_result'); |
|
| 228 | 39 | $bindResultMethod->invokeArgs($stmt, $rowReferences); |
|
| 229 | |||
| 230 | 39 | while (mysqli_stmt_fetch($stmt)) { |
|
| 231 | 39 | $row = array(); |
|
| 232 | 39 | foreach ($stmtRow as $key => $value) { |
|
| 233 | 39 | $row[$key] = $value; |
|
| 234 | } |
||
| 235 | |||
| 236 | 39 | $queryResult[] = $row; |
|
| 237 | } |
||
| 238 | |||
| 239 | 39 | mysqli_stmt_free_result($stmt); |
|
| 240 | } else { |
||
| 241 | 39 | $queryResult[] = mysqli_stmt_affected_rows($stmt); |
|
| 242 | } |
||
| 243 | } else { |
||
| 244 | 1 | $this->error($this->dbc->error, $this->dbc->errno); |
|
| 245 | $queryResult[] = false; |
||
| 246 | } |
||
| 247 | |||
| 248 | 39 | $result[$queryKey] = $queryResult; |
|
| 249 | } |
||
| 250 | |||
| 251 | 39 | mysqli_stmt_close($stmt); |
|
| 252 | } else { |
||
| 253 | $result = false; |
||
| 254 | } |
||
| 255 | |||
| 256 | 39 | if ($this->dbc->error) { |
|
| 257 | $this->error($this->dbc->error, $this->dbc->errno); |
||
| 258 | } |
||
| 259 | |||
| 260 | 39 | if ($multiQuery) { |
|
| 261 | return $result; |
||
| 262 | } else { |
||
| 263 | 39 | return $result[0]; |
|
| 264 | } |
||
| 265 | } |
||
| 266 | |||
| 267 | /** |
||
| 268 | * Uses monolog to log an error message |
||
| 269 | * |
||
| 270 | * @param string $error The error string |
||
| 271 | * @param int $id The error ID |
||
| 272 | * |
||
| 273 | * @throws Exception |
||
| 274 | */ |
||
| 275 | 1 | public function error($error, $id = null) |
|
| 276 | { |
||
| 277 | 1 | if (empty($error)) { |
|
| 278 | $error = "Unknown MySQL error - check for warnings generated by PHP"; |
||
| 279 | } |
||
| 280 | |||
| 281 | // Create a context array so that we can log the ID, if provided |
||
| 282 | 1 | $context = array(); |
|
| 283 | 1 | if ($id !== null) { |
|
| 284 | 1 | $context['id'] = $id; |
|
| 285 | } |
||
| 286 | |||
| 287 | 1 | $this->logger->addError($error, $context); |
|
| 288 | 1 | throw new Exception($error, $id); |
|
| 289 | } |
||
| 290 | } |
||
| 291 |
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.