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 WC_Data 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 WC_Data, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
16 | abstract class WC_Data { |
||
17 | |||
18 | /** |
||
19 | * Core data for this object, name value pairs (name + default value). |
||
20 | * @var array |
||
21 | */ |
||
22 | protected $_data = array(); |
||
23 | |||
24 | /** |
||
25 | * Stores meta in cache for future reads. |
||
26 | * A group must be set to to enable caching. |
||
27 | * @var string |
||
28 | */ |
||
29 | protected $_cache_group = ''; |
||
30 | |||
31 | /** |
||
32 | * Meta type. This should match up with |
||
33 | * the types avaiable at https://codex.wordpress.org/Function_Reference/add_metadata. |
||
34 | * WP defines 'post', 'user', 'comment', and 'term'. |
||
35 | */ |
||
36 | protected $_meta_type = 'post'; |
||
37 | |||
38 | /** |
||
39 | * This only needs set if you are using a custom metadata type (for example payment tokens. |
||
40 | * This should be the name of the field your table uses for associating meta with objects. |
||
41 | * For example, in payment_tokenmeta, this would be payment_token_id. |
||
42 | * @var string |
||
43 | */ |
||
44 | protected $object_id_field_for_meta = ''; |
||
45 | |||
46 | /** |
||
47 | * Stores additonal meta data. |
||
48 | * @var array |
||
49 | */ |
||
50 | protected $_meta_data = array(); |
||
51 | |||
52 | /** |
||
53 | * Internal meta keys we don't want exposed for the object. |
||
54 | * @var array |
||
55 | */ |
||
56 | protected $_internal_meta_keys = array(); |
||
57 | |||
58 | /** |
||
59 | * Returns the unique ID for this object. |
||
60 | * @return int |
||
61 | */ |
||
62 | abstract public function get_id(); |
||
63 | |||
64 | /** |
||
65 | * Creates new object in the database. |
||
66 | */ |
||
67 | abstract public function create(); |
||
68 | |||
69 | /** |
||
70 | * Read object from the database. |
||
71 | * @param int ID of the object to load. |
||
72 | */ |
||
73 | abstract public function read( $id ); |
||
74 | |||
75 | /** |
||
76 | * Updates object data in the database. |
||
77 | */ |
||
78 | abstract public function update(); |
||
79 | |||
80 | /** |
||
81 | * Updates object data in the database. |
||
82 | */ |
||
83 | abstract public function delete(); |
||
84 | |||
85 | /** |
||
86 | * Save should create or update based on object existance. |
||
87 | */ |
||
88 | abstract public function save(); |
||
89 | |||
90 | /** |
||
91 | * Change data to JSON format. |
||
92 | * @return string Data in JSON format. |
||
93 | */ |
||
94 | public function __toString() { |
||
97 | |||
98 | /** |
||
99 | * Returns all data for this object. |
||
100 | * @return array |
||
101 | */ |
||
102 | public function get_data() { |
||
105 | |||
106 | /** |
||
107 | * Get All Meta Data. |
||
108 | * @since 2.6.0 |
||
109 | * @return array |
||
110 | */ |
||
111 | public function get_meta_data() { |
||
114 | |||
115 | /** |
||
116 | * Internal meta keys we don't want exposed as part of meta_data. This is in |
||
117 | * addition to all data props with _ prefix. |
||
118 | * @since 2.6.0 |
||
119 | * @return array |
||
120 | */ |
||
121 | protected function prefix_key( $key ) { |
||
124 | |||
125 | /** |
||
126 | * Internal meta keys we don't want exposed as part of meta_data. This is in |
||
127 | * addition to all data props with _ prefix. |
||
128 | * @since 2.6.0 |
||
129 | * @return array |
||
130 | */ |
||
131 | protected function get_internal_meta_keys() { |
||
134 | |||
135 | /** |
||
136 | * Get Meta Data by Key. |
||
137 | * @since 2.6.0 |
||
138 | * @param string $key |
||
139 | * @param bool $single return first found meta with key, or all with $key |
||
140 | * @return mixed |
||
141 | */ |
||
142 | public function get_meta( $key = '', $single = true ) { |
||
156 | |||
157 | /** |
||
158 | * Set all meta data from array. |
||
159 | * @since 2.6.0 |
||
160 | * @param array $data Key/Value pairs |
||
161 | */ |
||
162 | public function set_meta_data( $data ) { |
||
163 | if ( ! empty( $data ) && is_array( $data ) ) { |
||
164 | foreach ( $data as $meta ) { |
||
165 | $meta = (array) $meta; |
||
166 | if ( isset( $meta['key'], $meta['value'], $meta['id'] ) ) { |
||
167 | $this->_meta_data[] = (object) array( |
||
168 | 'id' => $meta['id'], |
||
169 | 'key' => $meta['key'], |
||
170 | 'value' => $meta['value'], |
||
171 | ); |
||
172 | } |
||
173 | } |
||
174 | } |
||
175 | } |
||
176 | |||
177 | /** |
||
178 | * Add meta data. |
||
179 | * @since 2.6.0 |
||
180 | * @param string $key Meta key |
||
181 | * @param string $value Meta value |
||
182 | * @param bool $unique Should this be a unique key? |
||
183 | */ |
||
184 | public function add_meta_data( $key, $value, $unique = false ) { |
||
194 | |||
195 | /** |
||
196 | * Update meta data by key or ID, if provided. |
||
197 | * @since 2.6.0 |
||
198 | * @param string $key |
||
199 | * @param string $value |
||
200 | * @param int $meta_id |
||
201 | */ |
||
202 | public function update_meta_data( $key, $value, $meta_id = '' ) { |
||
203 | $array_key = ''; |
||
204 | if ( $meta_id ) { |
||
205 | $array_key = array_keys( wp_list_pluck( $this->_meta_data, 'id' ), $meta_id ); |
||
206 | } |
||
207 | if ( $array_key ) { |
||
208 | $this->_meta_data[ current( $array_key ) ] = (object) array( |
||
209 | 'id' => $meta_id, |
||
210 | 'key' => $key, |
||
211 | 'value' => $value, |
||
212 | ); |
||
213 | } else { |
||
214 | $this->add_meta_data( $key, $value, true ); |
||
215 | } |
||
216 | } |
||
217 | |||
218 | /** |
||
219 | * Delete meta data. |
||
220 | * @since 2.6.0 |
||
221 | * @param array $key Meta key |
||
222 | */ |
||
223 | public function delete_meta_data( $key ) { |
||
227 | |||
228 | /** |
||
229 | * Delete meta data. |
||
230 | * @since 2.6.0 |
||
231 | * @param int $mid Meta ID |
||
232 | */ |
||
233 | public function delete_meta_data_by_mid( $mid ) { |
||
237 | |||
238 | /** |
||
239 | * Read Meta Data from the database. Ignore any internal properties. |
||
240 | * @since 2.6.0 |
||
241 | */ |
||
242 | protected function read_meta_data() { |
||
243 | $this->_meta_data = array(); |
||
244 | $cache_loaded = false; |
||
245 | |||
246 | if ( ! $this->get_id() ) { |
||
247 | return; |
||
248 | } |
||
249 | |||
250 | if ( ! empty( $this->_cache_group ) ) { |
||
251 | $cache_key = WC_Cache_Helper::get_cache_prefix( $this->_cache_group ) . $this->get_id(); |
||
252 | $cached_meta = wp_cache_get( $cache_key, $this->_cache_group ); |
||
253 | |||
254 | if ( false !== $cached_meta ) { |
||
255 | $this->_meta_data = $cached_meta; |
||
256 | $cache_loaded = true; |
||
257 | } |
||
258 | } |
||
259 | |||
260 | if ( ! $cache_loaded ) { |
||
261 | global $wpdb; |
||
262 | $db_info = $this->_get_db_info(); |
||
263 | $raw_meta_data = $wpdb->get_results( $wpdb->prepare( " |
||
264 | SELECT " . $db_info['meta_id_field'] . ", meta_key, meta_value |
||
265 | FROM " . $db_info['table'] . " |
||
266 | WHERE " . $db_info['object_id_field'] . "=%d AND meta_key NOT LIKE 'wp\_%%' ORDER BY " . $db_info['meta_id_field'] . " |
||
267 | ", $this->get_id() ) ); |
||
268 | |||
269 | if ( $raw_meta_data ) { |
||
270 | foreach ( $raw_meta_data as $meta ) { |
||
271 | if ( in_array( $meta->meta_key, $this->get_internal_meta_keys() ) ) { |
||
272 | continue; |
||
273 | } |
||
274 | $this->_meta_data[] = (object) array( |
||
275 | 'id' => (int) $meta->{ $db_info['meta_id_field'] }, |
||
276 | 'key' => $meta->meta_key, |
||
277 | 'value' => maybe_unserialize( $meta->meta_value ), |
||
278 | ); |
||
279 | } |
||
280 | } |
||
281 | |||
282 | if ( ! empty( $this->_cache_group ) ) { |
||
283 | wp_cache_set( $cache_key, $this->_meta_data, $this->_cache_group ); |
||
284 | } |
||
285 | } |
||
286 | } |
||
287 | |||
288 | /** |
||
289 | * Update Meta Data in the database. |
||
290 | * @since 2.6.0 |
||
291 | */ |
||
292 | protected function save_meta_data() { |
||
293 | global $wpdb; |
||
294 | $db_info = $this->_get_db_info(); |
||
295 | $all_meta_ids = array_map( 'absint', $wpdb->get_col( $wpdb->prepare( " |
||
296 | SELECT " . $db_info['meta_id_field'] . " FROM " . $db_info['table'] . " |
||
297 | WHERE " . $db_info['object_id_field'] . " = %d", $this->get_id() ) . " |
||
298 | AND meta_key NOT IN ('" . implode( "','", array_map( 'esc_sql', $this->get_internal_meta_keys() ) ) . "') |
||
299 | AND meta_key NOT LIKE 'wp\_%%'; |
||
300 | " ) ); |
||
301 | $set_meta_ids = array(); |
||
302 | |||
303 | foreach ( $this->_meta_data as $array_key => $meta ) { |
||
304 | if ( empty( $meta->id ) ) { |
||
305 | $new_meta_id = add_metadata( $this->_meta_type, $this->get_id(), $meta->key, $meta->value, false ); |
||
306 | $set_meta_ids[] = $new_meta_id; |
||
307 | $this->_meta_data[ $array_key ]->id = $new_meta_id; |
||
308 | } else { |
||
309 | update_metadata_by_mid( $this->_meta_type, $meta->id, $meta->value, $meta->key ); |
||
310 | $set_meta_ids[] = absint( $meta->id ); |
||
311 | } |
||
312 | } |
||
313 | |||
314 | // Delete no longer set meta data |
||
315 | $delete_meta_ids = array_diff( $all_meta_ids, $set_meta_ids ); |
||
316 | foreach ( $delete_meta_ids as $meta_id ) { |
||
317 | delete_metadata_by_mid( $this->_meta_type, $meta_id ); |
||
318 | } |
||
319 | |||
320 | if ( ! empty( $this->_cache_group ) ) { |
||
321 | WC_Cache_Helper::incr_cache_prefix( $this->_cache_group ); |
||
322 | } |
||
323 | $this->read_meta_data(); |
||
324 | } |
||
325 | |||
326 | /** |
||
327 | * Table structure is slightly different between meta types, this function will return what we need to know. |
||
328 | * @since 2.6.0 |
||
329 | * @return array Array elements: table, object_id_field, meta_id_field |
||
330 | */ |
||
331 | protected function _get_db_info() { |
||
360 | |||
361 | /** |
||
362 | * Get internal data prop (raw). |
||
363 | * @param string ...$param Prop keys to retrieve. Supports multiple keys to get nested values. |
||
364 | * @return mixed |
||
365 | */ |
||
366 | protected function get_prop() { |
||
379 | |||
380 | /** |
||
381 | * Set internal data prop to specified value. |
||
382 | * @param int ...$param Prop keys followed by value to set. |
||
383 | * @return bool |
||
384 | */ |
||
385 | protected function set_prop() { |
||
405 | |||
406 | /** |
||
407 | * Returns an invalid data WP_Error object. |
||
408 | * @param string $message Error Message. |
||
409 | * @param mixed $data Data the user tried to set. |
||
410 | * @return WP_Error |
||
411 | */ |
||
412 | protected function error( $message = '', $data = '' ) { |
||
415 | } |
||
416 |
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.