EntityEnclosingRequest::configureRedirects()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 7
nc 2
nop 2
1
<?php
2
3
namespace Guzzle\Http\Message;
4
5
use Guzzle\Http\EntityBody;
6
use Guzzle\Http\EntityBodyInterface;
7
use Guzzle\Http\QueryString;
8
use Guzzle\Http\RedirectPlugin;
9
use Guzzle\Http\Exception\RequestException;
10
11
/**
12
 * HTTP request that sends an entity-body in the request message (POST, PUT, PATCH, DELETE)
13
 */
14
class EntityEnclosingRequest extends Request implements EntityEnclosingRequestInterface
15
{
16
    /** @var int When the size of the body is greater than 1MB, then send Expect: 100-Continue */
17
    protected $expectCutoff = 1048576;
18
19
    /** @var EntityBodyInterface $body Body of the request */
20
    protected $body;
21
22
    /** @var QueryString POST fields to use in the EntityBody */
23
    protected $postFields;
24
25
    /** @var array POST files to send with the request */
26
    protected $postFiles = array();
27
28
    public function __construct($method, $url, $headers = array())
29
    {
30
        $this->postFields = new QueryString();
31
        parent::__construct($method, $url, $headers);
32
    }
33
34
    /**
35
     * @return string
36
     */
37
    public function __toString()
38
    {
39
        // Only attempt to include the POST data if it's only fields
40
        if (count($this->postFields) && empty($this->postFiles)) {
41
            return parent::__toString() . (string) $this->postFields;
42
        }
43
44
        return parent::__toString() . $this->body;
45
    }
46
47
    public function setState($state, array $context = array())
48
    {
49
        parent::setState($state, $context);
50
        if ($state == self::STATE_TRANSFER && !$this->body && !count($this->postFields) && !count($this->postFiles)) {
51
            $this->setHeader('Content-Length', 0)->removeHeader('Transfer-Encoding');
52
        }
53
54
        return $this->state;
55
    }
56
57
    public function setBody($body, $contentType = null)
58
    {
59
        $this->body = EntityBody::factory($body);
0 ignored issues
show
Bug introduced by
It seems like $body defined by parameter $body on line 57 can also be of type object<Guzzle\Http\EntityBodyInterface>; however, Guzzle\Http\EntityBody::factory() does only seem to accept string|resource|object<Guzzle\Http\EntityBody>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and 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...
60
61
        // Auto detect the Content-Type from the path of the request if possible
62
        if ($contentType === null && !$this->hasHeader('Content-Type')) {
63
            $contentType = $this->body->getContentType();
64
        }
65
66
        if ($contentType) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $contentType of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
67
            $this->setHeader('Content-Type', $contentType);
68
        }
69
70
        // Always add the Expect 100-Continue header if the body cannot be rewound. This helps with redirects.
71
        if (!$this->body->isSeekable() && $this->expectCutoff !== false) {
72
            $this->setHeader('Expect', '100-Continue');
73
        }
74
75
        // Set the Content-Length header if it can be determined
76
        $size = $this->body->getContentLength();
77
        if ($size !== null && $size !== false) {
78
            $this->setHeader('Content-Length', $size);
79
            if ($size > $this->expectCutoff) {
80
                $this->setHeader('Expect', '100-Continue');
81
            }
82
        } elseif (!$this->hasHeader('Content-Length')) {
83
            if ('1.1' == $this->protocolVersion) {
84
                $this->setHeader('Transfer-Encoding', 'chunked');
85
            } else {
86
                throw new RequestException(
87
                    'Cannot determine Content-Length and cannot use chunked Transfer-Encoding when using HTTP/1.0'
88
                );
89
            }
90
        }
91
92
        return $this;
93
    }
94
95
    public function getBody()
96
    {
97
        return $this->body;
98
    }
99
100
    /**
101
     * Set the size that the entity body of the request must exceed before adding the Expect: 100-Continue header.
102
     *
103
     * @param int|bool $size Cutoff in bytes. Set to false to never send the expect header (even with non-seekable data)
104
     *
105
     * @return self
106
     */
107
    public function setExpectHeaderCutoff($size)
108
    {
109
        $this->expectCutoff = $size;
0 ignored issues
show
Documentation Bug introduced by
It seems like $size can also be of type boolean. However, the property $expectCutoff is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
110
        if ($size === false || !$this->body) {
111
            $this->removeHeader('Expect');
112
        } elseif ($this->body && $this->body->getSize() && $this->body->getSize() > $size) {
113
            $this->setHeader('Expect', '100-Continue');
114
        }
115
116
        return $this;
117
    }
118
119
    public function configureRedirects($strict = false, $maxRedirects = 5)
120
    {
121
        $this->getParams()->set(RedirectPlugin::STRICT_REDIRECTS, $strict);
122
        if ($maxRedirects == 0) {
123
            $this->getParams()->set(RedirectPlugin::DISABLE, true);
124
        } else {
125
            $this->getParams()->set(RedirectPlugin::MAX_REDIRECTS, $maxRedirects);
126
        }
127
128
        return $this;
129
    }
130
131
    public function getPostField($field)
132
    {
133
        return $this->postFields->get($field);
134
    }
135
136
    public function getPostFields()
137
    {
138
        return $this->postFields;
139
    }
140
141
    public function setPostField($key, $value)
142
    {
143
        $this->postFields->set($key, $value);
144
        $this->processPostFields();
145
146
        return $this;
147
    }
148
149
    public function addPostFields($fields)
150
    {
151
        $this->postFields->merge($fields);
152
        $this->processPostFields();
153
154
        return $this;
155
    }
156
157
    public function removePostField($field)
158
    {
159
        $this->postFields->remove($field);
160
        $this->processPostFields();
161
162
        return $this;
163
    }
164
165
    public function getPostFiles()
166
    {
167
        return $this->postFiles;
168
    }
169
170
    public function getPostFile($fieldName)
171
    {
172
        return isset($this->postFiles[$fieldName]) ? $this->postFiles[$fieldName] : null;
173
    }
174
175
    public function removePostFile($fieldName)
176
    {
177
        unset($this->postFiles[$fieldName]);
178
        $this->processPostFields();
179
180
        return $this;
181
    }
182
183
    public function addPostFile($field, $filename = null, $contentType = null, $postname = null)
184
    {
185
        $data = null;
186
187
        if ($field instanceof PostFileInterface) {
188
            $data = $field;
189
        } elseif (is_array($filename)) {
190
            // Allow multiple values to be set in a single key
191
            foreach ($filename as $file) {
192
                $this->addPostFile($field, $file, $contentType);
193
            }
194
            return $this;
195
        } elseif (!is_string($filename)) {
196
            throw new RequestException('The path to a file must be a string');
197
        } elseif (!empty($filename)) {
198
            // Adding an empty file will cause cURL to error out
199
            $data = new PostFile($field, $filename, $contentType, $postname);
200
        }
201
202
        if ($data) {
203
            if (!isset($this->postFiles[$data->getFieldName()])) {
204
                $this->postFiles[$data->getFieldName()] = array($data);
205
            } else {
206
                $this->postFiles[$data->getFieldName()][] = $data;
207
            }
208
            $this->processPostFields();
209
        }
210
211
        return $this;
212
    }
213
214
    public function addPostFiles(array $files)
215
    {
216
        foreach ($files as $key => $file) {
217
            if ($file instanceof PostFileInterface) {
218
                $this->addPostFile($file, null, null, false);
0 ignored issues
show
Documentation introduced by
$file is of type object<Guzzle\Http\Message\PostFileInterface>, but the function expects a string.

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...
Documentation introduced by
false is of type boolean, but the function expects a string|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...
219
            } elseif (is_string($file)) {
220
                // Convert non-associative array keys into 'file'
221
                if (is_numeric($key)) {
222
                    $key = 'file';
223
                }
224
                $this->addPostFile($key, $file, null, false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string|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...
225
            } else {
226
                throw new RequestException('File must be a string or instance of PostFileInterface');
227
            }
228
        }
229
230
        return $this;
231
    }
232
233
    /**
234
     * Determine what type of request should be sent based on post fields
235
     */
236
    protected function processPostFields()
237
    {
238
        if (!$this->postFiles) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->postFiles 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...
239
            $this->removeHeader('Expect')->setHeader('Content-Type', self::URL_ENCODED);
240
        } else {
241
            $this->setHeader('Content-Type', self::MULTIPART);
242
            if ($this->expectCutoff !== false) {
243
                $this->setHeader('Expect', '100-Continue');
244
            }
245
        }
246
    }
247
}
248