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:
Complex classes like DatabaseMssql often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use DatabaseMssql, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
31 | class DatabaseMssql extends DatabaseBase { |
||
32 | protected $mInsertId = null; |
||
33 | protected $mLastResult = null; |
||
34 | protected $mAffectedRows = null; |
||
35 | protected $mSubqueryId = 0; |
||
36 | protected $mScrollableCursor = true; |
||
37 | protected $mPrepareStatements = true; |
||
38 | protected $mBinaryColumnCache = null; |
||
39 | protected $mBitColumnCache = null; |
||
40 | protected $mIgnoreDupKeyErrors = false; |
||
41 | protected $mIgnoreErrors = []; |
||
42 | |||
43 | protected $mPort; |
||
44 | |||
45 | public function implicitGroupby() { |
||
48 | |||
49 | public function implicitOrderby() { |
||
52 | |||
53 | public function unionSupportsOrderAndLimit() { |
||
56 | |||
57 | /** |
||
58 | * Usually aborts on failure |
||
59 | * @param string $server |
||
60 | * @param string $user |
||
61 | * @param string $password |
||
62 | * @param string $dbName |
||
63 | * @throws DBConnectionError |
||
64 | * @return bool|resource|null |
||
65 | */ |
||
66 | public function open( $server, $user, $password, $dbName ) { |
||
115 | |||
116 | /** |
||
117 | * Closes a database connection, if it is open |
||
118 | * Returns success, true if already closed |
||
119 | * @return bool |
||
120 | */ |
||
121 | protected function closeConnection() { |
||
124 | |||
125 | /** |
||
126 | * @param bool|MssqlResultWrapper|resource $result |
||
127 | * @return bool|MssqlResultWrapper |
||
128 | */ |
||
129 | protected function resultObject( $result ) { |
||
141 | |||
142 | /** |
||
143 | * @param string $sql |
||
144 | * @return bool|MssqlResult |
||
145 | * @throws DBUnexpectedError |
||
146 | */ |
||
147 | protected function doQuery( $sql ) { |
||
221 | |||
222 | public function freeResult( $res ) { |
||
229 | |||
230 | /** |
||
231 | * @param MssqlResultWrapper $res |
||
232 | * @return stdClass |
||
233 | */ |
||
234 | public function fetchObject( $res ) { |
||
238 | |||
239 | /** |
||
240 | * @param MssqlResultWrapper $res |
||
241 | * @return array |
||
242 | */ |
||
243 | public function fetchRow( $res ) { |
||
246 | |||
247 | /** |
||
248 | * @param mixed $res |
||
249 | * @return int |
||
250 | */ |
||
251 | public function numRows( $res ) { |
||
266 | |||
267 | /** |
||
268 | * @param mixed $res |
||
269 | * @return int |
||
270 | */ |
||
271 | public function numFields( $res ) { |
||
278 | |||
279 | /** |
||
280 | * @param mixed $res |
||
281 | * @param int $n |
||
282 | * @return int |
||
283 | */ |
||
284 | public function fieldName( $res, $n ) { |
||
291 | |||
292 | /** |
||
293 | * This must be called after nextSequenceVal |
||
294 | * @return int|null |
||
295 | */ |
||
296 | public function insertId() { |
||
299 | |||
300 | /** |
||
301 | * @param MssqlResultWrapper $res |
||
302 | * @param int $row |
||
303 | * @return bool |
||
304 | */ |
||
305 | public function dataSeek( $res, $row ) { |
||
308 | |||
309 | /** |
||
310 | * @return string |
||
311 | */ |
||
312 | public function lastError() { |
||
325 | |||
326 | /** |
||
327 | * @param array $err |
||
328 | * @return string |
||
329 | */ |
||
330 | private function formatError( $err ) { |
||
333 | |||
334 | /** |
||
335 | * @return string |
||
336 | */ |
||
337 | public function lastErrno() { |
||
345 | |||
346 | /** |
||
347 | * @return int |
||
348 | */ |
||
349 | public function affectedRows() { |
||
352 | |||
353 | /** |
||
354 | * SELECT wrapper |
||
355 | * |
||
356 | * @param mixed $table Array or string, table name(s) (prefix auto-added) |
||
357 | * @param mixed $vars Array or string, field name(s) to be retrieved |
||
358 | * @param mixed $conds Array or string, condition(s) for WHERE |
||
359 | * @param string $fname Calling function name (use __METHOD__) for logs/profiling |
||
360 | * @param array $options Associative array of options (e.g. |
||
361 | * [ 'GROUP BY' => 'page_title' ]), see Database::makeSelectOptions |
||
362 | * code for list of supported stuff |
||
363 | * @param array $join_conds Associative array of table join conditions |
||
364 | * (optional) (e.g. [ 'page' => [ 'LEFT JOIN','page_latest=rev_id' ] ] |
||
365 | * @return mixed Database result resource (feed to Database::fetchObject |
||
366 | * or whatever), or false on failure |
||
367 | * @throws DBQueryError |
||
368 | * @throws DBUnexpectedError |
||
369 | * @throws Exception |
||
370 | */ |
||
371 | public function select( $table, $vars, $conds = '', $fname = __METHOD__, |
||
409 | |||
410 | /** |
||
411 | * SELECT wrapper |
||
412 | * |
||
413 | * @param mixed $table Array or string, table name(s) (prefix auto-added) |
||
414 | * @param mixed $vars Array or string, field name(s) to be retrieved |
||
415 | * @param mixed $conds Array or string, condition(s) for WHERE |
||
416 | * @param string $fname Calling function name (use __METHOD__) for logs/profiling |
||
417 | * @param array $options Associative array of options (e.g. [ 'GROUP BY' => 'page_title' ]), |
||
418 | * see Database::makeSelectOptions code for list of supported stuff |
||
419 | * @param array $join_conds Associative array of table join conditions (optional) |
||
420 | * (e.g. [ 'page' => [ 'LEFT JOIN','page_latest=rev_id' ] ] |
||
421 | * @return string The SQL text |
||
422 | */ |
||
423 | public function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__, |
||
454 | |||
455 | View Code Duplication | public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, |
|
467 | |||
468 | View Code Duplication | public function delete( $table, $conds, $fname = __METHOD__ ) { |
|
478 | |||
479 | /** |
||
480 | * Estimate rows in dataset |
||
481 | * Returns estimated count, based on SHOWPLAN_ALL output |
||
482 | * This is not necessarily an accurate estimate, so use sparingly |
||
483 | * Returns -1 if count cannot be found |
||
484 | * Takes same arguments as Database::select() |
||
485 | * @param string $table |
||
486 | * @param string $vars |
||
487 | * @param string $conds |
||
488 | * @param string $fname |
||
489 | * @param array $options |
||
490 | * @return int |
||
491 | */ |
||
492 | View Code Duplication | public function estimateRowCount( $table, $vars = '*', $conds = '', |
|
511 | |||
512 | /** |
||
513 | * Returns information about an index |
||
514 | * If errors are explicitly ignored, returns NULL on failure |
||
515 | * @param string $table |
||
516 | * @param string $index |
||
517 | * @param string $fname |
||
518 | * @return array|bool|null |
||
519 | */ |
||
520 | public function indexInfo( $table, $index, $fname = __METHOD__ ) { |
||
552 | |||
553 | /** |
||
554 | * INSERT wrapper, inserts an array into a table |
||
555 | * |
||
556 | * $arrToInsert may be a single associative array, or an array of these with numeric keys, for |
||
557 | * multi-row insert. |
||
558 | * |
||
559 | * Usually aborts on failure |
||
560 | * If errors are explicitly ignored, returns success |
||
561 | * @param string $table |
||
562 | * @param array $arrToInsert |
||
563 | * @param string $fname |
||
564 | * @param array $options |
||
565 | * @return bool |
||
566 | * @throws Exception |
||
567 | */ |
||
568 | public function insert( $table, $arrToInsert, $fname = __METHOD__, $options = [] ) { |
||
697 | |||
698 | /** |
||
699 | * INSERT SELECT wrapper |
||
700 | * $varMap must be an associative array of the form [ 'dest1' => 'source1', ... ] |
||
701 | * Source items may be literals rather than field names, but strings should |
||
702 | * be quoted with Database::addQuotes(). |
||
703 | * @param string $destTable |
||
704 | * @param array|string $srcTable May be an array of tables. |
||
705 | * @param array $varMap |
||
706 | * @param array $conds May be "*" to copy the whole table. |
||
707 | * @param string $fname |
||
708 | * @param array $insertOptions |
||
709 | * @param array $selectOptions |
||
710 | * @return null|ResultWrapper |
||
711 | * @throws Exception |
||
712 | */ |
||
713 | public function nativeInsertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__, |
||
735 | |||
736 | /** |
||
737 | * UPDATE wrapper. Takes a condition array and a SET array. |
||
738 | * |
||
739 | * @param string $table Name of the table to UPDATE. This will be passed through |
||
740 | * Database::tableName(). |
||
741 | * |
||
742 | * @param array $values An array of values to SET. For each array element, |
||
743 | * the key gives the field name, and the value gives the data |
||
744 | * to set that field to. The data will be quoted by |
||
745 | * Database::addQuotes(). |
||
746 | * |
||
747 | * @param array $conds An array of conditions (WHERE). See |
||
748 | * Database::select() for the details of the format of |
||
749 | * condition arrays. Use '*' to update all rows. |
||
750 | * |
||
751 | * @param string $fname The function name of the caller (from __METHOD__), |
||
752 | * for logging and profiling. |
||
753 | * |
||
754 | * @param array $options An array of UPDATE options, can be: |
||
755 | * - IGNORE: Ignore unique key conflicts |
||
756 | * - LOW_PRIORITY: MySQL-specific, see MySQL manual. |
||
757 | * @return bool |
||
758 | * @throws DBUnexpectedError |
||
759 | * @throws Exception |
||
760 | */ |
||
761 | function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) { |
||
782 | |||
783 | /** |
||
784 | * Makes an encoded list of strings from an array |
||
785 | * @param array $a Containing the data |
||
786 | * @param int $mode Constant |
||
787 | * - LIST_COMMA: comma separated, no field names |
||
788 | * - LIST_AND: ANDed WHERE clause (without the WHERE). See |
||
789 | * the documentation for $conds in Database::select(). |
||
790 | * - LIST_OR: ORed WHERE clause (without the WHERE) |
||
791 | * - LIST_SET: comma separated with field names, like a SET clause |
||
792 | * - LIST_NAMES: comma separated field names |
||
793 | * @param array $binaryColumns Contains a list of column names that are binary types |
||
794 | * This is a custom parameter only present for MS SQL. |
||
795 | * |
||
796 | * @throws DBUnexpectedError |
||
797 | * @return string |
||
798 | */ |
||
799 | public function makeList( $a, $mode = LIST_COMMA, $binaryColumns = [] ) { |
||
826 | |||
827 | /** |
||
828 | * @param string $table |
||
829 | * @param string $field |
||
830 | * @return int Returns the size of a text field, or -1 for "unlimited" |
||
831 | */ |
||
832 | public function textFieldSize( $table, $field ) { |
||
845 | |||
846 | /** |
||
847 | * Construct a LIMIT query with optional offset |
||
848 | * This is used for query pages |
||
849 | * |
||
850 | * @param string $sql SQL query we will append the limit too |
||
851 | * @param int $limit The SQL limit |
||
852 | * @param bool|int $offset The SQL offset (default false) |
||
853 | * @return array|string |
||
854 | * @throws DBUnexpectedError |
||
855 | */ |
||
856 | public function limitResult( $sql, $limit, $offset = false ) { |
||
900 | |||
901 | /** |
||
902 | * If there is a limit clause, parse it, strip it, and pass the remaining |
||
903 | * SQL through limitResult() with the appropriate parameters. Not the |
||
904 | * prettiest solution, but better than building a whole new parser. This |
||
905 | * exists becase there are still too many extensions that don't use dynamic |
||
906 | * sql generation. |
||
907 | * |
||
908 | * @param string $sql |
||
909 | * @return array|mixed|string |
||
910 | */ |
||
911 | public function LimitToTopN( $sql ) { |
||
926 | |||
927 | /** |
||
928 | * @return string Wikitext of a link to the server software's web site |
||
929 | */ |
||
930 | public function getSoftwareLink() { |
||
933 | |||
934 | /** |
||
935 | * @return string Version information from the database |
||
936 | */ |
||
937 | public function getServerVersion() { |
||
946 | |||
947 | /** |
||
948 | * @param string $table |
||
949 | * @param string $fname |
||
950 | * @return bool |
||
951 | */ |
||
952 | public function tableExists( $table, $fname = __METHOD__ ) { |
||
976 | |||
977 | /** |
||
978 | * Query whether a given column exists in the mediawiki schema |
||
979 | * @param string $table |
||
980 | * @param string $field |
||
981 | * @param string $fname |
||
982 | * @return bool |
||
983 | */ |
||
984 | public function fieldExists( $table, $field, $fname = __METHOD__ ) { |
||
1002 | |||
1003 | public function fieldInfo( $table, $field ) { |
||
1022 | |||
1023 | /** |
||
1024 | * Begin a transaction, committing any previously open transaction |
||
1025 | * @param string $fname |
||
1026 | */ |
||
1027 | protected function doBegin( $fname = __METHOD__ ) { |
||
1031 | |||
1032 | /** |
||
1033 | * End a transaction |
||
1034 | * @param string $fname |
||
1035 | */ |
||
1036 | protected function doCommit( $fname = __METHOD__ ) { |
||
1040 | |||
1041 | /** |
||
1042 | * Rollback a transaction. |
||
1043 | * No-op on non-transactional databases. |
||
1044 | * @param string $fname |
||
1045 | */ |
||
1046 | protected function doRollback( $fname = __METHOD__ ) { |
||
1050 | |||
1051 | /** |
||
1052 | * Escapes a identifier for use inm SQL. |
||
1053 | * Throws an exception if it is invalid. |
||
1054 | * Reference: http://msdn.microsoft.com/en-us/library/aa224033%28v=SQL.80%29.aspx |
||
1055 | * @param string $identifier |
||
1056 | * @throws InvalidArgumentException |
||
1057 | * @return string |
||
1058 | */ |
||
1059 | private function escapeIdentifier( $identifier ) { |
||
1076 | |||
1077 | /** |
||
1078 | * @param string $s |
||
1079 | * @return string |
||
1080 | */ |
||
1081 | public function strencode( $s ) { |
||
1086 | |||
1087 | /** |
||
1088 | * @param string|int|null|bool|Blob $s |
||
1089 | * @return string|int |
||
1090 | */ |
||
1091 | public function addQuotes( $s ) { |
||
1106 | |||
1107 | /** |
||
1108 | * @param string $s |
||
1109 | * @return string |
||
1110 | */ |
||
1111 | public function addIdentifierQuotes( $s ) { |
||
1115 | |||
1116 | /** |
||
1117 | * @param string $name |
||
1118 | * @return bool |
||
1119 | */ |
||
1120 | public function isQuotedIdentifier( $name ) { |
||
1123 | |||
1124 | /** |
||
1125 | * MS SQL supports more pattern operators than other databases (ex: [,],^) |
||
1126 | * |
||
1127 | * @param string $s |
||
1128 | * @return string |
||
1129 | */ |
||
1130 | protected function escapeLikeInternal( $s ) { |
||
1133 | |||
1134 | /** |
||
1135 | * MS SQL requires specifying the escape character used in a LIKE query |
||
1136 | * or using Square brackets to surround characters that are to be escaped |
||
1137 | * https://msdn.microsoft.com/en-us/library/ms179859.aspx |
||
1138 | * Here we take the Specify-Escape-Character approach since it's less |
||
1139 | * invasive, renders a query that is closer to other DB's and better at |
||
1140 | * handling square bracket escaping |
||
1141 | * |
||
1142 | * @return string Fully built LIKE statement |
||
1143 | */ |
||
1144 | View Code Duplication | public function buildLike() { |
|
1152 | |||
1153 | /** |
||
1154 | * @param string $db |
||
1155 | * @return bool |
||
1156 | */ |
||
1157 | public function selectDB( $db ) { |
||
1166 | |||
1167 | /** |
||
1168 | * @param array $options An associative array of options to be turned into |
||
1169 | * an SQL query, valid keys are listed in the function. |
||
1170 | * @return array |
||
1171 | */ |
||
1172 | public function makeSelectOptions( $options ) { |
||
1199 | |||
1200 | /** |
||
1201 | * Get the type of the DBMS, as it appears in $wgDBtype. |
||
1202 | * @return string |
||
1203 | */ |
||
1204 | public function getType() { |
||
1207 | |||
1208 | /** |
||
1209 | * @param array $stringList |
||
1210 | * @return string |
||
1211 | */ |
||
1212 | public function buildConcat( $stringList ) { |
||
1215 | |||
1216 | /** |
||
1217 | * Build a GROUP_CONCAT or equivalent statement for a query. |
||
1218 | * MS SQL doesn't have GROUP_CONCAT so we emulate it with other stuff (and boy is it nasty) |
||
1219 | * |
||
1220 | * This is useful for combining a field for several rows into a single string. |
||
1221 | * NULL values will not appear in the output, duplicated values will appear, |
||
1222 | * and the resulting delimiter-separated values have no defined sort order. |
||
1223 | * Code using the results may need to use the PHP unique() or sort() methods. |
||
1224 | * |
||
1225 | * @param string $delim Glue to bind the results together |
||
1226 | * @param string|array $table Table name |
||
1227 | * @param string $field Field name |
||
1228 | * @param string|array $conds Conditions |
||
1229 | * @param string|array $join_conds Join conditions |
||
1230 | * @return string SQL text |
||
1231 | * @since 1.23 |
||
1232 | */ |
||
1233 | public function buildGroupConcatField( $delim, $table, $field, $conds = '', |
||
1247 | |||
1248 | /** |
||
1249 | * Returns an associative array for fields that are of type varbinary, binary, or image |
||
1250 | * $table can be either a raw table name or passed through tableName() first |
||
1251 | * @param string $table |
||
1252 | * @return array |
||
1253 | */ |
||
1254 | View Code Duplication | private function getBinaryColumns( $table ) { |
|
1266 | |||
1267 | /** |
||
1268 | * @param string $table |
||
1269 | * @return array |
||
1270 | */ |
||
1271 | View Code Duplication | private function getBitColumns( $table ) { |
|
1283 | |||
1284 | private function populateColumnCaches() { |
||
1302 | |||
1303 | /** |
||
1304 | * @param string $name |
||
1305 | * @param string $format |
||
1306 | * @return string |
||
1307 | */ |
||
1308 | function tableName( $name, $format = 'quoted' ) { |
||
1309 | # Replace reserved words with better ones |
||
1310 | switch ( $name ) { |
||
1311 | case 'user': |
||
1312 | return $this->realTableName( 'mwuser', $format ); |
||
1313 | default: |
||
1314 | return $this->realTableName( $name, $format ); |
||
1315 | } |
||
1316 | } |
||
1317 | |||
1318 | /** |
||
1319 | * call this instead of tableName() in the updater when renaming tables |
||
1320 | * @param string $name |
||
1321 | * @param string $format One of quoted, raw, or split |
||
1322 | * @return string |
||
1323 | */ |
||
1324 | function realTableName( $name, $format = 'quoted' ) { |
||
1336 | |||
1337 | /** |
||
1338 | * Delete a table |
||
1339 | * @param string $tableName |
||
1340 | * @param string $fName |
||
1341 | * @return bool|ResultWrapper |
||
1342 | * @since 1.18 |
||
1343 | */ |
||
1344 | View Code Duplication | public function dropTable( $tableName, $fName = __METHOD__ ) { |
|
1354 | |||
1355 | /** |
||
1356 | * Called in the installer and updater. |
||
1357 | * Probably doesn't need to be called anywhere else in the codebase. |
||
1358 | * @param bool|null $value |
||
1359 | * @return bool|null |
||
1360 | */ |
||
1361 | public function prepareStatements( $value = null ) { |
||
1364 | |||
1365 | /** |
||
1366 | * Called in the installer and updater. |
||
1367 | * Probably doesn't need to be called anywhere else in the codebase. |
||
1368 | * @param bool|null $value |
||
1369 | * @return bool|null |
||
1370 | */ |
||
1371 | public function scrollableCursor( $value = null ) { |
||
1374 | |||
1375 | /** |
||
1376 | * Called in the installer and updater. |
||
1377 | * Probably doesn't need to be called anywhere else in the codebase. |
||
1378 | * @param array|null $value |
||
1379 | * @return array|null |
||
1380 | */ |
||
1381 | public function ignoreErrors( array $value = null ) { |
||
1384 | } // end DatabaseMssql class |
||
1385 |
If you define a variable conditionally, it can happen that it is not defined for all execution paths.
Let’s take a look at an example:
In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.
Available Fixes
Check for existence of the variable explicitly:
Define a default value for the variable:
Add a value for the missing path: