Completed
Push — master ( a2bf78...041ef5 )
by Drew
9s
created

MailChimp::formatResponse()   C

Complexity

Conditions 8
Paths 4

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 2 Features 1
Metric Value
c 5
b 2
f 1
dl 0
loc 22
rs 6.6037
cc 8
eloc 13
nc 4
nop 1
1
<?php
2
3
namespace DrewM\MailChimp;
4
5
/**
6
 * Super-simple, minimum abstraction MailChimp API v3 wrapper
7
 * MailChimp API v3: http://developer.mailchimp.com
8
 * This wrapper: https://github.com/drewm/mailchimp-api
9
 *
10
 * @author Drew McLellan <[email protected]>
11
 * @version 2.2
12
 */
13
class MailChimp
14
{
15
    private $api_key;
16
    private $api_endpoint = 'https://<dc>.api.mailchimp.com/3.0';
17
18
    /*  SSL Verification
19
        Read before disabling:
20
        http://snippets.webaware.com.au/howto/stop-turning-off-curlopt_ssl_verifypeer-and-fix-your-php-config/
21
    */
22
    public $verify_ssl = true;
23
24
    private $request_successful = false;
25
    private $last_error         = '';
26
    private $last_response      = array();
27
    private $last_request       = array();
28
29
    /**
30
     * Create a new instance
31
     * @param string $api_key Your MailChimp API key
32
     * @throws \Exception
33
     */
34
    public function __construct($api_key)
35
    {
36
        $this->api_key = $api_key;
37
38
        if (strpos($this->api_key, '-') === false) {
39
            throw new \Exception('Invalid MailChimp API key supplied.');
40
        }
41
42
        list(, $data_center) = explode('-', $this->api_key);
43
        $this->api_endpoint  = str_replace('<dc>', $data_center, $this->api_endpoint);
44
45
        $this->last_response = array('headers' => null, 'body' => null);
46
    }
47
48
    /**
49
     * Create a new instance of a Batch request. Optionally with the ID of an existing batch.
50
     * @param string $batch_id Optional ID of an existing batch, if you need to check its status for example.
51
     * @return Batch            New Batch object.
52
     */
53
    public function new_batch($batch_id = null)
54
    {
55
        return new Batch($this, $batch_id);
56
    }
57
58
    /**
59
     * Convert an email address into a 'subscriber hash' for identifying the subscriber in a method URL
60
     * @param   string $email The subscriber's email address
61
     * @return  string          Hashed version of the input
62
     */
63
    public function subscriberHash($email)
64
    {
65
        return md5(strtolower($email));
66
    }
67
68
    /**
69
     * Was the last request successful?
70
     * @return bool  True for success, false for failure
71
     */
72
    public function success()
73
    {
74
        return $this->request_successful;
75
    }
76
77
    /**
78
     * Get the last error returned by either the network transport, or by the API.
79
     * If something didn't work, this should contain the string describing the problem.
80
     * @return  array|false  describing the error
81
     */
82
    public function getLastError()
83
    {
84
        return $this->last_error ?: false;
85
    }
86
87
    /**
88
     * Get an array containing the HTTP headers and the body of the API response.
89
     * @return array  Assoc array with keys 'headers' and 'body'
90
     */
91
    public function getLastResponse()
92
    {
93
        return $this->last_response;
94
    }
95
96
    /**
97
     * Get an array containing the HTTP headers and the body of the API request.
98
     * @return array  Assoc array
99
     */
100
    public function getLastRequest()
101
    {
102
        return $this->last_request;
103
    }
104
105
    /**
106
     * Make an HTTP DELETE request - for deleting data
107
     * @param   string $method URL of the API request method
108
     * @param   array $args Assoc array of arguments (if any)
109
     * @param   int $timeout Timeout limit for request in seconds
110
     * @return  array|false   Assoc array of API response, decoded from JSON
111
     */
112
    public function delete($method, $args = array(), $timeout = 10)
113
    {
114
        return $this->makeRequest('delete', $method, $args, $timeout);
115
    }
116
117
    /**
118
     * Make an HTTP GET request - for retrieving data
119
     * @param   string $method URL of the API request method
120
     * @param   array $args Assoc array of arguments (usually your data)
121
     * @param   int $timeout Timeout limit for request in seconds
122
     * @return  array|false   Assoc array of API response, decoded from JSON
123
     */
124
    public function get($method, $args = array(), $timeout = 10)
125
    {
126
        return $this->makeRequest('get', $method, $args, $timeout);
127
    }
128
129
    /**
130
     * Make an HTTP PATCH request - for performing partial updates
131
     * @param   string $method URL of the API request method
132
     * @param   array $args Assoc array of arguments (usually your data)
133
     * @param   int $timeout Timeout limit for request in seconds
134
     * @return  array|false   Assoc array of API response, decoded from JSON
135
     */
136
    public function patch($method, $args = array(), $timeout = 10)
137
    {
138
        return $this->makeRequest('patch', $method, $args, $timeout);
139
    }
140
141
    /**
142
     * Make an HTTP POST request - for creating and updating items
143
     * @param   string $method URL of the API request method
144
     * @param   array $args Assoc array of arguments (usually your data)
145
     * @param   int $timeout Timeout limit for request in seconds
146
     * @return  array|false   Assoc array of API response, decoded from JSON
147
     */
148
    public function post($method, $args = array(), $timeout = 10)
149
    {
150
        return $this->makeRequest('post', $method, $args, $timeout);
151
    }
152
153
    /**
154
     * Make an HTTP PUT request - for creating new items
155
     * @param   string $method URL of the API request method
156
     * @param   array $args Assoc array of arguments (usually your data)
157
     * @param   int $timeout Timeout limit for request in seconds
158
     * @return  array|false   Assoc array of API response, decoded from JSON
159
     */
160
    public function put($method, $args = array(), $timeout = 10)
161
    {
162
        return $this->makeRequest('put', $method, $args, $timeout);
163
    }
164
165
    /**
166
     * Performs the underlying HTTP request. Not very exciting.
167
     * @param  string $http_verb The HTTP verb to use: get, post, put, patch, delete
168
     * @param  string $method The API method to be called
169
     * @param  array $args Assoc array of parameters to be passed
170
     * @param int $timeout
171
     * @return array|false Assoc array of decoded result
172
     * @throws \Exception
173
     */
174
    private function makeRequest($http_verb, $method, $args = array(), $timeout = 10)
175
    {
176
        if (!function_exists('curl_init') || !function_exists('curl_setopt')) {
177
            throw new \Exception("cURL support is required, but can't be found.");
178
        }
179
180
        $url = $this->api_endpoint . '/' . $method;
181
182
        $this->last_error         = '';
183
        $this->request_successful = false;
184
        $response                 = array('headers' => null, 'body' => null);
185
        $this->last_response      = $response;
186
187
        $this->last_request = array(
188
            'method'  => $http_verb,
189
            'path'    => $method,
190
            'url'     => $url,
191
            'body'    => '',
192
            'timeout' => $timeout,
193
        );
194
195
        $ch = curl_init();
196
        curl_setopt($ch, CURLOPT_URL, $url);
197
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
198
            'Accept: application/vnd.api+json',
199
            'Content-Type: application/vnd.api+json',
200
            'Authorization: apikey ' . $this->api_key
201
        ));
202
        curl_setopt($ch, CURLOPT_USERAGENT, 'DrewM/MailChimp-API/3.0 (github.com/drewm/mailchimp-api)');
203
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
204
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
205
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->verify_ssl);
206
        curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
207
        curl_setopt($ch, CURLOPT_ENCODING, '');
208
        curl_setopt($ch, CURLINFO_HEADER_OUT, true);
209
210
        switch ($http_verb) {
211
            case 'post':
212
                curl_setopt($ch, CURLOPT_POST, true);
213
                $this->attachRequestPayload($ch, $args);
214
                break;
215
216
            case 'get':
217
                $query = http_build_query($args);
218
                curl_setopt($ch, CURLOPT_URL, $url . '?' . $query);
219
                break;
220
221
            case 'delete':
222
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
223
                break;
224
225
            case 'patch':
226
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');
227
                $this->attachRequestPayload($ch, $args);
228
                break;
229
230
            case 'put':
231
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
232
                $this->attachRequestPayload($ch, $args);
233
                break;
234
        }
235
236
        $response['body']    = curl_exec($ch);
237
        $response['headers'] = curl_getinfo($ch);
238
239
        if (isset($response['headers']['request_header'])) {
240
            $this->last_request['headers'] = $response['headers']['request_header'];
241
        }
242
243
        if ($response['body'] === false) {
244
            $this->last_error = curl_error($ch);
245
        }
246
247
        curl_close($ch);
248
249
        return $this->formatResponse($response);
250
    }
251
252
    /**
253
     * Encode the data and attach it to the request
254
     * @param   resource $ch cURL session handle, used by reference
255
     * @param   array $data Assoc array of data to attach
256
     */
257
    private function attachRequestPayload(&$ch, $data)
258
    {
259
        $encoded = json_encode($data);
260
        $this->last_request['body'] = $encoded;
261
        curl_setopt($ch, CURLOPT_POSTFIELDS, $encoded);
262
    }
263
264
    /**
265
     * Decode the response and format any error messages for debugging
266
     * @param array $response The response from the curl request
267
     * @return array|false     The JSON decoded into an array
268
     */
269
    private function formatResponse($response)
270
    {
271
        $this->last_response = $response;
272
273
        if (!empty($response['body'])) {
274
275
            $d = json_decode($response['body'], true);
276
277
            if (isset($d['status']) && $d['status'] != '200' && isset($d['detail'])) {
278
                $this->last_error = sprintf('%d: %s', $d['status'], $d['detail']);
279
            } else {
280
                $this->request_successful = true;
281
            }
282
283
            return $d;
284
        } else if (!empty($response['headers']) && isset($response['headers']['http_code']) && ($response['headers']['http_code'] == 204)) {
285
            $this->request_successful = true;
286
            return true;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return true; (boolean) is incompatible with the return type documented by DrewM\MailChimp\MailChimp::formatResponse of type array|false.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
287
        }
288
289
        return false;
290
    }
291
}
292