These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Http\Client\Common; |
||
4 | |||
5 | use Http\Client\Common\Exception\LoopException; |
||
6 | use Http\Client\Exception as HttplugException; |
||
7 | use Http\Client\HttpAsyncClient; |
||
8 | use Http\Client\HttpClient; |
||
9 | use Http\Client\Promise\HttpFulfilledPromise; |
||
10 | use Http\Client\Promise\HttpRejectedPromise; |
||
11 | use Psr\Http\Client\ClientInterface; |
||
12 | use Psr\Http\Message\RequestInterface; |
||
13 | use Symfony\Component\OptionsResolver\OptionsResolver; |
||
14 | |||
15 | /** |
||
16 | * The client managing plugins and providing a decorator around HTTP Clients. |
||
17 | * |
||
18 | * @author Joel Wurtz <[email protected]> |
||
19 | */ |
||
20 | final class PluginClient implements HttpClient, HttpAsyncClient |
||
21 | { |
||
22 | /** |
||
23 | * An HTTP async client. |
||
24 | * |
||
25 | * @var HttpAsyncClient |
||
26 | */ |
||
27 | private $client; |
||
28 | |||
29 | /** |
||
30 | * The plugin chain. |
||
31 | * |
||
32 | * @var Plugin[] |
||
33 | */ |
||
34 | private $plugins; |
||
35 | |||
36 | /** |
||
37 | * A list of options. |
||
38 | * |
||
39 | * @var array |
||
40 | */ |
||
41 | private $options; |
||
42 | |||
43 | /** |
||
44 | * @param HttpClient|HttpAsyncClient $client |
||
45 | * @param Plugin[] $plugins |
||
46 | * @param array $options { |
||
47 | * |
||
48 | * @var int $max_restarts |
||
49 | * @var Plugin[] $debug_plugins an array of plugins that are injected between each normal plugin |
||
50 | * } |
||
51 | * |
||
52 | * @throws \RuntimeException if client is not an instance of HttpClient or HttpAsyncClient |
||
53 | */ |
||
54 | 11 | public function __construct($client, array $plugins = [], array $options = []) |
|
55 | { |
||
56 | 11 | if ($client instanceof HttpAsyncClient) { |
|
57 | 3 | $this->client = $client; |
|
58 | 8 | } elseif ($client instanceof HttpClient || $client instanceof ClientInterface) { |
|
59 | 8 | $this->client = new EmulatedHttpAsyncClient($client); |
|
60 | } else { |
||
61 | throw new \RuntimeException('Client must be an instance of Http\\Client\\HttpClient or Http\\Client\\HttpAsyncClient'); |
||
62 | } |
||
63 | |||
64 | 11 | $this->plugins = $plugins; |
|
65 | 11 | $this->options = $this->configure($options); |
|
66 | 11 | } |
|
67 | |||
68 | /** |
||
69 | * {@inheritdoc} |
||
70 | */ |
||
71 | 5 | public function sendRequest(RequestInterface $request) |
|
72 | { |
||
73 | // If we don't have an http client, use the async call |
||
74 | 5 | if (!($this->client instanceof HttpClient)) { |
|
75 | 1 | return $this->sendAsyncRequest($request)->wait(); |
|
76 | } |
||
77 | |||
78 | // Else we want to use the synchronous call of the underlying client, and not the async one in the case |
||
79 | // we have both an async and sync call |
||
80 | $pluginChain = $this->createPluginChain($this->plugins, function (RequestInterface $request) { |
||
81 | try { |
||
82 | 3 | return new HttpFulfilledPromise($this->client->sendRequest($request)); |
|
83 | } catch (HttplugException $exception) { |
||
84 | return new HttpRejectedPromise($exception); |
||
85 | } |
||
86 | 4 | }); |
|
87 | |||
88 | 4 | return $pluginChain($request)->wait(); |
|
89 | } |
||
90 | |||
91 | /** |
||
92 | * {@inheritdoc} |
||
93 | */ |
||
94 | 2 | public function sendAsyncRequest(RequestInterface $request) |
|
95 | { |
||
96 | $pluginChain = $this->createPluginChain($this->plugins, function (RequestInterface $request) { |
||
97 | 2 | return $this->client->sendAsyncRequest($request); |
|
98 | 2 | }); |
|
99 | |||
100 | 2 | return $pluginChain($request); |
|
101 | } |
||
102 | |||
103 | /** |
||
104 | * Configure the plugin client. |
||
105 | * |
||
106 | * @param array $options |
||
107 | * |
||
108 | * @return array |
||
109 | */ |
||
110 | 11 | private function configure(array $options = []) |
|
111 | { |
||
112 | 11 | if (isset($options['debug_plugins'])) { |
|
113 | 1 | @trigger_error('The "debug_plugins" option is deprecated since 1.5 and will be removed in 2.0.', E_USER_DEPRECATED); |
|
0 ignored issues
–
show
|
|||
114 | } |
||
115 | |||
116 | 11 | $resolver = new OptionsResolver(); |
|
117 | 11 | $resolver->setDefaults([ |
|
118 | 11 | 'max_restarts' => 10, |
|
119 | 'debug_plugins' => [], |
||
120 | ]); |
||
121 | |||
122 | $resolver |
||
123 | 11 | ->setAllowedTypes('debug_plugins', 'array') |
|
124 | ->setAllowedValues('debug_plugins', function (array $plugins) { |
||
125 | 11 | foreach ($plugins as $plugin) { |
|
126 | // Make sure each object passed with the `debug_plugins` is an instance of Plugin. |
||
127 | 1 | if (!$plugin instanceof Plugin) { |
|
128 | 1 | return false; |
|
129 | } |
||
130 | } |
||
131 | |||
132 | 11 | return true; |
|
133 | 11 | }); |
|
134 | |||
135 | 11 | return $resolver->resolve($options); |
|
136 | } |
||
137 | |||
138 | /** |
||
139 | * Create the plugin chain. |
||
140 | * |
||
141 | * @param Plugin[] $pluginList A list of plugins |
||
142 | * @param callable $clientCallable Callable making the HTTP call |
||
143 | * |
||
144 | * @return callable |
||
145 | */ |
||
146 | 6 | private function createPluginChain($pluginList, callable $clientCallable) |
|
147 | { |
||
148 | 6 | $firstCallable = $lastCallable = $clientCallable; |
|
149 | |||
150 | /* |
||
151 | * Inject debug plugins between each plugin. |
||
152 | */ |
||
153 | 6 | $pluginListWithDebug = $this->options['debug_plugins']; |
|
154 | 6 | foreach ($pluginList as $plugin) { |
|
155 | 2 | $pluginListWithDebug[] = $plugin; |
|
156 | 2 | $pluginListWithDebug = array_merge($pluginListWithDebug, $this->options['debug_plugins']); |
|
157 | } |
||
158 | |||
159 | 6 | while ($plugin = array_pop($pluginListWithDebug)) { |
|
160 | $lastCallable = function (RequestInterface $request) use ($plugin, $lastCallable, &$firstCallable) { |
||
161 | 2 | return $plugin->handleRequest($request, $lastCallable, $firstCallable); |
|
162 | 2 | }; |
|
163 | |||
164 | 2 | $firstCallable = $lastCallable; |
|
165 | } |
||
166 | |||
167 | 6 | $firstCalls = 0; |
|
168 | 6 | $firstCallable = function (RequestInterface $request) use ($lastCallable, &$firstCalls) { |
|
169 | 6 | if ($firstCalls > $this->options['max_restarts']) { |
|
170 | 1 | throw new LoopException('Too many restarts in plugin client', $request); |
|
171 | } |
||
172 | |||
173 | 6 | ++$firstCalls; |
|
174 | |||
175 | 6 | return $lastCallable($request); |
|
176 | 6 | }; |
|
177 | |||
178 | 6 | return $firstCallable; |
|
179 | } |
||
180 | } |
||
181 |
If you suppress an error, we recommend checking for the error condition explicitly: