AbstractBaseProvider   A
last analyzed

Complexity

Total Complexity 33

Size/Duplication

Total Lines 360
Duplicated Lines 0 %

Test Coverage

Coverage 81.72%

Importance

Changes 19
Bugs 0 Features 1
Metric Value
wmc 33
eloc 89
c 19
b 0
f 1
dl 0
loc 360
ccs 76
cts 93
cp 0.8172
rs 9.76

17 Methods

Rating   Name   Duplication   Size   Complexity  
A getAuthUrlParameters() 0 3 1
A getArrayOption() 0 7 2
A getBoolOption() 0 7 2
A __construct() 0 17 4
A getRedirectUrl() 0 3 1
A createConsumer() 0 5 1
A getRequiredStringParameter() 0 15 3
A generateState() 0 3 1
B createRequest() 0 38 6
A getScope() 0 3 1
A prepareRequest() 0 4 2
A hydrateResponse() 0 11 2
A executeRequest() 0 12 3
A setScope() 0 3 1
A request() 0 20 1
A getScopeInline() 0 3 1
A getConsumer() 0 3 1
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 453
    public function __construct(HttpStack $httpStack, SessionInterface $session, array $parameters)
59
    {
60 453
        if (isset($parameters['scope'])) {
61 451
            $this->setScope($parameters['scope']);
62
        }
63
64 453
        if (isset($parameters['redirectUri'])) {
65 451
            $this->redirectUri = $parameters['redirectUri'];
66
        }
67
68 453
        if (isset($parameters['options'])) {
69
            $this->options = $parameters['options'];
70
        }
71
72 453
        $this->consumer = $this->createConsumer($parameters);
73 453
        $this->httpStack = $httpStack;
74 453
        $this->session = $session;
75 453
    }
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 446
    protected function createConsumer(array $parameters): Consumer
93
    {
94 446
        return new Consumer(
95 446
            $this->getRequiredStringParameter('applicationId', $parameters),
96 446
            $this->getRequiredStringParameter('applicationSecret', $parameters)
97
        );
98
    }
99
100
    /**
101
     * @param string $key
102
     * @param array $parameters
103
     * @return string
104
     * @throws InvalidProviderConfiguration
105
     */
106 453
    protected function getRequiredStringParameter(string $key, array $parameters): string
107
    {
108 453
        if (!isset($parameters[$key])) {
109
            throw new InvalidProviderConfiguration(
110
                "Parameter '{$key}' doesn`t exists for '{$this->getName()}' provider configuration"
111
            );
112
        }
113
114 453
        if (!is_string($parameters[$key])) {
115
            throw new InvalidProviderConfiguration(
116
                "Parameter '{$key}' must be string inside '{$this->getName()}' provider configuration"
117
            );
118
        }
119
120 453
        return $parameters[$key];
121
    }
122
123
    /**
124
     * @param string $key
125
     * @param bool $default
126
     * @return bool
127
     */
128 25
    public function getBoolOption($key, $default): bool
129
    {
130 25
        if (array_key_exists($key, $this->options)) {
131 2
            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 35
    public function getArrayOption($key, array $default = []): array
143
    {
144 35
        if (array_key_exists($key, $this->options)) {
145
            return (array) $this->options[$key];
146
        }
147
148 35
        return $default;
149
    }
150
151
    /**
152
     * @return string
153
     */
154 81
    public function getRedirectUrl(): string
155
    {
156 81
        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
     * Sometimes it's needed to login user by access token on the server
194
     * You can pass all information related auth in json_encoded to the server
195
     * and use this method to instance new token
196
     *
197
     * @param array $information
198
     * @return mixed
199
     */
200
    abstract public function createAccessToken(array $information);
201
202
    /**
203
     * Get current user identity from social network by $accessToken
204
     *
205
     * @param AccessTokenInterface $accessToken
206
     * @return \SocialConnect\Common\Entity\User
207
     *
208
     * @throws \Psr\Http\Client\ClientExceptionInterface
209
     * @throws \SocialConnect\Provider\Exception\InvalidResponse
210
     */
211
    abstract public function getIdentity(AccessTokenInterface $accessToken);
212
213
    /**
214
     * @param RequestInterface $request
215
     * @return ResponseInterface
216
     * @throws InvalidResponse
217
     * @throws \Psr\Http\Client\ClientExceptionInterface
218
     */
219 103
    protected function executeRequest(RequestInterface $request): ResponseInterface
220
    {
221 103
        $response = $this->httpStack->sendRequest($request);
222
223 103
        $statusCode = $response->getStatusCode();
224 103
        if (200 <= $statusCode && 300 > $statusCode) {
225 56
            return $response;
226
        }
227
228 47
        throw new InvalidResponse(
229 47
            'API response with error code',
230
            $response
231
        );
232
    }
233
234
    /**
235
     * @param ResponseInterface $response
236
     * @return array
237
     * @throws InvalidResponse
238
     */
239 54
    protected function hydrateResponse(ResponseInterface $response): array
240
    {
241 54
        $result = json_decode($response->getBody()->getContents(), true);
242 54
        if (!$result) {
243 23
            throw new InvalidResponse(
244 23
                'API response is not a valid JSON object',
245
                $response
246
            );
247
        }
248
249 31
        return $result;
250
    }
251
252
    /**
253
     * This is a lifecycle method, should be redeclared inside Provider when it's needed to mutate $query or $headers
254
     *
255
     * @param string $method
256
     * @param string $uri
257
     * @param array $headers
258
     * @param array $query
259
     * @param AccessTokenInterface|null $accessToken Null is needed to allow send request for not OAuth
260
     */
261 31
    public function prepareRequest(string $method, string $uri, array &$headers, array &$query, AccessTokenInterface $accessToken = null): void
262
    {
263 31
        if ($accessToken) {
264 31
            $query['access_token'] = $accessToken->getToken();
265
        }
266 31
    }
267
268
    /**
269
     * @param string $method
270
     * @param string $url
271
     * @param array $query
272
     * @param AccessTokenInterface|null $accessToken
273
     * @param array|null $payload
274
     * @return array
275
     * @throws \Psr\Http\Client\ClientExceptionInterface
276
     */
277 76
    public function request(string $method, string $url, array $query, AccessTokenInterface $accessToken = null, array $payload = null)
278
    {
279 76
        $headers = [];
280
281 76
        $this->prepareRequest(
282 76
            $method,
283 76
            $this->getBaseUri() . $url,
284
            $headers,
285
            $query,
286
            $accessToken
287
        );
288
289 76
        return $this->hydrateResponse(
290 76
            $this->executeRequest(
291 76
                $this->createRequest(
292 76
                    $method,
293 76
                    $this->getBaseUri() . $url,
294
                    $query,
295
                    $headers,
296
                    $payload
297
                )
298
            )
299
        );
300
    }
301
302
    /**
303
     * @return array
304
     */
305
    public function getScope()
306
    {
307
        return $this->scope;
308
    }
309
310
    /**
311
     * @param array $scope
312
     */
313 419
    public function setScope(array $scope)
314
    {
315 419
        $this->scope = $scope;
316 419
    }
317
318
    /**
319
     * @return string
320
     */
321 18
    public function getScopeInline()
322
    {
323 18
        return implode(',', $this->scope);
324
    }
325
326
    /**
327
     * @return \SocialConnect\Provider\Consumer
328
     */
329 1
    public function getConsumer()
330
    {
331 1
        return $this->consumer;
332
    }
333
334
    /**
335
     * @param string $method
336
     * @param string $uri
337
     * @param array $query
338
     * @param array $headers
339
     * @param array|null $payload
340
     * @return RequestInterface
341
     */
342 77
    protected function createRequest(string $method, string $uri, array $query, array $headers, array $payload = null): RequestInterface
343
    {
344 77
        $url = $uri;
345
346 77
        if (count($query) > 0) {
347 64
            $url .= '?' . http_build_query($query);
348
        }
349
350 77
        $request = $this->httpStack->createRequest($method, $url);
351
352 77
        foreach ($headers as $k => $v) {
353 30
            $request = $request->withHeader($k, $v);
354
        }
355
356 77
        $method = strtoupper($request->getMethod());
357 77
        $withPayload = $method === 'POST' || $method === 'PUT';
358
359 77
        if ($payload) {
360
            if ($withPayload) {
361
                $payloadAsString = http_build_query($payload);
362
                $contentLength = mb_strlen($payloadAsString);
363
364
                $request = $request
365
                    ->withHeader('Content-Length', $contentLength)
366
                    ->withHeader('Content-Type', 'application/x-www-form-urlencoded')
367
                ;
368
369
                return $request->withBody(
370
                    $this->httpStack->createStream(
371
                        $payloadAsString
372
                    )
373
                );
374
            }
375
376
            throw new InvalidRequest('You are not able to send payload on HTTP method without body');
377
        }
378
379 77
        return $request;
380
    }
381
}
382