Completed
Push — master ( 30ef9c...66d5ad )
by
unknown
12s queued 11s
created

PublicAPIClient   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 301
Duplicated Lines 9.3 %

Coupling/Cohesion

Components 1
Dependencies 22

Importance

Changes 0
Metric Value
wmc 45
lcom 1
cbo 22
dl 28
loc 301
rs 8.8
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 2
A send() 0 22 3
A prepareRequest() 0 12 2
B handleNot200() 0 29 8
A readJson() 0 21 5
A ping() 0 9 3
A sendEvent() 14 14 4
A sendPostback() 14 14 5
A makeDecision() 0 33 5
B sendKycProof() 0 30 8

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like PublicAPIClient often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PublicAPIClient, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Covery\Client;
4
5
use Covery\Client\Envelopes\ValidatorV1;
6
use Covery\Client\Requests\Decision;
7
use Covery\Client\Requests\Event;
8
use Covery\Client\Requests\KycProof;
9
use Covery\Client\Requests\Ping;
10
use Covery\Client\Requests\Postback;
11
use Psr\Http\Message\RequestInterface;
12
use Psr\Http\Message\ResponseInterface;
13
use Psr\Log\LoggerInterface;
14
use Psr\Log\NullLogger;
15
16
class PublicAPIClient
17
{
18
    /**
19
     * @var CredentialsInterface
20
     */
21
    private $credentials;
22
23
    /**
24
     * @var TransportInterface
25
     */
26
    private $transport;
27
28
    /**
29
     * @var LoggerInterface
30
     */
31
    private $logger;
32
33
    /**
34
     * @var ValidatorV1
35
     */
36
    private $validator;
37
38
    /**
39
     * Client constructor.
40
     * @param CredentialsInterface $credentials
41
     * @param TransportInterface $transport
42
     * @param LoggerInterface|null $logger
43
     */
44
    public function __construct(
45
        CredentialsInterface $credentials,
46
        TransportInterface $transport,
47
        LoggerInterface $logger = null
48
    ) {
49
        $this->credentials = $credentials;
50
        $this->transport = $transport;
51
        $this->logger = $logger === null ? new NullLogger() : $logger;
52
        $this->validator = new ValidatorV1();
53
    }
54
55
    /**
56
     * Sends PSR-7 compatible request to Covery and returns
57
     *
58
     * @param RequestInterface $request
59
     * @return string
60
     * @throws IoException
61
     */
62
    public function send(RequestInterface $request)
63
    {
64
        $request = $this->prepareRequest($request);
65
        try {
66
            $this->logger->info('Sending request to ' . $request->getUri());
67
            $before = microtime(true);
68
            $response = $this->transport->send($request);
69
            $this->logger->info(sprintf('Request done in %.2f', microtime(true) - $before));
70
        } catch (\Exception $inner) {
71
            $this->logger->error($inner->getMessage(), ['exception' => $inner]);
72
            // Wrapping exception
73
            throw new IoException('Error sending request', 0, $inner);
74
        }
75
        $code = $response->getStatusCode();
76
        $this->logger->debug('Received status code ' . $code);
77
78
        if ($code >= 400) {
79
            $this->handleNot200($response);
80
        }
81
82
        return $response->getBody()->getContents();
83
    }
84
85
    /**
86
     * Utility method, that prepares and signs request
87
     *
88
     * @param RequestInterface $request
89
     * @return RequestInterface
90
     */
91
    private function prepareRequest(RequestInterface $request)
92
    {
93
        // Checking hostname presence
94
        $uri = $request->getUri();
95
        if ($uri->getHost() == '') {
96
            $request = $request->withUri(
97
                $uri->withHost(TransportInterface::DEFAULT_HOST)->withScheme(TransportInterface::DEFAULT_SCHEME)
98
            );
99
        }
100
101
        return $this->credentials->signRequest($request);
102
    }
103
104
    /**
105
     * Utility function, that handles error response from Covery
106
     *
107
     * @param ResponseInterface $response
108
     * @throws Exception
109
     */
110
    private function handleNot200(ResponseInterface $response)
111
    {
112
        // Analyzing response
113
        if ($response->hasHeader('X-Maxwell-Status') && $response->hasHeader('X-Maxwell-Error-Message')) {
114
            // Extended data available
115
            $message = $response->getHeaderLine('X-Maxwell-Error-Message');
116
            $type = $response->getHeaderLine('X-Maxwell-Error-Type');
117
            if (strpos($type, 'AuthorizationRequiredException') !== false) {
118
                $this->logger->error('Authentication failure ' . $message);
119
                throw new AuthException($message, $response->getStatusCode());
120
            }
121
122
            switch ($message) {
123
                case 'Empty auth token':
124
                case 'Empty signature':
125
                case 'Empty nonce':
126
                    $this->logger->error('Authentication failure ' . $message);
127
                    throw new AuthException($message, $response->getStatusCode());
128
            }
129
130
            $this->logger->error('Covery error ' . $message);
131
            throw new DeliveredException($message, $response->getStatusCode());
132
        } elseif ($response->hasHeader('X-General-Failure')) {
133
            // Remote fatal error
134
            throw new DeliveredException('Antifraud fatal error', $response->getStatusCode());
135
        }
136
137
        throw new Exception("Communication failed with status code {$response->getStatusCode()}");
138
    }
139
140
    /**
141
     * Utility method, that reads JSON data
142
     *
143
     * @param $string
144
     * @return mixed|null
145
     * @throws Exception
146
     */
147
    private function readJson($string)
148
    {
149
        if (!is_string($string)) {
150
            throw new Exception("Unable to read JSON - not a string received");
151
        }
152
        if (strlen($string) === 0) {
153
            return null;
154
        }
155
156
        $data = json_decode($string, true);
157
        if ($data === null) {
158
            $message = 'Unable to decode JSON';
159
            if (function_exists('json_last_error_msg')) {
160
                $message = json_last_error_msg();
161
            }
162
163
            throw new Exception($message);
164
        }
165
166
        return $data;
167
    }
168
169
    /**
170
     * Sends request to Covery and returns access level, associated with
171
     * used credentials
172
     *
173
     * This method can be used for Covery health check and availability
174
     * On any problem (network, credentials, server side) this method
175
     * will throw an exception
176
     *
177
     * @return string
178
     * @throws Exception
179
     */
180
    public function ping()
181
    {
182
        $data = $this->readJson($this->send(new Ping()));
183
        if (!is_array($data) || !isset($data['level'])) {
184
            throw new Exception("Malformed response");
185
        }
186
187
        return $data['level'];
188
    }
189
190
    /**
191
     * Sends envelope to Covery and returns it's ID on Covery side
192
     * Before sending, validation is performed
193
     *
194
     * @param EnvelopeInterface $envelope
195
     * @return int
196
     * @throws Exception
197
     */
198 View Code Duplication
    public function sendEvent(EnvelopeInterface $envelope)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
199
    {
200
        // Validating
201
        $this->validator->validate($envelope);
202
203
        // Sending
204
        $data = $this->readJson($this->send(new Event($envelope)));
205
206
        if (!is_array($data) || !isset($data['requestId']) || !is_int($data['requestId'])) {
207
            throw new Exception("Malformed response");
208
        }
209
210
        return $data['requestId'];
211
    }
212
213
    /**
214
     * Sends postback envelope to Covery and returns it's ID on Covery side
215
     * Before sending, validation is performed
216
     *
217
     * @param EnvelopeInterface $envelope
218
     * @return int
219
     * @throws Exception
220
     */
221 View Code Duplication
    public function sendPostback(EnvelopeInterface $envelope)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
222
    {
223
        // Validating
224
        $this->validator->validate($envelope);
225
226
        // Sending
227
        $data = $this->readJson($this->send(new Postback($envelope)));
228
229
        if (!is_array($data) || !isset($data['requestId']) || empty($data['requestId']) || !is_int($data['requestId'])) {
230
            throw new Exception("Malformed response");
231
        }
232
233
        return $data['requestId'];
234
    }
235
236
    /**
237
     * Sends envelope to Covery for analysis
238
     *
239
     * @param EnvelopeInterface $envelope
240
     * @return Result
241
     * @throws Exception
242
     */
243
    public function makeDecision(EnvelopeInterface $envelope)
244
    {
245
        // Validating
246
        $this->validator->validate($envelope);
247
248
        // Sending
249
        $data = $this->readJson($this->send(new Decision($envelope)));
250
251
        if (!is_array($data)) {
252
            throw new Exception("Malformed response");
253
        }
254
255
        try {
256
            return new Result(
257
                $data[ResultBaseField::REQUEST_ID],
258
                $data[ResultBaseField::TYPE],
259
                $data[ResultBaseField::CREATED_AT],
260
                $data[ResultBaseField::SEQUENCE_ID],
261
                $data[ResultBaseField::MERCHANT_USER_ID],
262
                $data[ResultBaseField::SCORE],
263
                $data[ResultBaseField::ACCEPT],
264
                $data[ResultBaseField::REJECT],
265
                $data[ResultBaseField::MANUAL],
266
                isset($data[ResultBaseField::REASON]) ? $data[ResultBaseField::REASON] : null,
267
                isset($data[ResultBaseField::ACTION]) ? $data[ResultBaseField::ACTION] : null,
268
                array_filter($data, function ($field) {
269
                    return !in_array($field, ResultBaseField::getAll());
270
                }, ARRAY_FILTER_USE_KEY)
271
            );
272
        } catch (\Exception $error) {
273
            throw new Exception('Malformed response', 0, $error);
274
        }
275
    }
276
277
    /**
278
     * Sends kycProof envelope to Covery and returns KycProofResult on Covery side
279
     *
280
     * @param EnvelopeInterface $envelope
281
     * @return KycProofResult
282
     * @throws EnvelopeValidationException
283
     * @throws Exception
284
     * @throws IoException
285
     */
286
    public function sendKycProof(EnvelopeInterface $envelope)
287
    {
288
        // Validating
289
        $this->validator->validate($envelope);
290
291
        // Sending
292
        $data = $this->readJson($this->send(new KycProof($envelope)));
293
294
        if (!is_array($data)) {
295
            throw new Exception("Malformed response");
296
        }
297
298
        try {
299
            return new KycProofResult(
300
                $data[KycProofResultBaseField::REQUEST_ID],
301
                $data[KycProofResultBaseField::TYPE],
302
                $data[KycProofResultBaseField::CREATED_AT],
303
                isset($data[KycProofResultBaseField::VERIFICATION_VIDEO]) ? $data[KycProofResultBaseField::VERIFICATION_VIDEO] : null,
304
                isset($data[KycProofResultBaseField::FACE_PROOF]) ? $data[KycProofResultBaseField::FACE_PROOF] : null,
305
                isset($data[KycProofResultBaseField::DOCUMENT_PROOF]) ? $data[KycProofResultBaseField::DOCUMENT_PROOF] : null,
306
                isset($data[KycProofResultBaseField::DOCUMENT_TWO_PROOF]) ? $data[KycProofResultBaseField::DOCUMENT_TWO_PROOF] : null,
307
                isset($data[KycProofResultBaseField::CONSENT_PROOF]) ? $data[KycProofResultBaseField::CONSENT_PROOF] : null,
308
                array_filter($data, function ($field) {
0 ignored issues
show
Unused Code introduced by
The call to KycProofResult::__construct() has too many arguments starting with array_filter($data, func..., ARRAY_FILTER_USE_KEY).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
309
                    return !in_array($field, KycProofResultBaseField::getAll());
310
                }, ARRAY_FILTER_USE_KEY)
311
            );
312
        } catch (\Exception $error) {
313
            throw new Exception('Malformed response', 0, $error);
314
        }
315
    }
316
}
317