Completed
Pull Request — master (#28)
by
unknown
02:30
created

MiddlewareFactory::newRetryDecider()   C

Complexity

Conditions 12
Paths 1

Size

Total Lines 72
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 72
ccs 27
cts 27
cp 1
rs 5.519
cc 12
eloc 40
nc 1
nop 0
crap 12

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