Completed
Pull Request — master (#64)
by Fabien
04:54
created

PluginClient::configure()   B

Complexity

Conditions 4
Paths 2

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 4.0032

Importance

Changes 0
Metric Value
dl 0
loc 27
ccs 16
cts 17
cp 0.9412
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 15
nc 2
nop 1
crap 4.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\Client\Promise\HttpFulfilledPromise;
10
use Http\Client\Promise\HttpRejectedPromise;
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 HttpFulfilledPromise($this->client->sendRequest($request));
82
            } catch (HttplugException $exception) {
83
                return new HttpRejectedPromise($exception);
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
        if (isset($options['debug_plugins'])) {
112 1
            @trigger_error('The "debug_plugins" option is deprecated since 1.5 and will be removed in 2.0.', E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
113 1
        }
114
115 9
        $resolver = new OptionsResolver();
116 9
        $resolver->setDefaults([
117 9
            'max_restarts' => 10,
118 9
            'debug_plugins' => [],
119 9
        ]);
120
121
        $resolver
122 9
            ->setAllowedTypes('debug_plugins', 'array')
123
            ->setAllowedValues('debug_plugins', function (array $plugins) {
124 9
                foreach ($plugins as $plugin) {
125
                    // Make sure each object passed with the `debug_plugins` is an instance of Plugin.
126 1
                    if (!$plugin instanceof Plugin) {
127
                        return false;
128
                    }
129 9
                }
130
131 9
                return true;
132 9
            });
133
134 9
        return $resolver->resolve($options);
135
    }
136
137
    /**
138
     * Create the plugin chain.
139
     *
140
     * @param Plugin[] $pluginList     A list of plugins
141
     * @param callable $clientCallable Callable making the HTTP call
142
     *
143
     * @return callable
144
     */
145 6
    private function createPluginChain($pluginList, callable $clientCallable)
146
    {
147 6
        $firstCallable = $lastCallable = $clientCallable;
148
149
        /*
150
         * Inject debug plugins between each plugin.
151
         */
152 6
        $pluginListWithDebug = $this->options['debug_plugins'];
153 6
        foreach ($pluginList as $plugin) {
154 2
            $pluginListWithDebug[] = $plugin;
155 2
            $pluginListWithDebug = array_merge($pluginListWithDebug, $this->options['debug_plugins']);
156 6
        }
157
158 6
        while ($plugin = array_pop($pluginListWithDebug)) {
159
            $lastCallable = function (RequestInterface $request) use ($plugin, $lastCallable, &$firstCallable) {
160 2
                return $plugin->handleRequest($request, $lastCallable, $firstCallable);
161 2
            };
162
163 2
            $firstCallable = $lastCallable;
164 2
        }
165
166 6
        $firstCalls = 0;
167 6
        $firstCallable = function (RequestInterface $request) use ($lastCallable, &$firstCalls) {
168 6
            if ($firstCalls > $this->options['max_restarts']) {
169 1
                throw new LoopException('Too many restarts in plugin client', $request);
170
            }
171
172 6
            ++$firstCalls;
173
174 6
            return $lastCallable($request);
175 6
        };
176
177 6
        return $firstCallable;
178
    }
179
}
180