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 Read 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 Read, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
19 | class Read extends Base { |
||
20 | |||
21 | |||
22 | |||
23 | |||
24 | |||
25 | public function __construct() { |
||
29 | |||
30 | /** |
||
31 | * Handles requests to get all (or a filtered subset) of entities for a particular model |
||
32 | * @param \WP_REST_Request $request |
||
33 | * @return \WP_REST_Response|\WP_Error |
||
34 | */ |
||
35 | View Code Duplication | public static function handle_request_get_all( \WP_REST_Request $request) { |
|
|
|||
36 | $controller = new Read(); |
||
37 | try{ |
||
38 | $matches = $controller->parse_route( |
||
39 | $request->get_route(), |
||
40 | '~' . \EED_Core_Rest_Api::ee_api_namespace_for_regex . '(.*)~', |
||
41 | array( 'version', 'model' ) |
||
42 | ); |
||
43 | $controller->set_requested_version( $matches[ 'version' ] ); |
||
44 | $model_name_singular = \EEH_Inflector::singularize_and_upper( $matches[ 'model' ] ); |
||
45 | if ( ! $controller->get_model_version_info()->is_model_name_in_this_version( $model_name_singular ) ) { |
||
46 | return $controller->send_response( |
||
47 | new \WP_Error( |
||
48 | 'endpoint_parsing_error', |
||
49 | sprintf( |
||
50 | __( 'There is no model for endpoint %s. Please contact event espresso support', 'event_espresso' ), |
||
51 | $model_name_singular |
||
52 | ) |
||
53 | ) |
||
54 | ); |
||
55 | } |
||
56 | return $controller->send_response( |
||
57 | $controller->get_entities_from_model( |
||
58 | $controller->get_model_version_info()->load_model( $model_name_singular ), |
||
59 | $request |
||
60 | ) |
||
61 | ); |
||
62 | } catch( \Exception $e ) { |
||
63 | return $controller->send_response( $e ); |
||
64 | } |
||
65 | } |
||
66 | |||
67 | /** |
||
68 | * Gets a single entity related to the model indicated in the path and its id |
||
69 | * |
||
70 | * @param \WP_Rest_Request $request |
||
71 | * @return \WP_REST_Response|\WP_Error |
||
72 | */ |
||
73 | View Code Duplication | public static function handle_request_get_one( \WP_Rest_Request $request ) { |
|
74 | $controller = new Read(); |
||
75 | try{ |
||
76 | $matches = $controller->parse_route( |
||
77 | $request->get_route(), |
||
78 | '~' . \EED_Core_Rest_Api::ee_api_namespace_for_regex . '(.*)/(.*)~', |
||
79 | array( 'version', 'model', 'id' ) ); |
||
80 | $controller->set_requested_version( $matches[ 'version' ] ); |
||
81 | $model_name_singular = \EEH_Inflector::singularize_and_upper( $matches[ 'model' ] ); |
||
82 | if ( ! $controller->get_model_version_info()->is_model_name_in_this_version( $model_name_singular ) ) { |
||
83 | return $controller->send_response( |
||
84 | new \WP_Error( |
||
85 | 'endpoint_parsing_error', |
||
86 | sprintf( |
||
87 | __( 'There is no model for endpoint %s. Please contact event espresso support', 'event_espresso' ), |
||
88 | $model_name_singular |
||
89 | ) |
||
90 | ) |
||
91 | ); |
||
92 | } |
||
93 | return $controller->send_response( |
||
94 | $controller->get_entity_from_model( |
||
95 | $controller->get_model_version_info()->load_model( $model_name_singular ), |
||
96 | $request |
||
97 | ) |
||
98 | ); |
||
99 | } catch( \Exception $e ) { |
||
100 | return $controller->send_response( $e ); |
||
101 | } |
||
102 | } |
||
103 | |||
104 | /** |
||
105 | * |
||
106 | * Gets all the related entities (or if its a belongs-to relation just the one) |
||
107 | * to the item with the given id |
||
108 | * |
||
109 | * @param \WP_REST_Request $request |
||
110 | * @return \WP_REST_Response|\WP_Error |
||
111 | */ |
||
112 | public static function handle_request_get_related( \WP_REST_Request $request ) { |
||
113 | $controller = new Read(); |
||
114 | try{ |
||
115 | $matches = $controller->parse_route( |
||
116 | $request->get_route(), |
||
117 | '~' . \EED_Core_Rest_Api::ee_api_namespace_for_regex . '(.*)/(.*)/(.*)~', |
||
118 | array( 'version', 'model', 'id', 'related_model' ) |
||
119 | ); |
||
120 | $controller->set_requested_version( $matches[ 'version' ] ); |
||
121 | $main_model_name_singular = \EEH_Inflector::singularize_and_upper( $matches[ 'model' ] ); |
||
122 | if ( ! $controller->get_model_version_info()->is_model_name_in_this_version( $main_model_name_singular ) ) { |
||
123 | return $controller->send_response( |
||
124 | new \WP_Error( |
||
125 | 'endpoint_parsing_error', |
||
126 | sprintf( |
||
127 | __( 'There is no model for endpoint %s. Please contact event espresso support', 'event_espresso' ), |
||
128 | $main_model_name_singular |
||
129 | ) |
||
130 | ) |
||
131 | ); |
||
132 | } |
||
133 | $main_model = $controller->get_model_version_info()->load_model( $main_model_name_singular ); |
||
134 | $related_model_name_singular = \EEH_Inflector::singularize_and_upper( $matches[ 'related_model' ] ); |
||
135 | if ( ! $controller->get_model_version_info()->is_model_name_in_this_version( $related_model_name_singular ) ) { |
||
136 | return $controller->send_response( |
||
137 | new \WP_Error( |
||
138 | 'endpoint_parsing_error', |
||
139 | sprintf( |
||
140 | __( 'There is no model for endpoint %s. Please contact event espresso support', 'event_espresso' ), |
||
141 | $related_model_name_singular |
||
142 | ) |
||
143 | ) |
||
144 | ); |
||
145 | } |
||
146 | |||
147 | return $controller->send_response( |
||
148 | $controller->get_entities_from_relation( |
||
149 | $request->get_param( 'id' ), |
||
150 | $main_model->related_settings_for( $related_model_name_singular ) , |
||
151 | $request |
||
152 | ) |
||
153 | ); |
||
154 | } catch( \Exception $e ) { |
||
155 | return $controller->send_response( $e ); |
||
156 | } |
||
157 | } |
||
158 | |||
159 | |||
160 | |||
161 | /** |
||
162 | * Gets a collection for the given model and filters |
||
163 | * |
||
164 | * @param \EEM_Base $model |
||
165 | * @param \WP_REST_Request $request |
||
166 | * @return array |
||
167 | */ |
||
168 | public function get_entities_from_model( $model, $request) { |
||
169 | $query_params = $this->create_model_query_params( $model, $request->get_params() ); |
||
170 | if( ! Capabilities::current_user_has_partial_access_to( $model, $query_params[ 'caps' ] ) ) { |
||
171 | $model_name_plural = \EEH_Inflector::pluralize_and_lower( $model->get_this_model_name() ); |
||
172 | return new \WP_Error( |
||
173 | sprintf( 'rest_%s_cannot_list', $model_name_plural ), |
||
174 | sprintf( |
||
175 | __( 'Sorry, you are not allowed to list %1$s. Missing permissions: %2$s', 'event_espresso' ), |
||
176 | $model_name_plural, |
||
177 | Capabilities::get_missing_permissions_string( $model, $query_params[ 'caps' ] ) |
||
178 | ), |
||
179 | array( 'status' => 403 ) |
||
180 | ); |
||
181 | } |
||
182 | |||
183 | $this->_set_debug_info( 'model query params', $query_params ); |
||
184 | $results = $model->get_all_wpdb_results( $query_params ); |
||
185 | $nice_results = array( ); |
||
186 | foreach ( $results as $result ) { |
||
187 | $nice_results[ ] = $this->create_entity_from_wpdb_result( |
||
188 | $model, |
||
189 | $result, |
||
190 | $request->get_param( 'include' ), |
||
191 | $query_params[ 'caps' ] |
||
192 | ); |
||
193 | } |
||
194 | return $nice_results; |
||
195 | } |
||
196 | |||
197 | /** |
||
198 | * Gets the collection for given relation object |
||
199 | * |
||
200 | * The same as Read::get_entities_from_model(), except if the relation |
||
201 | * is a HABTM relation, in which case it merges any non-foreign-key fields from |
||
202 | * the join-model-object into the results |
||
203 | * @param string $id the ID of the thing we are fetching related stuff from |
||
204 | * @param \EE_Model_Relation_Base $relation |
||
205 | * @param \WP_REST_Request $request |
||
206 | * @return array |
||
207 | */ |
||
208 | public function get_entities_from_relation( $id, $relation, $request ) { |
||
209 | $context = $this->validate_context( $request->get_param( 'caps' )); |
||
210 | $model = $relation->get_this_model(); |
||
211 | $related_model = $relation->get_other_model(); |
||
212 | //check if they can access the 1st model object |
||
213 | $query_params = array( array( $model->primary_key_name() => $id ),'limit' => 1 ); |
||
214 | if( $model instanceof \EEM_Soft_Delete_Base ){ |
||
215 | $query_params = $model->alter_query_params_so_deleted_and_undeleted_items_included($query_params); |
||
216 | } |
||
217 | $restricted_query_params = $query_params; |
||
218 | $restricted_query_params[ 'caps' ] = $context; |
||
219 | $this->_set_debug_info( 'main model query params', $restricted_query_params ); |
||
220 | $this->_set_debug_info( 'missing caps', Capabilities::get_missing_permissions_string( $related_model, $context ) ); |
||
221 | |||
222 | if( |
||
223 | ! ( |
||
224 | Capabilities::current_user_has_partial_access_to( $related_model, $context ) |
||
225 | && $model->exists( $restricted_query_params ) |
||
226 | ) |
||
227 | ){ |
||
228 | if( $relation instanceof \EE_Belongs_To_Relation ) { |
||
229 | $related_model_name_maybe_plural = strtolower( $related_model->get_this_model_name() ); |
||
230 | }else{ |
||
231 | $related_model_name_maybe_plural = \EEH_Inflector::pluralize_and_lower( $related_model->get_this_model_name() ); |
||
232 | } |
||
233 | return new \WP_Error( |
||
234 | sprintf( 'rest_%s_cannot_list', $related_model_name_maybe_plural ), |
||
235 | sprintf( |
||
236 | __( 'Sorry, you are not allowed to list %1$s related to %2$s. Missing permissions: %3$s', 'event_espresso' ), |
||
237 | $related_model_name_maybe_plural, |
||
238 | $relation->get_this_model()->get_this_model_name(), |
||
239 | implode( |
||
240 | ',', |
||
241 | array_keys( |
||
242 | Capabilities::get_missing_permissions( $related_model, $context ) |
||
243 | ) |
||
244 | ) |
||
245 | ), |
||
246 | array( 'status' => 403 ) |
||
247 | ); |
||
248 | } |
||
249 | $query_params = $this->create_model_query_params( $relation->get_other_model(), $request->get_params() ); |
||
250 | $query_params[0][ $relation->get_this_model()->get_this_model_name() . '.' . $relation->get_this_model()->primary_key_name() ] = $id; |
||
251 | $query_params[ 'default_where_conditions' ] = 'none'; |
||
252 | $query_params[ 'caps' ] = $context; |
||
253 | $this->_set_debug_info( 'model query params', $query_params ); |
||
254 | $results = $relation->get_other_model()->get_all_wpdb_results( $query_params ); |
||
255 | $nice_results = array(); |
||
256 | foreach( $results as $result ) { |
||
257 | $nice_result = $this->create_entity_from_wpdb_result( |
||
258 | $relation->get_other_model(), |
||
259 | $result, |
||
260 | $request->get_param( 'include' ), |
||
261 | $query_params[ 'caps' ] |
||
262 | ); |
||
263 | if( $relation instanceof \EE_HABTM_Relation ) { |
||
264 | //put the unusual stuff (properties from the HABTM relation) first, and make sure |
||
265 | //if there are conflicts we prefer the properties from the main model |
||
266 | $join_model_result = $this->create_entity_from_wpdb_result( |
||
267 | $relation->get_join_model(), |
||
268 | $result, |
||
269 | $request->get_param( 'include' ), |
||
270 | $query_params[ 'caps' ] |
||
271 | ); |
||
272 | $joined_result = array_merge( $nice_result, $join_model_result ); |
||
273 | //but keep the meta stuff from the main model |
||
274 | if( isset( $nice_result['meta'] ) ){ |
||
275 | $joined_result['meta'] = $nice_result['meta']; |
||
276 | } |
||
277 | $nice_result = $joined_result; |
||
278 | } |
||
279 | $nice_results[] = $nice_result; |
||
280 | } |
||
281 | if( $relation instanceof \EE_Belongs_To_Relation ){ |
||
282 | return array_shift( $nice_results ); |
||
283 | }else{ |
||
284 | return $nice_results; |
||
285 | } |
||
286 | } |
||
287 | |||
288 | |||
289 | |||
290 | /** |
||
291 | * Changes database results into REST API entities |
||
292 | * @param \EEM_Base $model |
||
293 | * @param array $db_row like results from $wpdb->get_results() |
||
294 | * @param string $include string indicating which fields to include in the response, |
||
295 | * including fields on related entities. |
||
296 | * Eg, when querying for events, an include string like: |
||
297 | * "...&include=EVT_name,EVT_desc,Datetime, Datetime.Ticket.TKT_ID, Datetime.Ticket.TKT_name, Datetime.Ticket.TKT_price" |
||
298 | * instructs us to only include the event's name and description, |
||
299 | * each related datetime, and each related datetime's ticket's name and price. |
||
300 | * Eg json would be: |
||
301 | * '{ |
||
302 | * "EVT_ID":12, |
||
303 | * "EVT_name":"star wars party", |
||
304 | * "EVT_desc":"so cool...", |
||
305 | * "datetimes":[{ |
||
306 | * "DTT_ID":123,..., |
||
307 | * "tickets":[{ |
||
308 | * "TKT_ID":234, |
||
309 | * "TKT_name":"student rate", |
||
310 | * "TKT_price":32.0 |
||
311 | * },...] |
||
312 | * }] |
||
313 | * }', |
||
314 | * ie, events with all their associated datetimes |
||
315 | * (including ones that are trashed) embedded in the json object, |
||
316 | * and each datetime also has each associated ticket embedded in its json object. |
||
317 | * @param string $context one of the return values from EEM_Base::valid_cap_contexts() |
||
318 | * @return array ready for being converted into json for sending to client |
||
319 | */ |
||
320 | public function create_entity_from_wpdb_result( $model, $db_row, $include, $context ) { |
||
465 | |||
466 | /** |
||
467 | * Gets the full URL to the resource, taking the requested version into account |
||
468 | * @param string $link_part_after_version_and_slash eg "events/10/datetimes" |
||
469 | * @return string url eg "http://mysite.com/wp-json/ee/v4.6/events/10/datetimes" |
||
470 | */ |
||
471 | public function get_versioned_link_to( $link_part_after_version_and_slash ) { |
||
474 | |||
475 | /** |
||
476 | * Gets the correct lowercase name for the relation in the API according |
||
477 | * to the relation's type |
||
478 | * @param string $relation_name |
||
479 | * @param \EE_Model_Relation_Base $relation_obj |
||
480 | * @return string |
||
481 | */ |
||
482 | public static function get_related_entity_name( $relation_name, $relation_obj ){ |
||
489 | |||
490 | // public function |
||
491 | |||
492 | |||
493 | /** |
||
494 | * Gets the one model object with the specified id for the specified model |
||
495 | * @param \EEM_Base $model |
||
496 | * @param \WP_REST_Request $request |
||
497 | * @return array |
||
498 | */ |
||
499 | public function get_entity_from_model( $model, $request ) { |
||
500 | $query_params = array( array( $model->primary_key_name() => $request->get_param( 'id' ) ),'limit' => 1); |
||
501 | if( $model instanceof \EEM_Soft_Delete_Base ){ |
||
502 | $query_params = $model->alter_query_params_so_deleted_and_undeleted_items_included($query_params); |
||
503 | } |
||
504 | $restricted_query_params = $query_params; |
||
505 | $restricted_query_params[ 'caps' ] = $this->validate_context( $request->get_param( 'caps' ) ); |
||
506 | $this->_set_debug_info( 'model query params', $restricted_query_params ); |
||
507 | $model_rows = $model->get_all_wpdb_results( $restricted_query_params ); |
||
508 | if ( ! empty ( $model_rows ) ) { |
||
509 | return $this->create_entity_from_wpdb_result( |
||
510 | $model, |
||
511 | array_shift( $model_rows ), |
||
512 | $request->get_param( 'include' ), |
||
513 | $this->validate_context( $request->get_param( 'caps' ) ) ); |
||
514 | } else { |
||
515 | //ok let's test to see if we WOULD have found it, had we not had restrictions from missing capabilities |
||
516 | $lowercase_model_name = strtolower( $model->get_this_model_name() ); |
||
517 | $model_rows_found_sans_restrictions = $model->get_all_wpdb_results( $query_params ); |
||
518 | if( ! empty( $model_rows_found_sans_restrictions ) ) { |
||
519 | //you got shafted- it existed but we didn't want to tell you! |
||
520 | return new \WP_Error( |
||
521 | 'rest_user_cannot_read', |
||
522 | sprintf( |
||
523 | __( 'Sorry, you cannot read this %1$s. Missing permissions are: %2$s', 'event_espresso' ), |
||
524 | strtolower( $model->get_this_model_name() ), |
||
525 | Capabilities::get_missing_permissions_string( |
||
526 | $model, |
||
527 | $this->validate_context( $request->get_param( 'caps' ) ) ) |
||
528 | ), |
||
529 | array( 'status' => 403 ) |
||
530 | ); |
||
531 | View Code Duplication | } else { |
|
532 | //it's not you. It just doesn't exist |
||
533 | return new \WP_Error( |
||
534 | sprintf( 'rest_%s_invalid_id', $lowercase_model_name ), |
||
535 | sprintf( __( 'Invalid %s ID.', 'event_espresso' ), $lowercase_model_name ), |
||
536 | array( 'status' => 404 ) |
||
537 | ); |
||
538 | } |
||
539 | } |
||
540 | } |
||
541 | |||
542 | /** |
||
543 | * If a context is provided which isn't valid, maybe it was added in a future |
||
544 | * version so just treat it as a default read |
||
545 | * |
||
546 | * @param string $context |
||
547 | * @return string array key of EEM_Base::cap_contexts_to_cap_action_map() |
||
548 | */ |
||
549 | public function validate_context( $context ) { |
||
560 | |||
561 | |||
562 | |||
563 | /** |
||
564 | * Translates API filter get parameter into $query_params array used by EEM_Base::get_all() |
||
565 | * |
||
566 | * @param \EEM_Base $model |
||
567 | * @param array $query_parameters from $_GET parameter @see Read:handle_request_get_all |
||
568 | * @return array like what EEM_Base::get_all() expects or FALSE to indicate |
||
569 | * that absolutely no results should be returned |
||
570 | * @throws \EE_Error |
||
571 | */ |
||
572 | public function create_model_query_params( $model, $query_parameters ) { |
||
640 | |||
641 | |||
642 | |||
643 | /** |
||
644 | * Changes the REST-style query params for use in the models |
||
645 | * |
||
646 | * @param \EEM_Base $model |
||
647 | * @param array $query_params sub-array from @see EEM_Base::get_all() |
||
648 | * @return array |
||
649 | */ |
||
650 | View Code Duplication | public function prepare_rest_query_params_key_for_models( $model, $query_params ) { |
|
672 | |||
673 | |||
674 | /** |
||
675 | * Parses the $include_string so we fetch all the field names relating to THIS model |
||
676 | * (ie have NO period in them), or for the provided model (ie start with the model |
||
677 | * name and then a period). |
||
678 | * @param string $include_string @see Read:handle_request_get_all |
||
679 | * @param string $model_name |
||
680 | * @return array of fields for this model. If $model_name is provided, then |
||
681 | * the fields for that model, with the model's name removed from each. |
||
682 | */ |
||
683 | public function extract_includes_for_this_model( $include_string, $model_name = null ) { |
||
718 | } |
||
719 | |||
721 | // End of file Read.php |
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.