Skip to content

Commit 0fef2e2

Browse files
author
root
committed
Initialize monorepo
1 parent 611d844 commit 0fef2e2

22 files changed

+1347
-1
lines changed

.gitignore

+4-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1-
1+
/.idea/
2+
/vendor/
3+
/composer.lock
4+
/.phpunit.result.cache

DelayStrategy/ConstantDelay.php

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Manyou\PromiseHttpClient\DelayStrategy;
6+
7+
use Manyou\PromiseHttpClient\DelayStrategyInterface;
8+
use Symfony\Component\HttpClient\Exception\InvalidArgumentException;
9+
use Symfony\Contracts\HttpClient\ResponseInterface;
10+
11+
use function sprintf;
12+
13+
class ConstantDelay implements DelayStrategyInterface
14+
{
15+
public function __construct(private ?int $delayMs)
16+
{
17+
if ($delayMs !== null && $delayMs < 0) {
18+
throw new InvalidArgumentException(sprintf(
19+
'Time of delay in milliseconds must be greater than zero: "%s" given.',
20+
$delayMs,
21+
));
22+
}
23+
}
24+
25+
public function getDelay(int $count, ?ResponseInterface $response = null): ?int
26+
{
27+
return $this->delayMs;
28+
}
29+
}

DelayStrategy/DelayCap.php

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Manyou\PromiseHttpClient\DelayStrategy;
6+
7+
use Manyou\PromiseHttpClient\DelayStrategyInterface;
8+
use Symfony\Component\HttpClient\Exception\InvalidArgumentException;
9+
use Symfony\Contracts\HttpClient\ResponseInterface;
10+
11+
use function sprintf;
12+
13+
class DelayCap implements DelayStrategyInterface
14+
{
15+
public function __construct(
16+
private int $maxMs,
17+
private DelayStrategyInterface $strategy,
18+
private bool $fallthrough = false,
19+
) {
20+
if ($maxMs < 0) {
21+
throw new InvalidArgumentException(sprintf(
22+
'Maximum time of delay in milliseconds must be greater than zero: "%s" given.',
23+
$maxMs,
24+
));
25+
}
26+
}
27+
28+
public function getDelay(int $count, ?ResponseInterface $response = null): ?int
29+
{
30+
$delay = $this->strategy->getDelay($count, $response);
31+
32+
if ($delay === null) {
33+
return null;
34+
}
35+
36+
if ($delay > $this->maxMs) {
37+
return $this->fallthrough ? null : $this->maxMs;
38+
}
39+
40+
return $delay;
41+
}
42+
}

DelayStrategy/DelayJitter.php

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Manyou\PromiseHttpClient\DelayStrategy;
6+
7+
use Manyou\PromiseHttpClient\DelayStrategyInterface;
8+
use Symfony\Component\HttpClient\Exception\InvalidArgumentException;
9+
use Symfony\Contracts\HttpClient\ResponseInterface;
10+
11+
use function mt_rand;
12+
use function round;
13+
use function sprintf;
14+
15+
class DelayJitter implements DelayStrategyInterface
16+
{
17+
public function __construct(private float $factor, private DelayStrategyInterface $strategy)
18+
{
19+
if ($factor < 0.0 || $factor > 1.0) {
20+
throw new InvalidArgumentException(sprintf(
21+
'Factor must in between of zero and one: "%s" given.',
22+
$factor,
23+
));
24+
}
25+
}
26+
27+
public function getDelay(int $count, ?ResponseInterface $response = null): ?int
28+
{
29+
if (null !== $delay = $this->strategy->getDelay($count, $response)) {
30+
$from = $this->strategy->getDelay($count - 1, $response);
31+
$to = $this->strategy->getDelay($count + 1, $response);
32+
$from = $delay - (int) round(($delay - $from) * $this->factor);
33+
$to = $delay + (int) round(($to - $delay) * $this->factor);
34+
$delay = mt_rand($from, $to);
35+
}
36+
37+
return $delay;
38+
}
39+
}

DelayStrategy/DelayStrategyChain.php

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Manyou\PromiseHttpClient\DelayStrategy;
6+
7+
use Manyou\PromiseHttpClient\DelayStrategyInterface;
8+
use Symfony\Contracts\HttpClient\ResponseInterface;
9+
10+
final class DelayStrategyChain implements DelayStrategyInterface
11+
{
12+
/** @var DelayStrategyInterface[] */
13+
private array $strategies;
14+
15+
public function __construct(DelayStrategyInterface ...$strategies)
16+
{
17+
$this->strategies = $strategies;
18+
}
19+
20+
public function getDelay(int $count, ?ResponseInterface $response = null): ?int
21+
{
22+
foreach ($this->strategies as $strategy) {
23+
$delay = $strategy->getDelay($count, $response);
24+
25+
if ($delay !== null) {
26+
return $delay;
27+
}
28+
}
29+
30+
return null;
31+
}
32+
33+
public static function createDefault(): self
34+
{
35+
return new self(
36+
new DelayCap(10000, new RetryAfterHeader(), true),
37+
new DelayCap(
38+
5000,
39+
new DelayJitter(
40+
0.5,
41+
new ExponentialBackOff(500, 1.5),
42+
),
43+
),
44+
);
45+
}
46+
}

DelayStrategy/ExponentialBackOff.php

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Manyou\PromiseHttpClient\DelayStrategy;
6+
7+
use Manyou\PromiseHttpClient\DelayStrategyInterface;
8+
use Symfony\Component\HttpClient\Exception\InvalidArgumentException;
9+
use Symfony\Contracts\HttpClient\ResponseInterface;
10+
11+
use function round;
12+
use function sprintf;
13+
14+
final class ExponentialBackOff implements DelayStrategyInterface
15+
{
16+
public function __construct(private int $initialMs, private float $multiplier)
17+
{
18+
if ($initialMs < 0) {
19+
throw new InvalidArgumentException(sprintf(
20+
'Initial time of delay in milliseconds must be greater than or equal to zero: "%s" given.',
21+
$initialMs,
22+
));
23+
}
24+
25+
if ($multiplier < 1.0) {
26+
throw new InvalidArgumentException(sprintf(
27+
'Multiplier must be greater than or equal to one: "%s" given.',
28+
$multiplier,
29+
));
30+
}
31+
}
32+
33+
public function getDelay(int $count, ?ResponseInterface $response = null): int
34+
{
35+
return (int) round(
36+
$this->initialMs * $this->multiplier ** ($count - 1),
37+
);
38+
}
39+
}

DelayStrategy/IncrementalBackOff.php

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Manyou\PromiseHttpClient\DelayStrategy;
6+
7+
use Manyou\PromiseHttpClient\DelayStrategyInterface;
8+
use Symfony\Component\HttpClient\Exception\InvalidArgumentException;
9+
use Symfony\Contracts\HttpClient\ResponseInterface;
10+
11+
use function sprintf;
12+
13+
final class IncrementalBackOff implements DelayStrategyInterface
14+
{
15+
public function __construct(private int $initialMs)
16+
{
17+
if ($initialMs < 0) {
18+
throw new InvalidArgumentException(sprintf(
19+
'Initial time of delay in milliseconds must be greater than or equal to zero: "%s" given.',
20+
$initialMs,
21+
));
22+
}
23+
24+
$this->initialMs = $initialMs;
25+
}
26+
27+
public function getDelay(int $count, ?ResponseInterface $response = null): int
28+
{
29+
return $this->initialMs * $count;
30+
}
31+
}

DelayStrategy/RetryAfterHeader.php

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Manyou\PromiseHttpClient\DelayStrategy;
6+
7+
use DateTimeImmutable;
8+
use DateTimeZone;
9+
use Manyou\PromiseHttpClient\DelayStrategyInterface;
10+
use Symfony\Contracts\HttpClient\ResponseInterface;
11+
12+
use function time;
13+
use function trim;
14+
15+
final class RetryAfterHeader implements DelayStrategyInterface
16+
{
17+
public function __construct(private ?int $mockTimestamp = null)
18+
{
19+
}
20+
21+
public function getDelay(int $count, ?ResponseInterface $response = null): ?int
22+
{
23+
if ($response !== null) {
24+
$headers = $response->getHeaders(false);
25+
26+
if (isset($headers['retry-after'][0])) {
27+
$seconds = $this->parseToSeconds($headers['retry-after'][0]);
28+
29+
if ($seconds !== null) {
30+
return $seconds * 1000;
31+
}
32+
}
33+
}
34+
35+
return null;
36+
}
37+
38+
/** @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After */
39+
private function parseToSeconds(string $value): ?int
40+
{
41+
$seconds = (int) $value = trim($value);
42+
43+
if ($seconds >= 0) {
44+
if ($value === (string) $seconds) {
45+
return $seconds;
46+
}
47+
48+
$value = DateTimeImmutable::createFromFormat('D, d M Y H:i:s \G\M\T', $value, new DateTimeZone('UTC'));
49+
50+
if ($value) {
51+
return $value->getTimestamp() - ($this->mockTimestamp ?? time());
52+
}
53+
}
54+
55+
return null;
56+
}
57+
}

DelayStrategyInterface.php

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Manyou\PromiseHttpClient;
6+
7+
use Symfony\Contracts\HttpClient\ResponseInterface;
8+
9+
interface DelayStrategyInterface
10+
{
11+
/**
12+
* @param int $count Number of retries, starts from 1
13+
*
14+
* @return int|null Time of delay in milliseconds
15+
*/
16+
public function getDelay(int $count, ?ResponseInterface $response = null): ?int;
17+
}

LICENSE

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Copyright (c) 2004-2022 Fabien Potencier
2+
Copyright (c) 2020-2022 Brent, Bohan Yang
3+
4+
Permission is hereby granted, free of charge, to any person obtaining a copy
5+
of this software and associated documentation files (the "Software"), to deal
6+
in the Software without restriction, including without limitation the rights
7+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
copies of the Software, and to permit persons to whom the Software is furnished
9+
to do so, subject to the following conditions:
10+
11+
The above copyright notice and this permission notice shall be included in all
12+
copies or substantial portions of the Software.
13+
14+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
THE SOFTWARE.

0 commit comments

Comments
 (0)