Completed
Push — master ( 0ca99d...7c70a3 )
by Oleg
02:52
created

OptimizelyApiClient::sendHttpRequest()   C

Complexity

Conditions 14
Paths 35

Size

Total Lines 72
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 32.4016

Importance

Changes 0
Metric Value
cc 14
eloc 43
nc 35
nop 5
dl 0
loc 72
ccs 24
cts 44
cp 0.5455
crap 32.4016
rs 5.4961
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @abstract API client for Optimizely.
4
 * @author Oleg Krivtsov <[email protected]>
5
 * @date 03 October 2016
6
 * @copyright (c) 2016, Web Marketing ROI
7
 */
8
namespace WebMarketingROI\OptimizelyPHP;
9
10
/**
11
 * Client for Optimizely REST API v2.
12
 */
13
class OptimizelyApiClient 
14
{
15
    /**
16
     * Auth credentials.
17
     * @var array
18
     */
19
    private $authCredentials = array();
20
    
21
    /**
22
     * API version.
23
     * @var string
24
     */
25
    private $apiVersion;
26
    
27
    /**
28
     * CURL handle.
29
     * @var resource 
30
     */
31
    private $curlHandle;
32
    
33
    /**
34
     * Instantiated services (used internally).
35
     * @var array
36
     */
37
    private $services = array();
38
    
39
    /**
40
     * Constructor.
41
     * @param array $authCredentials Auth credentials.
42
     * @param string $apiVersion Optional. Currently supported 'v2' only.
43
     */
44 8
    public function __construct($authCredentials, $apiVersion='v2')
45
    {
46 8
        if (!is_array($authCredentials)) {
47 1
            throw new \Exception('Auth credentials must be an array');            
48
        }
49
        
50 7
        if ($apiVersion!='v2') {
51 1
            throw new \Exception('Invalid API version passed');
52
        }
53
        
54 6
        $this->authCredentials = $authCredentials;
55 6
        $this->apiVersion = $apiVersion;
56
        
57 6
        $this->curlHandle = curl_init();
58 6
        if (!$this->curlHandle) {
59
            throw new \Exception('Error initializing CURL');
60
        }
61 6
    }
62
    
63
    /**
64
     * Returns API version (currently it is always 'v2').
65
     * @return string
66
     */
67 1
    public function getApiVersion()
68
    {
69 1
        return $this->apiVersion;
70
    }
71
    
72
    /**
73
     * Sets API version. 
74
     * @param string $apiVersion Currently, 'v2' only.
75
     */
76 1
    public function setApiVersion($apiVersion)
77
    {
78 1
        if ($apiVersion!='v2') {
79
            throw new \Exception('Invalid API version passed');
80
        }
81
        
82 1
        $this->apiVersion = $apiVersion;
83 1
    }
84
    
85
    /**
86
     * Returns auth credentials
87
     * @return array
88
     */
89 1
    public function getAuthCredentials()
90
    {
91 1
        return $this->authCredentials;
92
    }
93
    
94
    /**
95
     * Sets Auth credentials.
96
     * @param array $authCredentials
97
     */
98 1
    public function setAuthCredentials($authCredentials)
99
    {
100 1
        if (!is_array($authCredentials) || count($authCredentials)==0) {
101
            throw new \Exception('Auth credentials must be an array');            
102
        }
103
        
104 1
        $this->authCredentials = $authCredentials;
105 1
    }
106
    
107
    /**
108
     * Returns access token information as array.
109
     * @return array
110
     */
111 1
    public function getAccessToken()
112
    {
113 1
        $accessToken = array();
114 1
        if (is_array($this->authCredentials)) {
115 1
            if (isset($this->authCredentials['access_token'])) {
116 1
                $accessToken['access_token'] = $this->authCredentials['access_token'];
117 1
            }
118
            
119 1
            if (isset($this->authCredentials['access_token_timestamp'])) {
120
                $accessToken['access_token_timestamp'] = $this->authCredentials['access_token_timestamp'];
121
            }
122
            
123 1
            if (isset($this->authCredentials['token_type'])) {
124
                $accessToken['token_type'] = $this->authCredentials['token_type'];
125
            }
126
            
127 1
            if (isset($this->authCredentials['expires_in'])) {
128
                $accessToken['expires_in'] = $this->authCredentials['expires_in'];
129
            }
130 1
        }
131
        
132 1
        return $accessToken;
133
    }
134
    
135
    /**
136
     * Returns refresh token.
137
     * @return string
138
     */
139 1
    public function getRefreshToken()
140
    {
141 1
        if (is_array($this->authCredentials)) {
142 1
            if (isset($this->authCredentials['refresh_token'])) {
143
                return $this->authCredentials['refresh_token'];
144
            }
145 1
        }
146
        
147 1
        return null;
148
    }
149
    
150
    /**
151
     * Sends an HTTP request to the given URL and returns response in form of array. 
152
     * @param string $url The URL of Optimizely endpoint (relative, without host and API version).
153
     * @param array $queryParams The list of query parameters.
154
     * @param string $method HTTP method (GET or POST).
155
     * @param array $postData Data send in request body (only for POST method).
156
     * @param array $expectedResponseCodes List of HTTP response codes treated as success.
157
     * @return array Optimizely response in form of array.
158
     * @throws \Exception
159
     */
160 1
    public function sendApiRequest($url, $queryParams = array(), $method='GET', 
161
            $postData = array(), $expectedResponseCodes = array(200))
162
    {
163
        // If access token has expired, try to get another one with refresh token.
164 1
        if ($this->isAccessTokenExpired() && $this->getRefreshToken()!=null) {
165
            $this->getAccessTokenByRefreshToken();
166
        }
167
        
168
        // Produce absolute URL
169 1
        $url = 'https://api.optimizely.com/' . $this->apiVersion . $url;
170
        
171 1
        $result = $this->sendHttpRequest($url, $queryParams, $method, 
172 1
                $postData, $expectedResponseCodes);
173
        
174
        return $result;
175
    }
176
    
177
    /**
178
     * Sends an HTTP request to the given URL and returns response in form of array. 
179
     * @param string $url The URL of Optimizely endpoint.
180
     * @param array $queryParams The list of query parameters.
181
     * @param string $method HTTP method (GET or POST).
182
     * @param array $postData Data send in request body (only for POST method).
183
     * @param array $expectedResponseCodes List of HTTP response codes treated as success.
184
     * @return array Optimizely response in form of array.
185
     * @throws \Exception
186
     */
187 1
    private function sendHttpRequest($url, $queryParams = array(), $method='GET', 
188
            $postData = array(), $expectedResponseCodes = array(200))
189
    {
190
        // Check if CURL is initialized (it should have been initialized in 
191
        // constructor).
192 1
        if ($this->curlHandle==false) {
193
            throw new \Exception('CURL is not initialized', -1);
194
        }
195
        
196 1
        if ($method!='GET' && $method!='POST' && $method!='PUT' && 
197 1
            $method!='PATCH' && $method!='DELETE') {
198
            throw new \Exception('Invalid HTTP method passed: ' . $method, -1);
199
        }
200
        
201 1
        if (!isset($this->authCredentials['access_token'])) {
202
            throw new \Exception('OAuth access token is not set. You should pass ' . 
203
                    'it to the class constructor when initializing the Optimizely client.', -1);
204
        }
205
                
206
        // Append query parameters to URL.
207 1
        if (count($queryParams)!=0) {            
208
            $query = http_build_query($queryParams);
209
            $url .= '?' . $query;
210
        }
211
        
212
        $headers = array(
213 1
            "Authorization: Bearer " . $this->authCredentials['access_token'],
214
            "Content-Type: application/json"
215 1
            );
216 1
        $content = '';
217 1
        if (count($postData)!=0) {
218
            $content = json_encode($postData);            
219
        }
220 1
        $headers[] = "Content-length:" . strlen($content);            
221
        
222
        // Set HTTP options.
223 1
        curl_setopt($this->curlHandle, CURLOPT_URL, $url);
224 1
        curl_setopt($this->curlHandle, CURLOPT_CUSTOMREQUEST, $method);        
225 1
        if (count($postData)!=0) {
226
            curl_setopt($this->curlHandle, CURLOPT_POSTFIELDS, $content);            
227
        }
228 1
        curl_setopt($this->curlHandle, CURLOPT_RETURNTRANSFER, true);
229 1
        curl_setopt($this->curlHandle, CURLOPT_HEADER, false);        
230 1
        curl_setopt($this->curlHandle, CURLOPT_HTTPHEADER, $headers);
231
        
232
        // Execute HTTP request and get response.
233 1
        $result = curl_exec($this->curlHandle);
234 1
        if ($result === false) {
235
            $code = curl_errno($this->curlHandle);
236
            $error = curl_error($this->curlHandle);
237
            throw new \Exception("Failed to send HTTP request $method '$url', the error code was $code, error message was: '$error'", -1);
238
        }        
239
        
240
        // Check HTTP response code.
241 1
        $info = curl_getinfo($this->curlHandle);
242 1
        if (!in_array($info['http_code'], $expectedResponseCodes)) {
243 1
            throw new \Exception('Unexpected HTTP response code: ' . $info['http_code'] . 
244 1
                    '. Request was ' . $method . ' "' . $url . '". Response was "' . $result . '"',
245 1
                    $info['http_code']);
246
        }
247
        
248
        // JSON-decode response.
249
        $decodedResult = json_decode($result, true);
250
        if (!is_array($decodedResult)) {
251
            throw new \Exception('Could not JSON-decode the Optimizely response. Request was ' . 
252
                    $method . ' "' . $url . '". The response was: "' . $result . '"',
253
                    $info['http_code']);
254
        }
255
        
256
        // Return the response in form of array.
257
        return $decodedResult;
258
    }
259
    
260
    /**
261
     * Determines whether the access token has expired or not. Returns true if 
262
     * token has expired; false if token is valid.
263
     * @return boolean
264
     */
265 1
    private function isAccessTokenExpired() 
266
    {
267 1
        if(!isset($this->authCredentials['access_token'])) {
268
            return true; // We do not have access token.
269
        }
270
        
271 1
        if (!isset($this->authCredentials['expires_in']) || 
272 1
            !isset($this->authCredentials['access_token_timestamp'])) {
273 1
            return true; // Assume it has expired, since we can't tell for sure.
274
        } 
275
        
276
        $expiresIn = $this->authCredentials['expires_in'];
277
        $timestamp = $this->authCredentials['access_token_timestamp'];
278
        
279
        if ($timestamp + $expiresIn < time()) {
280
            // Access token has expired.
281
            return true;
282
        }
283
        
284
        // Access token is valid.
285
        return false;
286
    }
287
    
288
    /**
289
     * This method retrieves the access token by refresh token.
290
     * @return array
291
     */
292
    private function getAccessTokenByRefreshToken()
293
    {
294
        if (!isset($this->authCredentials['client_id']))
295
            throw new \Exception('OAuth 2.0 client ID is not set');
296
        
297
        if (!isset($this->authCredentials['client_secret']))
298
            throw new \Exception('OAuth 2.0 client secret is not set');
299
        
300
        if (!isset($this->authCredentials['refresh_token']))
301
            throw new \Exception('Refresh token is not set');
302
        
303
        $clientId = $this->authCredentials['client_id'];
304
        $clientSecret = $this->authCredentials['client_secret'];
305
        $refreshToken = $this->authCredentials['refresh_token'];
306
        
307
        $url = "https://app.optimizely.com/oauth2/token?refresh_token=$refreshToken&client_id=$clientId&client_secret=$clientSecret&grant_type=refresh_token";
308
        
309
        $response = $this->sendHttpRequest($url, array(), 'POST');
310
        
311
        if (!isset($response['access_token'])) {
312
            throw new \Exception('Not found access token in response. Request URL was "' . $url. '". Response was "' . print_r($response, true). '"');
313
        }
314
        
315
        $this->authCredentials['access_token'] = $response['access_token'];
316
        $this->authCredentials['token_type'] = $response['token_type'];
317
        $this->authCredentials['expires_in'] = $response['expires_in'];
318
        $this->authCredentials['access_token_timestamp'] = time(); 
319
    }
320
    
321
    /**
322
     * Provides access to API services (experiments, campaigns, etc.)
323
     * @method Audiences audiences()
324
     * @method Campaigns campaigns()
325
     * @method Events events()
326
     * @method Experiment experiments()
327
     * @method Pages pages()
328
     * @method Projects projects()
329
     */
330 1
    public function __call($name, $arguments)
331
    {
332
        $allowedServiceNames = array(
333 1
            'audiences',
334 1
            'campaigns',
335 1
            'events',
336 1
            'experiments',
337 1
            'pages',
338
            'projects'
339 1
        );
340
        
341
        // Check if the service name is valid
342 1
        if (!in_array($name, $allowedServiceNames)) {
343
            throw new \Exception("Unexpected service name: $name");
344
        }
345
        
346
        // Check if such service already instantiated
347 1
        if (isset($this->services[$this->apiVersion][$name])) {
348
            $service = $this->services[$this->apiVersion][$name];
349
        } else {
350
            // Instantiate the service
351 1
            $apiVersion = $this->apiVersion;
352 1
            $serviceName = ucwords($name);
353 1
            $className = "\\WebMarketingROI\\OptimizelyPHP\\Service\\$apiVersion\\$serviceName";
354 1
            $service = new $className($this); 
355 1
            $this->services[$apiVersion][$name] = $service;
356
        }
357
358 1
        return $service;
359
    }
360
}