Completed
Push — fm-support ( 624fa6...b0af51 )
by Konstantinos
09:12 queued 04:41
created

Database::closeConnection()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 0
cts 0
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 2
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
     * @todo Move this to the Service class
21
     * @var Database
22
     */
23
    private static $Database;
24
25
    /**
26
     * The database object used inside this class
27
     * @var PDO
28
     */
29
    private $dbc;
30
31
    /**
32
     * An instance of the logger
33
     * @var Logger
34
     */
35
    private $logger;
36
37
    /**
38
     * The id of the last row entered
39
     * @var int
40
     */
41
    private $last_id;
42
43
    /**
44
     * Create a new connection to the database
45
     *
46
     * @param string $host     The MySQL host
47
     * @param string $user     The MySQL user
48
     * @param string $password The MySQL password for the user
49
     * @param string $dbName   The MySQL database name
50
     */
51
    public function __construct($host, $user, $password, $dbName)
52 1
    {
53
        if (Service::getContainer()) {
54 1
            if ($logger = Service::getContainer()->get('monolog.logger.mysql')) {
55 1
                $this->logger = $logger;
56 1
            }
57
        }
58
59
        try {
60 1
            // TODO: Persist
61
            $this->dbc = new PDO(
62 1
                'mysql:host=' . $host . ';dbname=' . $dbName . ';charset=utf8',
63
                $user,
64
                $password,
65
                array(
66
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
67 1
68 1
                    // We are using MySQL, so there is no need to emulate
69
                    // prepared statements for databases that don't support
70
                    // them. This line makes sure all values are returned to PHP
71
                    // from MySQL in the correct type, and they are not all
72
                    // strings.
73
                    PDO::ATTR_EMULATE_PREPARES => false
74
                )
75
            );
76
        } catch (PDOException $e) {
77
            $this->logger->addAlert($e->getMessage());
78
            throw new Exception($e->getMessage(), $e->getCode());
79
        }
80
    }
81
82
    /**
83
     * Destroy this connection to the database
84
     */
85 39
    public function __destruct()
86
    {
87 39
        $this->closeConnection();
88 1
    }
89 1
90
    /**
91
     * Get an instance of the Database object
92
     *
93 1
     * This should be the main way to acquire access to the database
94 1
     *
95 1
     * @todo Move this to the Service class
96 1
     *
97 1
     * @return Database The Database object
98
     */
99
    public static function getInstance()
100
    {
101
        if (!self::$Database) {
102
            if (Service::getEnvironment() == 'test') {
103
                if (!Service::getParameter('bzion.testing.enabled')) {
104
                    throw new Exception('You have to specify a MySQL database for testing in the bzion.testing section of your configuration file.');
105
                }
106
107
                self::$Database = new self(
108
                    Service::getParameter('bzion.testing.host'),
109 39
                    Service::getParameter('bzion.testing.username'),
110
                    Service::getParameter('bzion.testing.password'),
111
                    Service::getParameter('bzion.testing.database')
112
                );
113
            } else {
114
                self::$Database = new self(
115
                    Service::getParameter('bzion.mysql.host'),
116
                    Service::getParameter('bzion.mysql.username'),
117
                    Service::getParameter('bzion.mysql.password'),
118
                    Service::getParameter('bzion.mysql.database')
119
                );
120
            }
121
        }
122
123
        return self::$Database;
124
    }
125
126
    /**
127
     * Close the current connection to the MySQL database
128
     */
129
    public function closeConnection()
130
    {
131
        $this->dbc = null;
132
    }
133 39
134
    /**
135 39
     * Tests whether or not the connection to the database is still active
136
     * @todo Make this work for PDO, or deprecate it if not needed
137
     * @return bool True if the connection is active
138
     */
139
    public function isConnected()
140
    {
141
        return true;
142
    }
143
144
    /**
145
     * Get the unique row ID of the last row that was inserted
146
     * @return int The ID of the row
147
     */
148
    public function getInsertId()
149
    {
150
        return $this->last_id;
151
    }
152
153
    /**
154
     * Prepares and executes a MySQL prepared INSERT/DELETE/UPDATE statement. <em>The second parameter is optional when using this function to execute a query with no placeholders.</em>
155
     *
156
     * @param  string      $queryText The prepared SQL statement that will be executed
157
     * @param  mixed|array $params    (Optional) The array of values that will be binded to the prepared statement
158
     * @return array       Returns an array of the values received from the query
159
     */
160 39 View Code Duplication
    public function execute($queryText, $params = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
161
    {
162 39
        if (!is_array($params)) {
163 39
            $params = array($params);
164
        }
165
166 39
        $debug = new DatabaseQuery($queryText, $params);
167
168 39
        $query = $this->doQuery($queryText, $params);
169
        $return = $query->rowCount();
170 39
171
        $debug->finish($return);
172 39
173
        return $return;
174
    }
175
176
    /**
177
     * Prepares and executes a MySQL prepared SELECT statement. <em>The second parameter is optional when using this function to execute a query with no placeholders.</em>
178
     *
179
     * @param  string      $queryText The prepared SQL statement that will be executed
180
     * @param  mixed|array $params    (Optional) The array of values that will be binded to the prepared statement
181
     * @return array       Returns an array of the values received from the query
182 39
     */
183 View Code Duplication
    public function query($queryText, $params = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
184 39
    {
185 39
        if (!is_array($params)) {
186 39
            $params = array($params);
187 39
        }
188 39
189
        $debug = new DatabaseQuery($queryText, $params);
190
191 39
        $return = $this->doQuery($queryText, $params)->fetchAll();
192 39
193 39
        $debug->finish($return);
194 39
195
        return $return;
196 39
    }
197 39
198
    /**
199
     * Perform a query
200 39
     * @param  string      $queryText The prepared SQL statement that will be executed
201 39
     * @param  null|array  $params    (Optional) The array of values that will be binded to the prepared statement
202 39
     *
203
     * @return PDOStatement The PDO statement
204
     */
205 39
    private function doQuery($queryText, $params = null)
206 39
    {
207 39
        try {
208 39
            $query = $this->dbc->prepare($queryText);
209 39
210
            if ($params !== null) {
211
                $i = 1;
212
                foreach ($params as $name => $param) {
213 39
                    // Guess parameter type
214 39
                    if (is_bool($param)) {
215 39
                        $param = (int) $param;
216 39
                        $type = PDO::PARAM_INT;
217
                    } elseif (is_int($param)) {
218 39
                        $type = PDO::PARAM_INT;
219 39
                    } elseif (is_null($param)) {
220 39
                        $type = PDO::PARAM_NULL;
221
                    } else {
222 39
                        $type = PDO::PARAM_STR;
223 39
                    }
224
225
                    if (is_string($name)&&0) {
226 39
                        $query->bindValue($name, $param, $type);
227 39
                    } else {
228 39
                        $query->bindValue($i++, $param, $type);
229
                    }
230 39
                }
231 39
            }
232 39
233 39
            $result = $query->execute();
234
            if ($result === false) {
235
                $this->error("Unknown error");
236 39
            }
237
238
            $this->last_id = $this->dbc->lastInsertId();
0 ignored issues
show
Documentation Bug introduced by
The property $last_id was declared of type integer, but $this->dbc->lastInsertId() is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
239 39
240
            return $query;
241 39
        } catch (PDOException $e) {
242
            $this->error($e->getMessage(), $e->getCode(), $e);
0 ignored issues
show
Documentation introduced by
$e is of type object<PDOException>, but the function expects a null|object<Throwable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
243
        }
244 1
    }
245
246
    /**
247
     * Start a MySQL transaction
248 39
     */
249
    public function startTransaction()
250
    {
251 39
        $this->dbc->beginTransaction();
252
    }
253
254
    /**
255
     * Commit the stored queries (usable only if a transaction has been started)
256 39
     *
257
     * This does not show an error if there are no queries to commit
258
     */
259
    public function commit()
260 39
    {
261
        $this->dbc->commit();
262
    }
263 39
264
    /**
265
     * Cancel all pending queries (does not finish the transaction
266
     */
267
    public function rollback()
268
    {
269
        $this->dbc->rollBack();
270 1
    }
271
272 1
    /**
273 1
     * Commit all pending queries and finalise the transaction
274
     */
275
    public function finishTransaction()
276
    {
277
        $this->dbc->commit();
278
    }
279
280
    /**
281
     * Uses monolog to log an error message
282
     *
283
     * @param string         $error    The error string
284
     * @param int            $id       The error ID
285
     * @param Throwable|null $previous The exception that caused the error (if any)
286
     *
287
     * @throws Exception
288
     */
289
    public function error($error, $id = null, Throwable $previous = null)
290
    {
291
        if (empty($error)) {
292
            $error = "Unknown MySQL error - check for warnings generated by PHP";
293
        }
294
295
        // Create a context array so that we can log the ID, if provided
296 1
        $context = array();
297
        if ($id !== null) {
298 1
            $context['id'] = $id;
299 1
        }
300 1
301
        $this->logger->addError($error, $context);
302
        throw new Exception($error, (int) $id, $previous);
303
    }
304
305
    /**
306
     * Serialize the object
307
     *
308
     * Prevents PDO from being erroneously serialized
309
     *
310 1
     * @return array The list of properties that should be serialized
311
     */
312 1
    public function __sleep()
313
    {
314
        return array('last_id');
315
    }
316
}
317