Completed
Push — master ( 3e6bf3...5edc96 )
by Konstantinos
04:41
created

Database   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 290
Duplicated Lines 10 %

Coupling/Cohesion

Components 2
Dependencies 3

Test Coverage

Coverage 77.78%

Importance

Changes 3
Bugs 0 Features 2
Metric Value
wmc 31
c 3
b 0
f 2
lcom 2
cbo 3
dl 29
loc 290
ccs 98
cts 126
cp 0.7778
rs 9.8

13 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 23 4
A __destruct() 0 4 1
A closeConnection() 0 4 1
A isConnected() 0 4 1
A getInsertId() 0 4 1
A execute() 15 15 2
A query() 14 14 2
A startTransaction() 0 4 1
A commit() 0 4 1
A rollback() 0 4 1
A finishTransaction() 0 4 1
A error() 0 17 3
A __sleep() 0 4 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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 PDO
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
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
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 1
            }
58 1
        }
59
60 1
        try {
61
            // TODO: Persist
62 1
            $this->dbc = new PDO(
63
                'mysql:host=' . $host . ';dbname=' . $dbName . ';charset=utf8',
64
                $user,
65
                $password,
66
                array(
67 1
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
68 1
                )
69
            );
70
        } catch (PDOException $e) {
71
            $this->logger->addAlert($e->getMessage());
72
            throw new Exception($e->getMessage(), $e->getCode());
73
        }
74
    }
75
76
    /**
77
     * Destroy this connection to the database
78
     */
79
    public function __destruct()
80
    {
81
        $this->closeConnection();
82
    }
83
84
    /**
85 39
     * Get an instance of the Database object
86
     *
87 39
     * This should be the main way to acquire access to the database
88 1
     *
89 1
     * @todo Move this to the Service class
90
     *
91 1
     * @return Database The Database object
92
     */
93 1
    public static function getInstance()
94 1
    {
95 1
        if (!self::$Database) {
96 1
            if (Service::getEnvironment() == 'test') {
97 1
                if (!Service::getParameter('bzion.testing.enabled')) {
98 1
                    throw new Exception('You have to specify a MySQL database for testing in the bzion.testing section of your configuration file.');
99 1
                }
100
101
                self::$Database = new self(
102
                    Service::getParameter('bzion.testing.host'),
103
                    Service::getParameter('bzion.testing.username'),
104
                    Service::getParameter('bzion.testing.password'),
105
                    Service::getParameter('bzion.testing.database')
106
                );
107 1
            } else {
108
                self::$Database = new self(
109 39
                    Service::getParameter('bzion.mysql.host'),
110
                    Service::getParameter('bzion.mysql.username'),
111
                    Service::getParameter('bzion.mysql.password'),
112
                    Service::getParameter('bzion.mysql.database')
113
                );
114
            }
115
        }
116
117
        return self::$Database;
118
    }
119
120
    /**
121
     * Close the current connection to the MySQL database
122
     */
123
    public function closeConnection()
124
    {
125
        @mysqli_close($this->dbc);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
126
    }
127
128
    /**
129
     * Tests whether or not the connection to the database is still active
130
     * @todo Make this work for PDO, or deprecate it if not needed
131
     * @return bool True if the connection is active
132
     */
133 39
    public function isConnected()
134
    {
135 39
        return true;
136
    }
137
138
    /**
139
     * Get the unique row ID of the last row that was inserted
140
     * @return int The ID of the row
141
     */
142
    public function getInsertId()
143
    {
144
        return $this->last_id;
145
    }
146
147
    /**
148
     * Prepares and executes a MySQL prepared INSERT/DELETE/UPDATE statement. <em>Second two parameter is optional when using this function to execute a query with no placeholders.</em>
149
     *
150
     * @param  string      $queryText The prepared SQL statement that will be executed
151
     * @param  mixed|array $params    (Optional) The array of values that will be binded to the prepared statement
152
     * @return array       Returns an array of the values received from the query
153
     */
154 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...
155
    {
156
        if (!is_array($params)) {
157
            $params = array($params);
158
        }
159
160 39
        $debug = new DatabaseQuery($queryText, $params);
161
162 39
        $query = $this->doQuery($queryText, $params);
163 39
        $return = $query->rowCount();
164 39
165
        $debug->finish($return);
166 39
167
        return $return;
168 39
    }
169
170 39
    /**
171
     * Prepares and executes a MySQL prepared SELECT statement. <em>Second two parameter is optional when using this function to execute a query with no placeholders.</em>
172 39
     *
173
     * @param  string      $queryText The prepared SQL statement that will be executed
174
     * @param  mixed|array $params    (Optional) The array of values that will be binded to the prepared statement
175
     * @return array       Returns an array of the values received from the query
176
     */
177 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...
178
    {
179
        if (!is_array($params)) {
180
            $params = array($params);
181
        }
182 39
183
        $debug = new DatabaseQuery($queryText, $params);
184 39
185 39
        $return = $this->doQuery($queryText, $params)->fetchAll();
186 39
187 39
        $debug->finish($return);
188 39
189 39
        return $return;
190
    }
191 39
192 39
    /**
193 39
     * Perform a query
194 39
     * @param  string      $queryText The prepared SQL statement that will be executed
195
     * @param  null|array  $params    (Optional) The array of values that will be binded to the prepared statement
196 39
     *
197 39
     * @return PDOStatement The PDO statement
198 39
     */
199
    private function doQuery($queryText, $params = null)
200 39
    {
201 39
        try {
202 39
            $query = $this->dbc->prepare($queryText);
203 39
204
            if ($params !== null) {
205 39
                $i = 1;
206 39
                foreach ($params as $name => $param) {
207 39
                    // Guess parameter type
208 39
                    if (is_bool($param)) {
209 39
                        $type = PDO::PARAM_BOOL;
210 39
                    } elseif (is_int($param)) {
211 39
                        $type = PDO::PARAM_INT;
212
                    } else {
213 39
                        $type = PDO::PARAM_STR;
214 39
                    }
215 39
216 39
                    if (is_string($name)) {
217
                        $query->bindValue($name, $param, $type);
218 39
                    } else {
219 39
                        $query->bindValue($i++, $param, $type);
220 39
                    }
221
                }
222 39
            }
223 39
224 39
            $query->execute();
225
            $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...
226 39
227 39
            return $query;
228 39
        } catch (PDOException $e) {
229
            $this->error($e->getMessage(), $e->getCode(), $e);
230 39
        }
231 39
    }
232 39
233 39
    /**
234 39
     * Start a MySQL transaction
235
     */
236 39
    public function startTransaction()
237 39
    {
238
        $this->dbc->beginTransaction();
239 39
    }
240 39
241 39
    /**
242
     * Commit the stored queries (usable only if a transaction has been started)
243 39
     *
244 1
     * This does not show an error if there are no queries to commit
245
     */
246
    public function commit()
247
    {
248 39
        $this->dbc->commit();
249 39
    }
250
251 39
    /**
252 39
     * Cancel all pending queries (does not finish the transaction
253
     */
254
    public function rollback()
255
    {
256 39
        $this->dbc->rollBack();
257
    }
258
259
    /**
260 39
     * Commit all pending queries and finalise the transaction
261
     */
262
    public function finishTransaction()
263 39
    {
264
        $this->dbc->commit();
265
    }
266
267
    /**
268
     * Uses monolog to log an error message
269
     *
270 1
     * @param string $error The error string
271
     * @param int    $id    The error ID
272 1
     *
273 1
     * @throws Exception
274
     */
275
    public function error($error, $id = null, $previous = null)
276
    {
277
        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
        $context = array();
283
        if ($id !== null) {
284
            $context['id'] = $id;
285
        }
286
287
        $id = 5;
288
289
        $this->logger->addError($error, $context);
290
        throw new Exception($error, $id, $previous);
291
    }
292
293
    /**
294
     * Serialize the object
295
     *
296 1
     * Prevents PDO from being erroneously serialized
297
     *
298 1
     * @return array The list of properties that should be serialized
299 1
     */
300 1
    public function __sleep()
301
    {
302
        return array('last_id');
303
    }
304
}
305