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 Writing_On_GitHub_Post 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.
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 Writing_On_GitHub_Post, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
10 | class Writing_On_GitHub_Post { |
||
11 | |||
12 | /** |
||
13 | * Api object |
||
14 | * |
||
15 | * @var Writing_On_GitHub_Api |
||
16 | */ |
||
17 | public $api; |
||
18 | |||
19 | /** |
||
20 | * Post ID |
||
21 | * @var integer |
||
22 | */ |
||
23 | public $id = 0; |
||
24 | |||
25 | /** |
||
26 | * Blob object |
||
27 | * @var Writing_On_GitHub_Blob |
||
28 | */ |
||
29 | public $blob; |
||
30 | |||
31 | /** |
||
32 | * Post object |
||
33 | * @var WP_Post |
||
34 | */ |
||
35 | public $post; |
||
36 | |||
37 | /** |
||
38 | * Post args. |
||
39 | * |
||
40 | * @var array |
||
41 | */ |
||
42 | protected $args; |
||
43 | |||
44 | /** |
||
45 | * Post meta. |
||
46 | * |
||
47 | * @var array |
||
48 | */ |
||
49 | protected $meta; |
||
50 | |||
51 | /** |
||
52 | * Whether the post has been saved. |
||
53 | * |
||
54 | * @var bool |
||
55 | */ |
||
56 | protected $new = true; |
||
57 | |||
58 | |||
59 | protected $old_github_path; |
||
60 | |||
61 | /** |
||
62 | * Instantiates a new Post object |
||
63 | * |
||
64 | * @param int|array $id_or_args Either a post ID or an array of arguments. |
||
65 | * @param Writing_On_GitHub_Api $api API object. |
||
66 | * |
||
67 | * @todo remove database operations from this method |
||
68 | */ |
||
69 | public function __construct( $id_or_args, Writing_On_GitHub_Api $api ) { |
||
70 | $this->api = $api; |
||
71 | |||
72 | if ( is_numeric( $id_or_args ) ) { |
||
73 | $this->id = (int) $id_or_args; |
||
74 | $this->post = get_post( $this->id ); |
||
|
|||
75 | $this->new = false; |
||
76 | } |
||
77 | |||
78 | if ( is_array( $id_or_args ) ) { |
||
79 | $this->args = $id_or_args; |
||
80 | |||
81 | if ( isset( $this->args['ID'] ) ) { |
||
82 | $this->post = get_post( $this->args['ID'] ); |
||
83 | |||
84 | if ( $this->post ) { |
||
85 | $this->id = $this->post->ID; |
||
86 | $this->new = false; |
||
87 | } else { |
||
88 | unset( $this->args['ID'] ); |
||
89 | } |
||
90 | } |
||
91 | } |
||
92 | } |
||
93 | |||
94 | public function id() { |
||
95 | return $this->id; |
||
96 | } |
||
97 | |||
98 | /** |
||
99 | * Returns the post type |
||
100 | */ |
||
101 | public function type() { |
||
102 | return $this->post->post_type; |
||
103 | } |
||
104 | |||
105 | /** |
||
106 | * Returns the post type |
||
107 | */ |
||
108 | public function status() { |
||
109 | return $this->post->post_status; |
||
110 | } |
||
111 | |||
112 | /** |
||
113 | * Returns the post name |
||
114 | */ |
||
115 | public function name() { |
||
116 | return $this->post->post_name; |
||
117 | } |
||
118 | |||
119 | /** |
||
120 | * Returns true if the post has a password |
||
121 | * @return bool |
||
122 | */ |
||
123 | public function has_password() { |
||
124 | return ! empty( $this->post->post_password ); |
||
125 | } |
||
126 | |||
127 | /** |
||
128 | * Combines the 2 content parts for GitHub |
||
129 | */ |
||
130 | public function github_content() { |
||
131 | $use_blob = wogh_is_dont_export_content() && $this->blob; |
||
132 | $content = $use_blob ? |
||
133 | $this->blob->post_content() : |
||
134 | $this->post_content(); |
||
135 | |||
136 | return $this->front_matter() . $content; |
||
137 | // $content = $this->front_matter() . $content; |
||
138 | // $ending = apply_filters( 'wogh_line_endings', "\n" ); |
||
139 | |||
140 | // return preg_replace( '~(*BSR_ANYCRLF)\R~', $ending, $content ); |
||
141 | } |
||
142 | |||
143 | /** |
||
144 | * The post's YAML frontmatter |
||
145 | * |
||
146 | * Returns String the YAML frontmatter, ready to be written to the file |
||
147 | */ |
||
148 | public function front_matter() { |
||
149 | return "---\n" . spyc_dump( $this->meta() ) . "---\n"; |
||
150 | } |
||
151 | |||
152 | /** |
||
153 | * Returns the post_content |
||
154 | * |
||
155 | * Markdownify's the content if applicable |
||
156 | */ |
||
157 | public function post_content() { |
||
158 | $content = $this->post->post_content; |
||
159 | |||
160 | if ( function_exists( 'wpmarkdown_html_to_markdown' ) ) { |
||
161 | $content = wpmarkdown_html_to_markdown( $content ); |
||
162 | } else if ( class_exists( 'WPCom_Markdown' ) ) { |
||
163 | if ( WPCom_Markdown::get_instance()->is_markdown( $this->post->ID ) ) { |
||
164 | $content = $this->post->post_content_filtered; |
||
165 | } |
||
166 | } |
||
167 | |||
168 | return apply_filters( 'wogh_content_export', $content, $this ); |
||
169 | } |
||
170 | |||
171 | public function old_github_path() { |
||
172 | return $this->old_github_path; |
||
173 | } |
||
174 | |||
175 | public function set_old_github_path( $path ) { |
||
176 | $this->old_github_path = $path; |
||
177 | update_post_meta( $this->id, '_wogh_github_path', $path ); |
||
178 | } |
||
179 | |||
180 | |||
181 | /** |
||
182 | * Retrieves or calculates the proper GitHub path for a given post |
||
183 | * |
||
184 | * Returns (string) the path relative to repo root |
||
185 | */ |
||
186 | public function github_path() { |
||
187 | $path = $this->github_directory() . $this->github_filename(); |
||
188 | |||
189 | return $path; |
||
190 | } |
||
191 | |||
192 | /** |
||
193 | * Get GitHub directory based on post |
||
194 | * |
||
195 | * @return string |
||
196 | */ |
||
197 | public function github_directory() { |
||
198 | if ( 'publish' !== $this->status() ) { |
||
199 | return apply_filters( 'wogh_directory_unpublished', '_drafts/', $this ); |
||
200 | } |
||
201 | |||
202 | $name = ''; |
||
203 | |||
204 | switch ( $this->type() ) { |
||
205 | case 'post': |
||
206 | $name = 'posts'; |
||
207 | break; |
||
208 | case 'page': |
||
209 | $name = 'pages'; |
||
210 | break; |
||
211 | default: |
||
212 | $obj = get_post_type_object( $this->type() ); |
||
213 | |||
214 | if ( $obj ) { |
||
215 | $name = strtolower( $obj->labels->name ); |
||
216 | } |
||
217 | |||
218 | if ( ! $name ) { |
||
219 | $name = ''; |
||
220 | } |
||
221 | } |
||
222 | |||
223 | if ( $name ) { |
||
224 | $name = '_' . $name . '/'; |
||
225 | } |
||
226 | |||
227 | return apply_filters( 'wogh_directory_published', $name, $this ); |
||
228 | } |
||
229 | |||
230 | /** |
||
231 | * Build GitHub filename based on post |
||
232 | */ |
||
233 | public function github_filename() { |
||
234 | if ( 'post' === $this->type() ) { |
||
235 | $filename = get_the_time( 'Y-m-d-', $this->id ) . $this->get_name() . '.md'; |
||
236 | } else { |
||
237 | $filename = $this->get_name() . '.md'; |
||
238 | } |
||
239 | |||
240 | return apply_filters( 'wogh_filename', $filename, $this ); |
||
241 | } |
||
242 | |||
243 | /** |
||
244 | * Returns a post slug we can use in the GitHub filename |
||
245 | * |
||
246 | * @return string |
||
247 | */ |
||
248 | protected function get_name() { |
||
249 | if ( '' !== $this->name() ) { |
||
250 | return $this->name(); |
||
251 | } |
||
252 | |||
253 | return sanitize_title( get_the_title( $this->post ) ); |
||
254 | } |
||
255 | |||
256 | /** |
||
257 | * is put on github |
||
258 | * @return boolean |
||
259 | */ |
||
260 | public function is_on_github() { |
||
261 | $sha = get_post_meta( $this->id, '_wogh_sha', true ); |
||
262 | $github_path = get_post_meta( $this->id, '_wogh_github_path', true ); |
||
263 | if ( $sha && $github_path ) { |
||
264 | return true; |
||
265 | } |
||
266 | return false; |
||
267 | } |
||
268 | |||
269 | /** |
||
270 | * Returns the URL for the post on GitHub. |
||
271 | * |
||
272 | * @return string |
||
273 | */ |
||
274 | public function github_view_url() { |
||
275 | $github_path = get_post_meta( $this->id, '_wogh_github_path', true ); |
||
276 | $repository = $this->api->fetch()->repository(); |
||
277 | $branch = $this->api->fetch()->branch(); |
||
278 | |||
279 | return "https://github.com/$repository/blob/$branch/$github_path"; |
||
280 | } |
||
281 | |||
282 | /** |
||
283 | * Returns the URL for the post on GitHub. |
||
284 | * |
||
285 | * @return string |
||
286 | */ |
||
287 | public function github_edit_url() { |
||
288 | $github_path = get_post_meta( $this->id, '_wogh_github_path', true ); |
||
289 | $repository = $this->api->fetch()->repository(); |
||
290 | $branch = $this->api->fetch()->branch(); |
||
291 | |||
292 | return "https://github.com/$repository/edit/$branch/$github_path"; |
||
293 | } |
||
294 | |||
295 | /** |
||
296 | * Retrieve post type directory from blob path. |
||
297 | * |
||
298 | * @param string $path Path string. |
||
299 | * |
||
300 | * @return string |
||
301 | */ |
||
302 | public function get_directory_from_path( $path ) { |
||
303 | $directory = explode( '/', $path ); |
||
304 | $directory = count( $directory ) > 0 ? $directory[0] : ''; |
||
305 | |||
306 | return $directory; |
||
307 | } |
||
308 | |||
309 | /** |
||
310 | * Determines the last author to modify the post |
||
311 | * |
||
312 | * Returns Array an array containing the author name and email |
||
313 | */ |
||
314 | public function last_modified_author() { |
||
315 | if ( $last_id = get_post_meta( $this->id, '_edit_last', true ) ) { |
||
316 | $user = get_userdata( $last_id ); |
||
317 | |||
318 | if ( $user ) { |
||
319 | return array( 'name' => $user->display_name, 'email' => $user->user_email ); |
||
320 | } |
||
321 | } |
||
322 | |||
323 | return array(); |
||
324 | } |
||
325 | |||
326 | /** |
||
327 | * The post's sha |
||
328 | * Cached as post meta, or will make a live call if need be |
||
329 | * |
||
330 | * Returns String the sha1 hash |
||
331 | */ |
||
332 | public function sha() { |
||
333 | $sha = get_post_meta( $this->id, '_wogh_sha', true ); |
||
334 | |||
335 | // If we've done a full export and we have no sha |
||
336 | // then we should try a live check to see if it exists. |
||
337 | // if ( ! $sha && 'yes' === get_option( '_wogh_fully_exported' ) ) { |
||
338 | |||
339 | // // @todo could we eliminate this by calling down the full tree and searching it |
||
340 | // $data = $this->api->fetch()->remote_contents( $this ); |
||
341 | |||
342 | // if ( ! is_wp_error( $data ) ) { |
||
343 | // update_post_meta( $this->id, '_wogh_sha', $data->sha ); |
||
344 | // $sha = $data->sha; |
||
345 | // } |
||
346 | // } |
||
347 | |||
348 | // if the sha still doesn't exist, then it's empty |
||
349 | if ( ! $sha || is_wp_error( $sha ) ) { |
||
350 | $sha = ''; |
||
351 | } |
||
352 | |||
353 | return $sha; |
||
354 | } |
||
355 | |||
356 | /** |
||
357 | * Save the sha to post |
||
358 | * |
||
359 | * @param string $sha |
||
360 | */ |
||
361 | public function set_sha( $sha ) { |
||
363 | } |
||
364 | |||
365 | /** |
||
366 | * The post's metadata |
||
367 | * |
||
368 | * Returns Array the post's metadata |
||
369 | */ |
||
370 | public function meta() { |
||
371 | $meta = array( |
||
372 | 'ID' => $this->id, |
||
373 | 'post_title' => get_the_title( $this->post ), |
||
374 | 'post_name' => $this->post->post_name, |
||
375 | 'author' => ( $author = get_userdata( $this->post->post_author ) ) ? $author->display_name : '', |
||
376 | 'post_date' => $this->post->post_date, |
||
377 | 'post_excerpt' => $this->post->post_excerpt, |
||
378 | 'layout' => get_post_type( $this->post ), |
||
379 | 'link' => get_permalink( $this->post ), |
||
380 | 'published' => 'publish' === $this->status() ? true : false, |
||
381 | 'tags' => wp_get_post_tags( $this->id, array( 'fields' => 'names' ) ), |
||
382 | 'categories' => wp_get_post_categories( $this->id, array( 'fields' => 'names' ) ) |
||
383 | ); |
||
384 | if ( empty($this->post->post_name) ) { |
||
385 | unset($meta['post_name']); |
||
386 | } |
||
387 | if ( empty($this->post->post_excerpt) ) { |
||
388 | unset($meta['post_excerpt']); |
||
389 | } |
||
390 | if ( 'yes' == get_option('wogh_ignore_author') ) { |
||
391 | unset($meta['author']); |
||
392 | } |
||
393 | |||
394 | //convert traditional post_meta values, hide hidden values, skip already populated values |
||
395 | // foreach ( get_post_custom( $this->id ) as $key => $value ) { |
||
396 | |||
397 | // if ( '_' === substr( $key, 0, 1 ) || isset( $meta[ $key ] ) ) { |
||
398 | // continue; |
||
399 | // } |
||
400 | |||
401 | // $meta[ $key ] = $value; |
||
402 | |||
403 | // } |
||
404 | |||
405 | return apply_filters( 'wogh_post_meta', $meta, $this ); |
||
406 | } |
||
407 | |||
408 | /** |
||
409 | * Returns whether the Post has been saved in the DB yet. |
||
410 | * |
||
411 | * @return bool |
||
412 | */ |
||
413 | public function is_new() { |
||
415 | } |
||
416 | |||
417 | /** |
||
418 | * Sets the Post's meta. |
||
419 | * |
||
420 | * @param array $meta |
||
421 | */ |
||
422 | public function set_meta( $meta ) { |
||
423 | $this->meta = $meta; |
||
424 | } |
||
425 | |||
426 | /** |
||
427 | * Returns the Post's arguments. |
||
428 | * |
||
429 | * @return array |
||
430 | */ |
||
431 | public function get_args() { |
||
432 | return $this->args; |
||
433 | } |
||
434 | |||
435 | /** |
||
436 | * Returns the Post's meta. |
||
437 | * |
||
438 | * @return array |
||
439 | */ |
||
440 | public function get_meta() { |
||
442 | } |
||
443 | |||
444 | /** |
||
445 | * Get the blob |
||
446 | * @return Writing_On_GitHub_Blob |
||
447 | */ |
||
448 | public function get_blob() { |
||
449 | return $this->blob; |
||
450 | } |
||
451 | |||
452 | /** |
||
453 | * Set the blob |
||
454 | * @param Writing_On_GitHub_Blob $blob |
||
455 | */ |
||
456 | public function set_blob( Writing_On_GitHub_Blob $blob ) { |
||
457 | $this->blob = $blob; |
||
458 | } |
||
459 | |||
460 | /** |
||
461 | * Sets the Post's WP_Post object. |
||
462 | * |
||
463 | * @param WP_Post $post |
||
464 | * |
||
465 | * @return $this |
||
466 | */ |
||
467 | public function set_post( WP_Post $post ) { |
||
468 | $this->post = $post; |
||
469 | $this->id = $post->ID; |
||
470 | |||
471 | return $this; |
||
472 | } |
||
473 | |||
474 | /** |
||
475 | * Transforms the Post into a Blob. |
||
476 | * |
||
477 | * @return Writing_On_GitHub_Blob |
||
478 | */ |
||
479 | public function to_blob() { |
||
487 | } |
||
488 | } |
||
489 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.