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 | * @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 |
||
|
|
|||
| 51 | */ |
||
| 52 | 1 | public function __construct($host, $user, $password, $dbName) |
|
| 75 | |||
| 76 | /** |
||
| 77 | * Destroy this connection to the database |
||
| 78 | */ |
||
| 79 | public function __destruct() |
||
| 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() |
||
| 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() |
|
| 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() |
||
| 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) |
|
| 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) |
|
| 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(); |
||
| 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() |
|
| 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() |
||
| 250 | |||
| 251 | 39 | /** |
|
| 252 | 39 | * Cancel all pending queries (does not finish the transaction |
|
| 253 | */ |
||
| 254 | public function rollback() |
||
| 258 | |||
| 259 | /** |
||
| 260 | 39 | * Commit all pending queries and finalise the transaction |
|
| 261 | */ |
||
| 262 | public function finishTransaction() |
||
| 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) |
||
| 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() |
|
| 304 | } |
||
| 305 |
Adding a
@returnannotation 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.