Completed
Push — main ( d84a15...26c7b4 )
by
unknown
04:34
created

MiddlewareFactory::newRetryDecider()   C

Complexity

Conditions 12
Paths 1

Size

Total Lines 75

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 75
c 0
b 0
f 0
rs 6.1186
cc 12
nc 1
nop 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Addwiki\Mediawiki\Api\Guzzle;
4
5
use GuzzleHttp\Exception\ConnectException;
6
use GuzzleHttp\Exception\TransferException;
7
use GuzzleHttp\Middleware;
8
use GuzzleHttp\Psr7\Request;
9
use GuzzleHttp\Psr7\Response;
10
use Psr\Log\LoggerAwareInterface;
11
use Psr\Log\LoggerInterface;
12
use Psr\Log\NullLogger;
13
14
/**
15
 * @access private
16
 *
17
 * @author Addshore
18
 */
19
class MiddlewareFactory implements LoggerAwareInterface {
20
21
	/**
22
	 * @var LoggerInterface
23
	 */
24
	private $logger;
25
26
	public function __construct() {
27
		$this->logger = new NullLogger();
28
	}
29
30
	/**
31
	 * @param LoggerInterface $logger The new Logger object.
32
	 */
33
	public function setLogger( LoggerInterface $logger ) {
34
		$this->logger = $logger;
35
	}
36
37
	/**
38
	 * @private
39
	 *
40
	 * @param bool $delay default to true, can be false to speed up tests
41
	 *
42
	 * @return callable
43
	 */
44
	public function retry( $delay = true ) {
45
		if ( $delay ) {
46
			return Middleware::retry( $this->newRetryDecider(), $this->getRetryDelay() );
47
		} else {
48
			return Middleware::retry( $this->newRetryDecider() );
49
		}
50
	}
51
52
	/**
53
	 * Returns a method that takes the number of retries and returns the number of miliseconds
54
	 * to wait
55
	 *
56
	 * @return callable
57
	 */
58
	private function getRetryDelay() {
59
		return function ( $numberOfRetries, Response $response = null ) {
60
			// The $response argument is only passed as of Guzzle 6.2.2.
61
			if ( $response !== null ) {
62
				// Retry-After may be a number of seconds or an absolute date (RFC 7231,
63
				// section 7.1.3).
64
				$retryAfter = $response->getHeaderLine( 'Retry-After' );
65
66
				if ( is_numeric( $retryAfter ) ) {
67
					return 1000 * $retryAfter;
68
				}
69
70
				if ( $retryAfter !== '' ) {
71
					$seconds = strtotime( $retryAfter ) - time();
72
					return 1000 * max( 1, $seconds );
73
				}
74
			}
75
76
			return 1000 * $numberOfRetries;
77
		};
78
	}
79
80
	/**
81
	 * @return callable
82
	 */
83
	private function newRetryDecider() {
84
		return function (
85
			$retries,
86
			Request $request,
87
			Response $response = null,
88
			TransferException $exception = null
89
		) {
90
			// Don't retry if we have run out of retries
91
			if ( $retries >= 5 ) {
92
				return false;
93
			}
94
95
			$shouldRetry = false;
96
97
			// Retry connection exceptions
98
			if ( $exception instanceof ConnectException ) {
99
				$shouldRetry = true;
100
			}
101
102
			if ( $response !== null ) {
103
				$data = json_decode( $response->getBody(), true );
104
105
				// Retry on server errors
106
				if ( $response->getStatusCode() >= 500 ) {
107
					$shouldRetry = true;
108
				}
109
110
				foreach ( $response->getHeader( 'Mediawiki-Api-Error' ) as $mediawikiApiErrorHeader ) {
111
					$RetryAfterResponseHeaderLine = $response->getHeaderLine( 'Retry-After' );
112
					if (
113
						// Retry if the API explicitly tells us to:
114
						// https://www.mediawiki.org/wiki/Manual:Maxlag_parameter
115
						$RetryAfterResponseHeaderLine
116
						||
117
						// Retry if we have a response with an API error worth retrying
118
						in_array(
119
							$mediawikiApiErrorHeader,
120
							[
121
								'ratelimited',
122
								'maxlag',
123
								'readonly',
124
								'internal_api_error_DBQueryError',
125
							]
126
						)
127
						||
128
						// Or if we have been stopped from saving as an 'anti-abuse measure'
129
						// Note: this tries to match "actionthrottledtext" i18n messagae for mediawiki
130
						(
131
							$mediawikiApiErrorHeader == 'failed-save' &&
132
							strstr( $data['error']['info'], 'anti-abuse measure' )
133
						)
134
					) {
135
						$shouldRetry = true;
136
					}
137
138
				}
139
			}
140
141
			// Log if we are retrying
142
			if ( $shouldRetry ) {
143
				$this->logger->warning(
144
					sprintf(
145
						'Retrying %s %s %s/5, %s',
146
						$request->getMethod(),
147
						$request->getUri(),
148
						$retries + 1,
149
						$response !== null ? 'status code: ' . $response->getStatusCode() :
150
							$exception->getMessage()
0 ignored issues
show
Bug introduced by
It seems like $exception is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
151
					)
152
				);
153
			}
154
155
			return $shouldRetry;
156
		};
157
	}
158
159
}
160