Completed
Pull Request — master (#33)
by Joel
03:18
created

PluginClient   A

Complexity

Total Complexity 11

Size/Duplication

Total Lines 130
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 93.48%

Importance

Changes 6
Bugs 3 Features 1
Metric Value
wmc 11
c 6
b 3
f 1
lcom 1
cbo 7
dl 0
loc 130
ccs 43
cts 46
cp 0.9348
rs 10

5 Methods

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