RequestFactory::visit_proxy()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 3
1
<?php
2
3
namespace Guzzle\Http\Message;
4
5
use Guzzle\Common\Collection;
6
use Guzzle\Common\Exception\InvalidArgumentException;
7
use Guzzle\Http\RedirectPlugin;
8
use Guzzle\Http\Url;
9
use Guzzle\Parser\ParserRegistry;
10
11
/**
12
 * Default HTTP request factory used to create the default {@see Request} and {@see EntityEnclosingRequest} objects.
13
 */
14
class RequestFactory implements RequestFactoryInterface
15
{
16
    /** @var RequestFactory Singleton instance of the default request factory */
17
    protected static $instance;
18
19
    /** @var array Hash of methods available to the class (provides fast isset() lookups) */
20
    protected $methods;
21
22
    /** @var string Class to instantiate for requests with no body */
23
    protected $requestClass = 'Guzzle\\Http\\Message\\Request';
24
25
    /** @var string Class to instantiate for requests with a body */
26
    protected $entityEnclosingRequestClass = 'Guzzle\\Http\\Message\\EntityEnclosingRequest';
27
28
    /**
29
     * Get a cached instance of the default request factory
30
     *
31
     * @return RequestFactory
32
     */
33
    public static function getInstance()
34
    {
35
        // @codeCoverageIgnoreStart
36
        if (!static::$instance) {
37
            static::$instance = new static();
38
        }
39
        // @codeCoverageIgnoreEnd
40
41
        return static::$instance;
42
    }
43
44
    public function __construct()
45
    {
46
        $this->methods = array_flip(get_class_methods(__CLASS__));
47
    }
48
49
    public function fromMessage($message)
50
    {
51
        $parsed = ParserRegistry::getInstance()->getParser('message')->parseRequest($message);
52
53
        if (!$parsed) {
54
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type declared by the interface Guzzle\Http\Message\Requ...yInterface::fromMessage of type Guzzle\Http\Message\RequestInterface.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
55
        }
56
57
        $request = $this->fromParts($parsed['method'], $parsed['request_url'],
58
            $parsed['headers'], $parsed['body'], $parsed['protocol'],
59
            $parsed['version']);
60
61
        // EntityEnclosingRequest adds an "Expect: 100-Continue" header when using a raw request body for PUT or POST
62
        // requests. This factory method should accurately reflect the message, so here we are removing the Expect
63
        // header if one was not supplied in the message.
64
        if (!isset($parsed['headers']['Expect']) && !isset($parsed['headers']['expect'])) {
65
            $request->removeHeader('Expect');
66
        }
67
68
        return $request;
69
    }
70
71
    public function fromParts(
72
        $method,
73
        array $urlParts,
74
        $headers = null,
75
        $body = null,
76
        $protocol = 'HTTP',
77
        $protocolVersion = '1.1'
78
    ) {
79
        return $this->create($method, Url::buildUrl($urlParts), $headers, $body)
80
                    ->setProtocolVersion($protocolVersion);
81
    }
82
83
    public function create($method, $url, $headers = null, $body = null, array $options = array())
84
    {
85
        $method = strtoupper($method);
86
87
        if ($method == 'GET' || $method == 'HEAD' || $method == 'TRACE') {
88
            // Handle non-entity-enclosing request methods
89
            $request = new $this->requestClass($method, $url, $headers);
90
            if ($body) {
91
                // The body is where the response body will be stored
92
                $type = gettype($body);
93
                if ($type == 'string' || $type == 'resource' || $type == 'object') {
94
                    $request->setResponseBody($body);
95
                }
96
            }
97
        } else {
98
            // Create an entity enclosing request by default
99
            $request = new $this->entityEnclosingRequestClass($method, $url, $headers);
100
            if ($body || $body === '0') {
101
                // Add POST fields and files to an entity enclosing request if an array is used
102
                if (is_array($body) || $body instanceof Collection) {
103
                    // Normalize PHP style cURL uploads with a leading '@' symbol
104
                    foreach ($body as $key => $value) {
105
                        if (is_string($value) && substr($value, 0, 1) == '@') {
106
                            $request->addPostFile($key, $value);
107
                            unset($body[$key]);
108
                        }
109
                    }
110
                    // Add the fields if they are still present and not all files
111
                    $request->addPostFields($body);
112
                } else {
113
                    // Add a raw entity body body to the request
114
                    $request->setBody($body, (string) $request->getHeader('Content-Type'));
115
                    if ((string) $request->getHeader('Transfer-Encoding') == 'chunked') {
116
                        $request->removeHeader('Content-Length');
117
                    }
118
                }
119
            }
120
        }
121
122
        if ($options) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $options of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
123
            $this->applyOptions($request, $options);
124
        }
125
126
        return $request;
127
    }
128
129
    /**
130
     * Clone a request while changing the method. Emulates the behavior of
131
     * {@see Guzzle\Http\Message\Request::clone}, but can change the HTTP method.
132
     *
133
     * @param RequestInterface $request Request to clone
134
     * @param string           $method  Method to set
135
     *
136
     * @return RequestInterface
137
     */
138
    public function cloneRequestWithMethod(RequestInterface $request, $method)
139
    {
140
        // Create the request with the same client if possible
141
        if ($request->getClient()) {
142
            $cloned = $request->getClient()->createRequest($method, $request->getUrl(), $request->getHeaders());
0 ignored issues
show
Bug introduced by
It seems like $request->getUrl() targeting Guzzle\Http\Message\RequestInterface::getUrl() can also be of type object<Guzzle\Http\Url>; however, Guzzle\Http\ClientInterface::createRequest() does only seem to accept string|array|null, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
Documentation introduced by
$request->getHeaders() is of type object<Guzzle\Http\Messa...eader\HeaderCollection>, but the function expects a array|object<Guzzle\Common\Collection>|null.

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...
143
        } else {
144
            $cloned = $this->create($method, $request->getUrl(), $request->getHeaders());
0 ignored issues
show
Documentation introduced by
$request->getHeaders() is of type object<Guzzle\Http\Messa...eader\HeaderCollection>, but the function expects a array|object<Guzzle\Common\Collection>|null.

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...
145
        }
146
147
        $cloned->getCurlOptions()->replace($request->getCurlOptions()->toArray());
148
        $cloned->setEventDispatcher(clone $request->getEventDispatcher());
149
        // Ensure that that the Content-Length header is not copied if changing to GET or HEAD
150
        if (!($cloned instanceof EntityEnclosingRequestInterface)) {
151
            $cloned->removeHeader('Content-Length');
152
        } elseif ($request instanceof EntityEnclosingRequestInterface) {
153
            $cloned->setBody($request->getBody());
0 ignored issues
show
Bug introduced by
It seems like $request->getBody() can be null; however, setBody() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
154
        }
155
        $cloned->getParams()->replace($request->getParams()->toArray());
156
        $cloned->dispatch('request.clone', array('request' => $cloned));
157
158
        return $cloned;
159
    }
160
161
    public function applyOptions(RequestInterface $request, array $options = array(), $flags = self::OPTIONS_NONE)
162
    {
163
        // Iterate over each key value pair and attempt to apply a config using function visitors
164
        foreach ($options as $key => $value) {
165
            $method = "visit_{$key}";
166
            if (isset($this->methods[$method])) {
167
                $this->{$method}($request, $value, $flags);
168
            }
169
        }
170
    }
171
172
    protected function visit_headers(RequestInterface $request, $value, $flags)
173
    {
174
        if (!is_array($value)) {
175
            throw new InvalidArgumentException('headers value must be an array');
176
        }
177
178
        if ($flags & self::OPTIONS_AS_DEFAULTS) {
179
            // Merge headers in but do not overwrite existing values
180
            foreach ($value as $key => $header) {
181
                if (!$request->hasHeader($key)) {
182
                    $request->setHeader($key, $header);
183
                }
184
            }
185
        } else {
186
            $request->addHeaders($value);
187
        }
188
    }
189
190
    protected function visit_body(RequestInterface $request, $value, $flags)
0 ignored issues
show
Unused Code introduced by
The parameter $flags is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
191
    {
192
        if ($request instanceof EntityEnclosingRequestInterface) {
193
            $request->setBody($value);
194
        } else {
195
            throw new InvalidArgumentException('Attempting to set a body on a non-entity-enclosing request');
196
        }
197
    }
198
199
    protected function visit_allow_redirects(RequestInterface $request, $value, $flags)
0 ignored issues
show
Unused Code introduced by
The parameter $flags is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
200
    {
201
        if ($value === false) {
202
            $request->getParams()->set(RedirectPlugin::DISABLE, true);
203
        }
204
    }
205
206
    protected function visit_auth(RequestInterface $request, $value, $flags)
0 ignored issues
show
Unused Code introduced by
The parameter $flags is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
207
    {
208
        if (!is_array($value)) {
209
            throw new InvalidArgumentException('auth value must be an array');
210
        }
211
212
        $request->setAuth($value[0], isset($value[1]) ? $value[1] : null, isset($value[2]) ? $value[2] : 'basic');
213
    }
214
215
    protected function visit_query(RequestInterface $request, $value, $flags)
216
    {
217
        if (!is_array($value)) {
218
            throw new InvalidArgumentException('query value must be an array');
219
        }
220
221
        if ($flags & self::OPTIONS_AS_DEFAULTS) {
222
            // Merge query string values in but do not overwrite existing values
223
            $query = $request->getQuery();
224
            $query->overwriteWith(array_diff_key($value, $query->toArray()));
225
        } else {
226
            $request->getQuery()->overwriteWith($value);
227
        }
228
    }
229
230
    protected function visit_cookies(RequestInterface $request, $value, $flags)
0 ignored issues
show
Unused Code introduced by
The parameter $flags is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
231
    {
232
        if (!is_array($value)) {
233
            throw new InvalidArgumentException('cookies value must be an array');
234
        }
235
236
        foreach ($value as $name => $v) {
237
            $request->addCookie($name, $v);
238
        }
239
    }
240
241
    protected function visit_events(RequestInterface $request, $value, $flags)
0 ignored issues
show
Unused Code introduced by
The parameter $flags is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
242
    {
243
        if (!is_array($value)) {
244
            throw new InvalidArgumentException('events value must be an array');
245
        }
246
247
        foreach ($value as $name => $method) {
248
            if (is_array($method)) {
249
                $request->getEventDispatcher()->addListener($name, $method[0], $method[1]);
250
            } else {
251
                $request->getEventDispatcher()->addListener($name, $method);
252
            }
253
        }
254
    }
255
256
    protected function visit_plugins(RequestInterface $request, $value, $flags)
0 ignored issues
show
Unused Code introduced by
The parameter $flags is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
257
    {
258
        if (!is_array($value)) {
259
            throw new InvalidArgumentException('plugins value must be an array');
260
        }
261
262
        foreach ($value as $plugin) {
263
            $request->addSubscriber($plugin);
264
        }
265
    }
266
267
    protected function visit_exceptions(RequestInterface $request, $value, $flags)
0 ignored issues
show
Unused Code introduced by
The parameter $flags is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
268
    {
269
        if ($value === false || $value === 0) {
270
            $dispatcher = $request->getEventDispatcher();
271
            foreach ($dispatcher->getListeners('request.error') as $listener) {
272
                if (is_array($listener) && $listener[0] == 'Guzzle\Http\Message\Request' && $listener[1] = 'onRequestError') {
273
                    $dispatcher->removeListener('request.error', $listener);
274
                    break;
275
                }
276
            }
277
        }
278
    }
279
280
    protected function visit_save_to(RequestInterface $request, $value, $flags)
0 ignored issues
show
Unused Code introduced by
The parameter $flags is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
281
    {
282
        $request->setResponseBody($value);
283
    }
284
285
    protected function visit_params(RequestInterface $request, $value, $flags)
0 ignored issues
show
Unused Code introduced by
The parameter $flags is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
286
    {
287
        if (!is_array($value)) {
288
            throw new InvalidArgumentException('params value must be an array');
289
        }
290
291
        $request->getParams()->overwriteWith($value);
292
    }
293
294
    protected function visit_timeout(RequestInterface $request, $value, $flags)
0 ignored issues
show
Unused Code introduced by
The parameter $flags is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
295
    {
296
        if (defined('CURLOPT_TIMEOUT_MS')) {
297
            $request->getCurlOptions()->set(CURLOPT_TIMEOUT_MS, $value * 1000);
298
        } else {
299
            $request->getCurlOptions()->set(CURLOPT_TIMEOUT, $value);
300
        }
301
    }
302
303
    protected function visit_connect_timeout(RequestInterface $request, $value, $flags)
0 ignored issues
show
Unused Code introduced by
The parameter $flags is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
304
    {
305
        if (defined('CURLOPT_CONNECTTIMEOUT_MS')) {
306
            $request->getCurlOptions()->set(CURLOPT_CONNECTTIMEOUT_MS, $value * 1000);
307
        } else {
308
            $request->getCurlOptions()->set(CURLOPT_CONNECTTIMEOUT, $value);
309
        }
310
    }
311
312
    protected function visit_debug(RequestInterface $request, $value, $flags)
0 ignored issues
show
Unused Code introduced by
The parameter $flags is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
313
    {
314
        if ($value) {
315
            $request->getCurlOptions()->set(CURLOPT_VERBOSE, true);
316
        }
317
    }
318
319
    protected function visit_verify(RequestInterface $request, $value, $flags)
0 ignored issues
show
Unused Code introduced by
The parameter $flags is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
320
    {
321
        $curl = $request->getCurlOptions();
322
        if ($value === true || is_string($value)) {
323
            $curl[CURLOPT_SSL_VERIFYHOST] = 2;
324
            $curl[CURLOPT_SSL_VERIFYPEER] = true;
325
            if ($value !== true) {
326
                $curl[CURLOPT_CAINFO] = $value;
327
            }
328 View Code Duplication
        } elseif ($value === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
329
            unset($curl[CURLOPT_CAINFO]);
330
            $curl[CURLOPT_SSL_VERIFYHOST] = 0;
331
            $curl[CURLOPT_SSL_VERIFYPEER] = false;
332
        }
333
    }
334
335
    protected function visit_proxy(RequestInterface $request, $value, $flags)
336
    {
337
        $request->getCurlOptions()->set(CURLOPT_PROXY, $value, $flags);
0 ignored issues
show
Unused Code introduced by
The call to Collection::set() has too many arguments starting with $flags.

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...
338
    }
339
340 View Code Duplication
    protected function visit_cert(RequestInterface $request, $value, $flags)
0 ignored issues
show
Unused Code introduced by
The parameter $flags is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
341
    {
342
        if (is_array($value)) {
343
            $request->getCurlOptions()->set(CURLOPT_SSLCERT, $value[0]);
344
            $request->getCurlOptions()->set(CURLOPT_SSLCERTPASSWD, $value[1]);
345
        } else {
346
            $request->getCurlOptions()->set(CURLOPT_SSLCERT, $value);
347
        }
348
    }
349
350 View Code Duplication
    protected function visit_ssl_key(RequestInterface $request, $value, $flags)
0 ignored issues
show
Unused Code introduced by
The parameter $flags is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
351
    {
352
        if (is_array($value)) {
353
            $request->getCurlOptions()->set(CURLOPT_SSLKEY, $value[0]);
354
            $request->getCurlOptions()->set(CURLOPT_SSLKEYPASSWD, $value[1]);
355
        } else {
356
            $request->getCurlOptions()->set(CURLOPT_SSLKEY, $value);
357
        }
358
    }
359
}
360