Completed
Pull Request — 23 (#431)
by Harald
10:27
created

Util   C

Complexity

Total Complexity 58

Size/Duplication

Total Lines 402
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 9

Importance

Changes 0
Metric Value
dl 0
loc 402
rs 6.3005
c 0
b 0
f 0
wmc 58
lcom 2
cbo 9

17 Methods

Rating   Name   Duplication   Size   Complexity  
A extractAttributeAsArray() 0 20 3
C throwStatusCodeException() 0 29 8
A returnObjectOrThrowException() 0 9 2
A cleanClassName() 0 63 1
A buildClassName() 0 18 1
A delimiterToCamelCase() 0 12 2
A delimiterToUnderscore() 0 4 1
A camelCaseToDelimiter() 0 4 1
B delimiterToCamelCaseArray() 0 21 5
A camelCaseToDelimiterArray() 0 14 4
A delimiterToUnderscoreArray() 0 9 2
A implodeAssociativeArray() 0 13 4
A attributesToString() 0 14 4
A verifyKeys() 0 13 2
B _flattenArray() 0 18 5
C _flattenUserKeys() 0 22 8
B _removeWildcardKeys() 0 14 5

How to fix   Complexity   

Complex Class

Complex classes like Util 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 Util, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace Braintree;
3
4
use DateTime;
5
use InvalidArgumentException;
6
7
/**
8
 * Braintree Utility methods
9
 * PHP version 5
10
 *
11
 * @copyright  2015 Braintree, a division of PayPal, Inc.
12
 */
13
14
class Util
15
{
16
    /**
17
     * extracts an attribute and returns an array of objects
18
     *
19
     * extracts the requested element from an array, and converts the contents
20
     * of its child arrays to objects of type $attributeName, or returns
21
     * an array with a single element containing the value of that array element
22
     *
23
     * @param array  $attribArray   attributes from a search response
24
     * @param string $attributeName indicates which element of the passed array to extract
25
     * @return array array of $attributeName objects, or a single element array
26
     */
27
    public static function extractAttributeAsArray(&$attribArray, $attributeName)
28
    {
29
        if(!isset($attribArray[$attributeName])):
30
            return [];
31
        endif;
32
33
        // get what should be an array from the passed array
34
        $data = $attribArray[$attributeName];
35
        // set up the class that will be used to convert each array element
36
        $classFactory = self::buildClassName($attributeName) . '::factory';
37
        if(is_array($data)):
38
            // create an object from the data in each element
39
            $objectArray = array_map($classFactory, $data);
40
        else:
41
            return [$data];
42
        endif;
43
44
        unset($attribArray[$attributeName]);
45
        return $objectArray;
46
    }
47
    /**
48
     * throws an exception based on the type of error
49
     * @param string $statusCode HTTP status code to throw exception from
50
     * @param null|string $message
51
     * @throws Exception multiple types depending on the error
52
     * @return void
53
     */
54
    public static function throwStatusCodeException($statusCode, $message=null)
55
    {
56
        switch($statusCode) {
57
        case 401:
58
            throw new Exception\Authentication();
59
            break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
60
        case 403:
61
            throw new Exception\Authorization($message);
62
            break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
63
        case 404:
64
            throw new Exception\NotFound();
65
            break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
66
        case 426:
67
            throw new Exception\UpgradeRequired();
68
            break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
69
        case 429:
70
            throw new Exception\TooManyRequests();
71
            break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
72
        case 500:
73
            throw new Exception\ServerError();
74
            break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
75
        case 503:
76
            throw new Exception\DownForMaintenance();
77
            break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
78
        default:
79
            throw new Exception\Unexpected('Unexpected HTTP_RESPONSE #' . $statusCode);
80
            break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
81
        }
82
    }
83
84
    /**
85
     *
86
     * @param string $className
87
     * @param object $resultObj
88
     * @return object returns the passed object if successful
89
     * @throws Exception\ValidationsFailed
90
     */
91
    public static function returnObjectOrThrowException($className, $resultObj)
92
    {
93
        $resultObjName = self::cleanClassName($className);
94
        if ($resultObj->success) {
95
            return $resultObj->$resultObjName;
96
        } else {
97
            throw new Exception\ValidationsFailed();
98
        }
99
    }
100
101
    /**
102
     * removes the  header from a classname
103
     *
104
     * @param string $name ClassName
105
     * @return camelCased classname minus  header
106
     */
107
    public static function cleanClassName($name)
108
    {
109
        $classNamesToResponseKeys = [
110
            'Braintree\CreditCard' => 'creditCard',
111
            'Braintree_CreditCard' => 'creditCard',
112
            'Braintree\CreditCardGateway' => 'creditCard',
113
            'Braintree_CreditCardGateway' => 'creditCard',
114
            'Braintree\Customer' => 'customer',
115
            'Braintree_Customer' => 'customer',
116
            'Braintree\CustomerGateway' => 'customer',
117
            'Braintree_CustomerGateway' => 'customer',
118
            'Braintree\Subscription' => 'subscription',
119
            'Braintree_Subscription' => 'subscription',
120
            'Braintree\SubscriptionGateway' => 'subscription',
121
            'Braintree_SubscriptionGateway' => 'subscription',
122
            'Braintree\Transaction' => 'transaction',
123
            'Braintree_Transaction' => 'transaction',
124
            'Braintree\TransactionGateway' => 'transaction',
125
            'Braintree_TransactionGateway' => 'transaction',
126
            'Braintree\CreditCardVerification' => 'verification',
127
            'Braintree_CreditCardVerification' => 'verification',
128
            'Braintree\CreditCardVerificationGateway' => 'verification',
129
            'Braintree_CreditCardVerificationGateway' => 'verification',
130
            'Braintree\AddOn' => 'addOn',
131
            'Braintree_AddOn' => 'addOn',
132
            'Braintree\AddOnGateway' => 'addOn',
133
            'Braintree_AddOnGateway' => 'addOn',
134
            'Braintree\Discount' => 'discount',
135
            'Braintree_Discount' => 'discount',
136
            'Braintree\DiscountGateway' => 'discount',
137
            'Braintree_DiscountGateway' => 'discount',
138
            'Braintree\Plan' => 'plan',
139
            'Braintree_Plan' => 'plan',
140
            'Braintree\PlanGateway' => 'plan',
141
            'Braintree_PlanGateway' => 'plan',
142
            'Braintree\Address' => 'address',
143
            'Braintree_Address' => 'address',
144
            'Braintree\AddressGateway' => 'address',
145
            'Braintree_AddressGateway' => 'address',
146
            'Braintree\SettlementBatchSummary' => 'settlementBatchSummary',
147
            'Braintree_SettlementBatchSummary' => 'settlementBatchSummary',
148
            'Braintree\SettlementBatchSummaryGateway' => 'settlementBatchSummary',
149
            'Braintree_SettlementBatchSummaryGateway' => 'settlementBatchSummary',
150
            'Braintree\Merchant' => 'merchant',
151
            'Braintree_Merchant' => 'merchant',
152
            'Braintree\MerchantGateway' => 'merchant',
153
            'Braintree_MerchantGateway' => 'merchant',
154
            'Braintree\MerchantAccount' => 'merchantAccount',
155
            'Braintree_MerchantAccount' => 'merchantAccount',
156
            'Braintree\MerchantAccountGateway' => 'merchantAccount',
157
            'Braintree_MerchantAccountGateway' => 'merchantAccount',
158
            'Braintree\OAuthCredentials' => 'credentials',
159
            'Braintree_OAuthCredentials' => 'credentials',
160
            'Braintree\OAuthResult' => 'result',
161
            'Braintree_OAuthResult' => 'result',
162
            'Braintree\PayPalAccount' => 'paypalAccount',
163
            'Braintree_PayPalAccount' => 'paypalAccount',
164
            'Braintree\PayPalAccountGateway' => 'paypalAccount',
165
            'Braintree_PayPalAccountGateway' => 'paypalAccount',
166
        ];
167
168
        return $classNamesToResponseKeys[$name];
169
    }
170
171
    /**
172
     *
173
     * @param string $name className
174
     * @return string ClassName
175
     */
176
    public static function buildClassName($name)
177
    {
178
        $responseKeysToClassNames = [
179
            'creditCard' => 'Braintree\CreditCard',
180
            'customer' => 'Braintree\Customer',
181
            'subscription' => 'Braintree\Subscription',
182
            'transaction' => 'Braintree\Transaction',
183
            'verification' => 'Braintree\CreditCardVerification',
184
            'addOn' => 'Braintree\AddOn',
185
            'discount' => 'Braintree\Discount',
186
            'plan' => 'Braintree\Plan',
187
            'address' => 'Braintree\Address',
188
            'settlementBatchSummary' => 'Braintree\SettlementBatchSummary',
189
            'merchantAccount' => 'Braintree\MerchantAccount',
190
        ];
191
192
        return (string) $responseKeysToClassNames[$name];
193
    }
194
195
    /**
196
     * convert alpha-beta-gamma to alphaBetaGamma
197
     *
198
     * @access public
199
     * @param string $string
200
     * @param null|string $delimiter
201
     * @return string modified string
202
     */
203
    public static function delimiterToCamelCase($string, $delimiter = '[\-\_]')
204
    {
205
        // php doesn't garbage collect functions created by create_function()
206
        // so use a static variable to avoid adding a new function to memory
207
        // every time this function is called.
208
        static $callback = null;
209
        if ($callback === null) {
210
            $callback = create_function('$matches', 'return strtoupper($matches[1]);');
211
        }
212
213
        return preg_replace_callback('/' . $delimiter . '(\w)/', $callback, $string);
214
    }
215
216
    /**
217
     * convert alpha-beta-gamma to alpha_beta_gamma
218
     *
219
     * @access public
220
     * @param string $string
221
     * @return string modified string
222
     */
223
    public static function delimiterToUnderscore($string)
224
    {
225
        return preg_replace('/-/', '_', $string);
226
    }
227
228
229
    /**
230
     * find capitals and convert to delimiter + lowercase
231
     *
232
     * @access public
233
     * @param string $string
234
     * @param null|string $delimiter
235
     * @return string modified string
236
     */
237
    public static function camelCaseToDelimiter($string, $delimiter = '-')
238
    {
239
        return strtolower(preg_replace('/([A-Z])/', "$delimiter\\1", $string));
240
    }
241
242
    public static function delimiterToCamelCaseArray($array, $delimiter = '[\-\_]')
243
    {
244
        $converted = [];
245
        foreach ($array as $key => $value) {
246
            if (is_string($key)) {
247
                $key = self::delimiterToCamelCase($key, $delimiter);
248
            }
249
250
            if (is_array($value)) {
251
                // Make an exception for custom fields, which must be underscore (can't be
252
                // camelCase).
253
                if ($key === 'customFields') {
254
                    $value = self::delimiterToUnderscoreArray($value);
255
                } else {
256
                    $value = self::delimiterToCamelCaseArray($value, $delimiter);
257
                }
258
            }
259
            $converted[$key] = $value;
260
        }
261
        return $converted;
262
    }
263
264
    public static function camelCaseToDelimiterArray($array, $delimiter = '-')
265
    {
266
        $converted = [];
267
        foreach ($array as $key => $value) {
268
            if (is_string($key)) {
269
                $key = self::camelCaseToDelimiter($key, $delimiter);
270
            }
271
            if (is_array($value)) {
272
                $value = self::camelCaseToDelimiterArray($value, $delimiter);
273
            }
274
            $converted[$key] = $value;
275
        }
276
        return $converted;
277
    }
278
279
    public static function delimiterToUnderscoreArray($array)
280
    {
281
        $converted = [];
282
        foreach ($array as $key => $value) {
283
            $key = self::delimiterToUnderscore($key);
284
            $converted[$key] = $value;
285
        }
286
        return $converted;
287
    }
288
289
    /**
290
     *
291
     * @param array $array associative array to implode
292
     * @param string $separator (optional, defaults to =)
293
     * @param string $glue (optional, defaults to ', ')
294
     * @return bool
295
     */
296
    public static function implodeAssociativeArray($array, $separator = '=', $glue = ', ')
297
    {
298
        // build a new array with joined keys and values
299
        $tmpArray = null;
300
        foreach ($array AS $key => $value) {
301
            if ($value instanceof DateTime) {
302
                $value = $value->format('r');
303
            }
304
            $tmpArray[] = $key . $separator . $value;
305
        }
306
        // implode and return the new array
307
        return (is_array($tmpArray)) ? implode($glue, $tmpArray) : false;
308
    }
309
310
    public static function attributesToString($attributes) {
311
        $printableAttribs = [];
312
        foreach ($attributes AS $key => $value) {
313
            if (is_array($value)) {
314
                $pAttrib = self::attributesToString($value);
315
            } else if ($value instanceof DateTime) {
316
                $pAttrib = $value->format(DateTime::RFC850);
317
            } else {
318
                $pAttrib = $value;
319
            }
320
            $printableAttribs[$key] = sprintf('%s', $pAttrib);
321
        }
322
        return self::implodeAssociativeArray($printableAttribs);
323
    }
324
325
    /**
326
     * verify user request structure
327
     *
328
     * compares the expected signature of a gateway request
329
     * against the actual structure sent by the user
330
     *
331
     * @param array $signature
332
     * @param array $attributes
333
     */
334
    public static function verifyKeys($signature, $attributes)
335
    {
336
        $validKeys = self::_flattenArray($signature);
337
        $userKeys = self::_flattenUserKeys($attributes);
338
        $invalidKeys = array_diff($userKeys, $validKeys);
339
        $invalidKeys = self::_removeWildcardKeys($validKeys, $invalidKeys);
340
341
        if(!empty($invalidKeys)) {
342
            asort($invalidKeys);
343
            $sortedList = join(', ', $invalidKeys);
344
            throw new InvalidArgumentException('invalid keys: ' . $sortedList);
345
        }
346
    }
347
    /**
348
     * flattens a numerically indexed nested array to a single level
349
     * @param array $keys
350
     * @param string $namespace
351
     * @return array
352
     */
353
    private static function _flattenArray($keys, $namespace = null)
354
    {
355
        $flattenedArray = [];
356
        foreach($keys AS $key) {
357
            if(is_array($key)) {
358
                $theKeys = array_keys($key);
359
                $theValues = array_values($key);
360
                $scope = $theKeys[0];
361
                $fullKey = empty($namespace) ? $scope : $namespace . '[' . $scope . ']';
362
                $flattenedArray = array_merge($flattenedArray, self::_flattenArray($theValues[0], $fullKey));
363
            } else {
364
                $fullKey = empty($namespace) ? $key : $namespace . '[' . $key . ']';
365
                $flattenedArray[] = $fullKey;
366
            }
367
        }
368
        sort($flattenedArray);
369
        return $flattenedArray;
370
    }
371
372
    private static function _flattenUserKeys($keys, $namespace = null)
373
    {
374
       $flattenedArray = [];
375
376
       foreach($keys AS $key => $value) {
377
           $fullKey = empty($namespace) ? $key : $namespace;
378
           if (!is_numeric($key) && $namespace != null) {
379
              $fullKey .= '[' . $key . ']';
380
           }
381
           if (is_numeric($key) && is_string($value)) {
382
              $fullKey .= '[' . $value . ']';
383
           }
384
           if(is_array($value)) {
385
               $more = self::_flattenUserKeys($value, $fullKey);
386
               $flattenedArray = array_merge($flattenedArray, $more);
387
           } else {
388
               $flattenedArray[] = $fullKey;
389
           }
390
       }
391
       sort($flattenedArray);
392
       return $flattenedArray;
393
    }
394
395
    /**
396
     * removes wildcard entries from the invalid keys array
397
     * @param array $validKeys
398
     * @param <array $invalidKeys
0 ignored issues
show
Documentation introduced by
The doc-type <array could not be parsed: Unknown type name "<" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
399
     * @return array
400
     */
401
    private static function _removeWildcardKeys($validKeys, $invalidKeys)
402
    {
403
        foreach($validKeys AS $key) {
404
            if (stristr($key, '[_anyKey_]')) {
405
                $wildcardKey = str_replace('[_anyKey_]', '', $key);
406
                foreach ($invalidKeys AS $index => $invalidKey) {
407
                    if (stristr($invalidKey, $wildcardKey)) {
408
                        unset($invalidKeys[$index]);
409
                    }
410
                }
411
            }
412
        }
413
        return $invalidKeys;
414
    }
415
}
416
class_alias('Braintree\Util', 'Braintree_Util');
417