Completed
Push — master ( f77e88...54a4fc )
by David
03:40
created

RetryPlugin   A

Complexity

Total Complexity 7

Size/Duplication

Total Lines 105
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 97.14%

Importance

Changes 0
Metric Value
wmc 7
lcom 1
cbo 3
dl 0
loc 105
ccs 34
cts 35
cp 0.9714
rs 10
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 19 1
A handleRequest() 0 35 5
A defaultDelay() 0 4 1
1
<?php
2
3
namespace Http\Client\Common\Plugin;
4
5
use Http\Client\Common\Plugin;
6
use Http\Client\Exception;
7
use Psr\Http\Message\RequestInterface;
8
use Psr\Http\Message\ResponseInterface;
9
use Symfony\Component\OptionsResolver\OptionsResolver;
10
11
/**
12
 * Retry the request if an exception is thrown.
13
 *
14
 * By default will retry only one time.
15
 *
16
 * @author Joel Wurtz <[email protected]>
17
 */
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 $delay;
31
32
    /**
33
     * @var callable
34
     */
35
    private $decider;
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 $decider A callback that gets a request and an exception to decide after a failure whether the request should be retried.
49
     *     @var callable $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 7
    public function __construct(array $config = [])
53
    {
54 7
        $resolver = new OptionsResolver();
55 7
        $resolver->setDefaults([
56 7
            'retries' => 1,
57
            'decider' => function (RequestInterface $request, Exception $e) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $e is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
58 3
                return true;
59 7
            },
60
            'delay' => __CLASS__.'::defaultDelay',
61
        ]);
62 7
        $resolver->setAllowedTypes('retries', 'int');
63 7
        $resolver->setAllowedTypes('decider', 'callable');
64 7
        $resolver->setAllowedTypes('delay', 'callable');
65 7
        $options = $resolver->resolve($config);
66
67 7
        $this->retry = $options['retries'];
68 7
        $this->decider = $options['decider'];
69 7
        $this->delay = $options['delay'];
70 7
    }
71
72
    /**
73
     * {@inheritdoc}
74
     */
75 4
    public function handleRequest(RequestInterface $request, callable $next, callable $first)
76
    {
77 4
        $chainIdentifier = spl_object_hash((object) $first);
78
79
        return $next($request)->then(function (ResponseInterface $response) use ($request, $chainIdentifier) {
80 3
            if (array_key_exists($chainIdentifier, $this->retryStorage)) {
81 2
                unset($this->retryStorage[$chainIdentifier]);
82
            }
83
84 3
            return $response;
85 4
        }, function (Exception $exception) use ($request, $next, $first, $chainIdentifier) {
86 3
            if (!array_key_exists($chainIdentifier, $this->retryStorage)) {
87 3
                $this->retryStorage[$chainIdentifier] = 0;
88
            }
89
90 3
            if ($this->retryStorage[$chainIdentifier] >= $this->retry) {
91 1
                unset($this->retryStorage[$chainIdentifier]);
92
93 1
                throw $exception;
94
            }
95
96 3
            if (!call_user_func($this->decider, $request, $exception)) {
97
                throw $exception;
98
            }
99
100 3
            $time = call_user_func($this->delay, $request, $exception, $this->retryStorage[$chainIdentifier]);
101 3
            usleep($time);
102
103
            // Retry in synchrone
104 3
            ++$this->retryStorage[$chainIdentifier];
105 3
            $promise = $this->handleRequest($request, $next, $first);
106
107 3
            return $promise->wait();
108 4
        });
109
    }
110
111
    /**
112
     * @param RequestInterface $request
113
     * @param Exception        $e
114
     * @param int              $retries The number of retries we made before. First time this get called it will be 0.
115
     *
116
     * @return int
117
     */
118 4
    public static function defaultDelay(RequestInterface $request, Exception $e, $retries)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $e is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
119
    {
120 4
        return pow(2, $retries) * 500000;
121
    }
122
}
123