Completed
Pull Request — master (#22)
by Tobias
03:34
created

PluginClient::configure()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 23
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 3.0032

Importance

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