Completed
Pull Request — master (#14)
by
unknown
01:33
created

PublicAPIClient::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
cc 2
nc 2
nop 3
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\Ping;
9
use Covery\Client\Requests\Postback;
10
use Psr\Http\Message\RequestInterface;
11
use Psr\Http\Message\ResponseInterface;
12
use Psr\Log\LoggerInterface;
13
use Psr\Log\NullLogger;
14
15
class PublicAPIClient
16
{
17
    /**
18
     * @var CredentialsInterface
19
     */
20
    private $credentials;
21
22
    /**
23
     * @var TransportInterface
24
     */
25
    private $transport;
26
27
    /**
28
     * @var LoggerInterface
29
     */
30
    private $logger;
31
32
    /**
33
     * @var ValidatorV1
34
     */
35
    private $validator;
36
37
    /**
38
     * Client constructor.
39
     * @param CredentialsInterface $credentials
40
     * @param TransportInterface $transport
41
     * @param LoggerInterface|null $logger
42
     */
43
    public function __construct(
44
        CredentialsInterface $credentials,
45
        TransportInterface $transport,
46
        LoggerInterface $logger = null
47
    ) {
48
        $this->credentials = $credentials;
49
        $this->transport = $transport;
50
        $this->logger = $logger === null ? new NullLogger() : $logger;
51
        $this->validator = new ValidatorV1();
52
    }
53
54
    /**
55
     * Sends PSR-7 compatible request to Covery and returns
56
     *
57
     * @param RequestInterface $request
58
     * @return string
59
     * @throws IoException
60
     */
61
    public function send(RequestInterface $request)
62
    {
63
        $request = $this->prepareRequest($request);
64
        try {
65
            $this->logger->info('Sending request to ' . $request->getUri());
66
            $before = microtime(true);
67
            $response = $this->transport->send($request);
68
            $this->logger->info(sprintf('Request done in %.2f', microtime(true) - $before));
69
        } catch (\Exception $inner) {
70
            $this->logger->error($inner->getMessage(), ['exception' => $inner]);
71
            // Wrapping exception
72
            throw new IoException('Error sending request', 0, $inner);
73
        }
74
        $code = $response->getStatusCode();
75
        $this->logger->debug('Received status code ' . $code);
76
77
        if ($code >= 400) {
78
            $this->handleNot200($response);
79
        }
80
81
        return $response->getBody()->getContents();
82
    }
83
84
    /**
85
     * Utility method, that prepares and signs request
86
     *
87
     * @param RequestInterface $request
88
     * @return RequestInterface
89
     */
90
    private function prepareRequest(RequestInterface $request)
91
    {
92
        // Checking hostname presence
93
        $uri = $request->getUri();
94
        if ($uri->getHost() == '') {
95
            $request = $request->withUri(
96
                $uri->withHost(TransportInterface::DEFAULT_HOST)->withScheme(TransportInterface::DEFAULT_SCHEME)
97
            );
98
        }
99
100
        return $this->credentials->signRequest($request);
101
    }
102
103
    /**
104
     * Utility function, that handles error response from Covery
105
     *
106
     * @param ResponseInterface $response
107
     * @throws Exception
108
     */
109
    private function handleNot200(ResponseInterface $response)
110
    {
111
        // Analyzing response
112
        if ($response->hasHeader('X-Maxwell-Status') && $response->hasHeader('X-Maxwell-Error-Message')) {
113
            // Extended data available
114
            $message = $response->getHeaderLine('X-Maxwell-Error-Message');
115
            $type = $response->getHeaderLine('X-Maxwell-Error-Type');
116
            if (strpos($type, 'AuthorizationRequiredException') !== false) {
117
                $this->logger->error('Authentication failure ' . $message);
118
                throw new AuthException($message, $response->getStatusCode());
119
            }
120
121
            switch ($message) {
122
                case 'Empty auth token':
123
                case 'Empty signature':
124
                case 'Empty nonce':
125
                    $this->logger->error('Authentication failure ' . $message);
126
                    throw new AuthException($message, $response->getStatusCode());
127
            }
128
129
            $this->logger->error('Covery error ' . $message);
130
            throw new DeliveredException($message, $response->getStatusCode());
131
        } elseif ($response->hasHeader('X-General-Failure')) {
132
            // Remote fatal error
133
            throw new DeliveredException('Antifraud fatal error', $response->getStatusCode());
134
        }
135
136
        throw new Exception("Communication failed with status code {$response->getStatusCode()}");
137
    }
138
139
    /**
140
     * Utility method, that reads JSON data
141
     *
142
     * @param $string
143
     * @return mixed|null
144
     * @throws Exception
145
     */
146
    private function readJson($string)
147
    {
148
        if (!is_string($string)) {
149
            throw new Exception("Unable to read JSON - not a string received");
150
        }
151
        if (strlen($string) === 0) {
152
            return null;
153
        }
154
155
        $data = json_decode($string, true);
156
        if ($data === null) {
157
            $message = 'Unable to decode JSON';
158
            if (function_exists('json_last_error_msg')) {
159
                $message = json_last_error_msg();
160
            }
161
162
            throw new Exception($message);
163
        }
164
165
        return $data;
166
    }
167
168
    /**
169
     * Sends request to Covery and returns access level, associated with
170
     * used credentials
171
     *
172
     * This method can be used for Covery health check and availability
173
     * On any problem (network, credentials, server side) this method
174
     * will throw an exception
175
     *
176
     * @return string
177
     * @throws Exception
178
     */
179
    public function ping()
180
    {
181
        $data = $this->readJson($this->send(new Ping()));
182
        if (!is_array($data) || !isset($data['level'])) {
183
            throw new Exception("Malformed response");
184
        }
185
186
        return $data['level'];
187
    }
188
189
    /**
190
     * Sends envelope to Covery and returns it's ID on Covery side
191
     * Before sending, validation is performed
192
     *
193
     * @param EnvelopeInterface $envelope
194
     * @return int
195
     * @throws Exception
196
     */
197 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...
198
    {
199
        // Validating
200
        $this->validator->validate($envelope);
201
202
        // Sending
203
        $data = $this->readJson($this->send(new Event($envelope)));
204
205
        if (!is_array($data) || !isset($data['requestId']) || !is_int($data['requestId'])) {
206
            throw new Exception("Malformed response");
207
        }
208
209
        return $data['requestId'];
210
    }
211
212
    /**
213
     * Sends postback envelope to Covery and returns it's ID on Covery side
214
     * Before sending, validation is performed
215
     *
216
     * @param EnvelopeInterface $envelope
217
     * @return int
218
     * @throws Exception
219
     */
220 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...
221
    {
222
        // Validating
223
        $this->validator->validate($envelope);
224
225
        // Sending
226
        $data = $this->readJson($this->send(new Postback($envelope)));
227
228
        if (!is_array($data) || !isset($data['requestId']) || !is_int($data['requestId'])) {
229
            throw new Exception("Malformed response");
230
        }
231
232
        return $data['requestId'];
233
    }
234
235
    /**
236
     * Sends envelope to Covery for analysis
237
     *
238
     * @param EnvelopeInterface $envelope
239
     * @return Result
240
     * @throws Exception
241
     */
242
    public function makeDecision(EnvelopeInterface $envelope)
243
    {
244
        // Validating
245
        $this->validator->validate($envelope);
246
247
        // Sending
248
        $data = $this->readJson($this->send(new Decision($envelope)));
249
250
        if (!is_array($data)) {
251
            throw new Exception("Malformed response");
252
        }
253
254
        try {
255
            return new Result(
256
                $data['requestId'],
257
                $data['score'],
258
                $data['accept'],
259
                $data['reject'],
260
                $data['manual']
261
            );
262
        } catch (\Exception $error) {
263
            throw new Exception('Malformed response', 0, $error);
264
        }
265
    }
266
}
267