Completed
Push — master ( cd17f3...38b66e )
by David
01:50 queued 12s
created

PluginClient   A

Complexity

Total Complexity 11

Size/Duplication

Total Lines 129
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 90.24%

Importance

Changes 0
Metric Value
wmc 11
lcom 1
cbo 6
dl 0
loc 129
ccs 37
cts 41
cp 0.9024
rs 10
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A sendRequest() 0 19 3
A sendAsyncRequest() 0 8 1
A configure() 0 11 1
A createPluginChain() 0 25 3
A __construct() 0 15 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Http\Client\Common;
6
7
use Http\Client\Common\Exception\LoopException;
8
use Http\Client\Exception as HttplugException;
9
use Http\Client\HttpAsyncClient;
10
use Http\Client\HttpClient;
11
use Http\Client\Promise\HttpFulfilledPromise;
12
use Http\Client\Promise\HttpRejectedPromise;
13
use Psr\Http\Client\ClientInterface;
14
use Psr\Http\Message\RequestInterface;
15
use Psr\Http\Message\ResponseInterface;
16
use Symfony\Component\OptionsResolver\OptionsResolver;
17
18
/**
19
 * The client managing plugins and providing a decorator around HTTP Clients.
20
 *
21
 * @author Joel Wurtz <[email protected]>
22
 */
23
final class PluginClient implements HttpClient, HttpAsyncClient
24
{
25
    /**
26
     * An HTTP async client.
27
     *
28
     * @var HttpAsyncClient|HttpClient
29
     */
30
    private $client;
31
32
    /**
33
     * The plugin chain.
34
     *
35
     * @var Plugin[]
36
     */
37
    private $plugins;
38
39
    /**
40
     * A list of options.
41
     *
42
     * @var array
43
     */
44
    private $options;
45
46
    /**
47
     * @param ClientInterface|HttpAsyncClient $client
48
     * @param Plugin[]                        $plugins
49
     * @param array                           $options {
50
     *
51
     *     @var int      $max_restarts
52
     * }
53
     */
54 10
    public function __construct($client, array $plugins = [], array $options = [])
55
    {
56 10
        if ($client instanceof HttpAsyncClient) {
57 3
            $this->client = $client;
58 7
        } elseif ($client instanceof ClientInterface) {
59 7
            $this->client = new EmulatedHttpAsyncClient($client);
60
        } else {
61
            throw new \TypeError(
62
                sprintf('%s::__construct(): Argument #1 ($client) must be of type %s|%s, %s given', self::class, ClientInterface::class, HttpAsyncClient::class, get_debug_type($client))
0 ignored issues
show
Unused Code introduced by
The call to TypeError::__construct() has too many arguments starting with sprintf('%s::__construct...et_debug_type($client)).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
63
            );
64
        }
65
66 10
        $this->plugins = $plugins;
67 10
        $this->options = $this->configure($options);
68 10
    }
69
70
    /**
71
     * {@inheritdoc}
72
     */
73 4
    public function sendRequest(RequestInterface $request): ResponseInterface
74
    {
75
        // If we don't have an http client, use the async call
76 4
        if (!($this->client instanceof ClientInterface)) {
77 1
            return $this->sendAsyncRequest($request)->wait();
78
        }
79
80
        // Else we want to use the synchronous call of the underlying client, and not the async one in the case
81
        // we have both an async and sync call
82
        $pluginChain = $this->createPluginChain($this->plugins, function (RequestInterface $request) {
83
            try {
84 2
                return new HttpFulfilledPromise($this->client->sendRequest($request));
0 ignored issues
show
Bug introduced by
The method sendRequest does only exist in Http\Client\HttpClient, but not in Http\Client\HttpAsyncClient.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
85
            } catch (HttplugException $exception) {
86
                return new HttpRejectedPromise($exception);
87
            }
88 3
        });
89
90 3
        return $pluginChain($request)->wait();
91
    }
92
93
    /**
94
     * {@inheritdoc}
95
     */
96 2
    public function sendAsyncRequest(RequestInterface $request)
97
    {
98
        $pluginChain = $this->createPluginChain($this->plugins, function (RequestInterface $request) {
99 2
            return $this->client->sendAsyncRequest($request);
0 ignored issues
show
Bug introduced by
The method sendAsyncRequest does only exist in Http\Client\HttpAsyncClient, but not in Http\Client\HttpClient.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
100 2
        });
101
102 2
        return $pluginChain($request);
103
    }
104
105
    /**
106
     * Configure the plugin client.
107
     */
108 10
    private function configure(array $options = []): array
109
    {
110 10
        $resolver = new OptionsResolver();
111 10
        $resolver->setDefaults([
112 10
            'max_restarts' => 10,
113
        ]);
114
115 10
        $resolver->setAllowedTypes('max_restarts', 'int');
116
117 10
        return $resolver->resolve($options);
118
    }
119
120
    /**
121
     * Create the plugin chain.
122
     *
123
     * @param Plugin[] $pluginList     A list of plugins
124
     * @param callable $clientCallable Callable making the HTTP call
125
     */
126 5
    private function createPluginChain(array $pluginList, callable $clientCallable): callable
127
    {
128 5
        $firstCallable = $lastCallable = $clientCallable;
129
130 5
        while ($plugin = array_pop($pluginList)) {
131
            $lastCallable = function (RequestInterface $request) use ($plugin, $lastCallable, &$firstCallable) {
132 1
                return $plugin->handleRequest($request, $lastCallable, $firstCallable);
133 1
            };
134
135 1
            $firstCallable = $lastCallable;
136
        }
137
138 5
        $firstCalls = 0;
139
        $firstCallable = function (RequestInterface $request) use ($lastCallable, &$firstCalls) {
140 5
            if ($firstCalls > $this->options['max_restarts']) {
141 1
                throw new LoopException('Too many restarts in plugin client', $request);
142
            }
143
144 5
            ++$firstCalls;
145
146 5
            return $lastCallable($request);
147 5
        };
148
149 5
        return $firstCallable;
150
    }
151
}
152