Completed
Pull Request — master (#58)
by Chad
02:58
created

Util   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 304
Duplicated Lines 6.25 %

Coupling/Cohesion

Components 2
Dependencies 0

Importance

Changes 0
Metric Value
wmc 45
lcom 2
cbo 0
dl 19
loc 304
rs 8.3673
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A getExceptionInfo() 0 11 1
A ensure() 11 11 3
A ensureNot() 8 8 3
B buildException() 0 20 5
A raiseException() 0 8 2
A throwIfNotType() 0 9 2
A getExceptionAliases() 0 4 1
A setExceptionAliases() 0 4 1
B handleBoolCase() 0 11 6
A handleNullCase() 0 8 3
B handleStringCase() 0 18 7
B handleDefaultCase() 0 11 5
B handleTypesToVariables() 0 50 6

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 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
3
namespace TraderInteractive;
4
5
use ErrorException;
6
use Exception;
7
use InvalidArgumentException;
8
use ReflectionClass;
9
use ReflectionException;
10
use Throwable;
11
12
/**
13
 * Static class with various application functions.
14
 */
15
final class Util
16
{
17
    private static $exceptionAliases = ['http' => '\TraderInteractive\HttpException'];
18
19
    /**
20
     * Returns exception info in array.
21
     *
22
     * @param Throwable $t the exception to return info on
23
     *
24
     * @return array like:
25
     * <pre>
26
     * [
27
     *     'type' => 'Exception',
28
     *     'message' => 'a message',
29
     *     'code' => 0,
30
     *     'file' => '/somePath',
31
     *     'line' => 434,
32
     *     'trace' => 'a stack trace',
33
     * ]
34
     * </pre>
35
     */
36
    public static function getExceptionInfo(Throwable $t) : array
37
    {
38
        return [
39
            'type' => get_class($t),
40
            'message' => $t->getMessage(),
41
            'code' => $t->getCode(),
42
            'file' => $t->getFile(),
43
            'line' => $t->getLine(),
44
            'trace' => $t->getTraceAsString(),
45
        ];
46
    }
47
48
    /**
49
     * Ensures that $valueToEnsure is equal to $valueToCheck or it throws
50
     *
51
     * Can be used like: $result = ensure(true, is_string('boo'))
52
     * Or like: $result = ensure(true, is_string('boo'), 'the message')
53
     * Or like: $result = ensure(true, is_string('boo'), 'MyException', ['the message', 2])
54
     * Or like: $result = ensure(true, is_string('boo'), new MyException('the message', 2))
55
     *
56
     * @param mixed                 $valueToEnsure the value to throw on if $valueToCheck equals it
57
     * @param mixed                 $valueToCheck  the value to check against $valueToEnsure
58
     * @param null|string|Exception $exception     null, a fully qualified exception class name, string for an Exception
59
     *                                             message, or an Exception.  The fully qualified exception class name
60
     *                                             could also be an alias in getExceptionAliases()
61
     * @param array|null            $exceptionArgs arguments to pass to a new instance of $exception. If using this
62
     *                                             parameter make sure these arguments match the constructor for an
63
     *                                             exception of type $exception.
64
     *
65
     * @return mixed returns $valueToCheck
66
     *
67
     * @throws Exception if $valueToEnsure !== $valueToCheck
68
     * @throws InvalidArgumentException if $exception was not null, a string, or an Exception
69
     */
70 View Code Duplication
    public static function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exceptionArgs = null)
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...
71
    {
72
        if ($valueToEnsure !== $valueToCheck) {
73
            throw self::buildException(
74
                $exception ?: "'{$valueToEnsure}' did not equal '{$valueToCheck}'",
75
                $exceptionArgs
76
            );
77
        }
78
79
        return $valueToCheck;
80
    }
81
82
    /**
83
     * Ensures that $valueToThrowOn is not equal to $valueToCheck or it throws
84
     *
85
     * Can be used like: $curl = ensureNot(false, curl_init('boo'))
86
     * Or like: $curl = ensureNot(false, curl_init('boo'), 'bad message')
87
     * Or like: $curl = ensureNot(false, curl_init('boo'), 'MyException', ['bad message', 2])
88
     * Or like: $curl = ensureNot(false, curl_init('boo'), new MyException('bad message', 2))
89
     *
90
     * @param mixed                 $valueToThrowOn the value to throw on if $valueToCheck equals it
91
     * @param mixed                 $valueToCheck   the value to check against $valueToThrowOn
92
     * @param null|string|Exception $exception      null, a fully qualified exception class name, string for an
93
     *                                              Exception message, or an Exception.  The fully qualified exception
94
     *                                              class name could also be an alias in getExceptionAliases()
95
     * @param array|null            $exceptionArgs  arguments to pass to a new instance of $exception. If using this
96
     *                                              parameter make sure these arguments match the constructor for an
97
     *                                              exception of type $exception.
98
     *
99
     * @return mixed returns $valueToCheck
100
     *
101
     * @throws Exception if $valueToThrowOn === $valueToCheck
102
     * @throws InvalidArgumentException if $exception was not null, a string, or an Exception
103
     */
104 View Code Duplication
    public static function ensureNot($valueToThrowOn, $valueToCheck, $exception = null, array $exceptionArgs = null)
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...
105
    {
106
        if ($valueToThrowOn === $valueToCheck) {
107
            throw self::buildException($exception ?: "'{$valueToThrowOn}' equals '{$valueToCheck}'", $exceptionArgs);
108
        }
109
110
        return $valueToCheck;
111
    }
112
113
    /**
114
     * Helper method to return exception created from ensure[Not] call input.
115
     *
116
     * @param mixed      $exception     Null, a fully qualified exception class name, string for an Exception message,
117
     *                                  or an Exception.  The fully qualified exception class name could also be an
118
     *                                  alias in getExceptionAliases()
119
     * @param array|null $exceptionArgs Arguments to pass to a new instance of $exception. If using this parameter make
120
     *                                  sure these arguments match the constructor for an exception of type $exception.
121
     *
122
     * @return Throwable
123
     *
124
     * @throws ReflectionException
125
     */
126
    private static function buildException($exception, array $exceptionArgs = null) : Throwable
127
    {
128
        if ($exception instanceof Exception) {
129
            return $exception;
130
        }
131
132
        if (!is_string($exception)) {
133
            throw new InvalidArgumentException('$exception was not null, a string, or an Exception');
134
        }
135
136
        if (empty($exceptionArgs)) {
137
            return new Exception($exception);
138
        }
139
140
        if (array_key_exists($exception, self::$exceptionAliases)) {
141
            $exception = self::$exceptionAliases[$exception];
142
        }
143
144
        return (new ReflectionClass($exception))->newInstanceArgs($exceptionArgs);
145
    }
146
147
    /**
148
     * Throws a new ErrorException based on the error information provided. To be
149
     * used as a callback for @see set_error_handler()
150
     *
151
     * @param int    $level   The level of the exception.
152
     * @param string $message The message the exception will give.
153
     * @param string $file    The file that the error occurred in.
154
     * @param string $line    The line that the exception occurred upon.
155
     *
156
     * @return bool false
157
     *
158
     * @throws ErrorException
159
     */
160
    public static function raiseException(int $level, string $message, string $file = null, string $line = null) : bool
161
    {
162
        if (error_reporting() === 0) {
163
            return false;
164
        }
165
166
        throw new ErrorException($message, 0, $level, $file, $line);
167
    }
168
169
    /**
170
     * Throws an exception if specified variables are not of given types.
171
     *
172
     * @param array $typesToVariables like ['string' => [$var1, $var2], 'int' => [$var1, $var2]] or
173
     *                                ['string' => $var1, 'integer' => [1, $var2]]. Supported types are the suffixes
174
     *                                of the is_* functions such as string for is_string and int for is_int
175
     * @param bool  $failOnWhitespace whether to fail strings if they are whitespace
176
     * @param bool  $allowNulls       whether to allow null values to pass through
177
     *
178
     * @return void
179
     *
180
     * @throws InvalidArgumentException if a key in $typesToVariables was not a string
181
     * @throws InvalidArgumentException if a key in $typesToVariables did not have an is_ function
182
     * @throws InvalidArgumentException if a variable is not of correct type
183
     * @throws InvalidArgumentException if a variable is whitespace and $failOnWhitespace is set
184
     */
185
    public static function throwIfNotType(
186
        array $typesToVariables,
187
        bool $failOnWhitespace = false,
188
        bool $allowNulls = false
189
    ) {
190
        foreach ($typesToVariables as $type => $variablesOrVariable) {
191
            self::handleTypesToVariables($failOnWhitespace, $allowNulls, $variablesOrVariable, $type);
192
        }
193
    }
194
195
    /**
196
     * Return the exception aliases.
197
     *
198
     * @return array array where keys are aliases and values are strings to a fully qualified exception class names.
199
     */
200
    public static function getExceptionAliases() : array
201
    {
202
        return self::$exceptionAliases;
203
    }
204
205
    /**
206
     * Set the exception aliases.
207
     *
208
     * @param array $aliases array where keys are aliases and values are strings to a fully qualified exception class
209
     *                       names.
210
     */
211
    public static function setExceptionAliases(array $aliases)
212
    {
213
        self::$exceptionAliases = $aliases;
214
    }
215
216
    private static function handleBoolCase(bool $allowNulls, array $variables)
217
    {
218
        foreach ($variables as $i => $variable) {
219
            //using the continue here not negative checks to make use of short cutting optimization.
220
            if ($variable === false || $variable === true || ($allowNulls && $variable === null)) {
221
                continue;
222
            }
223
224
            throw new InvalidArgumentException("variable at position '{$i}' was not a boolean");
225
        }
226
    }
227
228
    private static function handleNullCase(array $variables)
229
    {
230
        foreach ($variables as $i => $variable) {
231
            if ($variable !== null) {
232
                throw new InvalidArgumentException("variable at position '{$i}' was not null");
233
            }
234
        }
235
    }
236
237
    private static function handleStringCase(bool $failOnWhitespace, bool $allowNulls, array $variables, string $type)
238
    {
239
        foreach ($variables as $i => $variable) {
240
            if (is_string($variable)) {
241
                if ($failOnWhitespace && trim($variable) === '') {
242
                    throw new InvalidArgumentException("variable at position '{$i}' was whitespace");
243
                }
244
245
                continue;
246
            }
247
248
            if ($allowNulls && $variable === null) {
249
                continue;
250
            }
251
252
            throw new InvalidArgumentException("variable at position '{$i}' was not a '{$type}'");
253
        }
254
    }
255
256
    private static function handleDefaultCase(bool $allowNulls, string $type, array $variables)
257
    {
258
        $isFunction = "is_{$type}";
259
        foreach ($variables as $i => $variable) {
260
            if ($isFunction($variable) || ($allowNulls && $variable === null)) {
261
                continue;
262
            }
263
264
            throw new InvalidArgumentException("variable at position '{$i}' was not a '{$type}'");
265
        }
266
    }
267
268
    private static function handleTypesToVariables(
269
        bool $failOnWhitespace,
270
        bool $allowNulls,
271
        $variablesOrVariable,
272
        $type
273
    ) {
274
        $variables = [$variablesOrVariable];
275
        if (is_array($variablesOrVariable)) {
276
            $variables = $variablesOrVariable;
277
        }
278
279
        //cast ok since an integer won't match any of the cases.
280
        //the similar code in the cases is an optimization for those type where faster checks can be made.
281
        $typeString = (string)$type;
282
        if ($typeString === 'bool') {
283
            self::handleBoolCase($allowNulls, $variables);
284
            return;
285
        }
286
287
        if ($typeString === 'null') {
288
            self::handleNullCase($variables);
289
            return;
290
        }
291
292
        if ($typeString === 'string') {
293
            self::handleStringCase($failOnWhitespace, $allowNulls, $variables, $type);
294
            return;
295
        }
296
297
        $defaults = [
298
            'array',
299
            'callable',
300
            'double',
301
            'float',
302
            'int',
303
            'integer',
304
            'long',
305
            'numeric',
306
            'object',
307
            'real',
308
            'resource',
309
            'scalar',
310
        ];
311
        if (in_array($typeString, $defaults)) {
312
            self::handleDefaultCase($allowNulls, $type, $variables);
313
            return;
314
        }
315
316
        throw new InvalidArgumentException('a type was not one of the is_ functions');
317
    }
318
}
319