Passed
Pull Request — master (#40)
by
unknown
02:04
created

ServiceClient::executeAll()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 25

Duplication

Lines 12
Ratio 48 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 12
loc 25
ccs 0
cts 22
cp 0
rs 9.52
c 0
b 0
f 0
cc 3
nc 1
nop 2
crap 12
1
<?php
2
namespace GuzzleHttp\Command;
3
4
use GuzzleHttp\ClientInterface as HttpClient;
5
use GuzzleHttp\Command\Exception\CommandException;
6
use GuzzleHttp\HandlerStack;
7
use GuzzleHttp\Promise;
8
use GuzzleHttp\Promise\PromiseInterface;
9
use Psr\Http\Message\RequestInterface;
10
use Psr\Http\Message\ResponseInterface;
11
12
/**
13
 * The Guzzle ServiceClient serves as the foundation for creating web service
14
 * clients that interact with RPC-style APIs.
15
 */
16
class ServiceClient implements ServiceClientInterface
17
{
18
    /** @var HttpClient HTTP client used to send requests */
19
    private $httpClient;
20
21
    /** @var HandlerStack */
22
    private $handlerStack;
23
    
24
    /** @var callable */
25
    private $commandToRequestTransformer;
26
27
    /** @var callable */
28
    private $responseToResultTransformer;
29
30
    /**
31
     * Instantiates a Guzzle ServiceClient for making requests to a web service.
32
     *
33
     * @param HttpClient $httpClient A fully-configured Guzzle HTTP client that
34
     *     will be used to perform the underlying HTTP requests.
35
     * @param callable $commandToRequestTransformer A callable that transforms
36
     *     a Command into a Request. The function should accept a
37
     *     `GuzzleHttp\Command\CommandInterface` object and return a
38
     *     `Psr\Http\Message\RequestInterface` object.
39
     * @param callable $responseToResultTransformer A callable that transforms a
40
     *     Response into a Result. The function should accept a
41
     *     `Psr\Http\Message\ResponseInterface` object (and optionally a
42
     *     `Psr\Http\Message\RequestInterface` object) and return a
43
     *     `GuzzleHttp\Command\ResultInterface` object.
44
     * @param HandlerStack $commandHandlerStack A Guzzle HandlerStack, which can
45
     *     be used to add command-level middleware to the service client.
46
     */
47
    public function __construct(
48
        HttpClient $httpClient,
49
        callable $commandToRequestTransformer,
50
        callable $responseToResultTransformer,
51
        HandlerStack $commandHandlerStack = null
52
    ) {
53
        $this->httpClient = $httpClient;
54
        $this->commandToRequestTransformer = $commandToRequestTransformer;
55
        $this->responseToResultTransformer = $responseToResultTransformer;
56
        $this->handlerStack = $commandHandlerStack ?: new HandlerStack();
57
        $this->handlerStack->setHandler($this->createCommandHandler());
58
    }
59
60
    public function getHttpClient()
61
    {
62
        return $this->httpClient;
63
    }
64
65
    public function getHandlerStack()
66
    {
67
        return $this->handlerStack;
68
    }
69
70
    public function getCommand($name, array $params = [])
71
    {
72
        return new Command($name, $params, clone $this->handlerStack);
73
    }
74
75
    public function execute(CommandInterface $command)
76
    {
77
        return $this->executeAsync($command)->wait();
78
    }
79
80
    public function executeAsync(CommandInterface $command)
81
    {
82
        $stack = $command->getHandlerStack() ?: $this->handlerStack;
83
        $handler = $stack->resolve();
84
85
        return $handler($command);
86
    }
87
88
    public function executeAll($commands, array $options = [])
89
    {
90
        // Modify provided callbacks to track results.
91
        $results = [];
92 View Code Duplication
        $options['fulfilled'] = function ($v, $k) use (&$results, $options) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
93
            if (isset($options['fulfilled'])) {
94
                $options['fulfilled']($v, $k);
95
            }
96
            $results[$k] = $v;
97
        };
98 View Code Duplication
        $options['rejected'] = function ($v, $k) use (&$results, $options) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
99
            if (isset($options['rejected'])) {
100
                $options['rejected']($v, $k);
101
            }
102
            $results[$k] = $v;
103
        };
104
105
        // Execute multiple commands synchronously, then sort and return the results.
106
        return $this->executeAllAsync($commands, $options)
107
            ->then(function () use (&$results) {
108
                ksort($results);
109
                return $results;
110
            })
111
            ->wait();
112
    }
113
114
    public function executeAllAsync($commands, array $options = [])
115
    {
116
        // Apply default concurrency.
117
        if (!isset($options['concurrency'])) {
118
            $options['concurrency'] = 25;
119
        }
120
121
        // Convert the iterator of commands to a generator of promises.
122
        $commands = Promise\iter_for($commands);
123
        $promises = function () use ($commands) {
124
            foreach ($commands as $key => $command) {
125
                if (!$command instanceof CommandInterface) {
126
                    throw new \InvalidArgumentException('The iterator must '
127
                        . 'yield instances of ' . CommandInterface::class);
128
                }
129
                yield $key => $this->executeAsync($command);
130
            }
131
        };
132
133
        // Execute the commands using a pool.
134
        return (new Promise\EachPromise($promises(), $options))->promise();
135
    }
136
137
    /**
138
     * Creates and executes a command for an operation by name.
139
     *
140
     * @param string $name Name of the command to execute.
141
     * @param array $args Arguments to pass to the getCommand method.
142
     *
143
     * @return ResultInterface|PromiseInterface
144
     * @see \GuzzleHttp\Command\ServiceClientInterface::getCommand
145
     */
146
    public function __call($name, array $args)
147
    {
148
        $args = isset($args[0]) ? $args[0] : [];
149
        if (substr($name, -5) === 'Async') {
150
            $command = $this->getCommand(substr($name, 0, -5), $args);
151
            return $this->executeAsync($command);
152
        } else {
153
            return $this->execute($this->getCommand($name, $args));
154
        }
155
    }
156
157
    /**
158
     * Defines the main handler for commands that uses the HTTP client.
159
     *
160
     * @return callable
161
     */
162
    private function createCommandHandler()
163
    {
164
        return function (CommandInterface $command) {
165
            return Promise\coroutine(function () use ($command) {
166
                // Prepare the HTTP options.
167
                $opts = $command['@http'] ?: [];
168
                unset($command['@http']);
169
170
                try {
171
                    // Prepare the request from the command and send it.
172
                    $request = $this->transformCommandToRequest($command);
173
                    $promise = $this->httpClient->sendAsync($request, $opts);
174
175
                    // Create a result from the response.
176
                    $response = (yield $promise);
177
                    yield $this->transformResponseToResult($response, $request, $command);
178
                } catch (\Exception $e) {
179
                    throw CommandException::fromPrevious($command, $e);
180
                }
181
            });
182
        };
183
    }
184
185
    /**
186
     * Transforms a Command object into a Request object.
187
     *
188
     * @param CommandInterface $command
189
     * @return RequestInterface
190
     */
191
    private function transformCommandToRequest(CommandInterface $command)
192
    {
193
        $transform = $this->commandToRequestTransformer;
194
195
        return $transform($command);
196
    }
197
198
199
    /**
200
     * Transforms a Response object, also using data from the Request object,
201
     * into a Result object.
202
     *
203
     * @param ResponseInterface $response
204
     * @param RequestInterface $request
205
     * @param CommandInterface $command
206
     * @return ResultInterface
207
     */
208
    private function transformResponseToResult(
209
        ResponseInterface $response,
210
        RequestInterface $request,
211
        CommandInterface $command
212
    ) {
213
        $transform = $this->responseToResultTransformer;
214
215
        return $transform($response, $request, $command);
216
    }
217
}
218