Completed
Pull Request — master (#22)
by Tobias
05:24
created

PluginClient   A

Complexity

Total Complexity 12

Size/Duplication

Total Lines 143
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 93.88%

Importance

Changes 2
Bugs 0 Features 1
Metric Value
wmc 12
c 2
b 0
f 1
lcom 1
cbo 7
dl 0
loc 143
ccs 46
cts 49
cp 0.9388
rs 10

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 3
A sendRequest() 0 19 3
A sendAsyncRequest() 0 8 1
A configure() 0 10 1
B createPluginChain() 0 34 4
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
     * }
49
     *
50
     * @throws \RuntimeException if client is not an instance of HttpClient or HttpAsyncClient
51
     */
52 8
    public function __construct($client, array $plugins = [], array $options = [])
53
    {
54 8
        if ($client instanceof HttpAsyncClient) {
55 3
            $this->client = $client;
56 8
        } elseif ($client instanceof HttpClient) {
57 5
            $this->client = new EmulatedHttpAsyncClient($client);
58 5
        } else {
59
            throw new \RuntimeException('Client must be an instance of Http\\Client\\HttpClient or Http\\Client\\HttpAsyncClient');
60
        }
61
62 8
        $this->plugins = $plugins;
63 8
        $this->options = $this->configure($options);
64 8
    }
65
66
    /**
67
     * {@inheritdoc}
68
     */
69 4
    public function sendRequest(RequestInterface $request)
70
    {
71
        // If we don't have an http client, use the async call
72 4
        if (!($this->client instanceof HttpClient)) {
73 1
            return $this->sendAsyncRequest($request)->wait();
74
        }
75
76
        // Else we want to use the synchronous call of the underlying client, and not the async one in the case
77
        // we have both an async and sync call
78
        $pluginChain = $this->createPluginChain($this->plugins, function (RequestInterface $request) {
79
            try {
80 2
                return new FulfilledPromise($this->client->sendRequest($request));
81
            } catch (HttplugException $exception) {
82
                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...
83
            }
84 3
        });
85
86 3
        return $pluginChain($request)->wait();
87
    }
88
89
    /**
90
     * {@inheritdoc}
91
     */
92 2
    public function sendAsyncRequest(RequestInterface $request)
93
    {
94
        $pluginChain = $this->createPluginChain($this->plugins, function (RequestInterface $request) {
95 2
            return $this->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
    private function configure(array $options = [])
109
    {
110 8
        $resolver = new OptionsResolver();
111 8
        $resolver->setDefaults([
112 8
            'max_restarts' => 10,
113 8
            'debug_plugins' => []
114 8
        ]);
115
116 8
        return $resolver->resolve($options);
117
    }
118
119
    /**
120
     * Create the plugin chain.
121
     *
122
     * @param Plugin[] $pluginList     A list of plugins
123
     * @param callable $clientCallable Callable making the HTTP call
124
     *
125
     * @return callable
126
     */
127 5
    private function createPluginChain($pluginList, callable $clientCallable)
128
    {
129 5
        $firstCallable = $lastCallable = $clientCallable;
130
131
        /*
132
         * Inject debug plugins between each plugin.  
133
         */
134 5
        $pluginListWithDegug = $this->options['debug_plugins'];
135 5
        foreach ($pluginList as $plugin) {
136 1
            $pluginListWithDegug[] = $plugin;
137 1
            $pluginListWithDegug = array_merge($pluginListWithDegug, $this->options['debug_plugins']);
138 5
        }
139
140 5
        while ($plugin = array_pop($pluginListWithDegug)) {
141
            $lastCallable = function (RequestInterface $request) use ($plugin, $lastCallable, &$firstCallable) {
142 1
                return $plugin->handleRequest($request, $lastCallable, $firstCallable);
143 1
            };
144
145 1
            $firstCallable = $lastCallable;
146 1
        }
147
148 5
        $firstCalls = 0;
149 5
        $firstCallable = function (RequestInterface $request) use ($lastCallable, &$firstCalls) {
150 5
            if ($firstCalls > $this->options['max_restarts']) {
151 1
                throw new LoopException('Too many restarts in plugin client', $request);
152
            }
153
154 5
            ++$firstCalls;
155
156 5
            return $lastCallable($request);
157 5
        };
158
159 5
        return $firstCallable;
160
    }
161
}
162