1 | <?php |
||
18 | final class RetryPlugin implements Plugin |
||
19 | { |
||
20 | /** |
||
21 | * Number of retry before sending an exception. |
||
22 | * |
||
23 | * @var int |
||
24 | */ |
||
25 | private $retry; |
||
26 | |||
27 | /** |
||
28 | * @var callable |
||
29 | */ |
||
30 | private $exceptionDelay; |
||
31 | |||
32 | /** |
||
33 | * @var callable |
||
34 | */ |
||
35 | private $exceptionDecider; |
||
36 | |||
37 | /** |
||
38 | * Store the retry counter for each request. |
||
39 | * |
||
40 | * @var array |
||
41 | */ |
||
42 | private $retryStorage = []; |
||
43 | |||
44 | /** |
||
45 | * @param array $config { |
||
46 | * |
||
47 | * @var int $retries Number of retries to attempt if an exception occurs before letting the exception bubble up. |
||
48 | * @var callable $exception_decider A callback that gets a request and an exception to decide after a failure whether the request should be retried. |
||
49 | * @var callable $exception_delay A callback that gets a request, an exception and the number of retries and returns how many microseconds we should wait before trying again. |
||
50 | * } |
||
51 | */ |
||
52 | 8 | public function __construct(array $config = []) |
|
53 | { |
||
54 | 8 | if (array_key_exists('decider', $config)) { |
|
55 | if (array_key_exists('exception_decider', $config)) { |
||
56 | throw new \InvalidArgumentException('Do not set both the old "decider" and new "exception_decider" options'); |
||
57 | } |
||
58 | trigger_error('The "decider" option has been deprecated in favour of "exception_decider"', E_USER_DEPRECATED); |
||
59 | $config['exception_decider'] = $config['decider']; |
||
60 | unset($config['decider']); |
||
61 | } |
||
62 | 8 | if (array_key_exists('delay', $config)) { |
|
63 | if (array_key_exists('exception_delay', $config)) { |
||
64 | throw new \InvalidArgumentException('Do not set both the old "delay" and new "exception_delay" options'); |
||
65 | } |
||
66 | trigger_error('The "delay" option has been deprecated in favour of "exception_delay"', E_USER_DEPRECATED); |
||
67 | $config['exception_delay'] = $config['delay']; |
||
68 | unset($config['delay']); |
||
69 | } |
||
70 | |||
71 | 8 | $resolver = new OptionsResolver(); |
|
72 | 8 | $resolver->setDefaults([ |
|
73 | 8 | 'retries' => 1, |
|
74 | 'exception_decider' => function (RequestInterface $request, Exception $e) { |
||
75 | 3 | return true; |
|
76 | 8 | }, |
|
77 | 'exception_delay' => __CLASS__.'::defaultDelay', |
||
78 | ]); |
||
79 | 8 | $resolver->setAllowedTypes('retries', 'int'); |
|
80 | 8 | $resolver->setAllowedTypes('exception_decider', 'callable'); |
|
81 | 8 | $resolver->setAllowedTypes('exception_delay', 'callable'); |
|
82 | 8 | $options = $resolver->resolve($config); |
|
83 | |||
84 | 8 | $this->retry = $options['retries']; |
|
85 | 8 | $this->exceptionDecider = $options['exception_decider']; |
|
86 | 8 | $this->exceptionDelay = $options['exception_delay']; |
|
87 | 8 | } |
|
88 | |||
89 | /** |
||
90 | * {@inheritdoc} |
||
91 | */ |
||
92 | 5 | public function handleRequest(RequestInterface $request, callable $next, callable $first) |
|
93 | { |
||
94 | 5 | $chainIdentifier = spl_object_hash((object) $first); |
|
95 | |||
96 | return $next($request)->then(function (ResponseInterface $response) use ($request, $chainIdentifier) { |
||
97 | 3 | if (array_key_exists($chainIdentifier, $this->retryStorage)) { |
|
98 | 2 | unset($this->retryStorage[$chainIdentifier]); |
|
99 | } |
||
100 | |||
101 | 3 | return $response; |
|
102 | 5 | }, function (Exception $exception) use ($request, $next, $first, $chainIdentifier) { |
|
103 | 4 | if (!array_key_exists($chainIdentifier, $this->retryStorage)) { |
|
104 | 4 | $this->retryStorage[$chainIdentifier] = 0; |
|
105 | } |
||
106 | |||
107 | 4 | if ($this->retryStorage[$chainIdentifier] >= $this->retry) { |
|
108 | 1 | unset($this->retryStorage[$chainIdentifier]); |
|
109 | |||
110 | 1 | throw $exception; |
|
111 | } |
||
112 | |||
113 | 4 | if (!call_user_func($this->exceptionDecider, $request, $exception)) { |
|
114 | 1 | throw $exception; |
|
115 | } |
||
116 | |||
117 | 3 | $time = call_user_func($this->exceptionDelay, $request, $exception, $this->retryStorage[$chainIdentifier]); |
|
118 | 3 | usleep($time); |
|
119 | |||
120 | // Retry in synchrone |
||
121 | 3 | ++$this->retryStorage[$chainIdentifier]; |
|
122 | 3 | $promise = $this->handleRequest($request, $next, $first); |
|
123 | |||
124 | 3 | return $promise->wait(); |
|
125 | 5 | }); |
|
126 | } |
||
127 | |||
128 | /** |
||
129 | * @param RequestInterface $request |
||
130 | * @param Exception $e |
||
131 | * @param int $retries The number of retries we made before. First time this get called it will be 0. |
||
132 | * |
||
133 | * @return int |
||
134 | */ |
||
135 | 4 | public static function defaultDelay(RequestInterface $request, Exception $e, $retries) |
|
139 | } |
||
140 |
This check looks from parameters that have been defined for a function or method, but which are not used in the method body.