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 WordPoints_DB_Query 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 WordPoints_DB_Query, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
19 | class WordPoints_DB_Query { |
||
20 | |||
21 | /** |
||
22 | * The name of the table this query class is for. |
||
23 | * |
||
24 | * This should be the full name of the table, including the prefix. You will |
||
25 | * therefore likely need to define it from inside your constructor. |
||
26 | * |
||
27 | * @since 2.1.0 |
||
28 | * |
||
29 | * @var string |
||
30 | */ |
||
31 | protected $table_name; |
||
32 | |||
33 | /** |
||
34 | * The columns in the table being queried. |
||
35 | * |
||
36 | * The keys are the names of the columns. The values are arrays that support the |
||
37 | * following keys: |
||
38 | * |
||
39 | * - format (required) The format (%s, %d, or %f) to use when passing the values |
||
40 | * for this format to $wpdb->prepare(). |
||
41 | * - values (optional) An array of values that this column can have. Any values |
||
42 | * that aren't in this list will be discarded from a query. |
||
43 | * - unsigned (optional) Whether the value is unsigned. If this is true, values |
||
44 | * for this column will be rejected if they are not positive. |
||
45 | * - is_date (optional) Whether this is a DATETIME field. If so date queries will |
||
46 | * be supported. |
||
47 | * |
||
48 | * For each column in this array, the following query args are supported: |
||
49 | * |
||
50 | * - "{$column}" A single value that this column should have. |
||
51 | * - "{$column}__compare" How to compare the above value to the value in the DB. |
||
52 | * The default is '='. |
||
53 | * - "{$column}__in" An array of values that this column may have. |
||
54 | * - "{$column}__not_in" An array of values that this column may not have. |
||
55 | * |
||
56 | * Where {$column} is the name of the column. |
||
57 | * |
||
58 | * The "{$column}" query arg takes precedence over the "{$column}__in" and |
||
59 | * "{$column}__not_in" query args. |
||
60 | * |
||
61 | * However, if the column specifies that is_date is true, then the above are not |
||
62 | * supported, and the following are offered instead: |
||
63 | * |
||
64 | * - "{$column}_query" Arguments to pass to a WP_Date_Query. |
||
65 | * |
||
66 | * @since 2.1.0 |
||
67 | * |
||
68 | * @var array[] |
||
69 | */ |
||
70 | protected $columns = array(); |
||
71 | |||
72 | /** |
||
73 | * The slug of the meta type. |
||
74 | * |
||
75 | * If this is defined, the 'meta_query', 'meta_key', 'meta_value', |
||
76 | * 'meta_compare', and 'meta_type' args are supported, and will be passed to |
||
77 | * WP_Meta_Query. |
||
78 | * |
||
79 | * @since 2.1.0 |
||
80 | * |
||
81 | * @var string |
||
82 | */ |
||
83 | protected $meta_type; |
||
84 | |||
85 | /** |
||
86 | * The default values for the query args. |
||
87 | * |
||
88 | * You can override this entirely if needed, or just modify it in your |
||
89 | * constructor before calling parent::__construct(). |
||
90 | * |
||
91 | * @since 2.1.0 |
||
92 | * |
||
93 | * @var array |
||
94 | */ |
||
95 | protected $defaults = array( |
||
96 | 'start' => 0, |
||
97 | 'order' => 'DESC', |
||
98 | ); |
||
99 | |||
100 | /** |
||
101 | * A list of args that are deprecated and information about their replacements. |
||
102 | * |
||
103 | * Each element of the array should contain the following key-value pairs: |
||
104 | * |
||
105 | * - 'replacement' - The replacement arg. |
||
106 | * - 'version' - The version in which this arg was deprecated. |
||
107 | * - 'class' - The class this arg is from. Usually you will just want to |
||
108 | * use `__CLASS__` here. |
||
109 | * |
||
110 | * @since 2.3.0 |
||
111 | * |
||
112 | * @var string[][] |
||
113 | */ |
||
114 | protected $deprecated_args = array(); |
||
115 | |||
116 | /** |
||
117 | * The query arguments. |
||
118 | * |
||
119 | * @since 2.1.0 |
||
120 | * |
||
121 | * @type array $args |
||
122 | */ |
||
123 | protected $args = array(); |
||
124 | |||
125 | /** |
||
126 | * Whether the query is ready for execution, or still needs to be prepared. |
||
127 | * |
||
128 | * @since 2.1.0 |
||
129 | * |
||
130 | * @type bool $is_query_ready |
||
131 | */ |
||
132 | protected $is_query_ready = false; |
||
133 | |||
134 | /** |
||
135 | * The SELECT statement for the query. |
||
136 | * |
||
137 | * @since 2.1.0 |
||
138 | * |
||
139 | * @type string $select |
||
140 | */ |
||
141 | protected $select; |
||
142 | |||
143 | /** |
||
144 | * The SELECT COUNT statement for a count query. |
||
145 | * |
||
146 | * @since 2.1.0 |
||
147 | * |
||
148 | * @type string $select_count |
||
149 | */ |
||
150 | protected $select_count = 'SELECT COUNT(*)'; |
||
151 | |||
152 | /** |
||
153 | * The JOIN query with the meta table. |
||
154 | * |
||
155 | * @since 2.1.0 |
||
156 | * |
||
157 | * @type string $meta_join |
||
158 | */ |
||
159 | protected $meta_join; |
||
160 | |||
161 | /** |
||
162 | * The WHERE clause for the query. |
||
163 | * |
||
164 | * @since 2.1.0 |
||
165 | * |
||
166 | * @type string $where |
||
167 | */ |
||
168 | protected $where; |
||
169 | |||
170 | /** |
||
171 | * The array of conditions for the WHERE clause. |
||
172 | * |
||
173 | * @since 2.1.0 |
||
174 | * |
||
175 | * @type array $wheres |
||
176 | */ |
||
177 | protected $wheres = array(); |
||
178 | |||
179 | /** |
||
180 | * The LIMIT clause for the query. |
||
181 | * |
||
182 | * @since 2.1.0 |
||
183 | * |
||
184 | * @type string $limit |
||
185 | */ |
||
186 | protected $limit; |
||
187 | |||
188 | /** |
||
189 | * The ORDER clause for the query. |
||
190 | * |
||
191 | * @since 2.1.0 |
||
192 | * |
||
193 | * @type string $order |
||
194 | */ |
||
195 | protected $order; |
||
196 | |||
197 | /** |
||
198 | * Holds the meta query object when a meta query is being performed. |
||
199 | * |
||
200 | * @since 2.1.0 |
||
201 | * |
||
202 | * @type WP_Meta_Query $meta_query |
||
203 | */ |
||
204 | protected $meta_query; |
||
205 | |||
206 | // |
||
207 | // Public Methods. |
||
208 | // |
||
209 | |||
210 | /** |
||
211 | * Construct the class. |
||
212 | * |
||
213 | * All of the arguments are expected *not* to be SQL escaped. |
||
214 | * |
||
215 | * @since 2.1.0 |
||
216 | * |
||
217 | * @see WP_Meta_Query for the proper arguments for 'meta_query', 'meta_key', 'meta_value', 'meta_compare', and 'meta_type'. |
||
218 | * |
||
219 | * @param array $args { |
||
220 | * The arguments for the query. |
||
221 | * |
||
222 | * @type string|array $fields Fields to include in the results. Default is all fields. |
||
223 | * @type int $limit The maximum number of results to return. Default is null (no limit). |
||
224 | * @type int $start The start for the LIMIT clause. Default: 0. |
||
225 | * @type string $order_by The field to use to order the results. |
||
226 | * @type string $order The order for the query: ASC or DESC (default). |
||
227 | * @type string $meta_key See WP_Meta_Query. |
||
228 | * @type mixed $meta_value See WP_Meta_Query. |
||
229 | * @type string $meta_compare See WP_Meta_Query. |
||
230 | * @type string $meta_type See WP_Meta_Query. |
||
231 | * @type array $meta_query See WP_Meta_Query. |
||
232 | * } |
||
233 | */ |
||
234 | public function __construct( $args = array() ) { |
||
253 | |||
254 | /** |
||
255 | * Get a query arg. |
||
256 | * |
||
257 | * @since 2.1.0 |
||
258 | * |
||
259 | * @param string $arg The query arg whose value to retrieve. |
||
260 | * |
||
261 | * @return mixed|null The query arg's value, or null if it isn't set. |
||
262 | */ |
||
263 | public function get_arg( $arg ) { |
||
282 | |||
283 | /** |
||
284 | * Set arguments for the query. |
||
285 | * |
||
286 | * All of the arguments supported by the constructor may be passed in here, and |
||
287 | * will be merged into the array of existing args. |
||
288 | * |
||
289 | * @since 2.1.0 |
||
290 | * |
||
291 | * @param array $args A list of arguments to set and their values. |
||
292 | * |
||
293 | * @return WordPoints_DB_Query To allow for method chaining. |
||
294 | */ |
||
295 | public function set_args( array $args ) { |
||
318 | |||
319 | /** |
||
320 | * Count the number of results. |
||
321 | * |
||
322 | * When used with a query that contains a LIMIT clause, this method currently |
||
323 | * returns the count of the query ignoring the LIMIT, as would be the case with |
||
324 | * any similar query. However, this behaviour is not hardened and should not be |
||
325 | * relied upon. Make inquiry before assuming the constancy of this behaviour. |
||
326 | * |
||
327 | * @since 2.1.0 |
||
328 | * |
||
329 | * @return int The number of results. |
||
330 | */ |
||
331 | public function count() { |
||
339 | |||
340 | /** |
||
341 | * Get the results for the query. |
||
342 | * |
||
343 | * @since 2.1.0 |
||
344 | * |
||
345 | * @param string $method The method to use. Options are 'results', 'row', 'col', |
||
346 | * and 'var'. |
||
347 | * |
||
348 | * @return mixed The results of the query, or false on failure. |
||
349 | */ |
||
350 | public function get( $method = 'results' ) { |
||
367 | |||
368 | /** |
||
369 | * Get the SQL for the query. |
||
370 | * |
||
371 | * This function can return the SQL for a SELECT or SELECT COUNT query. To |
||
372 | * specify which one to return, set the $select_type parameter. Defaults to |
||
373 | * SELECT. |
||
374 | * |
||
375 | * This function is public for debugging purposes. |
||
376 | * |
||
377 | * @since 2.1.0 |
||
378 | * |
||
379 | * @param string $select_type The type of query, SELECT, or SELECT COUNT. |
||
380 | * |
||
381 | * @return string The SQL for the query. |
||
382 | */ |
||
383 | public function get_sql( $select_type = 'SELECT' ) { |
||
398 | |||
399 | // |
||
400 | // Filter Methods. |
||
401 | // |
||
402 | |||
403 | /** |
||
404 | * Filter date query valid columns for WP_Date_Query. |
||
405 | * |
||
406 | * @since 2.1.0 |
||
407 | * |
||
408 | * @WordPress\filter date_query_valid_columns Added and subsequently removed by |
||
409 | * self::prepare_date_where(). |
||
410 | * |
||
411 | * @param string[] $valid_columns The names of the valid columns for date queries. |
||
412 | * |
||
413 | * @return string[] The valid columns. |
||
414 | */ |
||
415 | public function date_query_valid_columns_filter( $valid_columns ) { |
||
426 | |||
427 | // |
||
428 | // Protected Methods. |
||
429 | // |
||
430 | |||
431 | /** |
||
432 | * Prepare the query. |
||
433 | * |
||
434 | * @since 2.1.0 |
||
435 | */ |
||
436 | protected function prepare_query() { |
||
448 | |||
449 | /** |
||
450 | * Prepare the select statement. |
||
451 | * |
||
452 | * @since 2.1.0 |
||
453 | */ |
||
454 | protected function prepare_select() { |
||
479 | |||
480 | /** |
||
481 | * Validates a value against an array of sanitizing functions. |
||
482 | * |
||
483 | * @since 2.1.0 |
||
484 | * |
||
485 | * @param mixed $value The value to validate. |
||
486 | * @param callable[] $validators The validators to validate it against. |
||
487 | * |
||
488 | * @return mixed The validated value, or false if invalid. |
||
489 | */ |
||
490 | protected function validate_value( $value, $validators ) { |
||
503 | |||
504 | /** |
||
505 | * Validates an array of values against an array of sanitizing functions. |
||
506 | * |
||
507 | * @since 2.1.0 |
||
508 | * |
||
509 | * @param array $values The values to validate. |
||
510 | * @param callable[] $validators The validators to validate each value against. |
||
511 | * |
||
512 | * @return array The validated values, with any invalid ones removed. |
||
513 | */ |
||
514 | protected function validate_values( $values, $validators ) { |
||
527 | |||
528 | /** |
||
529 | * Validate an unsigned column. |
||
530 | * |
||
531 | * The value must be positive, zero-inclusive. We can't just use |
||
532 | * wordpoints_posint() because it is zero exclusive. |
||
533 | * |
||
534 | * @since 2.1.0 |
||
535 | * |
||
536 | * @param mixed $value The value to validate. |
||
537 | * |
||
538 | * @return int|false The validated value or false. |
||
539 | */ |
||
540 | protected function validate_unsigned_column( $value ) { |
||
548 | |||
549 | /** |
||
550 | * Get an array of validating/sanitizing functions for the values of a column. |
||
551 | * |
||
552 | * @since 2.1.0 |
||
553 | * |
||
554 | * @param array $data The data for the column. |
||
555 | * |
||
556 | * @return callable[] The validation functions. |
||
557 | */ |
||
558 | protected function get_validators_for_column( $data ) { |
||
573 | |||
574 | /** |
||
575 | * Prepare the conditions for the WHERE clause for a column. |
||
576 | * |
||
577 | * @since 2.1.0 |
||
578 | * |
||
579 | * @param string $column The column name. |
||
580 | * @param array $data The column data. |
||
581 | */ |
||
582 | protected function prepare_column_where( $column, $data ) { |
||
593 | |||
594 | /** |
||
595 | * Prepare a single-value condition for the WHERE clause for a column. |
||
596 | * |
||
597 | * @since 2.1.0 |
||
598 | * |
||
599 | * @param string $column The name of the column |
||
600 | * @param array $data The column data. |
||
601 | */ |
||
602 | protected function prepare_column( $column, $data ) { |
||
631 | |||
632 | /** |
||
633 | * Get the comparator for a column. |
||
634 | * |
||
635 | * @since 2.1.0 |
||
636 | * |
||
637 | * @param string $column The column name. |
||
638 | * @param array $data The column data. |
||
639 | * |
||
640 | * @return string The comparator for the column. |
||
641 | */ |
||
642 | protected function get_comparator_for_column( $column, $data ) { |
||
663 | |||
664 | /** |
||
665 | * Prepare the IN or NOT IN conditions for a column. |
||
666 | * |
||
667 | * @since 2.1.0 |
||
668 | * |
||
669 | * @param string $column The name of the column. |
||
670 | * @param array $data The column data. |
||
671 | * @param string $type The type of IN clause, IN or NOT IN. |
||
672 | */ |
||
673 | protected function prepare_column__in( $column, $data, $type = 'IN' ) { |
||
706 | |||
707 | /** |
||
708 | * Prepare the WHERE clause for the query. |
||
709 | * |
||
710 | * @since 2.1.0 |
||
711 | */ |
||
712 | protected function prepare_where() { |
||
731 | |||
732 | /** |
||
733 | * Prepare the LIMIT clause for the query. |
||
734 | * |
||
735 | * @since 2.1.0 |
||
736 | */ |
||
737 | protected function prepare_limit() { |
||
770 | |||
771 | /** |
||
772 | * Prepare the ORDER BY clause for the query. |
||
773 | * |
||
774 | * @since 2.1.0 |
||
775 | */ |
||
776 | protected function prepare_order_by() { |
||
821 | |||
822 | /** |
||
823 | * Prepare the date query for a column. |
||
824 | * |
||
825 | * @since 2.1.0 |
||
826 | * |
||
827 | * @param string $column The name of the column. |
||
828 | */ |
||
829 | protected function prepare_date_where( $column ) { |
||
849 | |||
850 | /** |
||
851 | * Prepare the meta query. |
||
852 | * |
||
853 | * @since 2.1.0 |
||
854 | */ |
||
855 | protected function prepare_meta_where() { |
||
892 | } |
||
893 | |||
895 |
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.