Completed
Push — master ( 38b66e...6aaedf )
by David
01:26
created

PluginClient::createPluginChain()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Http\Client\Common;
6
7
use Http\Client\Exception as HttplugException;
8
use Http\Client\HttpAsyncClient;
9
use Http\Client\HttpClient;
10
use Http\Client\Promise\HttpFulfilledPromise;
11
use Http\Client\Promise\HttpRejectedPromise;
12
use Psr\Http\Client\ClientInterface;
13
use Psr\Http\Message\RequestInterface;
14
use Psr\Http\Message\ResponseInterface;
15
use Symfony\Component\OptionsResolver\OptionsResolver;
16
17
/**
18
 * The client managing plugins and providing a decorator around HTTP Clients.
19
 *
20
 * @author Joel Wurtz <[email protected]>
21
 */
22
final class PluginClient implements HttpClient, HttpAsyncClient
23
{
24
    /**
25
     * An HTTP async client.
26
     *
27
     * @var HttpAsyncClient|HttpClient
28
     */
29
    private $client;
30
31
    /**
32
     * The plugin chain.
33
     *
34
     * @var Plugin[]
35
     */
36
    private $plugins;
37
38
    /**
39
     * A list of options.
40
     *
41
     * @var array
42
     */
43
    private $options;
44
45
    /**
46
     * @param ClientInterface|HttpAsyncClient $client  An HTTP async client
47
     * @param Plugin[]                        $plugins A plugin chain
48
     * @param array                           $options {
49
     *
50
     *     @var int $max_restarts
51
     * }
52
     */
53 10
    public function __construct($client, array $plugins = [], array $options = [])
54
    {
55 10
        if ($client instanceof HttpAsyncClient) {
56 3
            $this->client = $client;
57 7
        } elseif ($client instanceof ClientInterface) {
58 7
            $this->client = new EmulatedHttpAsyncClient($client);
59
        } else {
60
            throw new \TypeError(
61
                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...
62
            );
63
        }
64
65 10
        $this->plugins = $plugins;
66 10
        $this->options = $this->configure($options);
67 10
    }
68
69
    /**
70
     * {@inheritdoc}
71
     */
72 4
    public function sendRequest(RequestInterface $request): ResponseInterface
73
    {
74
        // If we don't have an http client, use the async call
75 4
        if (!($this->client instanceof ClientInterface)) {
76 1
            return $this->sendAsyncRequest($request)->wait();
77
        }
78
79
        // Else we want to use the synchronous call of the underlying client, and not the async one in the case
80
        // we have both an async and sync call
81
        $pluginChain = $this->createPluginChain($this->plugins, function (RequestInterface $request) {
82
            try {
83 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...
84
            } catch (HttplugException $exception) {
85
                return new HttpRejectedPromise($exception);
86
            }
87 3
        });
88
89 3
        return $pluginChain($request)->wait();
90
    }
91
92
    /**
93
     * {@inheritdoc}
94
     */
95 2
    public function sendAsyncRequest(RequestInterface $request)
96
    {
97
        $pluginChain = $this->createPluginChain($this->plugins, function (RequestInterface $request) {
98 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...
99 2
        });
100
101 2
        return $pluginChain($request);
102
    }
103
104
    /**
105
     * Configure the plugin client.
106
     */
107 10
    private function configure(array $options = []): array
108
    {
109 10
        $resolver = new OptionsResolver();
110 10
        $resolver->setDefaults([
111 10
            'max_restarts' => 10,
112
        ]);
113
114 10
        $resolver->setAllowedTypes('max_restarts', 'int');
115
116 10
        return $resolver->resolve($options);
117
    }
118
119
    /**
120
     * Create the plugin chain.
121
     *
122
     * @param Plugin[] $plugins        A plugin chain
123
     * @param callable $clientCallable Callable making the HTTP call
124
     */
125 5
    private function createPluginChain(array $plugins, callable $clientCallable): callable
126
    {
127 5
        return new PluginChain($plugins, $clientCallable, $this->options);
128
    }
129
}
130