Completed
Push — master ( 462f98...6f4e70 )
by mingyoung
02:40
created

HasHttpRequests::fixJsonIssue()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 2
nop 1
dl 0
loc 10
ccs 0
cts 9
cp 0
crap 12
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the overtrue/wechat.
5
 *
6
 * (c) overtrue <[email protected]>
7
 *
8
 * This source file is subject to the MIT license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace EasyWeChat\Kernel\Traits;
13
14
use EasyWeChat\Kernel\Http\Response;
15
use EasyWeChat\Kernel\Support\Collection;
16
use GuzzleHttp\Client;
17
use GuzzleHttp\ClientInterface;
18
use GuzzleHttp\HandlerStack;
19
use Psr\Http\Message\ResponseInterface;
20
21
/**
22
 * Trait HasHttpRequests.
23
 *
24
 * @author overtrue <[email protected]>
25
 */
26
trait HasHttpRequests
27
{
28
    /**
29
     * @var \GuzzleHttp\Client
30
     */
31
    protected $httpClient;
32
33
    /**
34
     * @var array
35
     */
36
    protected $middlewares = [];
37
38
    /**
39
     * @var \GuzzleHttp\HandlerStack
40
     */
41
    protected $handlerStack;
42
43
    /**
44
     * @var array
45
     */
46
    protected static $defaults = [
47
        'curl' => [
48
            CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4,
49
        ],
50
    ];
51
52
    /**
53
     * Set guzzle default settings.
54
     *
55
     * @param array $defaults
56
     */
57
    public static function setDefaultOptions($defaults = [])
58
    {
59
        self::$defaults = $defaults;
60
    }
61
62
    /**
63
     * Return current guzzle default settings.
64
     *
65
     * @return array
66
     */
67
    public static function getDefaultOptions(): array
68
    {
69
        return self::$defaults;
70
    }
71
72
    /**
73
     * Set GuzzleHttp\Client.
74
     *
75
     * @param \GuzzleHttp\ClientInterface $httpClient
76
     *
77
     * @return $this
78
     */
79
    public function setHttpClient(ClientInterface $httpClient)
80
    {
81
        $this->httpClient = $httpClient;
0 ignored issues
show
Documentation Bug introduced by
$httpClient is of type object<GuzzleHttp\ClientInterface>, but the property $httpClient was declared to be of type object<GuzzleHttp\Client>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof 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 given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
82
83
        return $this;
84
    }
85
86
    /**
87
     * Return GuzzleHttp\Client instance.
88
     *
89
     * @return \GuzzleHttp\Client
90
     */
91
    public function getHttpClient(): Client
92
    {
93
        if (!($this->httpClient instanceof ClientInterface)) {
94
            $this->httpClient = new Client();
95
        }
96
97
        return $this->httpClient;
98
    }
99
100
    /**
101
     * Add a middleware.
102
     *
103
     * @param callable    $middleware
104
     * @param null|string $name
105
     *
106
     * @return $this
107
     */
108
    public function pushMiddleware(callable $middleware, string $name = null)
109
    {
110
        if ($name) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $name of type null|string 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...
111
            $this->middlewares[$name] = $middleware;
112
        } else {
113
            array_push($this->middlewares, $middleware);
114
        }
115
116
        return $this;
117
    }
118
119
    /**
120
     * Return all middlewares.
121
     *
122
     * @return array
123
     */
124
    public function getMiddlewares(): array
125
    {
126
        return $this->middlewares;
127
    }
128
129
    /**
130
     * Make a request.
131
     *
132
     * @param string $url
133
     * @param string $method
134
     * @param array  $options
135
     *
136
     * @return \Psr\Http\Message\ResponseInterface
137
     */
138
    public function request($url, $method = 'GET', $options = []): ResponseInterface
139
    {
140
        $method = strtoupper($method);
141
142
        $options = array_merge(self::$defaults, $options, ['handler' => $this->getHandlerStack()]);
143
144
        $options = $this->fixJsonIssue($options);
145
146
        if (!empty($this->baseUri)) {
147
            $options['base_uri'] = $this->baseUri;
0 ignored issues
show
Bug introduced by
The property baseUri does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
148
        }
149
150
        $response = $this->getHttpClient()->request($method, $url, $options);
151
        $response->getBody()->rewind();
152
153
        return $response;
154
    }
155
156
    /**
157
     * @param \GuzzleHttp\HandlerStack $handlerStack
158
     *
159
     * @return $this
160
     */
161
    public function setHandlerStack(HandlerStack $handlerStack)
162
    {
163
        $this->handlerStack = $handlerStack;
164
165
        return $this;
166
    }
167
168
    /**
169
     * Build a handler stack.
170
     *
171
     * @return \GuzzleHttp\HandlerStack
172
     */
173
    public function getHandlerStack(): HandlerStack
174
    {
175
        if ($this->handlerStack) {
176
            return $this->handlerStack;
177
        }
178
179
        $this->handlerStack = HandlerStack::create();
180
181
        foreach ($this->middlewares as $name => $middleware) {
182
            $this->handlerStack->push($middleware, $name);
183
        }
184
185
        return $this->handlerStack;
186
    }
187
188
    /**
189
     * @param \Psr\Http\Message\ResponseInterface $response
190
     * @param string                              $type
191
     *
192
     * @return array|\EasyWeChat\Kernel\Support\Collection|object|string
193
     */
194
    protected function resolveResponse(ResponseInterface $response, string $type)
195
    {
196
        $response = Response::buildFromGuzzleResponse($response);
0 ignored issues
show
Compatibility introduced by
$response of type object<Psr\Http\Message\ResponseInterface> is not a sub-type of object<GuzzleHttp\Psr7\Response>. It seems like you assume a concrete implementation of the interface Psr\Http\Message\ResponseInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
197
198
        switch ($type) {
199
            case 'collection':
200
                return $response->toCollection();
201
            case 'array':
202
                return $response->toArray();
203
            case 'object':
204
                return $response->toObject();
205
            case 'raw':
206
            default:
207
                $response->getBody()->rewind();
208
                if (class_exists($type)) {
209
                    return new $type($response);
210
                }
211
212
                return $response;
213
        }
214
    }
215
216
    /**
217
     * @param mixed  $response
218
     * @param string $type
219
     *
220
     * @return array|\EasyWeChat\Kernel\Support\Collection|object|string
221
     */
222
    protected function transformResponseToType($response, string $type)
223
    {
224
        if ($response instanceof ResponseInterface) {
225
            $response = Response::buildFromGuzzleResponse($response);
0 ignored issues
show
Compatibility introduced by
$response of type object<Psr\Http\Message\ResponseInterface> is not a sub-type of object<GuzzleHttp\Psr7\Response>. It seems like you assume a concrete implementation of the interface Psr\Http\Message\ResponseInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
226
        } elseif (($response instanceof Collection) || is_array($response) || is_object($response)) {
227
            $response = new Response(200, [], json_encode($response));
228
        }
229
230
        return $this->resolveResponse($response, $type);
231
    }
232
233
    /**
234
     * @param array $options
235
     *
236
     * @return array
237
     */
238
    protected function fixJsonIssue(array $options): array
239
    {
240
        if (isset($options['json']) && is_array($options['json'])) {
241
            $options['headers'] = array_merge($options['headers'] ?? [], ['Content-Type' => 'application/json']);
242
            $options['body'] = \GuzzleHttp\json_encode($options['json'], JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE);
243
            unset($options['json']);
244
        }
245
246
        return $options;
247
    }
248
}
249