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 |
||
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) |
||
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() |
||
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) |
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) |
|
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(); |
||
239 | 39 | ||
240 | return $query; |
||
241 | 39 | } catch (PDOException $e) { |
|
242 | $this->error($e->getMessage(), $e->getCode(), $e); |
||
243 | } |
||
244 | 1 | } |
|
245 | |||
246 | /** |
||
247 | * Start a MySQL transaction |
||
248 | 39 | */ |
|
249 | public function startTransaction() |
||
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() |
||
263 | 39 | ||
264 | /** |
||
265 | * Cancel all pending queries (does not finish the transaction |
||
266 | */ |
||
267 | public function rollback() |
||
271 | |||
272 | 1 | /** |
|
273 | 1 | * Commit all pending queries and finalise the transaction |
|
274 | */ |
||
275 | public function finishTransaction() |
||
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) |
||
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() |
|
316 | } |
||
317 |
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.