Completed
Pull Request — master (#14)
by Márk
03:49
created

PluginClient::sendRequest()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.1406

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 19
ccs 6
cts 8
cp 0.75
rs 9.4285
cc 3
eloc 9
nc 2
nop 1
crap 3.1406
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\Promise\FulfilledPromise;
10
use Http\Promise\RejectedPromise;
11
use Psr\Http\Message\RequestInterface;
12
use Symfony\Component\OptionsResolver\OptionsResolver;
13
14
/**
15
 * The client managing plugins and providing a decorator around HTTP Clients.
16
 *
17
 * @author Joel Wurtz <[email protected]>
18
 *
19
 * TODO: make class final in version 2.0, once plugins does not extend it anymore.
20
 */
21
/*final*/ class PluginClient implements HttpClient, HttpAsyncClient
22
{
23
    /**
24
     * An HTTP async client.
25
     *
26
     * @var HttpAsyncClient
27
     */
28
    private $client;
29
30
    /**
31
     * The plugin chain.
32
     *
33
     * @var Plugin[]
34
     */
35
    private $plugins;
36
37
    /**
38
     * A list of options.
39
     *
40
     * @var array
41
     */
42
    private $options;
43
44
    /**
45
     * @param HttpClient|HttpAsyncClient $client
46
     * @param Plugin[]                   $plugins
47
     * @param array                      $options {
48
     *
49
     *     @var int $max_restarts
50
     * }
51
     *
52
     * @throws \RuntimeException if client is not an instance of HttpClient or HttpAsyncClient
53
     */
54 8
    public function __construct($client, array $plugins = [], array $options = [])
55
    {
56 8
        if ($client instanceof HttpAsyncClient) {
57 3
            $this->client = $client;
58 8
        } elseif ($client instanceof HttpClient) {
59 5
            $this->client = new EmulatedHttpAsyncClient($client);
60 5
        } else {
61
            throw new \RuntimeException('Client must be an instance of Http\\Client\\HttpClient or Http\\Client\\HttpAsyncClient');
62
        }
63
64 8
        $this->plugins = $plugins;
65 8
        $this->options = $this->configure($options);
66 8
    }
67
68
    /**
69
     * {@inheritdoc}
70
     */
71 4
    public function sendRequest(RequestInterface $request)
72
    {
73
        // If we don't have an http client, use the async call
74 4
        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 2
                return new FulfilledPromise($this->client->sendRequest($request));
83
            } catch (HttplugException $exception) {
84
                return new RejectedPromise($exception);
0 ignored issues
show
Documentation introduced by
$exception is of type object<Http\Client\Exception>, but the function expects a object<Exception>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
85
            }
86 3
        });
87
88 3
        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 8
    private function configure(array $options = [])
111
    {
112 8
        $resolver = new OptionsResolver();
113 8
        $resolver->setDefaults([
114 8
            'max_restarts' => 10,
115 8
        ]);
116
117 8
        return $resolver->resolve($options);
118
    }
119
120
    /**
121
     * Create the plugin chain.
122
     *
123
     * @param Plugin[] $pluginList     A list of plugins
124
     * @param callable $clientCallable Callable making the HTTP call
125
     *
126
     * @return callable
127
     */
128 5
    private function createPluginChain($pluginList, callable $clientCallable)
129
    {
130 5
        $firstCallable = $lastCallable = $clientCallable;
131
132 5
        while ($plugin = array_pop($pluginList)) {
133
            $lastCallable = function (RequestInterface $request) use ($plugin, $lastCallable, &$firstCallable) {
134 1
                return $plugin->handleRequest($request, $lastCallable, $firstCallable);
135 1
            };
136
137 1
            $firstCallable = $lastCallable;
138 1
        }
139
140 5
        $firstCalls = 0;
141 5
        $firstCallable = function (RequestInterface $request) use ($lastCallable, &$firstCalls) {
142 5
            if ($firstCalls > $this->options['max_restarts']) {
143 1
                throw $this->createLoopException($request);
144
            }
145
146 5
            ++$firstCalls;
147
148 5
            return $lastCallable($request);
149 5
        };
150
151 5
        return $firstCallable;
152
    }
153
154
    /**
155
     * Creates a new loop exception.
156
     *
157
     * @param RequestInterface $request
158
     *
159
     * @return LoopException
160
     *
161
     * TODO: Remove once plugin client is removed from plugins.
162
     */
163 1
    protected function createLoopException(RequestInterface $request)
164
    {
165 1
        return new LoopException('Too many restarts in plugin client', $request);
166
    }
167
}
168