Completed
Push — master ( 9946c3...f4950d )
by Felix
09:27
created

ExponentialBackoff::calculateDelay()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
namespace SchulzeFelix\AdWords;
4
5
/**
6
 * Exponential backoff implementation.
7
 */
8
class ExponentialBackoff
9
{
10
    const MAX_DELAY_MICROSECONDS = 120000000;
11
12
    /**
13
     * @var int
14
     */
15
    private $retries;
16
17
    /**
18
     * @var callable
19
     */
20
    private $retryFunction;
21
22
    /**
23
     * @var callable
24
     */
25
    private $delayFunction;
26
27
    /**
28
     * @param int $retries [optional] Number of retries for a failed request.
29
     * @param callable $retryFunction [optional] returns bool for whether or not to retry
30
     */
31
    public function __construct($retries = null, callable $retryFunction = null)
32
    {
33
        $this->retries = $retries !== null ? (int) $retries : 3;
34
        $this->retryFunction = $retryFunction;
35
        $this->delayFunction = function ($delay) {
36
            usleep($delay);
37
        };
38
    }
39
40
    /**
41
     * Executes the retry process.
42
     *
43
     * @param callable $function
44
     * @param array $arguments [optional]
45
     * @return mixed
46
     * @throws \Exception The last exception caught while retrying.
47
     */
48
    public function execute(callable $function, array $arguments = [])
49
    {
50
        $delayFunction = $this->delayFunction;
51
        $retryAttempt = 0;
52
        $exception = null;
53
54
        while (true) {
55
            try {
56
                return call_user_func_array($function, $arguments);
57
            } catch (\Exception $exception) {
58
                if ($this->retryFunction) {
59
                    if (! call_user_func($this->retryFunction, $exception)) {
60
                        throw $exception;
61
                    }
62
                }
63
64
                if ($exception->getCode() == 403) {
65
                    break;
66
                }
67
68
                if ($retryAttempt >= $this->retries) {
69
                    break;
70
                }
71
72
                $delayFunction($this->calculateDelay($retryAttempt));
73
                $retryAttempt++;
74
            }
75
        }
76
77
        throw $exception;
78
    }
79
80
    /**
81
     * @param callable $delayFunction
82
     * @return void
83
     */
84
    public function setDelayFunction(callable $delayFunction)
85
    {
86
        $this->delayFunction = $delayFunction;
87
    }
88
89
    /**
90
     * Calculates exponential delay.
91
     *
92
     * @param int $attempt
93
     * @return int
94
     */
95
    private function calculateDelay($attempt)
96
    {
97
        return min(
98
            mt_rand(0, 1000000) + (pow(2, $attempt) * 1000000),
99
            self::MAX_DELAY_MICROSECONDS
100
        );
101
    }
102
}
103