Issues (7)

src/HttpClient.php (2 issues)

1
<?php
2
3
namespace IproSoftwareApi;
4
5
use BadMethodCallException;
6
use GuzzleHttp\ClientInterface;
7
use GuzzleHttp\Exception\GuzzleException;
8
use IproSoftwareApi\Contracts\AccessToken as AccessTokenInterface;
9
use IproSoftwareApi\Contracts\AccessTokenCacher;
10
use IproSoftwareApi\DTOs\ClientCredentials;
11
use IproSoftwareApi\Exceptions\IproSoftwareApiAccessTokenException;
12
use IproSoftwareApi\Exceptions\IproSoftwareApiException;
13
use Psr\Http\Message\ResponseInterface;
14
15
/**
16
 * Class HttpClient.
17
 *
18
 * @method get($path = '', array $options = [])
19
 * @method post($path = '', array $options = [])
20
 * @method put($path = '', array $options = [])
21
 * @method delete($path = '', array $options = [])
22
 * @method head($path = '', array $options = [])
23
 * @method patch($path = '', array $options = [])
24
 */
25
class HttpClient implements Contracts\HttpClient
26
{
27
    /**
28
     * HTTP Methods.
29
     */
30
    const HTTP_METHOD_GET    = 'GET';
31
    const HTTP_METHOD_POST   = 'POST';
32
    const HTTP_METHOD_PUT    = 'PUT';
33
    const HTTP_METHOD_DELETE = 'DELETE';
34
    const HTTP_METHOD_HEAD   = 'HEAD';
35
    const HTTP_METHOD_PATCH  = 'PATCH';
36
37
    const HTTP_METHODS = [
38
        self::HTTP_METHOD_GET,
39
        self::HTTP_METHOD_POST,
40
        self::HTTP_METHOD_PUT,
41
        self::HTTP_METHOD_DELETE,
42
        self::HTTP_METHOD_HEAD,
43
        self::HTTP_METHOD_PATCH,
44
    ];
45
46
    /**
47
     * @var string
48
     */
49
    protected $accessTokenClass;
50
51
    /**
52
     * @var AccessTokenCacher
53
     */
54
    protected $cacheManager;
55
56
    /**
57
     * @var null|callable
58
     */
59
    protected $responseFilter;
60
61
    /**
62
     * @var ClientCredentials
63
     */
64
    protected $clientCredentials;
65
66
    /**
67
     * @var ClientInterface
68
     */
69
    protected $http;
70
71
    /**
72
     * @var AccessTokenInterface
73
     */
74
    protected $accessToken;
75
76
    /**
77
     * HttpClient constructor.
78
     *
79
     * @param AccessTokenCacher $cacheManager
80
     * @param ClientCredentials $clientCredentials
81
     * @param array $httpConfiguration
82
     */
83 11
    public function __construct(ClientCredentials $clientCredentials, AccessTokenCacher $cacheManager, array $httpConfiguration = [])
84
    {
85 11
        $this->cacheManager      = $cacheManager;
86 11
        $this->clientCredentials = $clientCredentials;
87
88 11
        $this->accessTokenClass = $httpConfiguration['access_token_class']
89 11
            ?? \IproSoftwareApi\AccessToken\AccessToken::class;
90
91 11
        $configs = $httpConfiguration['client_conf'] ?? [];
92 11
        if (!isset($configs['base_uri'])) {
93 11
            $configs['base_uri'] = $this->clientCredentials->apiHost;
94
        }
95 11
        $this->http = new \GuzzleHttp\Client($configs);
96
    }
97
98
    /**
99
     * @param $method
100
     * @param $arguments
101
     *
102
     * @return mixed|ResponseInterface
103
     * @throws GuzzleException
104
     *
105
     * @throws IproSoftwareApiAccessTokenException
106
     */
107 1
    public function __call($method, $arguments)
108
    {
109 1
        if (in_array(strtoupper($method), self::HTTP_METHODS)) {
110 1
            return $this->request(strtoupper($method), $arguments[0], $arguments[1] ?? []);
111
        }
112
113 1
        throw new BadMethodCallException('Method ' . $method . ' not found on ' . get_class() . '.', 500);
114
    }
115
116
    /**
117
     * @param AccessTokenCacher $cacheManager
118
     *
119
     * @return static
120
     */
121 3
    public function setCacheManager(AccessTokenCacher $cacheManager): static
122
    {
123 3
        $this->cacheManager = $cacheManager;
124
125 3
        return $this;
126
    }
127
128
    /**
129
     * @param callable|null $responseFilter
130
     *
131
     * @return static
132
     */
133 1
    public function setResponseFilter(?callable $responseFilter): static
134
    {
135 1
        $this->responseFilter = $responseFilter;
136
137 1
        return $this;
138
    }
139
140
    /**
141
     * @param ClientInterface $http
142
     *
143
     * @return static
144
     */
145 5
    public function setHttp(ClientInterface $http): static
146
    {
147 5
        $this->http = $http;
148
149 5
        return $this;
150
    }
151
152
    /**
153
     * @param null $option
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $option is correct as it would always require null to be passed?
Loading history...
154
     *
155
     * @return mixed
156
     * @throws IproSoftwareApiException
157
     *
158
     */
159 2
    public function getConfig($option = null)
160
    {
161 2
        if (!is_null($this->http)) {
162 2
            return $this->http->getConfig($option);
163
        }
164
165 1
        throw new IproSoftwareApiException('Http client not specified');
166
    }
167
168
    /**
169
     * @param $method
170
     * @param string $path
171
     * @param array $options
172
     *
173
     * @return mixed|ResponseInterface
174
     * @throws IproSoftwareApiAccessTokenException
175
     *
176
     * @throws GuzzleException
177
     */
178 3
    public function request($method, $path = '', array $options = []): ResponseInterface
179
    {
180 3
        if (!$this->hasAccessToken()) {
181 1
            $this->generateAccessToken();
182
        }
183
184 3
        if (!isset($options['headers']['Authorization'])) {
185 3
            $options['headers']['Authorization'] = $this->accessToken->getAuthorizationHeader();
0 ignored issues
show
The method getAuthorizationHeader() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

185
            /** @scrutinizer ignore-call */ 
186
            $options['headers']['Authorization'] = $this->accessToken->getAuthorizationHeader();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
186
        }
187
188 3
        if (is_string($path) && !empty($path)) {
189 3
            if ($path[0] == '/') {
190 1
                $path = substr($path, 1);
191
            }
192
        }
193
194 3
        $response = $this->http->request($method, $path, $options);
195
196 3
        if (is_callable($this->responseFilter)) {
197 1
            $response = ($this->responseFilter)($response, $options, $path, $method);
198
        }
199
200 3
        return $response;
201
    }
202
203
    /**
204
     * @return bool
205
     */
206 7
    public function hasAccessToken(): bool
207
    {
208 7
        return $this->accessToken instanceof AccessTokenInterface && $this->accessToken->hasAccessToken();
209
    }
210
211
    /**
212
     * @return AccessTokenInterface
213
     * @throws IproSoftwareApiAccessTokenException
214
     *
215
     */
216 4
    public function generateAccessToken(): AccessTokenInterface
217
    {
218 4
        $this->accessToken = $this->cacheManager->get();
219
220
        // If empty access token or expired then make request for new token
221 4
        if (!$this->hasAccessToken()) {
222 2
            $this->receiveAccessToken();
223
        }
224
225 3
        return $this->accessToken;
226
    }
227
228
    /**
229
     * @throws IproSoftwareApiAccessTokenException
230
     */
231 2
    protected function receiveAccessToken()
232
    {
233 2
        $response = $this->http->post($this->clientCredentials->tokenEndpoint, [
234 2
            'auth' => [
235 2
                $this->clientCredentials->clientId,
236 2
                $this->clientCredentials->clientSecret,
237 2
            ],
238 2
            'form_params' => [
239 2
                'grant_type' => 'client_credentials',
240 2
            ],
241 2
        ]);
242
243 2
        if ($response->getStatusCode() != 200) {
244 1
            throw new IproSoftwareApiAccessTokenException($response, 'Get Access Token Error');
245
        }
246
247 1
        $this->accessToken = call_user_func(
248 1
            [$this->accessTokenClass, 'makeFromApiResponse'],
249 1
            $response
250 1
        );
251
252 1
        $this->cacheManager->put($this->accessToken);
253
    }
254
}
255