Completed
Push — master ( c6f8f4...6bb56d )
by Joel
02:27
created

PluginClient::configure()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 2
Bugs 2 Features 1
Metric Value
c 2
b 2
f 1
dl 0
loc 9
ccs 6
cts 6
cp 1
rs 9.6667
cc 1
eloc 5
nc 1
nop 1
crap 1
1
<?php
2
3
namespace Http\Client\Plugin;
4
5
use Http\Client\Common\EmulatedHttpAsyncClient;
6
use Http\Client\Exception;
7
use Http\Client\HttpAsyncClient;
8
use Http\Client\HttpClient;
9
use Http\Client\Plugin\Exception\LoopException;
10
use Http\Promise\FulfilledPromise;
11
use Http\Promise\RejectedPromise;
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
class PluginClient implements HttpClient, HttpAsyncClient
21
{
22
    /**
23
     * An HTTP async client.
24
     *
25
     * @var HttpAsyncClient
26
     */
27
    protected $client;
28
29
    /**
30
     * The plugin chain.
31
     *
32
     * @var Plugin[]
33
     */
34
    protected $plugins;
35
36
    /**
37
     * A list of options.
38
     *
39
     * @var array
40
     */
41
    protected $options;
42
43
    /**
44
     * @param HttpClient|HttpAsyncClient $client
45
     * @param Plugin[]                   $plugins
46
     * @param array                      $options
47
     *
48
     * @throws \RuntimeException if client is not an instance of HttpClient or HttpAsyncClient
49
     */
50 8
    public function __construct($client, array $plugins = [], array $options = [])
51
    {
52 8
        if ($client instanceof HttpAsyncClient) {
53 3
            $this->client = $client;
54 8
        } elseif ($client instanceof HttpClient) {
55 5
            $this->client = new EmulatedHttpAsyncClient($client);
56 5
        } else {
57
            throw new \RuntimeException('Client must be an instance of Http\\Client\\HttpClient or Http\\Client\\HttpAsyncClient');
58
        }
59
60 8
        $this->plugins = $plugins;
61 8
        $this->options = $this->configure($options);
62 8
    }
63
64
    /**
65
     * {@inheritdoc}
66
     */
67 4
    public function sendRequest(RequestInterface $request)
68
    {
69
        // If we don't have an http client, use the async call
70 4
        if (!($this->client instanceof HttpClient)) {
71 1
            return $this->sendAsyncRequest($request)->wait();
72
        }
73
74
        // Else we want to use the synchronous call of the underlying client, and not the async one in the case
75
        // we have both an async and sync call
76 3
        $client = $this->client;
77
        $pluginChain = $this->createPluginChain($this->plugins, function (RequestInterface $request) use ($client) {
78
            try {
79 2
                return new FulfilledPromise($client->sendRequest($request));
80
            } catch (Exception $exception) {
81
                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...
82
            }
83 3
        });
84
85 3
        return $pluginChain($request)->wait();
86
    }
87
88
    /**
89
     * {@inheritdoc}
90
     */
91 2
    public function sendAsyncRequest(RequestInterface $request)
92
    {
93 2
        $client = $this->client;
94
        $pluginChain = $this->createPluginChain($this->plugins, function (RequestInterface $request) use ($client) {
95 2
            return $client->sendAsyncRequest($request);
96 2
        });
97
98 2
        return $pluginChain($request);
99
    }
100
101
    /**
102
     * Configure the plugin client.
103
     *
104
     * @param array $options
105
     *
106
     * @return array
107
     */
108 8
    protected function configure(array $options = [])
109
    {
110 8
        $resolver = new OptionsResolver();
111 8
        $resolver->setDefaults([
112 8
            'max_restarts' => 10,
113 8
        ]);
114
115 8
        return $resolver->resolve($options);
116
    }
117
118
    /**
119
     * Create the plugin chain.
120
     *
121
     * @param Plugin[] $pluginList     A list of plugins
122
     * @param callable $clientCallable Callable making the HTTP call
123
     *
124
     * @return callable
125
     */
126 5
    private function createPluginChain($pluginList, callable $clientCallable)
127
    {
128 5
        $options = $this->options;
129 5
        $firstCallable = $lastCallable = $clientCallable;
130
131 5
        while ($plugin = array_pop($pluginList)) {
132
            $lastCallable = function (RequestInterface $request) use ($plugin, $lastCallable, &$firstCallable) {
133 1
                return $plugin->handleRequest($request, $lastCallable, $firstCallable);
134 1
            };
135
136 1
            $firstCallable = $lastCallable;
137 1
        }
138
139 5
        $firstCalls = 0;
140 5
        $firstCallable = function (RequestInterface $request) use ($options, $lastCallable, &$firstCalls) {
141 5
            if ($firstCalls > $options['max_restarts']) {
142 1
                throw new LoopException('Too many restarts in plugin client', $request);
143
            }
144
145 5
            ++$firstCalls;
146
147 5
            return $lastCallable($request);
148 5
        };
149
150 5
        return $firstCallable;
151
    }
152
}
153