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 EEM_CPT_Base 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 EEM_CPT_Base, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
19 | abstract class EEM_CPT_Base extends EEM_Soft_Delete_Base{ |
||
20 | |||
21 | /** |
||
22 | * @var string post_status_publish - the wp post status for published cpts |
||
23 | */ |
||
24 | const post_status_publish = 'publish'; |
||
25 | |||
26 | /** |
||
27 | * @var string post_status_future - the wp post status for scheduled cpts |
||
28 | */ |
||
29 | const post_status_future = 'future'; |
||
30 | |||
31 | /** |
||
32 | * @var string post_status_draft - the wp post status for draft cpts |
||
33 | */ |
||
34 | const post_status_draft = 'draft'; |
||
35 | |||
36 | /** |
||
37 | * @var string post_status_pending - the wp post status for pending cpts |
||
38 | */ |
||
39 | const post_status_pending = 'pending'; |
||
40 | |||
41 | /** |
||
42 | * @var string post_status_private - the wp post status for private cpts |
||
43 | */ |
||
44 | const post_status_private = 'private'; |
||
45 | |||
46 | /** |
||
47 | * @var string post_status_trashed - the wp post status for trashed cpts |
||
48 | */ |
||
49 | const post_status_trashed = 'trash'; |
||
50 | |||
51 | /** |
||
52 | * This is an array of custom statuses for the given CPT model (modified by children) |
||
53 | * format: |
||
54 | * array( |
||
55 | * 'status_name' => array( |
||
56 | * 'label' => __('Status Name', 'event_espresso'), |
||
57 | * 'public' => TRUE //whether a public status or not. |
||
58 | * ) |
||
59 | * ) |
||
60 | * @var array |
||
61 | */ |
||
62 | protected $_custom_stati = array(); |
||
63 | |||
64 | |||
65 | |||
66 | /** |
||
67 | * Adds a relationship to Term_Taxonomy for each CPT_Base |
||
68 | * |
||
69 | * @param string $timezone |
||
70 | * @throws \EE_Error |
||
71 | */ |
||
72 | protected function __construct( $timezone = NULL ){ |
||
73 | |||
74 | //adds a relationship to Term_Taxonomy for all these models. For this to work |
||
75 | //Term_Relationship must have a relation to each model subclassing EE_CPT_Base explicitly |
||
76 | //eg, in EEM_Term_Relationship, inside the _model_relations array, there must be an entry |
||
77 | //with key equalling the subclassing model's model name (eg 'Event' or 'Venue'), and the value |
||
78 | //must also be new EE_HABTM_Relation('Term_Relationship'); |
||
79 | $this->_model_relations['Term_Taxonomy'] =new EE_HABTM_Relation('Term_Relationship'); |
||
80 | $primary_table_name = NULL; |
||
81 | //add the common _status field to all CPT primary tables. |
||
82 | foreach ( $this->_tables as $alias => $table_obj ) { |
||
83 | if ( $table_obj instanceof EE_Primary_Table ) { |
||
84 | $primary_table_name = $alias; |
||
85 | } |
||
86 | } |
||
87 | //set default wp post statuses if child has not already set. |
||
88 | if ( ! isset( $this->_fields[$primary_table_name]['status'] )) { |
||
89 | $this->_fields[$primary_table_name]['status'] = new EE_WP_Post_Status_Field('post_status', __("Event Status", "event_espresso"), false, 'draft'); |
||
90 | } |
||
91 | View Code Duplication | if( ! isset( $this->_fields[$primary_table_name]['to_ping'])){ |
|
|
|||
92 | $this->_fields[$primary_table_name]['to_ping'] = new EE_DB_Only_Text_Field('to_ping', __( 'To Ping', 'event_espresso' ), FALSE, ''); |
||
1 ignored issue
–
show
|
|||
93 | } |
||
94 | View Code Duplication | if( ! isset( $this->_fields[$primary_table_name]['pinged'])){ |
|
95 | $this->_fields[$primary_table_name]['pinged'] = new EE_DB_Only_Text_Field('pinged', __( 'Pinged', 'event_espresso' ), FALSE, ''); |
||
1 ignored issue
–
show
|
|||
96 | } |
||
97 | |||
98 | View Code Duplication | if( ! isset( $this->_fields[$primary_table_name]['comment_status'])){ |
|
99 | $this->_fields[$primary_table_name]['comment_status'] = new EE_Plain_Text_Field('comment_status', __('Comment Status', 'event_espresso' ), FALSE, 'open'); |
||
1 ignored issue
–
show
|
|||
100 | } |
||
101 | |||
102 | View Code Duplication | if( ! isset( $this->_fields[$primary_table_name]['ping_status'])){ |
|
103 | $this->_fields[$primary_table_name]['ping_status'] = new EE_Plain_Text_Field('ping_status', __('Ping Status', 'event_espresso'), FALSE, 'open'); |
||
1 ignored issue
–
show
|
|||
104 | } |
||
105 | |||
106 | View Code Duplication | if( ! isset( $this->_fields[$primary_table_name]['post_content_filtered'])){ |
|
107 | $this->_fields[$primary_table_name]['post_content_filtered'] = new EE_DB_Only_Text_Field('post_content_filtered', __( 'Post Content Filtered', 'event_espresso' ), FALSE, ''); |
||
1 ignored issue
–
show
|
|||
108 | } |
||
109 | if( ! isset( $this->_model_relations[ 'Post_Meta' ] ) ) { |
||
110 | //don't block deletes though because we want to maintain the current behaviour |
||
111 | $this->_model_relations[ 'Post_Meta' ] = new EE_Has_Many_Relation( false ); |
||
112 | } |
||
113 | parent::__construct($timezone); |
||
114 | |||
115 | } |
||
116 | |||
117 | |||
118 | |||
119 | /** |
||
120 | * @return array |
||
121 | */ |
||
122 | public function public_event_stati() { |
||
126 | |||
127 | |||
128 | |||
129 | /** |
||
130 | * Searches for field on this model of type 'deleted_flag'. if it is found, |
||
131 | * returns it's name. BUT That doesn't apply to CPTs. We should instead use post_status_field_name |
||
132 | * @return string |
||
133 | * @throws EE_Error |
||
134 | */ |
||
135 | public function deleted_field_name(){ |
||
138 | |||
139 | |||
140 | |||
141 | /** |
||
142 | * Gets the field's name that sets the post status |
||
143 | * @return string |
||
144 | * @throws EE_Error |
||
145 | */ |
||
146 | View Code Duplication | public function post_status_field_name(){ |
|
147 | $field = $this->get_a_field_of_type('EE_WP_Post_Status_Field'); |
||
148 | if($field){ |
||
149 | return $field->get_name(); |
||
150 | }else{ |
||
151 | throw new EE_Error(sprintf(__('We are trying to find the post status flag field on %s, but none was found. Are you sure there is a field of type EE_Trashed_Flag_Field in %s constructor?','event_espresso'),get_class($this),get_class($this))); |
||
152 | } |
||
153 | } |
||
154 | |||
155 | |||
156 | |||
157 | /** |
||
158 | * Alters the query params so that only trashed/soft-deleted items are considered |
||
159 | * @param array $query_params like EEM_Base::get_all's $query_params |
||
160 | * @return array like EEM_Base::get_all's $query_params |
||
161 | */ |
||
162 | protected function _alter_query_params_so_only_trashed_items_included($query_params){ |
||
163 | $post_status_field_name=$this->post_status_field_name(); |
||
164 | $query_params[0][$post_status_field_name]=self::post_status_trashed; |
||
165 | return $query_params; |
||
166 | } |
||
167 | |||
168 | |||
169 | |||
170 | /** |
||
171 | * Alters the query params so each item's deleted status is ignored. |
||
172 | * @param array $query_params |
||
173 | * @return array |
||
174 | */ |
||
175 | protected function _alter_query_params_so_deleted_and_undeleted_items_included($query_params){ |
||
176 | $post_status_field_name=$this->post_status_field_name(); |
||
177 | $query_params[0][$post_status_field_name]=array('IN',array_keys($this->get_status_array())); |
||
178 | return $query_params; |
||
179 | } |
||
180 | |||
181 | |||
182 | |||
183 | /** |
||
184 | * Performs deletes or restores on items. Both soft-deleted and non-soft-deleted items considered. |
||
185 | * @param boolean $delete true to indicate deletion, false to indicate restoration |
||
186 | * @param array $query_params like EEM_Base::get_all |
||
187 | * @return boolean success |
||
188 | */ |
||
189 | function delete_or_restore($delete=true,$query_params = array()){ |
||
190 | $post_status_field_name=$this->post_status_field_name(); |
||
191 | $query_params = $this->_alter_query_params_so_deleted_and_undeleted_items_included($query_params); |
||
192 | $new_status = $delete ? self::post_status_trashed : 'draft'; |
||
193 | if ( $this->update (array($post_status_field_name=>$new_status), $query_params )) { |
||
194 | return TRUE; |
||
195 | } else { |
||
196 | return FALSE; |
||
197 | } |
||
198 | } |
||
199 | |||
200 | |||
201 | |||
202 | /** |
||
203 | * meta_table |
||
204 | * returns first EE_Secondary_Table table name |
||
205 | * @access public |
||
206 | * @return string |
||
207 | */ |
||
208 | public function meta_table() { |
||
209 | $meta_table = $this->_get_other_tables(); |
||
210 | $meta_table = reset( $meta_table ); |
||
211 | return $meta_table instanceof EE_Secondary_Table ? $meta_table->get_table_name() : NULL; |
||
212 | } |
||
213 | |||
214 | |||
215 | |||
216 | |||
217 | /** |
||
218 | * This simply returns an array of the meta table fields (useful for when we just need to update those fields) |
||
219 | * @param bool $all triggers whether we include DB_Only fields or JUST non DB_Only fields. Defaults to false (no db only fields) |
||
220 | * @return array |
||
221 | */ |
||
222 | public function get_meta_table_fields( $all = FALSE ) { |
||
223 | $all_fields = $fields_to_return = array(); |
||
224 | foreach ( $this->_tables as $alias => $table_obj ) { |
||
225 | if ( $table_obj instanceof EE_Secondary_Table ) |
||
226 | $all_fields = array_merge( $this->_get_fields_for_table($alias), $all_fields ); |
||
227 | } |
||
228 | |||
229 | if ( !$all ) { |
||
230 | foreach ( $all_fields as $name => $obj ) { |
||
231 | if ( $obj instanceof EE_DB_Only_Field_Base ) |
||
232 | continue; |
||
233 | $fields_to_return[] = $name; |
||
234 | } |
||
235 | } else { |
||
236 | $fields_to_return = array_keys($all_fields); |
||
237 | } |
||
238 | |||
239 | return $fields_to_return; |
||
240 | } |
||
241 | |||
242 | |||
243 | |||
244 | /** |
||
245 | * Adds an event category with the specified name and description to the specified |
||
246 | * $cpt_model_object. Intelligently adds a term if necessary, and adds a term_taxonomy if necessary, |
||
247 | * and adds an entry in the term_relationship if necessary. |
||
248 | * @param EE_CPT_Base $cpt_model_object |
||
249 | * @param string $category_name (used to derive the term slug too) |
||
250 | * @param string $category_description |
||
251 | * @param int $parent_term_taxonomy_id |
||
252 | * @return EE_Term_Taxonomy |
||
253 | */ |
||
254 | function add_event_category(EE_CPT_Base $cpt_model_object, $category_name, $category_description ='',$parent_term_taxonomy_id = null){ |
||
255 | //create term |
||
256 | require_once( EE_MODELS . 'EEM_Term.model.php'); |
||
257 | //first, check for a term by the same name or slug |
||
258 | $category_slug = sanitize_title($category_name); |
||
259 | $term = EEM_Term::instance()->get_one(array(array('OR'=>array('name'=>$category_name,'slug'=>$category_slug)))); |
||
260 | if( ! $term ){ |
||
261 | $term = EE_Term::new_instance(array( |
||
262 | 'name'=>$category_name, |
||
263 | 'slug'=>$category_slug |
||
264 | )); |
||
265 | $term->save(); |
||
266 | } |
||
267 | //make sure there's a term-taxonomy entry too |
||
268 | require_once( EE_MODELS . 'EEM_Term_Taxonomy.model.php'); |
||
269 | $term_taxonomy = EEM_Term_Taxonomy::instance()->get_one(array(array('term_id'=>$term->ID(),'taxonomy'=>EE_Event_Category_Taxonomy))); |
||
270 | /** @var $term_taxonomy EE_Term_Taxonomy */ |
||
271 | if( ! $term_taxonomy ){ |
||
272 | $term_taxonomy = EE_Term_Taxonomy::new_instance(array( |
||
273 | 'term_id'=>$term->ID(), |
||
274 | 'taxonomy'=>EE_Event_Category_Taxonomy, |
||
275 | 'description'=>$category_description, |
||
276 | 'count'=>1, |
||
277 | 'parent'=>$parent_term_taxonomy_id |
||
278 | )); |
||
279 | $term_taxonomy->save(); |
||
280 | }else{ |
||
281 | $term_taxonomy->set_count($term_taxonomy->count() + 1); |
||
282 | $term_taxonomy->save(); |
||
283 | } |
||
284 | return $this->add_relationship_to($cpt_model_object, $term_taxonomy, 'Term_Taxonomy'); |
||
285 | } |
||
286 | |||
287 | |||
288 | /** |
||
289 | * Removed the category specified by name as having a relation to this event. |
||
290 | * Does not remove the term or term_taxonomy. |
||
291 | * @param EE_CPT_Base $cpt_model_object_event |
||
292 | * @param string $category_name name of the event category (term) |
||
293 | * @return bool |
||
294 | */ |
||
295 | function remove_event_category(EE_CPT_Base $cpt_model_object_event, $category_name){ |
||
296 | //find the term_taxonomy by that name |
||
297 | $term_taxonomy = $this->get_first_related($cpt_model_object_event, 'Term_Taxonomy', array(array('Term.name'=>$category_name,'taxonomy'=>EE_Event_Category_Taxonomy))); |
||
298 | /** @var $term_taxonomy EE_Term_Taxonomy */ |
||
299 | if( $term_taxonomy ){ |
||
300 | $term_taxonomy->set_count($term_taxonomy->count() - 1); |
||
301 | $term_taxonomy->save(); |
||
302 | } |
||
303 | return $this->remove_relationship_to($cpt_model_object_event, $term_taxonomy, 'Term_Taxonomy'); |
||
304 | } |
||
305 | |||
306 | |||
307 | |||
308 | |||
309 | /** |
||
310 | * This is a wrapper for the WordPress get_the_post_thumbnail() function that returns the feature image for the given CPT ID. It accepts the same params as what get_the_post_thumbnail() accepts. |
||
311 | * |
||
312 | * @link http://codex.wordpress.org/Function_Reference/get_the_post_thumbnail |
||
313 | * @access public |
||
314 | * @param int $id the ID for the cpt we want the feature image for |
||
315 | * @param string|array $size (optional) Image size. Defaults to 'post-thumbnail' but can also be a 2-item array representing width and height in pixels (i.e. array(32,32) ). |
||
316 | * @param string|array $attr Optional. Query string or array of attributes. |
||
317 | * @return string HTML image element |
||
318 | */ |
||
319 | public function get_feature_image( $id, $size = 'thumbnail', $attr = '' ) { |
||
322 | |||
323 | |||
324 | |||
325 | |||
326 | |||
327 | |||
328 | /** |
||
329 | * Just a handy way to get the list of post statuses currently registered with WP. |
||
330 | * @global array $wp_post_statuses set in wp core for storing all the post stati |
||
331 | * @return array |
||
332 | */ |
||
333 | public function get_post_statuses(){ |
||
334 | global $wp_post_statuses; |
||
335 | $statuses = array(); |
||
336 | foreach($wp_post_statuses as $post_status => $args_object){ |
||
337 | $statuses[$post_status] = $args_object->label; |
||
338 | } |
||
339 | return $statuses; |
||
340 | } |
||
341 | |||
342 | |||
343 | |||
344 | /** |
||
345 | * public method that can be used to retrieve the protected status array on the instantiated cpt model |
||
346 | * @return array array of statuses. |
||
347 | */ |
||
348 | public function get_status_array() { |
||
349 | $statuses = $this->get_post_statuses(); |
||
350 | //first the global filter |
||
351 | $statuses = apply_filters( 'FHEE_EEM_CPT_Base__get_status_array', $statuses ); |
||
352 | //now the class specific filter |
||
353 | $statuses = apply_filters( 'FHEE_EEM_' . get_class($this) . '__get_status_array', $statuses ); |
||
354 | return $statuses; |
||
355 | } |
||
356 | |||
357 | |||
358 | |||
359 | /** |
||
360 | * this returns the post statuses that are NOT the default wordpress status |
||
361 | * @return array |
||
362 | */ |
||
363 | public function get_custom_post_statuses() { |
||
364 | $new_stati = array(); |
||
365 | foreach ( $this->_custom_stati as $status => $props ) { |
||
366 | $new_stati[$status] = $props['label']; |
||
367 | } |
||
368 | return $new_stati; |
||
369 | } |
||
370 | |||
371 | |||
372 | |||
373 | /** |
||
374 | * Creates a child of EE_CPT_Base given a WP_Post or array of wpdb results which |
||
375 | * are a row from the posts table. If we're missing any fields required for the model, |
||
376 | * we just fetch the entire entry from the DB (ie, if you want to use this to save DB queries, |
||
377 | * make sure you are attaching all the model's fields onto the post) |
||
378 | * @param WP_Post|array $post |
||
379 | * @return EE_CPT_Base |
||
380 | */ |
||
381 | public function instantiate_class_from_post_object_orig($post){ |
||
382 | $post = (array)$post; |
||
383 | $has_all_necessary_fields_for_table = true; |
||
384 | //check if the post has fields on the meta table already |
||
385 | View Code Duplication | foreach($this->_get_other_tables() as $table_obj){ |
|
386 | $fields_for_that_table = $this->_get_fields_for_table($table_obj->get_table_alias()); |
||
387 | foreach($fields_for_that_table as $field_obj){ |
||
388 | if( ! isset($post[$field_obj->get_table_column()]) |
||
389 | && ! isset($post[$field_obj->get_qualified_column()])){ |
||
390 | $has_all_necessary_fields_for_table = false; |
||
391 | } |
||
392 | } |
||
393 | } |
||
394 | //if we don't have all the fields we need, then just fetch the proper model from the DB |
||
395 | if( ! $has_all_necessary_fields_for_table){ |
||
396 | |||
397 | return $this->get_one_by_ID($post['ID']); |
||
398 | }else{ |
||
399 | return $this->instantiate_class_from_array_or_object($post); |
||
400 | } |
||
401 | } |
||
402 | |||
403 | |||
404 | |||
405 | /** |
||
406 | * @param null $post |
||
407 | * @return EE_Base_Class|EE_Soft_Delete_Base_Class |
||
408 | */ |
||
409 | public function instantiate_class_from_post_object( $post = NULL ){ |
||
410 | if ( empty( $post )) { |
||
411 | global $post; |
||
412 | } |
||
413 | $post = (array)$post; |
||
414 | $tables_needing_to_be_queried = array(); |
||
415 | //check if the post has fields on the meta table already |
||
416 | View Code Duplication | foreach($this->get_tables() as $table_obj){ |
|
417 | $fields_for_that_table = $this->_get_fields_for_table($table_obj->get_table_alias()); |
||
418 | foreach($fields_for_that_table as $field_obj){ |
||
419 | if( ! isset($post[$field_obj->get_table_column()]) |
||
420 | && ! isset($post[$field_obj->get_qualified_column()])){ |
||
421 | $tables_needing_to_be_queried[$table_obj->get_table_alias()] = $table_obj; |
||
422 | } |
||
423 | } |
||
424 | } |
||
425 | //if we don't have all the fields we need, then just fetch the proper model from the DB |
||
426 | if( $tables_needing_to_be_queried){ |
||
427 | if(count($tables_needing_to_be_queried) == 1 && reset($tables_needing_to_be_queried) instanceof EE_Secondary_Table){ |
||
428 | //so we're only missing data from a secondary table. Well that's not too hard to query for |
||
429 | $table_to_query = reset($tables_needing_to_be_queried); |
||
430 | $missing_data = $this->_do_wpdb_query( 'get_row', array( 'SELECT * FROM ' . $table_to_query->get_table_name() . ' WHERE ' . $table_to_query->get_fk_on_table() . ' = ' . $post['ID'], ARRAY_A )); |
||
431 | if ( ! empty( $missing_data )) { |
||
432 | $post = array_merge( $post, $missing_data ); |
||
433 | } |
||
434 | } else { |
||
435 | return $this->get_one_by_ID($post['ID']); |
||
436 | } |
||
437 | } |
||
438 | return $this->instantiate_class_from_array_or_object($post); |
||
439 | |||
440 | } |
||
441 | |||
442 | |||
443 | |||
444 | /** |
||
445 | * Gets the post type associated with this |
||
446 | * @throws EE_Error |
||
447 | * @return string |
||
448 | */ |
||
449 | public function post_type(){ |
||
461 | |||
462 | } |
||
463 |
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.