Util::ensureNot()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 3
nc 2
nop 4
dl 0
loc 7
rs 10
c 0
b 0
f 0
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 mixed      $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
     *
62
     * @param array|null $exceptionArgs            arguments to pass to a new instance of $exception. If using this
63
     *                                             parameter make sure these arguments match the constructor for an
64
     *                                             exception of type $exception.
65
     *
66
     * @return mixed returns $valueToCheck
67
     *
68
     * @throws Exception if $valueToEnsure !== $valueToCheck
69
     * @throws InvalidArgumentException if $exception was not null, a string, or an Exception
70
     */
71
    public static function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exceptionArgs = null)
72
    {
73
        if ($valueToEnsure !== $valueToCheck) {
74
            throw self::buildException(
75
                $exception ?: "'{$valueToEnsure}' did not equal '{$valueToCheck}'",
76
                $exceptionArgs
77
            );
78
        }
79
80
        return $valueToCheck;
81
    }
82
83
    /**
84
     * Ensures that $valueToThrowOn is not equal to $valueToCheck or it throws
85
     *
86
     * Can be used like: $curl = ensureNot(false, curl_init('boo'))
87
     * Or like: $curl = ensureNot(false, curl_init('boo'), 'bad message')
88
     * Or like: $curl = ensureNot(false, curl_init('boo'), 'MyException', ['bad message', 2])
89
     * Or like: $curl = ensureNot(false, curl_init('boo'), new MyException('bad message', 2))
90
     *
91
     * @param mixed                 $valueToThrowOn the value to throw on if $valueToCheck equals it
92
     * @param mixed                 $valueToCheck   the value to check against $valueToThrowOn
93
     * @param null|string|Exception $exception      null, a fully qualified exception class name, string for an
94
     *                                              Exception message, or an Exception.  The fully qualified exception
95
     *                                              class name could also be an alias in getExceptionAliases()
96
     * @param array|null            $exceptionArgs  arguments to pass to a new instance of $exception. If using this
97
     *                                              parameter make sure these arguments match the constructor for an
98
     *                                              exception of type $exception.
99
     *
100
     * @return mixed returns $valueToCheck
101
     *
102
     * @throws Exception if $valueToThrowOn === $valueToCheck
103
     * @throws InvalidArgumentException if $exception was not null, a string, or an Exception
104
     */
105
    public static function ensureNot($valueToThrowOn, $valueToCheck, $exception = null, array $exceptionArgs = null)
106
    {
107
        if ($valueToThrowOn === $valueToCheck) {
108
            throw self::buildException($exception ?: "'{$valueToThrowOn}' equals '{$valueToCheck}'", $exceptionArgs);
109
        }
110
111
        return $valueToCheck;
112
    }
113
114
    /**
115
     * Helper method to return exception created from ensure[Not] call input.
116
     *
117
     * @param mixed      $exception     Null, a fully qualified exception class name, string for an Exception message,
118
     *                                  or an Exception.  The fully qualified exception class name could also be an
119
     *                                  alias in getExceptionAliases()
120
     * @param array|null $exceptionArgs Arguments to pass to a new instance of $exception. If using this parameter make
121
     *                                  sure these arguments match the constructor for an exception of type $exception.
122
     *
123
     * @return Throwable
124
     *
125
     * @throws ReflectionException
126
     */
127
    private static function buildException($exception, array $exceptionArgs = null) : Throwable
128
    {
129
        if ($exception instanceof Exception) {
130
            return $exception;
131
        }
132
133
        if (!is_string($exception)) {
134
            throw new InvalidArgumentException('$exception was not null, a string, or an Exception');
135
        }
136
137
        if (empty($exceptionArgs)) {
138
            return new Exception($exception);
139
        }
140
141
        if (array_key_exists($exception, self::$exceptionAliases)) {
142
            $exception = self::$exceptionAliases[$exception];
143
        }
144
145
        return (new ReflectionClass($exception))->newInstanceArgs($exceptionArgs);
146
    }
147
148
    /**
149
     * Throws a new ErrorException based on the error information provided. To be
150
     * used as a callback for @see set_error_handler()
151
     *
152
     * @param int    $level   The level of the exception.
153
     * @param string $message The message the exception will give.
154
     * @param string $file    The file that the error occurred in.
155
     * @param string $line    The line that the exception occurred upon.
156
     *
157
     * @return bool false
158
     *
159
     * @throws ErrorException
160
     */
161
    public static function raiseException(int $level, string $message, string $file = null, string $line = null) : bool
162
    {
163
        if (error_reporting() === 0) {
164
            return false;
165
        }
166
167
        throw new ErrorException($message, 0, $level, $file, $line);
0 ignored issues
show
Bug introduced by
It seems like $line can also be of type string; however, parameter $line of ErrorException::__construct() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

167
        throw new ErrorException($message, 0, $level, $file, /** @scrutinizer ignore-type */ $line);
Loading history...
168
    }
169
170
    /**
171
     * Throws an exception if specified variables are not of given types.
172
     *
173
     * @param array $typesToVariables like ['string' => [$var1, $var2], 'int' => [$var1, $var2]] or
174
     *                                ['string' => $var1, 'integer' => [1, $var2]]. Supported types are the suffixes
175
     *                                of the is_* functions such as string for is_string and int for is_int
176
     * @param bool  $failOnWhitespace whether to fail strings if they are whitespace
177
     * @param bool  $allowNulls       whether to allow null values to pass through
178
     *
179
     * @return void
180
     *
181
     * @throws InvalidArgumentException if a key in $typesToVariables was not a string
182
     * @throws InvalidArgumentException if a key in $typesToVariables did not have an is_ function
183
     * @throws InvalidArgumentException if a variable is not of correct type
184
     * @throws InvalidArgumentException if a variable is whitespace and $failOnWhitespace is set
185
     */
186
    public static function throwIfNotType(
187
        array $typesToVariables,
188
        bool $failOnWhitespace = false,
189
        bool $allowNulls = false
190
    ) {
191
        foreach ($typesToVariables as $type => $variablesOrVariable) {
192
            self::handleTypesToVariables($failOnWhitespace, $allowNulls, $variablesOrVariable, $type);
193
        }
194
    }
195
196
    /**
197
     * Return the exception aliases.
198
     *
199
     * @return array array where keys are aliases and values are strings to a fully qualified exception class names.
200
     */
201
    public static function getExceptionAliases() : array
202
    {
203
        return self::$exceptionAliases;
204
    }
205
206
    /**
207
     * Set the exception aliases.
208
     *
209
     * @param array $aliases array where keys are aliases and values are strings to a fully qualified exception class
210
     *                       names.
211
     */
212
    public static function setExceptionAliases(array $aliases)
213
    {
214
        self::$exceptionAliases = $aliases;
215
    }
216
217
    private static function handleBoolCase(bool $allowNulls, array $variables)
218
    {
219
        foreach ($variables as $i => $variable) {
220
            //using the continue here not negative checks to make use of short cutting optimization.
221
            if ($variable === false || $variable === true || ($allowNulls && $variable === null)) {
222
                continue;
223
            }
224
225
            throw new InvalidArgumentException("variable at position '{$i}' was not a boolean");
226
        }
227
    }
228
229
    private static function handleNullCase(array $variables)
230
    {
231
        foreach ($variables as $i => $variable) {
232
            if ($variable !== null) {
233
                throw new InvalidArgumentException("variable at position '{$i}' was not null");
234
            }
235
        }
236
    }
237
238
    private static function handleStringCase(bool $failOnWhitespace, bool $allowNulls, array $variables, string $type)
239
    {
240
        foreach ($variables as $i => $variable) {
241
            if (is_string($variable)) {
242
                if ($failOnWhitespace && trim($variable) === '') {
243
                    throw new InvalidArgumentException("variable at position '{$i}' was whitespace");
244
                }
245
246
                continue;
247
            }
248
249
            if ($allowNulls && $variable === null) {
250
                continue;
251
            }
252
253
            throw new InvalidArgumentException("variable at position '{$i}' was not a '{$type}'");
254
        }
255
    }
256
257
    private static function handleDefaultCase(bool $allowNulls, string $type, array $variables)
258
    {
259
        $isFunction = "is_{$type}";
260
        foreach ($variables as $i => $variable) {
261
            if ($isFunction($variable) || ($allowNulls && $variable === null)) {
262
                continue;
263
            }
264
265
            throw new InvalidArgumentException("variable at position '{$i}' was not a '{$type}'");
266
        }
267
    }
268
269
    private static function handleTypesToVariables(
270
        bool $failOnWhitespace,
271
        bool $allowNulls,
272
        $variablesOrVariable,
273
        $type
274
    ) {
275
        $variables = [$variablesOrVariable];
276
        if (is_array($variablesOrVariable)) {
277
            $variables = $variablesOrVariable;
278
        }
279
280
        //cast ok since an integer won't match any of the cases.
281
        //the similar code in the cases is an optimization for those type where faster checks can be made.
282
        $typeString = (string)$type;
283
        if ($typeString === 'bool') {
284
            self::handleBoolCase($allowNulls, $variables);
285
            return;
286
        }
287
288
        if ($typeString === 'null') {
289
            self::handleNullCase($variables);
290
            return;
291
        }
292
293
        if ($typeString === 'string') {
294
            self::handleStringCase($failOnWhitespace, $allowNulls, $variables, $type);
295
            return;
296
        }
297
298
        $defaults = [
299
            'array',
300
            'callable',
301
            'double',
302
            'float',
303
            'int',
304
            'integer',
305
            'long',
306
            'numeric',
307
            'object',
308
            'real',
309
            'resource',
310
            'scalar',
311
        ];
312
        if (in_array($typeString, $defaults)) {
313
            self::handleDefaultCase($allowNulls, $type, $variables);
314
            return;
315
        }
316
317
        throw new InvalidArgumentException('a type was not one of the is_ functions');
318
    }
319
}
320