1
|
|
|
<?php declare(strict_types=1); |
2
|
|
|
|
3
|
|
|
namespace ApiClients\Foundation\Transport; |
4
|
|
|
|
5
|
|
|
use ApiClients\Foundation\Middleware\Locator\Locator; |
6
|
|
|
use ApiClients\Foundation\Middleware\MiddlewareRunner; |
7
|
|
|
use Clue\React\Buzz\Browser; |
8
|
|
|
use Psr\Http\Message\RequestInterface; |
9
|
|
|
use Psr\Http\Message\ResponseInterface; |
10
|
|
|
use React\EventLoop\LoopInterface; |
11
|
|
|
use React\Promise\PromiseInterface; |
12
|
|
|
use React\Stream\ReadableStreamInterface; |
13
|
|
|
use RingCentral\Psr7\Uri; |
14
|
|
|
use Throwable; |
15
|
|
|
use function React\Promise\reject; |
16
|
|
|
use function React\Promise\resolve; |
17
|
|
|
|
18
|
|
|
final class Client implements ClientInterface |
19
|
|
|
{ |
20
|
|
|
const DEFAULT_OPTIONS = [ |
21
|
|
|
Options::SCHEMA => 'https', |
22
|
|
|
Options::PATH => '/', |
23
|
|
|
Options::HEADERS => [], |
24
|
|
|
]; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* @var LoopInterface |
28
|
|
|
*/ |
29
|
|
|
protected $loop; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* @var Locator |
33
|
|
|
*/ |
34
|
|
|
protected $locator; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* @var Browser |
38
|
|
|
*/ |
39
|
|
|
protected $browser; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* @var array |
43
|
|
|
*/ |
44
|
|
|
protected $options = []; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* @var string[] |
48
|
|
|
*/ |
49
|
|
|
protected $middleware = []; |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* @param LoopInterface $loop |
53
|
|
|
* @param Locator $locator |
54
|
|
|
* @param Browser $buzz |
55
|
|
|
* @param array $options |
56
|
|
|
*/ |
57
|
18 |
|
public function __construct( |
58
|
|
|
LoopInterface $loop, |
59
|
|
|
Locator $locator, |
60
|
|
|
Browser $buzz, |
61
|
|
|
array $options = [] |
62
|
|
|
) { |
63
|
18 |
|
$this->loop = $loop; |
64
|
18 |
|
$this->locator = $locator; |
65
|
18 |
|
$this->browser = $buzz; |
66
|
18 |
|
$this->options = $options + self::DEFAULT_OPTIONS; |
67
|
|
|
|
68
|
18 |
|
if (isset($this->options[Options::MIDDLEWARE])) { |
69
|
16 |
|
$this->middleware = $this->options[Options::MIDDLEWARE]; |
70
|
|
|
} |
71
|
18 |
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* @param RequestInterface $request |
75
|
|
|
* @param array $options |
76
|
|
|
* @return PromiseInterface |
77
|
|
|
*/ |
78
|
17 |
|
public function request(RequestInterface $request, array $options = []): PromiseInterface |
79
|
|
|
{ |
80
|
17 |
|
$body = $request->getBody(); |
81
|
17 |
|
if ($body instanceof ReadableStreamInterface) { |
82
|
1 |
|
$body->pause(); |
83
|
|
|
} |
84
|
|
|
|
85
|
17 |
|
$options = $this->applyRequestOptions($options); |
86
|
17 |
|
$request = $this->applyApiSettingsToRequest($request, $options); |
87
|
17 |
|
$executioner = $this->constructMiddlewares($options); |
88
|
|
|
|
89
|
17 |
View Code Duplication |
return $executioner->pre($request)->then(function (RequestInterface $request) use ($options) { |
|
|
|
|
90
|
17 |
|
$body = $request->getBody(); |
91
|
17 |
|
if ($body instanceof ReadableStreamInterface) { |
92
|
1 |
|
$this->loop->futureTick(function () use ($body) { |
93
|
1 |
|
$body->resume(); |
94
|
1 |
|
}); |
95
|
|
|
} |
96
|
|
|
|
97
|
17 |
|
return resolve($this->browser->send( |
98
|
17 |
|
$request |
99
|
|
|
)); |
100
|
|
|
}, function (ResponseInterface $response) { |
101
|
|
|
return resolve($response); |
102
|
|
|
})->then(function (ResponseInterface $response) use ($executioner) { |
103
|
8 |
|
$body = $response->getBody(); |
104
|
8 |
|
if ($body instanceof ReadableStreamInterface) { |
105
|
|
|
$body->pause(); |
106
|
|
|
} |
107
|
|
|
|
108
|
8 |
|
return $executioner->post($response); |
109
|
|
View Code Duplication |
})->then(function (ResponseInterface $response) { |
|
|
|
|
110
|
8 |
|
$body = $response->getBody(); |
111
|
8 |
|
if ($body instanceof ReadableStreamInterface) { |
112
|
|
|
$this->loop->futureTick(function () use ($body) { |
113
|
|
|
$body->resume(); |
114
|
|
|
}); |
115
|
|
|
} |
116
|
|
|
|
117
|
8 |
|
return resolve($response); |
118
|
|
|
})->otherwise(function (Throwable $throwable) use ($executioner) { |
119
|
9 |
|
return reject($executioner->error($throwable)); |
120
|
17 |
|
}); |
121
|
|
|
} |
122
|
|
|
|
123
|
17 |
|
public function applyRequestOptions(array $options): array |
124
|
|
|
{ |
125
|
17 |
|
if (!isset($this->options[Options::DEFAULT_REQUEST_OPTIONS])) { |
126
|
17 |
|
return $options; |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
return array_merge_recursive( |
130
|
|
|
$this->options[Options::DEFAULT_REQUEST_OPTIONS], |
131
|
|
|
$options |
132
|
|
|
); |
133
|
|
|
} |
134
|
|
|
|
135
|
17 |
|
protected function constructMiddlewares(array $options): MiddlewareRunner |
136
|
|
|
{ |
137
|
17 |
|
$set = $this->middleware; |
138
|
|
|
|
139
|
17 |
|
if (isset($options[Options::MIDDLEWARE])) { |
140
|
|
|
$set = $this->combinedMiddlewares($options[Options::MIDDLEWARE]); |
141
|
|
|
} |
142
|
|
|
|
143
|
17 |
|
$args = []; |
144
|
17 |
|
$args[] = $options; |
145
|
17 |
|
foreach ($set as $middleware) { |
146
|
16 |
|
$args[] = $this->locator->get($middleware); |
147
|
|
|
} |
148
|
|
|
|
149
|
17 |
|
return new MiddlewareRunner(...$args); |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
protected function combinedMiddlewares(array $extraMiddlewares): array |
153
|
|
|
{ |
154
|
|
|
$set = $this->middleware; |
155
|
|
|
|
156
|
|
|
foreach ($extraMiddlewares as $middleware) { |
157
|
|
|
if (in_array($middleware, $set, true)) { |
158
|
|
|
continue; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
$set[] = $middleware; |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
return $set; |
165
|
|
|
} |
166
|
|
|
|
167
|
17 |
|
protected function applyApiSettingsToRequest(RequestInterface $request, array $options): RequestInterface |
168
|
|
|
{ |
169
|
17 |
|
$options = array_replace_recursive($this->options, $options); |
170
|
17 |
|
$uri = $request->getUri(); |
171
|
17 |
|
if (strpos((string)$uri, '://') === false) { |
172
|
4 |
|
$uri = Uri::resolve( |
173
|
4 |
|
new Uri( |
174
|
4 |
|
$options[Options::SCHEMA] . |
175
|
4 |
|
'://' . |
176
|
4 |
|
$options[Options::HOST] . |
177
|
4 |
|
(isset($options[Options::PORT]) ? ':' . $options[Options::PORT] : '') . |
178
|
4 |
|
$options[Options::PATH] |
179
|
|
|
), |
180
|
4 |
|
$request->getUri() |
181
|
|
|
); |
182
|
|
|
} |
183
|
|
|
|
184
|
17 |
|
foreach ($options[Options::HEADERS] as $key => $value) { |
185
|
10 |
|
$request = $request->withAddedHeader($key, $value); |
186
|
|
|
} |
187
|
|
|
|
188
|
17 |
|
return $request->withUri($uri); |
189
|
|
|
} |
190
|
|
|
} |
191
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.