Completed
Push — instant-search-master ( 9522da...404687 )
by
unknown
07:50 queued 10s
created

Full_Sync_Immediately   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 385
Duplicated Lines 13.51 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 0
Metric Value
wmc 43
lcom 1
cbo 6
dl 52
loc 385
rs 8.96
c 0
b 0
f 0

19 Methods

Rating   Name   Duplication   Size   Complexity  
A update_status() 0 3 1
A set_status() 0 3 1
A name() 0 3 1
A init_full_sync_listeners() 0 2 1
B start() 0 52 7
A is_started() 0 3 1
A get_status() 0 10 1
A is_finished() 0 3 1
A reset_data() 0 4 1
A clear_status() 0 3 1
A get_initial_progress() 0 14 2
A get_content_range() 14 14 5
A get_range() 29 29 5
A continue_enqueuing() 0 3 1
A continue_sending() 9 9 4
A send() 0 20 3
A get_remaining_modules_to_send() 0 29 5
A send_full_sync_end() 0 19 1
A update_sent_progress_action() 0 3 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

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 Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Full_Sync_Immediately 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 Full_Sync_Immediately, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Full sync module.
4
 *
5
 * @package automattic/jetpack-sync
6
 */
7
8
namespace Automattic\Jetpack\Sync\Modules;
9
10
use Automattic\Jetpack\Sync\Defaults;
11
use Automattic\Jetpack\Sync\Lock;
12
use Automattic\Jetpack\Sync\Modules;
13
use Automattic\Jetpack\Sync\Settings;
14
15
/**
16
 * This class does a full resync of the database by
17
 * sending an outbound action for every single object
18
 * that we care about.
19
 */
20
class Full_Sync_Immediately extends Module {
21
	/**
22
	 * Prefix of the full sync status option name.
23
	 *
24
	 * @var string
25
	 */
26
	const STATUS_OPTION = 'jetpack_sync_full_status';
27
28
	/**
29
	 * Sync Lock name.
30
	 *
31
	 * @var string
32
	 */
33
	const LOCK_NAME = 'full_sync';
34
35
	/**
36
	 * Sync module name.
37
	 *
38
	 * @access public
39
	 *
40
	 * @return string
41
	 */
42
	public function name() {
43
		return 'full-sync';
44
	}
45
46
	/**
47
	 * Initialize action listeners for full sync.
48
	 *
49
	 * @access public
50
	 *
51
	 * @param callable $callable Action handler callable.
52
	 */
53
	public function init_full_sync_listeners( $callable ) {
54
	}
55
56
	/**
57
	 * Start a full sync.
58
	 *
59
	 * @access public
60
	 *
61
	 * @param array $full_sync_config Full sync configuration.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $full_sync_config not be array|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
62
	 *
63
	 * @return bool Always returns true at success.
64
	 */
65
	public function start( $full_sync_config = null ) {
66
		// There was a full sync in progress.
67
		if ( $this->is_started() && ! $this->is_finished() ) {
68
			/**
69
			 * Fires when a full sync is cancelled.
70
			 *
71
			 * @since 4.2.0
72
			 */
73
			do_action( 'jetpack_full_sync_cancelled' );
74
			$this->send_action( 'jetpack_full_sync_cancelled' );
75
		}
76
77
		// Remove all evidence of previous full sync items and status.
78
		$this->reset_data();
79
80
		if ( ! is_array( $full_sync_config ) ) {
81
			$full_sync_config = Defaults::$default_full_sync_config;
82
			if ( is_multisite() ) {
83
				$full_sync_config['network_options'] = 1;
84
			}
85
		}
86
87
		if ( isset( $full_sync_config['users'] ) && 'initial' === $full_sync_config['users'] ) {
88
			$full_sync_config['users'] = Modules::get_module( 'users' )->get_initial_sync_user_config();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Automattic\Jetpack\Sync\Modules\Module as the method get_initial_sync_user_config() does only exist in the following sub-classes of Automattic\Jetpack\Sync\Modules\Module: Automattic\Jetpack\Sync\Modules\Users. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
89
		}
90
91
		$this->update_status(
92
			array(
93
				'started'  => time(),
94
				'config'   => $full_sync_config,
95
				'progress' => $this->get_initial_progress( $full_sync_config ),
96
			)
97
		);
98
99
		$range = $this->get_content_range( $full_sync_config );
0 ignored issues
show
Unused Code introduced by
The call to Full_Sync_Immediately::get_content_range() has too many arguments starting with $full_sync_config.

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.

Loading history...
100
		/**
101
		 * Fires when a full sync begins. This action is serialized
102
		 * and sent to the server so that it knows a full sync is coming.
103
		 *
104
		 * @param array $full_sync_config Sync configuration for all sync modules.
105
		 * @param array $range Range of the sync items, containing min and max IDs for some item types.
106
		 * @param array $empty The modules with no items to sync during a full sync.
107
		 *
108
		 * @since 4.2.0
109
		 * @since 7.3.0 Added $range arg.
110
		 * @since 7.4.0 Added $empty arg.
111
		 */
112
		do_action( 'jetpack_full_sync_start', $full_sync_config, $range );
113
		$this->send_action( 'jetpack_full_sync_start', array( $full_sync_config, $range ) );
114
115
		return true;
116
	}
117
118
	/**
119
	 * Whether full sync has started.
120
	 *
121
	 * @access public
122
	 *
123
	 * @return boolean
124
	 */
125
	public function is_started() {
126
		return ! ! $this->get_status()['started'];
127
	}
128
129
	/**
130
	 * Retrieve the status of the current full sync.
131
	 *
132
	 * @access public
133
	 *
134
	 * @return array Full sync status.
135
	 */
136
	public function get_status() {
137
		$default = array(
138
			'started'  => false,
139
			'finished' => false,
140
			'progress' => array(),
141
			'config'   => array(),
142
		);
143
144
		return wp_parse_args( \Jetpack_Options::get_raw_option( self::STATUS_OPTION ), $default );
145
	}
146
147
	/**
148
	 * Whether full sync has finished.
149
	 *
150
	 * @access public
151
	 *
152
	 * @return boolean
153
	 */
154
	public function is_finished() {
155
		return ! ! $this->get_status()['finished'];
156
	}
157
158
	/**
159
	 * Clear all the full sync data.
160
	 *
161
	 * @access public
162
	 */
163
	public function reset_data() {
164
		$this->clear_status();
165
		( new Lock() )->remove( self::LOCK_NAME );
166
	}
167
168
	/**
169
	 * Clear all the full sync status options.
170
	 *
171
	 * @access public
172
	 */
173
	public function clear_status() {
174
		\Jetpack_Options::delete_raw_option( self::STATUS_OPTION );
175
	}
176
177
	/**
178
	 * Updates the status of the current full sync.
179
	 *
180
	 * @access public
181
	 *
182
	 * @param array $values New values to set.
183
	 *
184
	 * @return bool True if success.
185
	 */
186
	public function update_status( $values ) {
187
		return $this->set_status( wp_parse_args( $values, $this->get_status() ) );
188
	}
189
190
	/**
191
	 * Retrieve the status of the current full sync.
192
	 *
193
	 * @param array $values New values to set.
194
	 *
195
	 * @access public
196
	 *
197
	 * @return boolean Full sync status.
198
	 */
199
	public function set_status( $values ) {
200
		return \Jetpack_Options::update_raw_option( self::STATUS_OPTION, $values );
201
	}
202
203
	/**
204
	 * Given an initial Full Sync configuration get the initial status.
205
	 *
206
	 * @param array $full_sync_config Full sync configuration.
207
	 *
208
	 * @return array Initial Sent status.
209
	 */
210
	public function get_initial_progress( $full_sync_config ) {
211
		// Set default configuration, calculate totals, and save configuration if totals > 0.
212
		$status = array();
213
		foreach ( $full_sync_config as $name => $config ) {
214
			$module          = Modules::get_module( $name );
215
			$status[ $name ] = array(
216
				'total'    => $module->total( $config ),
217
				'sent'     => 0,
218
				'finished' => false,
219
			);
220
		}
221
222
		return $status;
223
	}
224
225
	/**
226
	 * Get the range for content (posts and comments) to sync.
227
	 *
228
	 * @access private
229
	 *
230
	 * @return array Array of range (min ID, max ID, total items) for all content types.
231
	 */
232 View Code Duplication
	private function get_content_range() {
233
		$range  = array();
234
		$config = $this->get_status()['config'];
235
		// Add range only when syncing all objects.
236
		if ( true === isset( $config['posts'] ) && $config['posts'] ) {
237
			$range['posts'] = $this->get_range( 'posts' );
238
		}
239
240
		if ( true === isset( $config['comments'] ) && $config['comments'] ) {
241
			$range['comments'] = $this->get_range( 'comments' );
242
		}
243
244
		return $range;
245
	}
246
247
	/**
248
	 * Get the range (min ID, max ID and total items) of items to sync.
249
	 *
250
	 * @access public
251
	 *
252
	 * @param string $type Type of sync item to get the range for.
253
	 *
254
	 * @return array Array of min ID, max ID and total items in the range.
255
	 */
256 View Code Duplication
	public function get_range( $type ) {
257
		global $wpdb;
258
		if ( ! in_array( $type, array( 'comments', 'posts' ), true ) ) {
259
			return array();
260
		}
261
262
		switch ( $type ) {
263
			case 'posts':
264
				$table     = $wpdb->posts;
265
				$id        = 'ID';
266
				$where_sql = Settings::get_blacklisted_post_types_sql();
267
268
				break;
269
			case 'comments':
270
				$table     = $wpdb->comments;
271
				$id        = 'comment_ID';
272
				$where_sql = Settings::get_comments_filter_sql();
273
				break;
274
		}
275
276
		// TODO: Call $wpdb->prepare on the following query.
277
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
278
		$results = $wpdb->get_results( "SELECT MAX({$id}) as max, MIN({$id}) as min, COUNT({$id}) as count FROM {$table} WHERE {$where_sql}" );
0 ignored issues
show
Bug introduced by
The variable $id does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $table does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $where_sql does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
279
		if ( isset( $results[0] ) ) {
280
			return $results[0];
281
		}
282
283
		return array();
284
	}
285
286
	/**
287
	 * Continue sending instead of enqueueing.
288
	 *
289
	 * @access public
290
	 */
291
	public function continue_enqueuing() {
292
		$this->continue_sending();
293
	}
294
295
	/**
296
	 * Continue sending.
297
	 *
298
	 * @access public
299
	 */
300 View Code Duplication
	public function continue_sending() {
301
		if ( ! ( new Lock() )->attempt( self::LOCK_NAME ) || ! $this->is_started() || $this->get_status()['finished'] ) {
302
			return;
303
		}
304
305
		$this->send();
306
307
		( new Lock() )->remove( self::LOCK_NAME );
308
	}
309
310
	/**
311
	 * Immediately send the next items to full sync.
312
	 *
313
	 * @access public
314
	 */
315
	public function send() {
316
		$config = $this->get_status()['config'];
317
318
		$max_duration = Settings::get_setting( 'full_sync_send_duration' );
319
		$send_until   = microtime( true ) + $max_duration;
320
321
		$progress = $this->get_status()['progress'];
322
323
		foreach ( $this->get_remaining_modules_to_send() as $module ) {
324
			$progress[ $module->name() ] = $module->send_full_sync_actions( $config[ $module->name() ], $progress[ $module->name() ], $send_until );
325
			if ( ! $progress[ $module->name() ]['finished'] ) {
326
				$this->update_status( array( 'progress' => $progress ) );
327
328
				return;
329
			}
330
		}
331
332
		$this->send_full_sync_end();
333
		$this->update_status( array( 'progress' => $progress ) );
334
	}
335
336
	/**
337
	 * Get Modules that are configured to Full Sync and haven't finished sending
338
	 *
339
	 * @return array
340
	 */
341
	public function get_remaining_modules_to_send() {
342
		$status = $this->get_status();
343
344
		return array_filter(
345
			Modules::get_modules(),
346
			/**
347
			 * Select configured and not finished modules.
348
			 *
349
			 * @return bool
350
			 * @var $module Module
351
			 */
352
			function ( $module ) use ( $status ) {
353
				// Skip module if not configured for this sync or module is done.
354
				if ( ! isset( $status['config'][ $module->name() ] ) ) {
355
					return false;
356
				}
357
				if ( ! $status['config'][ $module->name() ] ) {
358
					return false;
359
				}
360
				if ( isset( $status['progress'][ $module->name() ]['finished'] ) ) {
361
					if ( true === $status['progress'][ $module->name() ]['finished'] ) {
362
						return false;
363
					}
364
				}
365
366
				return true;
367
			}
368
		);
369
	}
370
371
	/**
372
	 * Send 'jetpack_full_sync_end' and update 'finished' status.
373
	 *
374
	 * @access public
375
	 */
376
	public function send_full_sync_end() {
377
		$range = $this->get_content_range();
378
379
		/**
380
		 * Fires when a full sync ends. This action is serialized
381
		 * and sent to the server.
382
		 *
383
		 * @param string $checksum Deprecated since 7.3.0 - @see https://github.com/Automattic/jetpack/pull/11945/
384
		 * @param array $range Range of the sync items, containing min and max IDs for some item types.
385
		 *
386
		 * @since 4.2.0
387
		 * @since 7.3.0 Added $range arg.
388
		 */
389
		do_action( 'jetpack_full_sync_end', '', $range );
390
		$this->send_action( 'jetpack_full_sync_end', array( '', $range ) );
391
392
		// Setting autoload to true means that it's faster to check whether we should continue enqueuing.
393
		$this->update_status( array( 'finished' => time() ) );
394
	}
395
396
	/**
397
	 * Empty Function as we don't close buffers on Immediate Full Sync.
398
	 *
399
	 * @param Array $actions an array of actions, ignored for queueless sync.
400
	 */
401
	public function update_sent_progress_action( $actions ) {
402
		return;
403
	}
404
}
405