Completed
Push — master ( 327515...051294 )
by Indra
02:26
created

ServiceClient::executeAllAsync()   B

Complexity

Conditions 4
Paths 2

Size

Total Lines 22
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
dl 0
loc 22
ccs 0
cts 13
cp 0
rs 8.9197
c 0
b 0
f 0
cc 4
eloc 11
nc 2
nop 2
crap 20
1
<?php
2
3
namespace IndraGunawan\RestService;
4
5
use GuzzleHttp\Client as HttpClient;
6
use GuzzleHttp\Command\Command;
7
use GuzzleHttp\Command\CommandInterface;
8
use GuzzleHttp\Command\ServiceClientInterface;
9
use GuzzleHttp\Exception\BadResponseException;
10
use GuzzleHttp\HandlerStack;
11
use GuzzleHttp\Promise;
12
use GuzzleHttp\Promise\PromiseInterface;
13
use IndraGunawan\RestService\Exception\BadRequestException;
14
use IndraGunawan\RestService\Exception\CommandException;
15
use IndraGunawan\RestService\Exception\ValidatorException;
16
use Psr\Http\Message\RequestInterface;
17
use Psr\Http\Message\ResponseInterface;
18
19
/**
20
 * The Guzzle ServiceClient serves as the foundation for creating web service
21
 * clients that interact with RPC-style APIs.
22
 */
23
class ServiceClient implements ServiceClientInterface
24
{
25
    /**
26
     * @var HttpClient HTTP client used to send requests
27
     */
28
    private $httpClient;
29
30
    /**
31
     * @var HandlerStack
32
     */
33
    private $handlerStack;
34
35
    /**
36
     * @var callable
37
     */
38
    private $commandToRequestTransformer;
39
40
    /**
41
     * @var callable
42
     */
43
    private $responseToResultTransformer;
44
45
    /**
46
     * @var callable
47
     */
48
    private $badResponseExceptionParser;
49
50
    /**
51
     * @param string $specificationFile
52
     * @param array  $config
53
     * @param string $cacheDir
54
     * @param bool   $debug
55
     *
56
     * @throws \IndraGunawan\RestService\Exception\InvalidSpecificationException
57
     */
58 2
    public function __construct($specificationFile, array $config = [], $cacheDir = null, $debug = false)
59
    {
60 2
        $this->httpClient = new HttpClient(isset($config['httpClient']) ? $config['httpClient'] : []);
61 2
        $this->handlerStack = new HandlerStack();
62 2
        $this->handlerStack->setHandler($this->createCommandHandler());
63
64 2
        $service = new Service(
65 2
            $specificationFile,
66 2
            (isset($config['defaults']) && is_array($config['defaults'])) ? $config['defaults'] : [],
67 2
            $cacheDir,
68
            $debug
69 2
        );
70
71
        $builder = new Builder($service);
72
73
        $this->commandToRequestTransformer = $builder->commandToRequestTransformer();
74
        $this->responseToResultTransformer = $builder->responseToResultTransformer();
75
        $this->badResponseExceptionParser = $builder->badResponseExceptionParser();
76
    }
77
78
    public function getHttpClient()
79
    {
80
        return $this->httpClient;
81
    }
82
83
    public function getHandlerStack()
84
    {
85
        return $this->handlerStack;
86
    }
87
88
    /**
89
     * {@inheritdoc}
90
     */
91
    public function getCommand($name, array $params = [])
92
    {
93
        return new Command($name, $params, clone $this->handlerStack);
94
    }
95
96
    /**
97
     * {@inheritdoc}
98
     */
99
    public function execute(CommandInterface $command)
100
    {
101
        return $this->executeAsync($command)->wait();
102
    }
103
104
    /**
105
     * {@inheritdoc}
106
     */
107
    public function executeAsync(CommandInterface $command)
108
    {
109
        $stack = $command->getHandlerStack() ?: $this->handlerStack;
110
        $handler = $stack->resolve();
111
112
        return $handler($command);
113
    }
114
115
    /**
116
     * {@inheritdoc}
117
     */
118
    public function executeAll($commands, array $options = [])
119
    {
120
        // Modify provided callbacks to track results.
121
        $results = [];
122
        $options['fulfilled'] = function ($v, $k) use (&$results, $options) {
123
            if (isset($options['fulfilled'])) {
124
                $options['fulfilled']($v, $k);
125
            }
126
            $results[$k] = $v;
127
        };
128
        $options['rejected'] = function ($v, $k) use (&$results, $options) {
129
            if (isset($options['rejected'])) {
130
                $options['rejected']($v, $k);
131
            }
132
            $results[$k] = $v;
133
        };
134
135
        // Execute multiple commands synchronously, then sort and return the results.
136
        return $this->executeAllAsync($commands, $options)
137
            ->then(function () use (&$results) {
138
                ksort($results);
139
140
                return $results;
141
            })
142
            ->wait();
143
    }
144
145
    /**
146
     * {@inheritdoc}
147
     */
148
    public function executeAllAsync($commands, array $options = [])
149
    {
150
        // Apply default concurrency.
151
        if (!isset($options['concurrency'])) {
152
            $options['concurrency'] = 25;
153
        }
154
155
        // Convert the iterator of commands to a generator of promises.
156
        $commands = Promise\iter_for($commands);
157
        $promises = function () use ($commands) {
158
            foreach ($commands as $key => $command) {
159
                if (!$command instanceof CommandInterface) {
160
                    throw new \InvalidArgumentException('The iterator must '
161
                        .'yield instances of '.CommandInterface::class);
162
                }
163
                yield $key => $this->executeAsync($command);
164
            }
165
        };
166
167
        // Execute the commands using a pool.
168
        return (new Promise\EachPromise($promises(), $options))->promise();
169
    }
170
171
    /**
172
     * Creates and executes a command for an operation by name.
173
     *
174
     * @param string $name Name of the command to execute.
175
     * @param array  $args Arguments to pass to the getCommand method.
176
     *
177
     * @throws \IndraGunawan\RestService\Exception\BadRequestException
178
     * @throws \IndraGunawan\RestService\Exception\BadResponseException
179
     * @throws \IndraGunawan\RestService\Exception\CommandException
180
     *
181
     * @return ResultInterface|PromiseInterface
182
     *
183
     * @see \GuzzleHttp\Command\ServiceClientInterface::getCommand
184
     */
185
    public function __call($name, array $args)
186
    {
187
        $args = isset($args[0]) ? $args[0] : [];
188
        if (substr($name, -5) === 'Async') {
189
            $command = $this->getCommand(substr($name, 0, -5), $args);
190
191
            return $this->executeAsync($command);
192
        } else {
193
            return $this->execute($this->getCommand($name, $args));
194
        }
195
    }
196
197
    /**
198
     * Defines the main handler for commands that uses the HTTP client.
199
     *
200
     * @throws \IndraGunawan\RestService\Exception\BadRequestException
201
     * @throws \IndraGunawan\RestService\Exception\BadResponseException
202
     * @throws \IndraGunawan\RestService\Exception\CommandException
203
     *
204
     * @return callable
205
     */
206
    private function createCommandHandler()
207
    {
208
        return function (CommandInterface $command) {
209
            return Promise\coroutine(function () use ($command) {
210
                // Prepare the HTTP options.
211
                $opts = $command['@http'] ?: [];
212
213
                try {
214
                    // Prepare the request from the command and send it.
215
                    $request = $this->transformCommandToRequest($command);
216
                    $promise = $this->httpClient->sendAsync($request, $opts);
217
218
                    // Create a result from the response.
219
                    $response = (yield $promise);
220
                    yield $this->transformResponseToResult($response, $request, $command);
221
                } catch (ValidatorException $e) {
222
                    throw new BadRequestException($e->getField(), $e->getErrorMessage(), $e);
223
                } catch (BadResponseException $e) {
224
                    $this->parseBadResponseException($command, $e);
225
                } catch (\Exception $e) {
226
                    throw new CommandException($e->getMessage(), $command, $e);
227
                }
228
            });
229 2
        };
230
    }
231
232
    /**
233
     * Transforms a Command object into a Request object.
234
     *
235
     * @param CommandInterface $command
236
     *
237
     * @return RequestInterface
238
     */
239
    private function transformCommandToRequest(CommandInterface $command)
240
    {
241
        $transform = $this->commandToRequestTransformer;
242
243
        return $transform($command);
244
    }
245
246
    /**
247
     * Transforms a Response object, also using data from the Request object,
248
     * into a Result object.
249
     *
250
     * @param ResponseInterface $response
251
     * @param RequestInterface  $request
252
     *
253
     * @return ResultInterface
254
     */
255
    private function transformResponseToResult(
256
        ResponseInterface $response,
257
        RequestInterface $request,
258
        CommandInterface $command
259
    ) {
260
        $transform = $this->responseToResultTransformer;
261
262
        return $transform($response, $request, $command);
263
    }
264
265
    /**
266
     * Parse BadResponseException when retrive response.
267
     *
268
     * @param CommandInterface     $command
269
     * @param BadResponseException $e
270
     *
271
     * @throws \IndraGunawan\RestService\Exception\BadResponseException
272
     */
273
    private function parseBadResponseException(CommandInterface $command, BadResponseException $e)
274
    {
275
        $parser = $this->badResponseExceptionParser;
276
277
        $parser($command, $e);
278
    }
279
}
280