Completed
Branch 3.x (e636ad)
by Дмитрий
05:24
created

AbstractBaseProvider::createRequest()   A

Complexity

Conditions 5
Paths 12

Size

Total Lines 36
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 6.4704

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 5
eloc 19
c 3
b 0
f 0
nc 12
nop 5
dl 0
loc 36
ccs 11
cts 18
cp 0.6111
crap 6.4704
rs 9.3222
1
<?php
2
/**
3
 * SocialConnect project
4
 * @author: Patsura Dmitry https://github.com/ovr <[email protected]>
5
 */
6
declare(strict_types=1);
7
8
namespace SocialConnect\Provider;
9
10
use Psr\Http\Client\ClientInterface;
11
use Psr\Http\Message\RequestFactoryInterface;
12
use Psr\Http\Message\RequestInterface;
13
use Psr\Http\Message\ResponseInterface;
14
use SocialConnect\Provider\Exception\InvalidProviderConfiguration;
15
use SocialConnect\Common\HttpStack;
16
use SocialConnect\Provider\Exception\InvalidResponse;
17
use SocialConnect\Provider\Session\SessionInterface;
18
19
abstract class AbstractBaseProvider
20
{
21
    /**
22
     * @var Consumer
23
     */
24
    protected $consumer;
25
26
    /**
27
     * @var array
28
     */
29
    protected $scope = [];
30
31
    /**
32
     * @var HttpStack
33
     */
34
    protected $httpStack;
35
36
    /**
37
     * @var string
38
     */
39
    protected $redirectUri;
40
41
    /**
42
     * @var SessionInterface
43
     */
44
    protected $session;
45
46
    /**
47
     * @var array
48
     */
49
    protected $options = [];
50
51
    /**
52
     * @param HttpStack $httpStack
53
     * @param SessionInterface $session
54
     * @param array $parameters
55
     * @throws InvalidProviderConfiguration
56
     */
57 445
    public function __construct(HttpStack $httpStack, SessionInterface $session, array $parameters)
58
    {
59 445
        if (isset($parameters['scope'])) {
60 443
            $this->setScope($parameters['scope']);
61
        }
62
63 445
        if (isset($parameters['redirectUri'])) {
64 443
            $this->redirectUri = $parameters['redirectUri'];
65
        }
66
67 445
        if (isset($parameters['options'])) {
68
            $this->options = $parameters['options'];
69
        }
70
71 445
        $this->consumer = $this->createConsumer($parameters);
72 445
        $this->httpStack = $httpStack;
73 445
        $this->session = $session;
74 445
    }
75
76
    /**
77
     * @param int $bytes Default it's 16 bytes / 128 bit / 16 symbols / 32 symbols in hex
78
     * @return string
79
     * @throws \Exception
80
     */
81 52
    protected function generateState(int $bytes = 16): string
82
    {
83 52
        return bin2hex(random_bytes($bytes));
84
    }
85
86
    /**
87
     * @param array $parameters
88
     * @return Consumer
89
     * @throws InvalidProviderConfiguration
90
     */
91 438
    protected function createConsumer(array $parameters): Consumer
92
    {
93 438
        return new Consumer(
94 438
            $this->getRequiredStringParameter('applicationId', $parameters),
95 438
            $this->getRequiredStringParameter('applicationSecret', $parameters)
96
        );
97
    }
98
99
    /**
100
     * @param string $key
101
     * @param array $parameters
102
     * @return string
103
     * @throws InvalidProviderConfiguration
104
     */
105 445
    protected function getRequiredStringParameter(string $key, array $parameters): string
106
    {
107 445
        if (!isset($parameters[$key])) {
108
            throw new InvalidProviderConfiguration(
109
                "Parameter '{$key}' doesn`t exists for '{$this->getName()}' provider configuration"
110
            );
111
        }
112
113 445
        if (!is_string($parameters[$key])) {
114
            throw new InvalidProviderConfiguration(
115
                "Parameter '{$key}' must be string inside '{$this->getName()}' provider configuration"
116
            );
117
        }
118
119 445
        return $parameters[$key];
120
    }
121
122
    /**
123
     * @param string $key
124
     * @param bool $default
125
     * @return bool
126
     */
127 24
    public function getBoolOption($key, $default): bool
128
    {
129 24
        if (array_key_exists($key, $this->options)) {
130 1
            return (bool) $this->options[$key];
131
        }
132
133 23
        return $default;
134
    }
135
136
    /**
137
     * @param string $key
138
     * @param array $default
139
     * @return array
140
     */
141 32
    public function getArrayOption($key, array $default = []): array
142
    {
143 32
        if (array_key_exists($key, $this->options)) {
144
            return (array) $this->options[$key];
145
        }
146
147 32
        return $default;
148
    }
149
150
    /**
151
     * @return string
152
     */
153 80
    public function getRedirectUrl(): string
154
    {
155 80
        return str_replace('${provider}', $this->getName(), $this->redirectUri);
156
    }
157
158
    /**
159
     * Default parameters for auth url, can be redeclared inside implementation of the Provider
160
     *
161
     * @return array
162
     */
163 23
    public function getAuthUrlParameters(): array
164
    {
165 23
        return $this->getArrayOption('auth.parameters', []);
166
    }
167
168
    /**
169
     * @return string
170
     */
171
    abstract public function getBaseUri();
172
173
    /**
174
     * Return Provider's name
175
     *
176
     * @return string
177
     */
178
    abstract public function getName();
179
180
    /**
181
     * @param array $requestParameters
182
     * @return \SocialConnect\Provider\AccessTokenInterface
183
     */
184
    abstract public function getAccessTokenByRequestParameters(array $requestParameters);
185
186
    /**
187
     * @return string
188
     */
189
    abstract public function makeAuthUrl(): string;
190
191
    /**
192
     * Get current user identity from social network by $accessToken
193
     *
194
     * @param AccessTokenInterface $accessToken
195
     * @return \SocialConnect\Common\Entity\User
196
     *
197
     * @throws \Psr\Http\Client\ClientExceptionInterface
198
     * @throws \SocialConnect\Provider\Exception\InvalidResponse
199
     */
200
    abstract public function getIdentity(AccessTokenInterface $accessToken);
201
202
    /**
203
     * @param RequestInterface $request
204
     * @return ResponseInterface
205
     * @throws InvalidResponse
206
     * @throws \Psr\Http\Client\ClientExceptionInterface
207
     */
208 103
    protected function executeRequest(RequestInterface $request): ResponseInterface
209
    {
210 103
        $response = $this->httpStack->sendRequest($request);
211
212 103
        $statusCode = $response->getStatusCode();
213 103
        if (200 <= $statusCode && 300 > $statusCode) {
214 56
            return $response;
215
        }
216
217 47
        throw new InvalidResponse(
218 47
            'API response with error code',
219
            $response
220
        );
221
    }
222
223
    /**
224
     * @param ResponseInterface $response
225
     * @return array
226
     * @throws InvalidResponse
227
     */
228 54
    protected function hydrateResponse(ResponseInterface $response): array
229
    {
230 54
        $result = json_decode($response->getBody()->getContents(), true);
231 54
        if (!$result) {
232 23
            throw new InvalidResponse(
233 23
                'API response is not a valid JSON object',
234
                $response
235
            );
236
        }
237
238 31
        return $result;
239
    }
240
241
    /**
242
     * This is a lifecycle method, should be redeclared inside Provider when it's needed to mutate $query or $headers
243
     *
244
     * @param string $method
245
     * @param string $uri
246
     * @param array $headers
247
     * @param array $query
248
     * @param AccessTokenInterface|null $accessToken Null is needed to allow send request for not OAuth
249
     */
250 32
    public function prepareRequest(string $method, string $uri, array &$headers, array &$query, AccessTokenInterface $accessToken = null): void
251
    {
252 32
        if ($accessToken) {
253 32
            $query['access_token'] = $accessToken->getToken();
254
        }
255 32
    }
256
257
    /**
258
     * @param string $method
259
     * @param string $url
260
     * @param array $query
261
     * @param AccessTokenInterface|null $accessToken
262
     * @param array|null $payload
263
     * @return array
264
     * @throws \Psr\Http\Client\ClientExceptionInterface
265
     */
266 76
    public function request(string $method, string $url, array $query, AccessTokenInterface $accessToken = null, array $payload = null)
267
    {
268 76
        $headers = [];
269
270 76
        $this->prepareRequest(
271 76
            $method,
272 76
            $this->getBaseUri() . $url,
273
            $headers,
274
            $query,
275
            $accessToken
276
        );
277
278 76
        return $this->hydrateResponse(
279 76
            $this->executeRequest(
280 76
                $this->createRequest(
281 76
                    $method,
282 76
                    $this->getBaseUri() . $url,
283
                    $query,
284
                    $headers,
285
                    $payload
286
                )
287
            )
288
        );
289
    }
290
291
    /**
292
     * @return array
293
     */
294
    public function getScope()
295
    {
296
        return $this->scope;
297
    }
298
299
    /**
300
     * @param array $scope
301
     */
302 411
    public function setScope(array $scope)
303
    {
304 411
        $this->scope = $scope;
305 411
    }
306
307
    /**
308
     * @return string
309
     */
310 18
    public function getScopeInline()
311
    {
312 18
        return implode(',', $this->scope);
313
    }
314
315
    /**
316
     * @return \SocialConnect\Provider\Consumer
317
     */
318 1
    public function getConsumer()
319
    {
320 1
        return $this->consumer;
321
    }
322
323
    /**
324
     * @param string $method
325
     * @param string $uri
326
     * @param array $query
327
     * @param array $headers
328
     * @param array|null $payload
329
     * @return RequestInterface
330
     */
331 77
    protected function createRequest(string $method, string $uri, array $query, array $headers, array $payload = null): RequestInterface
332
    {
333 77
        $url = $uri;
334
335 77
        if (count($query) > 0) {
336 65
            $url .= '?' . http_build_query($query);
337
        }
338
339 77
        $request = $this->httpStack->createRequest($method, $url);
340
341 77
        foreach ($headers as $k => $v) {
342 29
            $request = $request->withHeader($k, $v);
343
        }
344
345 77
        $contentLength = 0;
346
347 77
        if ($payload) {
348
            $payloadAsString = http_build_query($payload);
349
            $contentLength = mb_strlen($payloadAsString);
0 ignored issues
show
Unused Code introduced by
The assignment to $contentLength is dead and can be removed.
Loading history...
350
351
            $request = $request
352
                ->withHeader('Content-Type', 'application/x-www-form-urlencoded');
353
354
            return $request->withBody(
355
                $this->httpStack->createStream(
356
                    $payloadAsString
357
                )
358
            );
359
        }
360
361 77
        if ($request->getMethod() === 'POST') {
362
            $request = $request
363
                ->withHeader('Content-Length', $contentLength);
364
        }
365
366 77
        return $request;
367
    }
368
}
369