Completed
Push — master ( 955d05...19c28e )
by David
03:37
created

src/PluginClient.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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\Client\ClientInterface;
12
use Psr\Http\Message\RequestInterface;
13
use Symfony\Component\OptionsResolver\OptionsResolver;
14
15
/**
16
 * The client managing plugins and providing a decorator around HTTP Clients.
17
 *
18
 * @author Joel Wurtz <[email protected]>
19
 */
20
final class PluginClient implements HttpClient, HttpAsyncClient
21
{
22
    /**
23
     * An HTTP async client.
24
     *
25
     * @var HttpAsyncClient
26
     */
27
    private $client;
28
29
    /**
30
     * The plugin chain.
31
     *
32
     * @var Plugin[]
33
     */
34
    private $plugins;
35
36
    /**
37
     * A list of options.
38
     *
39
     * @var array
40
     */
41
    private $options;
42
43
    /**
44
     * @param HttpClient|HttpAsyncClient $client
45
     * @param Plugin[]                   $plugins
46
     * @param array                      $options {
47
     *
48
     *     @var int      $max_restarts
49
     *     @var Plugin[] $debug_plugins an array of plugins that are injected between each normal plugin
50
     * }
51
     *
52
     * @throws \RuntimeException if client is not an instance of HttpClient or HttpAsyncClient
53
     */
54 11
    public function __construct($client, array $plugins = [], array $options = [])
55
    {
56 11
        if ($client instanceof HttpAsyncClient) {
57 3
            $this->client = $client;
58 8
        } elseif ($client instanceof HttpClient || $client instanceof ClientInterface) {
0 ignored issues
show
The class Psr\Http\Client\ClientInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
59 8
            $this->client = new EmulatedHttpAsyncClient($client);
60
        } else {
61
            throw new \RuntimeException('Client must be an instance of Http\\Client\\HttpClient or Http\\Client\\HttpAsyncClient');
62
        }
63
64 11
        $this->plugins = $plugins;
65 11
        $this->options = $this->configure($options);
66 11
    }
67
68
    /**
69
     * {@inheritdoc}
70
     */
71 5
    public function sendRequest(RequestInterface $request)
72
    {
73
        // If we don't have an http client, use the async call
74 5
        if (!($this->client instanceof HttpClient)) {
75 1
            return $this->sendAsyncRequest($request)->wait();
76
        }
77
78
        // Else we want to use the synchronous call of the underlying client, and not the async one in the case
79
        // we have both an async and sync call
80
        $pluginChain = $this->createPluginChain($this->plugins, function (RequestInterface $request) {
81
            try {
82 3
                return new HttpFulfilledPromise($this->client->sendRequest($request));
83
            } catch (HttplugException $exception) {
84
                return new HttpRejectedPromise($exception);
85
            }
86 4
        });
87
88 4
        return $pluginChain($request)->wait();
89
    }
90
91
    /**
92
     * {@inheritdoc}
93
     */
94 2
    public function sendAsyncRequest(RequestInterface $request)
95
    {
96
        $pluginChain = $this->createPluginChain($this->plugins, function (RequestInterface $request) {
97 2
            return $this->client->sendAsyncRequest($request);
98 2
        });
99
100 2
        return $pluginChain($request);
101
    }
102
103
    /**
104
     * Configure the plugin client.
105
     *
106
     * @param array $options
107
     *
108
     * @return array
109
     */
110 11
    private function configure(array $options = [])
111
    {
112 11
        if (isset($options['debug_plugins'])) {
113 1
            @trigger_error('The "debug_plugins" option is deprecated since 1.5 and will be removed in 2.0.', E_USER_DEPRECATED);
114
        }
115
116 11
        $resolver = new OptionsResolver();
117 11
        $resolver->setDefaults([
118 11
            'max_restarts' => 10,
119
            'debug_plugins' => [],
120
        ]);
121
122
        $resolver
123 11
            ->setAllowedTypes('debug_plugins', 'array')
124
            ->setAllowedValues('debug_plugins', function (array $plugins) {
125 11
                foreach ($plugins as $plugin) {
126
                    // Make sure each object passed with the `debug_plugins` is an instance of Plugin.
127 1
                    if (!$plugin instanceof Plugin) {
128 1
                        return false;
129
                    }
130
                }
131
132 11
                return true;
133 11
            });
134
135 11
        return $resolver->resolve($options);
136
    }
137
138
    /**
139
     * Create the plugin chain.
140
     *
141
     * @param Plugin[] $pluginList     A list of plugins
142
     * @param callable $clientCallable Callable making the HTTP call
143
     *
144
     * @return callable
145
     */
146 6
    private function createPluginChain($pluginList, callable $clientCallable)
147
    {
148 6
        $firstCallable = $lastCallable = $clientCallable;
149
150
        /*
151
         * Inject debug plugins between each plugin.
152
         */
153 6
        $pluginListWithDebug = $this->options['debug_plugins'];
154 6
        foreach ($pluginList as $plugin) {
155 2
            $pluginListWithDebug[] = $plugin;
156 2
            $pluginListWithDebug = array_merge($pluginListWithDebug, $this->options['debug_plugins']);
157
        }
158
159 6
        while ($plugin = array_pop($pluginListWithDebug)) {
160
            $lastCallable = function (RequestInterface $request) use ($plugin, $lastCallable, &$firstCallable) {
161 2
                return $plugin->handleRequest($request, $lastCallable, $firstCallable);
162 2
            };
163
164 2
            $firstCallable = $lastCallable;
165
        }
166
167 6
        $firstCalls = 0;
168 6
        $firstCallable = function (RequestInterface $request) use ($lastCallable, &$firstCalls) {
169 6
            if ($firstCalls > $this->options['max_restarts']) {
170 1
                throw new LoopException('Too many restarts in plugin client', $request);
171
            }
172
173 6
            ++$firstCalls;
174
175 6
            return $lastCallable($request);
176 6
        };
177
178 6
        return $firstCallable;
179
    }
180
}
181