Completed
Pull Request — master (#43)
by Markus
07:40
created

PluginClient::setPlugins()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 2
Metric Value
c 2
b 0
f 2
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 9.4286
cc 2
eloc 3
nc 2
nop 1
crap 2
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
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
     * @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 = $this->setPlugins($plugins);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->setPlugins($plugins) of type null is incompatible with the declared type array<integer,object<Http\Client\Plugin\Plugin>> of property $plugins.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
Bug introduced by
Are you sure the assignment to $this->plugins is correct as $this->setPlugins($plugins) (which targets Http\Client\Plugin\PluginClient::setPlugins()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
61 8
        $this->options = $this->configure($options);
62 8
    }
63
64
    /**
65
     * Append a plugin to the end of the queue.
66
     *
67 4
     * @param Plugin $plugin
68
     */
69
    public function addPlugin(Plugin $plugin)
70 4
    {
71 1
        $this->plugins[] = $plugin;
72
    }
73
74
    /**
75
     * Get all active plugins.
76
     *
77
     * @return Plugin[]
78 2
     */
79
    public function getPlugins()
80
    {
81
        return $this->plugins;
82 3
    }
83
84 3
    /**
85
     * Assign plugins to the client.
86
     *
87
     * @param Plugin[] $plugins
88
     */
89
    public function setPlugins(array $plugins = [])
90 2
    {
91
        foreach ($plugins as $plugin) {
92
            $this->addPlugin($plugin);
93 2
        }
94 2
    }
95
96 2
    /**
97
     * {@inheritdoc}
98
     */
99
    public function sendRequest(RequestInterface $request)
100
    {
101
        // If we don't have an http client, use the async call
102
        if (!($this->client instanceof HttpClient)) {
103
            return $this->sendAsyncRequest($request)->wait();
104
        }
105
106 8
        // Else we want to use the synchronous call of the underlying client, and not the async one in the case
107
        // we have both an async and sync call
108 8
        $pluginChain = $this->createPluginChain($this->plugins, function (RequestInterface $request) {
109 8
            try {
110 8
                return new FulfilledPromise($this->client->sendRequest($request));
111 8
            } catch (Exception $exception) {
112
                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...
113 8
            }
114
        });
115
116
        return $pluginChain($request)->wait();
117
    }
118
119
    /**
120
     * {@inheritdoc}
121
     */
122
    public function sendAsyncRequest(RequestInterface $request)
123
    {
124 5
        $pluginChain = $this->createPluginChain($this->plugins, function (RequestInterface $request) {
125
            return $this->client->sendAsyncRequest($request);
126 5
        });
127
128 5
        return $pluginChain($request);
129
    }
130 1
131 1
    /**
132
     * Configure the plugin client.
133 1
     *
134 1
     * @param array $options
135
     *
136 5
     * @return array
137 5
     */
138 5
    private function configure(array $options = [])
139 1
    {
140
        $resolver = new OptionsResolver();
141
        $resolver->setDefaults([
142 5
            'max_restarts' => 10,
143
        ]);
144 5
145 5
        return $resolver->resolve($options);
146
    }
147 5
148
    /**
149
     * Create the plugin chain.
150
     *
151
     * @param Plugin[] $pluginList     A list of plugins
152
     * @param callable $clientCallable Callable making the HTTP call
153
     *
154
     * @return callable
155
     */
156
    private function createPluginChain($pluginList, callable $clientCallable)
157
    {
158
        $firstCallable = $lastCallable = $clientCallable;
159
160
        while ($plugin = array_pop($pluginList)) {
161
            $lastCallable = function (RequestInterface $request) use ($plugin, $lastCallable, &$firstCallable) {
162
                return $plugin->handleRequest($request, $lastCallable, $firstCallable);
163
            };
164
165
            $firstCallable = $lastCallable;
166
        }
167
168
        $firstCalls = 0;
169
        $firstCallable = function (RequestInterface $request) use ($lastCallable, &$firstCalls) {
170
            if ($firstCalls > $this->options['max_restarts']) {
171
                throw new LoopException('Too many restarts in plugin client', $request);
172
            }
173
174
            ++$firstCalls;
175
176
            return $lastCallable($request);
177
        };
178
179
        return $firstCallable;
180
    }
181
}
182