Completed
Push — master ( 74929b...718547 )
by Tobias
03:47
created

RestClient::getAttachment()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 9
ccs 6
cts 6
cp 1
rs 9.6666
cc 1
eloc 6
nc 1
nop 1
crap 1
1
<?PHP
2
3
/*
4
 * Copyright (C) 2013-2016 Mailgun
5
 *
6
 * This software may be modified and distributed under the terms
7
 * of the MIT license. See the LICENSE file for details.
8
 */
9
10
namespace Mailgun\Connection;
11
12
use Http\Client\HttpClient;
13
use Http\Discovery\HttpClientDiscovery;
14
use Http\Discovery\MessageFactoryDiscovery;
15
use Http\Message\MultipartStream\MultipartStreamBuilder;
16
use Mailgun\Connection\Exceptions\GenericHTTPError;
17
use Mailgun\Connection\Exceptions\InvalidCredentials;
18
use Mailgun\Connection\Exceptions\MissingEndpoint;
19
use Mailgun\Connection\Exceptions\MissingRequiredParameters;
20
use Mailgun\Constants\Api;
21
use Mailgun\Constants\ExceptionMessages;
22
use Psr\Http\Message\ResponseInterface;
23
24
/**
25
 * This class is a wrapper for the HTTP client.
26
 */
27
class RestClient
28
{
29
    /**
30
     * Your API key.
31
     *
32
     * @var string
33
     */
34
    private $apiKey;
35
36
    /**
37
     * @var HttpClient
38
     */
39
    protected $httpClient;
40
41
    /**
42
     * @var string
43
     */
44
    protected $apiHost;
45
46
    /**
47
     * The version of the API to use.
48
     *
49
     * @var string
50
     */
51
    protected $apiVersion = 'v2';
52
53
    /**
54
     * If we should use SSL or not.
55
     *
56
     * @var bool
57
     *
58
     * @deprecated To be removed in 3.0
59
     */
60
    protected $sslEnabled = true;
61
62
    /**
63
     * @param string     $apiKey
64
     * @param string     $apiHost
65
     * @param HttpClient $httpClient
66
     */
67 7
    public function __construct($apiKey, $apiHost, HttpClient $httpClient = null)
68
    {
69 7
        $this->apiKey = $apiKey;
70 7
        $this->apiHost = $apiHost;
71 7
        $this->httpClient = $httpClient;
72 7
    }
73
74
    /**
75
     * @param string $method
76
     * @param string $uri
77
     * @param mixed  $body
78
     * @param array  $files
79
     * @param array  $headers
80
     *
81
     * @throws GenericHTTPError
82
     * @throws InvalidCredentials
83
     * @throws MissingEndpoint
84
     * @throws MissingRequiredParameters
85
     *
86
     * @return \stdClass
87
     */
88
    protected function send($method, $uri, $body = null, $files = [], array $headers = [])
89
    {
90
        $headers['User-Agent'] = Api::SDK_USER_AGENT.'/'.Api::SDK_VERSION;
91
        $headers['Authorization'] = 'Basic '.base64_encode(sprintf('%s:%s', Api::API_USER, $this->apiKey));
92
93
        if (!empty($files)) {
94
            $builder = new MultipartStreamBuilder();
95
            foreach ($files as $file) {
96
                $builder->addResource($file['name'], $file['contents'], $file);
97
            }
98
            $body = $builder->build();
99
            $headers['Content-Type'] = 'multipart/form-data; boundary="'.$builder->getBoundary().'"';
100
        } elseif (is_array($body)) {
101
            $body = http_build_query($body);
102
            $headers['Content-Type'] = 'application/x-www-form-urlencoded';
103
        }
104
105
        $request = MessageFactoryDiscovery::find()->createRequest($method, $this->getApiUrl($uri), $headers, $body);
106
        $response = $this->getHttpClient()->sendRequest($request);
107
108
        return $this->responseHandler($response);
109
    }
110
111
    /**
112
     * @param string $url
113
     *
114
     * @throws GenericHTTPError
115
     * @throws InvalidCredentials
116
     * @throws MissingEndpoint
117
     * @throws MissingRequiredParameters
118
     *
119
     * @return \stdClass
120
     */
121 2
    public function getAttachment($url)
122
    {
123 2
        $headers['User-Agent'] = Api::SDK_USER_AGENT.'/'.Api::SDK_VERSION;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$headers was never initialized. Although not strictly required by PHP, it is generally a good practice to add $headers = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
124 2
        $headers['Authorization'] = 'Basic '.base64_encode(sprintf('%s:%s', Api::API_USER, $this->apiKey));
125 2
        $request = MessageFactoryDiscovery::find()->createRequest('get', $url, $headers);
126 2
        $response = HttpClientDiscovery::find()->sendRequest($request);
127
128 2
        return $this->responseHandler($response);
129
    }
130
131
    /**
132
     * @param string $endpointUrl
133
     * @param array  $postData
134
     * @param array  $files
135
     *
136
     * @throws GenericHTTPError
137
     * @throws InvalidCredentials
138
     * @throws MissingEndpoint
139
     * @throws MissingRequiredParameters
140
     *
141
     * @return \stdClass
142
     */
143 5
    public function post($endpointUrl, array $postData = [], $files = [])
144
    {
145 5
        $postFiles = [];
146
147 5
        $fields = ['message', 'attachment', 'inline'];
148 5
        foreach ($fields as $fieldName) {
149 5 View Code Duplication
            if (isset($files[$fieldName])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
150 4
                if (is_array($files[$fieldName])) {
151 4
                    $fileIndex = 0;
152 4
                    foreach ($files[$fieldName] as $file) {
153 4
                        $postFiles[] = $this->prepareFile($fieldName, $file, $fileIndex);
154 4
                        ++$fileIndex;
155 4
                    }
156 4
                } else {
157
                    $postFiles[] = $this->prepareFile($fieldName, $files[$fieldName]);
158
                }
159 4
            }
160 5
        }
161
162 5
        $postDataMultipart = [];
163 5 View Code Duplication
        foreach ($postData as $key => $value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
164 5
            if (is_array($value)) {
165 2
                $index = 0;
166 2
                foreach ($value as $subValue) {
167 2
                    $postDataMultipart[] = [
168 2
                        'name' => sprintf('%s[%d]', $key, $index++),
169 2
                        'contents' => $subValue,
170
                    ];
171 2
                }
172 2
            } else {
173 5
                $postDataMultipart[] = [
174 5
                    'name' => $key,
175 5
                    'contents' => $value,
176
                ];
177
            }
178 5
        }
179
180 5
        return $this->send('POST', $endpointUrl, [], array_merge($postDataMultipart, $postFiles));
181
    }
182
183
    /**
184
     * @param string $endpointUrl
185
     * @param array  $queryString
186
     *
187
     * @throws GenericHTTPError
188
     * @throws InvalidCredentials
189
     * @throws MissingEndpoint
190
     * @throws MissingRequiredParameters
191
     *
192
     * @return \stdClass
193
     */
194
    public function get($endpointUrl, $queryString = [])
195
    {
196
        return $this->send('GET', $endpointUrl.'?'.http_build_query($queryString));
197
    }
198
199
    /**
200
     * @param string $endpointUrl
201
     *
202
     * @throws GenericHTTPError
203
     * @throws InvalidCredentials
204
     * @throws MissingEndpoint
205
     * @throws MissingRequiredParameters
206
     *
207
     * @return \stdClass
208
     */
209
    public function delete($endpointUrl)
210
    {
211
        return $this->send('DELETE', $endpointUrl);
212
    }
213
214
    /**
215
     * @param string $endpointUrl
216
     * @param mixed  $putData
217
     *
218
     * @throws GenericHTTPError
219
     * @throws InvalidCredentials
220
     * @throws MissingEndpoint
221
     * @throws MissingRequiredParameters
222
     *
223
     * @return \stdClass
224
     */
225
    public function put($endpointUrl, $putData)
226
    {
227
        return $this->send('PUT', $endpointUrl, $putData);
228
    }
229
230
    /**
231
     * @param ResponseInterface $responseObj
232
     *
233
     * @throws GenericHTTPError
234
     * @throws InvalidCredentials
235
     * @throws MissingEndpoint
236
     * @throws MissingRequiredParameters
237
     *
238
     * @return \stdClass
239
     */
240 2
    public function responseHandler(ResponseInterface $responseObj)
241
    {
242 2
        $httpResponseCode = (int) $responseObj->getStatusCode();
243
244
        switch ($httpResponseCode) {
245 2
        case 200:
246 1
            $data = (string) $responseObj->getBody();
247 1
            $jsonResponseData = json_decode($data, false);
248 1
            $result = new \stdClass();
249
            // return response data as json if possible, raw if not
250 1
            $result->http_response_body = $data && $jsonResponseData === null ? $data : $jsonResponseData;
251 1
            $result->http_response_code = $httpResponseCode;
252
253 1
            return $result;
254 1
        case 400:
255
            throw new MissingRequiredParameters(ExceptionMessages::EXCEPTION_MISSING_REQUIRED_PARAMETERS.$this->getResponseExceptionMessage($responseObj));
256 1
        case 401:
257
            throw new InvalidCredentials(ExceptionMessages::EXCEPTION_INVALID_CREDENTIALS);
258 1
        case 404:
259
            throw new MissingEndpoint(ExceptionMessages::EXCEPTION_MISSING_ENDPOINT.$this->getResponseExceptionMessage($responseObj));
260 1
        default:
261 1
            throw new GenericHTTPError(ExceptionMessages::EXCEPTION_GENERIC_HTTP_ERROR, $httpResponseCode, $responseObj->getBody());
262 1
        }
263
    }
264
265
    /**
266
     * @param ResponseInterface $responseObj
267
     *
268
     * @return string
269
     */
270
    protected function getResponseExceptionMessage(ResponseInterface $responseObj)
271
    {
272
        $body = (string) $responseObj->getBody();
273
        $response = json_decode($body);
274
        if (json_last_error() == JSON_ERROR_NONE && isset($response->message)) {
275
            return ' '.$response->message;
276
        }
277
278
        return '';
279
    }
280
281
    /**
282
     * Prepare a file for the postBody.
283
     *
284
     * @param string       $fieldName
285
     * @param string|array $filePath
286
     * @param int          $fileIndex
287
     *
288
     * @return array
289
     */
290 4
    protected function prepareFile($fieldName, $filePath, $fileIndex = 0)
291
    {
292 4
        $filename = null;
293
294 4
        if (is_array($filePath) && isset($filePath['fileContent'])) {
295
            // File from memory
296 1
            $filename = $filePath['filename'];
297 1
            $resource = fopen('php://temp', 'r+');
298 1
            fwrite($resource, $filePath['fileContent']);
299 1
            rewind($resource);
300 1
        } else {
301
            // Backward compatibility code
302 4
            if (is_array($filePath) && isset($filePath['filePath'])) {
303 4
                $filename = $filePath['remoteName'];
304 4
                $filePath = $filePath['filePath'];
305 4
            }
306
307
            // Remove leading @ symbol
308 4
            if (strpos($filePath, '@') === 0) {
309 2
                $filePath = substr($filePath, 1);
310 2
            }
311
312 4
            $resource = fopen($filePath, 'r');
313
        }
314
315
        // Add index for multiple file support
316 4
        $fieldName .= '['.$fileIndex.']';
317
318
        return [
319 4
            'name' => $fieldName,
320 4
            'contents' => $resource,
321 4
            'filename' => $filename,
322 4
        ];
323
    }
324
325
    /**
326
     * @return HttpClient
327
     */
328
    protected function getHttpClient()
329
    {
330
        if ($this->httpClient === null) {
331
            $this->httpClient = HttpClientDiscovery::find();
332
        }
333
334
        return $this->httpClient;
335
    }
336
337
    /**
338
     * @param string $uri
339
     *
340
     * @return string
341
     */
342
    private function getApiUrl($uri)
343
    {
344
        return $this->generateEndpoint($this->apiHost, $this->apiVersion, $this->sslEnabled).$uri;
0 ignored issues
show
Deprecated Code introduced by
The property Mailgun\Connection\RestClient::$sslEnabled has been deprecated with message: To be removed in 3.0

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
345
    }
346
347
    /**
348
     * @param string $apiEndpoint
349
     * @param string $apiVersion
350
     * @param bool   $ssl
351
     *
352
     * @return string
353
     */
354
    private function generateEndpoint($apiEndpoint, $apiVersion, $ssl)
355
    {
356
        return ($ssl ? 'https://' : 'http://').$apiEndpoint.'/'.$apiVersion.'/';
357
    }
358
359
    /**
360
     * @param string $apiVersion
361
     *
362
     * @return RestClient
363
     */
364
    public function setApiVersion($apiVersion)
365
    {
366
        $this->apiVersion = $apiVersion;
367
368
        return $this;
369
    }
370
371
    /**
372
     * @param bool $sslEnabled
373
     *
374
     * @return RestClient
375
     *
376
     * @deprecated To be removed in 3.0
377
     */
378
    public function setSslEnabled($sslEnabled)
379
    {
380
        $this->sslEnabled = $sslEnabled;
0 ignored issues
show
Deprecated Code introduced by
The property Mailgun\Connection\RestClient::$sslEnabled has been deprecated with message: To be removed in 3.0

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
381
382
        return $this;
383
    }
384
}
385