Completed
Pull Request — master (#30)
by Drew
06:57
created

AbstractApi   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 382
Duplicated Lines 11.26 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 35.7%

Importance

Changes 6
Bugs 3 Features 0
Metric Value
wmc 43
c 6
b 3
f 0
lcom 1
cbo 6
dl 43
loc 382
ccs 40
cts 112
cp 0.357
rs 8.3157

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
B __call() 0 15 5
A getFields() 0 4 1
A getField() 0 10 3
A get() 0 6 1
A head() 0 8 1
A post() 0 8 1
A postRaw() 0 10 1
A patch() 12 12 1
A put() 0 18 4
A delete() 10 10 1
B createParametersBody() 0 20 9
A getPath() 0 8 2
A validateRequiredParameters() 8 8 3
A validateAllowedParameters() 0 19 4
A validateAtLeastOneOf() 13 13 3
A removeExcessParameters() 0 9 2

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 AbstractApi 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 AbstractApi, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Trello\Api;
4
5
use Trello\Client;
6
use Trello\HttpClient\Message\ResponseMediator;
7
use Trello\Exception\InvalidArgumentException;
8
use Trello\Exception\BadMethodCallException;
9
use Trello\Exception\MissingArgumentException;
10
use \DateTime;
11
12
/**
13
 * Abstract class for Api classes
14
 *
15
 * @author Christian Daguerre <[email protected]>
16
 * @author Joseph Bielawski <[email protected]>
17
 */
18
abstract class AbstractApi implements ApiInterface
19
{
20
    /**
21
     * API sub namespace
22
     *
23
     * @var string
24
     */
25
    protected $path;
26
27
    /**
28
     * The client
29
     *
30
     * @var Client
31
     */
32
    protected $client;
33
34
    /**
35
     * @var array
36
     */
37
    public static $fields;
38
39
    /**
40
     * These are values that are being passed to Trello in our requests, that are not needed,
41
     *  and are causing Trello to give a "Error parsing body: too many parameters" error.
42 334
     * 
43
     * @var array
44 334
     */
45 334
    protected $ignore_on_update_fields = [
46
        'actions',
47
        'board',
48
        'list',
49
        'attachments',
50
        'idLabels',
51
        'badges',
52
        'checklists',
53
        'stickers',
54
        'labels',
55
        'idChecklists',
56
        'members',
57
        'membersVoted',
58
        'descData',
59
    ];
60
61
    /**
62
     * @param Client $client
63
     */
64
    public function __construct(Client $client)
65
    {
66
        $this->client = $client;
67
    }
68
69
    /**
70
     * Catches any undefined "get{$field}" calls, and passes them
71
     * to the getField() if the $field is in the $this->fields property
72
     *
73
     * @param string $method    called method
74
     * @param array  $arguments array of arguments passed to called method
75
     *
76
     * @return array
77
     *
78
     * @throws BadMethodCallException If the method does not start with "get"
79
     *                                or the field is not included in the $fields property
80
     */
81
    public function __call($method, $arguments)
82
    {
83
        if (isset($this->fields) && substr($method, 0, 3) === 'get') {
84
            $property = lcfirst(substr($method, 3));
85
            if (in_array($property, $this->fields) && count($arguments) === 2) {
86
                return $this->getField($arguments[0], $arguments[1]);
87
            }
88
        }
89
90
        throw new BadMethodCallException(sprintf(
91
            'There is no method named "%s" in class "%s".',
92
            $method,
93
            get_called_class()
94
        ));
95 5
    }
96
97 5
    /**
98 2
     * Get field names (properties)
99
     *
100
     * @return array array of fields
101 3
     */
102
    public function getFields()
103 3
    {
104
        return static::$fields;
105
    }
106
107
    /**
108
     * Get a field value by field name
109
     *
110
     * @param string $id    the board's id
111
     * @param string $field the field
112
     *
113
     * @return mixed field value
114
     *
115
     * @throws InvalidArgumentException If the field does not exist
116
     */
117
    public function getField($id, $field)
118
    {
119
        if (!in_array($field, static::$fields)) {
120
            throw new InvalidArgumentException(sprintf('There is no field named %s.', $field));
121
        }
122
123
        $response = $this->get($this->path.'/'.rawurlencode($id).'/'.rawurlencode($field));
124
125
        return isset($response['_value']) ? $response['_value'] : $response;
126
    }
127
128
    /**
129
     * Send a GET request with query parameters.
130
     *
131
     * @param string $path           Request path.
132
     * @param array  $parameters     GET parameters.
133
     * @param array  $requestHeaders Request Headers.
134
     *
135
     * @return \Guzzle\Http\EntityBodyInterface|mixed|string
136
     */
137
    protected function get($path, array $parameters = array(), $requestHeaders = array())
138
    {
139
        $response = $this->client->getHttpClient()->get($path, $parameters, $requestHeaders);
140
141
        return ResponseMediator::getContent($response);
142
    }
143
144
    /**
145
     * Send a HEAD request with query parameters
146
     *
147
     * @param string $path           Request path.
148
     * @param array  $parameters     HEAD parameters.
149
     * @param array  $requestHeaders Request headers.
150
     *
151
     * @return \Guzzle\Http\Message\Response
152
     */
153
    protected function head($path, array $parameters = array(), $requestHeaders = array())
154
    {
155
        $response = $this->client->getHttpClient()->request($path, null, 'HEAD', $requestHeaders, array(
156
            'query' => $parameters,
157
        ));
158
159
        return $response;
160
    }
161
162
    /**
163
     * Send a POST request with JSON-encoded parameters.
164
     *
165
     * @param string $path           Request path.
166
     * @param array  $parameters     POST parameters to be JSON encoded.
167
     * @param array  $requestHeaders Request headers.
168
     *
169
     * @return mixed
170
     */
171
    protected function post($path, array $parameters = array(), $requestHeaders = array())
172
    {
173
        return $this->postRaw(
174
            $path,
175
            $this->createParametersBody($parameters),
176
            $requestHeaders
177
        );
178
    }
179
180
    /**
181
     * Send a POST request with raw data.
182
     *
183
     * @param string $path           Request path.
184
     * @param mixed  $body           Request body.
185
     * @param array  $requestHeaders Request headers.
186
     *
187
     * @return \Guzzle\Http\EntityBodyInterface|mixed|string
188
     */
189
    protected function postRaw($path, $body, $requestHeaders = array())
190
    {
191
        $response = $this->client->getHttpClient()->post(
192
            $path,
193
            $body,
194
            $requestHeaders
195
        );
196
197
        return ResponseMediator::getContent($response);
198
    }
199
200
    /**
201
     * Send a PATCH request with JSON-encoded parameters.
202
     *
203
     * @param string $path           Request path.
204
     * @param array  $parameters     POST parameters to be JSON encoded.
205
     * @param array  $requestHeaders Request headers.
206
     *
207
     * @return mixed
208
     */
209 View Code Duplication
    protected function patch($path, array $parameters = array(), $requestHeaders = array())
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...
210
    {
211
        $parameters = $this->removeExcessParameters($parameters);
212
213
        $response = $this->client->getHttpClient()->patch(
214
            $path,
215
            $this->createParametersBody($parameters),
216
            $requestHeaders
217
        );
218
219
        return ResponseMediator::getContent($response);
220
    }
221
222
    /**
223
     * Send a PUT request with JSON-encoded parameters.
224
     *
225
     * @param string $path           Request path.
226
     * @param array  $parameters     POST parameters to be JSON encoded.
227
     * @param array  $requestHeaders Request headers.
228
     *
229
     * @return mixed
230
     */
231
    protected function put($path, array $parameters = array(), $requestHeaders = array())
232
    {
233
        foreach ($parameters as $name => $parameter) {
234
            if (is_bool($parameter)) {
235
                $parameters[$name] = $parameter ? 'true' : 'false';
236
            }
237
        }
238
239
        $parameters = $this->removeExcessParameters($parameters);
240
        
241
        $response = $this->client->getHttpClient()->put(
242
            $path,
243
            $this->createParametersBody($parameters),
244
            $requestHeaders
245
        );
246
247
        return ResponseMediator::getContent($response);
248
    }
249
250
    /**
251
     * Send a DELETE request with JSON-encoded parameters.
252
     *
253
     * @param string $path           Request path.
254
     * @param array  $parameters     POST parameters to be JSON encoded.
255
     * @param array  $requestHeaders Request headers.
256
     *
257
     * @return mixed
258
     */
259 View Code Duplication
    protected function delete($path, array $parameters = array(), $requestHeaders = array())
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...
260
    {
261
        $response = $this->client->getHttpClient()->delete(
262
            $path,
263
            $this->createParametersBody($parameters),
264
            $requestHeaders
265
        );
266
267
        return ResponseMediator::getContent($response);
268
    }
269
270
    /**
271
     * Prepare request parameters.
272 191
     *
273
     * @param array $parameters Request parameters
274 191
     *
275 91
     * @return null|string
276
     */
277
    protected function createParametersBody(array $parameters)
278 100
    {
279
        foreach ($parameters as $name => $parameter) {
280
            if (is_bool($parameter)) {
281
                $parameters[$name] = $parameter ? 'true' : 'false';
282
            } elseif (is_array($parameter)) {
283
                foreach ($parameter as $subName => $subParameter) {
284
                    if (is_bool($subParameter)) {
285
                        $subParameter = $subParameter ? 'true' : 'false';
286
                    }
287
                    $parameters[$name.'/'.$subName] = $subParameter;
288
                }
289 30
                unset($parameters[$name]);
290
            } elseif ($parameter instanceof DateTime) {
291 30
                $parameters[$name] = $parameter->format($parameter::ATOM);
292 30
            }
293 18
        }
294
295 15
        return $parameters;
296 12
    }
297
298
    protected function getPath($id = null)
299
    {
300
        if ($id) {
301
            return preg_replace('/\#id\#/', $id, $this->path);
302
        }
303
304
        return $this->path;
305
    }
306
307
    /**
308
     * Validate parameters array
309
     *
310 70
     * @param string[] $required required properties (array keys)
311
     * @param array $params   array to check for existence of the required keys
312 70
     *
313 61
     * @throws MissingArgumentException if a required parameter is missing
314 61
     */
315 View Code Duplication
    protected function validateRequiredParameters(array $required, array $params)
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...
316 70
    {
317 70
        foreach ($required as $param) {
318 21
            if (!isset($params[$param])) {
319 21
                throw new MissingArgumentException(sprintf('The "%s" parameter is required.', $param));
320 21
            }
321 21
        }
322
    }
323 21
324
    /**
325 49
     * Validate allowed parameters array
326
     * Checks whether the passed parameters are allowed
327 49
     *
328
     * @param string[]        $allowed allowed properties
329
     * @param array|string $params  array to check
330
     * @param string $paramName
331
     *
332
     * @return array array of validated parameters
333
     *
334
     * @throws InvalidArgumentException if a parameter is not allowed
335
     */
336
    protected function validateAllowedParameters(array $allowed, $params, $paramName)
337
    {
338
        if (!is_array($params)) {
339
            $params = array($params);
340
        }
341 6
342
        foreach ($params as $param) {
343 6
            if (!in_array($param, $allowed)) {
344 6
                throw new InvalidArgumentException(sprintf(
345 3
                    'The "%s" parameter may contain only values within "%s". "%s" given.',
346
                    $paramName,
347 4
                    implode(", ", $allowed),
348
                    $param
349 3
                ));
350 3
            }
351 3
        }
352 3
353
        return $params;
354
    }
355
356
    /**
357
     * Validate that the params array includes at least one of
358
     * the keys in a given array
359
     *
360
     * @param string[] $atLeastOneOf allowed properties
361
     * @param array $params       array to check
362
     *
363
     * @return boolean
364
     *
365
     * @throws MissingArgumentException
366
     */
367 View Code Duplication
    protected function validateAtLeastOneOf(array $atLeastOneOf, array $params)
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...
368
    {
369
        foreach ($atLeastOneOf as $param) {
370
            if (isset($params[$param])) {
371
                return true;
372
            }
373
        }
374
375
        throw new MissingArgumentException(sprintf(
376
            'You need to provide at least one of the following parameters "%s".',
377
            implode('", "', $atLeastOneOf)
378
        ));
379
    }
380
381
    /**
382
     * Remove our fields that don't need to be passed to Trello.
383
     * This prevents trello from throwing a "Error parsing body: too many parameters" error
384
     * on requests with a card that has a lot of content.
385
     * 
386
     * @param array $parameters
387
     *
388
     * @return array
389
     */
390
    protected function removeExcessParameters($parameters)
391
    {
392
        // Remove our fields that don't need to be passed to Trello.
393
        foreach($this->ignore_on_update_fields as $ignored_field) {
394
            unset($parameters[$ignored_field]);
395
        }
396
        
397
        return $parameters;
398
    }
399
}
400