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 Jetpack_Lazy_Images 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 Jetpack_Lazy_Images, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
5 | class Jetpack_Lazy_Images { |
||
6 | private static $__instance = null; |
||
7 | |||
8 | /** |
||
9 | * Singleton implementation |
||
10 | * |
||
11 | * @return object |
||
12 | */ |
||
13 | public static function instance() { |
||
14 | if ( is_null( self::$__instance ) ) { |
||
15 | self::$__instance = new Jetpack_Lazy_Images(); |
||
16 | } |
||
17 | |||
18 | return self::$__instance; |
||
19 | } |
||
20 | |||
21 | /** |
||
22 | * Registers actions |
||
23 | */ |
||
24 | private function __construct() { |
||
25 | if ( is_admin() ) { |
||
26 | return; |
||
27 | } |
||
28 | |||
29 | /** |
||
30 | * Whether the lazy-images module should load. |
||
31 | * |
||
32 | * This filter is not prefixed with jetpack_ to provide a smoother migration |
||
33 | * process from the WordPress Lazy Load plugin. |
||
34 | * |
||
35 | * @module lazy-images |
||
36 | * |
||
37 | * @since 5.6.0 |
||
38 | * |
||
39 | * @param bool true Whether lazy image loading should occur. |
||
40 | */ |
||
41 | if ( ! apply_filters( 'lazyload_is_enabled', true ) ) { |
||
42 | return; |
||
43 | } |
||
44 | |||
45 | if ( Jetpack_AMP_Support::is_amp_request() ) { |
||
46 | return; |
||
47 | } |
||
48 | |||
49 | add_action( 'wp_head', array( $this, 'setup_filters' ), 9999 ); // we don't really want to modify anything in <head> since it's mostly all metadata |
||
50 | add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) ); |
||
51 | |||
52 | // Do not lazy load avatar in admin bar |
||
53 | add_action( 'admin_bar_menu', array( $this, 'remove_filters' ), 0 ); |
||
54 | |||
55 | add_filter( 'wp_kses_allowed_html', array( $this, 'allow_lazy_attributes' ) ); |
||
56 | add_action( 'wp_head', array( $this, 'add_nojs_fallback' ) ); |
||
57 | } |
||
58 | |||
59 | View Code Duplication | public function setup_filters() { |
|
60 | add_filter( 'the_content', array( $this, 'add_image_placeholders' ), PHP_INT_MAX ); // run this later, so other content filters have run, including image_add_wh on WP.com |
||
61 | add_filter( 'post_thumbnail_html', array( $this, 'add_image_placeholders' ), PHP_INT_MAX ); |
||
62 | add_filter( 'get_avatar', array( $this, 'add_image_placeholders' ), PHP_INT_MAX ); |
||
63 | add_filter( 'widget_text', array( $this, 'add_image_placeholders' ), PHP_INT_MAX ); |
||
64 | add_filter( 'get_image_tag', array( $this, 'add_image_placeholders' ), PHP_INT_MAX); |
||
65 | add_filter( 'wp_get_attachment_image_attributes', array( __CLASS__, 'process_image_attributes' ), PHP_INT_MAX ); |
||
66 | } |
||
67 | |||
68 | View Code Duplication | public function remove_filters() { |
|
69 | remove_filter( 'the_content', array( $this, 'add_image_placeholders' ), PHP_INT_MAX ); |
||
70 | remove_filter( 'post_thumbnail_html', array( $this, 'add_image_placeholders' ), PHP_INT_MAX ); |
||
71 | remove_filter( 'get_avatar', array( $this, 'add_image_placeholders' ), PHP_INT_MAX ); |
||
72 | remove_filter( 'widget_text', array( $this, 'add_image_placeholders' ), PHP_INT_MAX ); |
||
73 | remove_filter( 'get_image_tag', array( $this, 'add_image_placeholders' ), PHP_INT_MAX); |
||
74 | remove_filter( 'wp_get_attachment_image_attributes', array( __CLASS__, 'process_image_attributes' ), PHP_INT_MAX ); |
||
75 | } |
||
76 | |||
77 | /** |
||
78 | * Ensure that our lazy image attributes are not filtered out of image tags. |
||
79 | * |
||
80 | * @param array $allowed_tags The allowed tags and their attributes. |
||
81 | * @return array |
||
82 | */ |
||
83 | public function allow_lazy_attributes( $allowed_tags ) { |
||
84 | if ( ! isset( $allowed_tags['img'] ) ) { |
||
85 | return $allowed_tags; |
||
86 | } |
||
87 | |||
88 | // But, if images are allowed, ensure that our attributes are allowed! |
||
89 | $img_attributes = array_merge( $allowed_tags['img'], array( |
||
90 | 'data-lazy-src' => 1, |
||
91 | 'data-lazy-srcset' => 1, |
||
92 | 'data-lazy-sizes' => 1, |
||
93 | ) ); |
||
94 | |||
95 | $allowed_tags['img'] = $img_attributes; |
||
96 | |||
97 | return $allowed_tags; |
||
98 | } |
||
99 | |||
100 | public function add_image_placeholders( $content ) { |
||
101 | // Don't lazyload for feeds, previews |
||
102 | if ( is_feed() || is_preview() ) { |
||
103 | return $content; |
||
104 | } |
||
105 | |||
106 | // Don't lazy-load if the content has already been run through previously |
||
107 | if ( false !== strpos( $content, 'data-lazy-src' ) ) { |
||
108 | return $content; |
||
109 | } |
||
110 | |||
111 | // This is a pretty simple regex, but it works |
||
112 | $content = preg_replace_callback( '#<(img)([^>]+?)(>(.*?)</\\1>|[\/]?>)#si', array( __CLASS__, 'process_image' ), $content ); |
||
113 | |||
114 | return $content; |
||
115 | } |
||
116 | |||
117 | /** |
||
118 | * Returns true when a given string of classes contains a class signifying lazy images |
||
119 | * should not process the image. |
||
120 | * |
||
121 | * @since 5.9.0 |
||
122 | * |
||
123 | * @param string $classes A string of space-separated classes. |
||
124 | * @return bool |
||
125 | */ |
||
126 | public static function should_skip_image_with_blacklisted_class( $classes ) { |
||
155 | |||
156 | /** |
||
157 | * Processes images in content by acting as the preg_replace_callback |
||
158 | * |
||
159 | * @since 5.6.0 |
||
160 | * |
||
161 | * @param array $matches |
||
162 | * |
||
163 | * @return string The image with updated lazy attributes |
||
164 | */ |
||
165 | static function process_image( $matches ) { |
||
185 | |||
186 | /** |
||
187 | * Given an array of image attributes, updates the `src`, `srcset`, and `sizes` attributes so |
||
188 | * that they load lazily. |
||
189 | * |
||
190 | * @since 5.7.0 |
||
191 | * |
||
192 | * @param array $attributes |
||
193 | * |
||
194 | * @return array The updated image attributes array with lazy load attributes |
||
195 | */ |
||
196 | static function process_image_attributes( $attributes ) { |
||
277 | |||
278 | /** |
||
279 | * Adds JavaScript to check if the current browser supports JavaScript as well as some styles to hide lazy |
||
280 | * images when the browser does not support JavaScript. |
||
281 | * |
||
282 | * @return void |
||
283 | */ |
||
284 | public function add_nojs_fallback() { |
||
299 | |||
300 | /** |
||
301 | * Retrieves the placeholder image after running it through the lazyload_images_placeholder_image filter. |
||
302 | * |
||
303 | * @param array $attributes An array of attributes to be added to the image. |
||
304 | * |
||
305 | * @return string The placeholder image source. |
||
306 | */ |
||
307 | public static function get_placeholder_image( $attributes = array() ) { |
||
336 | |||
337 | /** |
||
338 | * Given the attributes about an image, will attempt to determine the height, width, and aspect ratio of an image. |
||
339 | * |
||
340 | * @param array $attributes An array of image attributes. |
||
341 | * |
||
342 | * @return bool|array |
||
343 | */ |
||
344 | public static function get_image_dimensions( $attributes ) { |
||
397 | |||
398 | private static function flatten_kses_hair_data( $attributes ) { |
||
405 | |||
406 | private static function build_attributes_string( $attributes ) { |
||
417 | |||
418 | public function enqueue_assets() { |
||
430 | } |
||
431 |
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.