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 WP_REST_Revisions_Controller 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 WP_REST_Revisions_Controller, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
3 | class WP_REST_Revisions_Controller extends WP_REST_Controller { |
||
4 | |||
5 | private $parent_post_type; |
||
6 | private $parent_controller; |
||
7 | private $parent_base; |
||
8 | |||
9 | public function __construct( $parent_post_type ) { |
||
17 | |||
18 | /** |
||
19 | * Register routes for revisions based on post types supporting revisions |
||
20 | */ |
||
21 | public function register_routes() { |
||
51 | |||
52 | /** |
||
53 | * Check if a given request has access to get revisions |
||
54 | * |
||
55 | * @param WP_REST_Request $request Full data about the request. |
||
56 | * @return WP_Error|boolean |
||
57 | */ |
||
58 | public function get_items_permissions_check( $request ) { |
||
71 | |||
72 | /** |
||
73 | * Get a collection of revisions |
||
74 | * |
||
75 | * @param WP_REST_Request $request Full data about the request. |
||
76 | * @return WP_Error|WP_REST_Response |
||
77 | */ |
||
78 | public function get_items( $request ) { |
||
94 | |||
95 | /** |
||
96 | * Check if a given request has access to get a specific revision |
||
97 | * |
||
98 | * @param WP_REST_Request $request Full data about the request. |
||
99 | * @return WP_Error|boolean |
||
100 | */ |
||
101 | public function get_item_permissions_check( $request ) { |
||
104 | |||
105 | /** |
||
106 | * Get one revision from the collection |
||
107 | * |
||
108 | * @param WP_REST_Request $request Full data about the request. |
||
109 | * @return WP_Error|array |
||
110 | */ |
||
111 | public function get_item( $request ) { |
||
126 | |||
127 | /** |
||
128 | * Check if a given request has access to delete a revision |
||
129 | * |
||
130 | * @param WP_REST_Request $request Full details about the request. |
||
131 | * @return WP_Error|boolean |
||
132 | */ |
||
133 | View Code Duplication | public function delete_item_permissions_check( $request ) { |
|
134 | |||
135 | $response = $this->get_items_permissions_check( $request ); |
||
136 | if ( ! $response || is_wp_error( $response ) ) { |
||
137 | return $response; |
||
138 | } |
||
139 | |||
140 | $post = $this->get_post( $request['id'] ); |
||
141 | if ( ! $post ) { |
||
142 | return new WP_Error( 'rest_post_invalid_id', __( 'Invalid revision id.' ), array( 'status' => 404 ) ); |
||
143 | } |
||
144 | $post_type = get_post_type_object( 'revision' ); |
||
145 | return current_user_can( $post_type->cap->delete_post, $post->ID ); |
||
146 | } |
||
147 | |||
148 | /** |
||
149 | * Delete a single revision |
||
150 | * |
||
151 | * @param WP_REST_Request $request Full details about the request |
||
152 | * @return WP_Error|boolean |
||
153 | */ |
||
154 | public function delete_item( $request ) { |
||
173 | |||
174 | /** |
||
175 | * Prepare the revision for the REST response |
||
176 | * |
||
177 | * @param WP_Post $post Post revision object. |
||
178 | * @param WP_REST_Request $request Request object. |
||
179 | * @return WP_REST_Response $response |
||
180 | */ |
||
181 | public function prepare_item_for_response( $post, $request ) { |
||
182 | |||
183 | $schema = $this->get_item_schema(); |
||
184 | |||
185 | $data = array(); |
||
186 | |||
187 | if ( ! empty( $schema['properties']['author'] ) ) { |
||
188 | $data['author'] = $post->post_author; |
||
189 | } |
||
190 | |||
191 | View Code Duplication | if ( ! empty( $schema['properties']['date'] ) ) { |
|
192 | $data['date'] = $this->prepare_date_response( $post->post_date_gmt, $post->post_date ); |
||
193 | } |
||
194 | |||
195 | if ( ! empty( $schema['properties']['date_gmt'] ) ) { |
||
196 | $data['date_gmt'] = $this->prepare_date_response( $post->post_date_gmt ); |
||
197 | } |
||
198 | |||
199 | if ( ! empty( $schema['properties']['id'] ) ) { |
||
200 | $data['id'] = $post->ID; |
||
201 | } |
||
202 | |||
203 | View Code Duplication | if ( ! empty( $schema['properties']['modified'] ) ) { |
|
204 | $data['modified'] = $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified ); |
||
205 | } |
||
206 | |||
207 | View Code Duplication | if ( ! empty( $schema['properties']['modified_gmt'] ) ) { |
|
208 | $data['modified_gmt'] = $this->prepare_date_response( $post->post_modified_gmt ); |
||
209 | } |
||
210 | |||
211 | if ( ! empty( $schema['properties']['parent'] ) ) { |
||
212 | $data['parent'] = (int) $post->post_parent; |
||
213 | } |
||
214 | |||
215 | if ( ! empty( $schema['properties']['slug'] ) ) { |
||
216 | $data['slug'] = $post->post_name; |
||
217 | } |
||
218 | |||
219 | View Code Duplication | if ( ! empty( $schema['properties']['guid'] ) ) { |
|
220 | $data['guid'] = array( |
||
221 | /** This filter is documented in wp-includes/post-template.php */ |
||
222 | 'rendered' => apply_filters( 'get_the_guid', $post->guid ), |
||
223 | 'raw' => $post->guid, |
||
224 | ); |
||
225 | } |
||
226 | |||
227 | if ( ! empty( $schema['properties']['title'] ) ) { |
||
228 | $data['title'] = array( |
||
229 | 'raw' => $post->post_title, |
||
230 | 'rendered' => get_the_title( $post->ID ), |
||
231 | ); |
||
232 | } |
||
233 | |||
234 | View Code Duplication | if ( ! empty( $schema['properties']['content'] ) ) { |
|
235 | |||
236 | $data['content'] = array( |
||
237 | 'raw' => $post->post_content, |
||
238 | /** This filter is documented in wp-includes/post-template.php */ |
||
239 | 'rendered' => apply_filters( 'the_content', $post->post_content ), |
||
240 | ); |
||
241 | } |
||
242 | |||
243 | if ( ! empty( $schema['properties']['excerpt'] ) ) { |
||
244 | $data['excerpt'] = array( |
||
245 | 'raw' => $post->post_excerpt, |
||
246 | 'rendered' => $this->prepare_excerpt_response( $post->post_excerpt, $post ), |
||
247 | ); |
||
248 | } |
||
249 | |||
250 | $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; |
||
251 | $data = $this->add_additional_fields_to_object( $data, $request ); |
||
252 | $data = $this->filter_response_by_context( $data, $context ); |
||
253 | $response = rest_ensure_response( $data ); |
||
254 | |||
255 | if ( ! empty( $data['parent'] ) ) { |
||
256 | $response->add_link( 'parent', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->parent_base, $data['parent'] ) ) ); |
||
257 | } |
||
258 | |||
259 | /** |
||
260 | * Filter a revision returned from the API. |
||
261 | * |
||
262 | * Allows modification of the revision right before it is returned. |
||
263 | * |
||
264 | * @param WP_REST_Response $response The response object. |
||
265 | * @param WP_Post $post The original revision object. |
||
266 | * @param WP_REST_Request $request Request used to generate the response. |
||
267 | */ |
||
268 | return apply_filters( 'rest_prepare_revision', $response, $post, $request ); |
||
269 | } |
||
270 | |||
271 | /** |
||
272 | * Check the post_date_gmt or modified_gmt and prepare any post or |
||
273 | * modified date for single post output. |
||
274 | * |
||
275 | * @param string $date_gmt |
||
276 | * @param string|null $date |
||
277 | * @return string|null ISO8601/RFC3339 formatted datetime. |
||
278 | */ |
||
279 | View Code Duplication | protected function prepare_date_response( $date_gmt, $date = null ) { |
|
290 | |||
291 | /** |
||
292 | * Get the revision's schema, conforming to JSON Schema |
||
293 | * |
||
294 | * @return array |
||
295 | */ |
||
296 | public function get_item_schema() { |
||
374 | |||
375 | /** |
||
376 | * Get the query params for collections |
||
377 | * |
||
378 | * @return array |
||
379 | */ |
||
380 | public function get_collection_params() { |
||
385 | |||
386 | /** |
||
387 | * Check the post excerpt and prepare it for single post output. |
||
388 | * |
||
389 | * @param string $excerpt |
||
390 | * @return string|null $excerpt |
||
391 | */ |
||
392 | protected function prepare_excerpt_response( $excerpt, $post ) { |
||
403 | |||
404 | } |
||
405 |
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.