Completed
Push — 3.x ( e636ad...7bb25c )
by Дмитрий
03:18
created

AbstractBaseProvider::createRequest()   B

Complexity

Conditions 6
Paths 24

Size

Total Lines 38
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 9.2805

Importance

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