Passed
Pull Request — master (#204)
by Marcin
07:42
created

Validator::assertIsIntRange()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 7
c 1
b 0
f 0
nc 3
nop 4
dl 0
loc 12
ccs 1
cts 1
cp 1
crap 4
rs 10
1
<?php
2
declare(strict_types=1);
3
4
namespace MarcinOrlowski\ResponseBuilder;
5
6
/**
7
 * Laravel API Response Builder
8
 *
9
 * @package   MarcinOrlowski\ResponseBuilder
10
 *
11
 * @author    Marcin Orlowski <mail (#) marcinOrlowski (.) com>
12
 * @copyright 2016-2021 Marcin Orlowski
13
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
14
 * @link      https://github.com/MarcinOrlowski/laravel-api-response-builder
15
 */
16
17
use MarcinOrlowski\ResponseBuilder\Exceptions as Ex;
18
use MarcinOrlowski\ResponseBuilder\ResponseBuilder as RB;
19
20
/**
21
 * Data validator helper
22
 */
23
final class Validator
24
{
25
	/**
26
	 * Checks if given $val is of type boolean
27
	 *
28
	 * @param string $var_name Name of the key to be used if exception is thrown.
29
	 * @param mixed  $value    Variable to be asserted.
30
	 *
31
	 * @return void
32
	 *
33
	 * @throws Ex\InvalidTypeException
34
	 * @throws Ex\NotBooleanException
35
	 *
36
	 */
37
	public static function assertIsBool(string $var_name, $value): void
38
	{
39
		self::assertIsType($var_name, $value, [Type::BOOLEAN], Ex\NotBooleanException::class);
40
	}
41
42
	/**
43
	 * Checks if given $val is of type integer
44
	 *
45
	 * @param string $var_name Name of the key to be used if exception is thrown.
46 2
	 * @param mixed  $value    Variable to be asserted.
47
	 *
48 2
	 * @return void
49 1
	 *
50
	 * @throws Ex\InvalidTypeException
51
	 * @throws Ex\NotIntegerException
52
	 */
53
	public static function assertIsInt(string $var_name, $value): void
54
	{
55
		self::assertIsType($var_name, $value, [Type::INTEGER], Ex\NotIntegerException::class);
56
	}
57
58
	/**
59
	 * Checks if given $val is of type array
60
	 *
61 87
	 * @param string $var_name Name of the key to be used if exception is thrown.
62
	 * @param mixed  $value    Variable to be asserted.
63 87
	 *
64 87
	 * @return void
65
	 *
66
	 * @throws Ex\InvalidTypeException
67
	 * @throws Ex\NotArrayException
68
	 */
69
	public static function assertIsArray(string $var_name, $value): void
70
	{
71
		self::assertIsType($var_name, $value, [Type::ARRAY], Ex\NotArrayException::class);
72
	}
73
74
	/**
75
	 * Checks if given $val is an object
76 2
	 *
77
	 * @param string $var_name Name of the key to be used if exception is thrown.
78 2
	 * @param mixed  $value    Variable to be asserted.
79 1
	 *
80
	 * @return void
81
	 *
82
	 * @throws Ex\InvalidTypeException
83
	 * @throws Ex\NotObjectException
84
	 */
85
	public static function assertIsObject(string $var_name, $value): void
86
	{
87
		self::assertIsType($var_name, $value, [Type::OBJECT], Ex\NotObjectException::class);
88
	}
89
90
	/**
91 14
	 * Checks if given $cls_cls_or_obj is either an object or name of existing class.
92
	 *
93 14
	 * @param string        $var_name
94 13
	 * @param string|object $cls_or_obj
95
	 *
96
	 * @return void
97
	 *
98
	 * @throws Ex\InvalidTypeException
99
	 * @throws Ex\ClassNotFound
100
	 */
101
	public static function assertIsObjectOrExistingClass(string $var_name, $cls_or_obj): void
102
	{
103
		self::assertIsType($var_name, $cls_or_obj, [Type::EXISTING_CLASS, Type::OBJECT]);
104
	}
105
106 13
	/**
107
	 * Checks if given $val is of type string
108 13
	 *
109 10
	 * @param string $var_name Label or name of the variable to be used in exception message (if thrown).
110
	 * @param mixed  $value    Variable to be asserted.
111
	 *
112
	 * @return void
113
	 *
114
	 * @throws Ex\InvalidTypeException
115
	 * @throws Ex\NotStringException
116
	 */
117
	public static function assertIsString(string $var_name, $value): void
118
	{
119
		self::assertIsType($var_name, $value, [Type::STRING], Ex\NotStringException::class);
120
	}
121
122 87
	/**
123
	 * @param string $var_name Label or name of the variable to be used in exception message (if thrown).
124 87
	 * @param mixed  $value    Variable to be asserted.
125
	 * @param int    $min      Min allowed value (inclusive)
126 87
	 * @param int    $max      Max allowed value (inclusive)
127 1
	 *
128 1
	 * @return void
129
	 *
130
	 * @throws \InvalidArgumentException
131 87
	 * @throws \OutOfBoundsException
132 4
	 * @throws Ex\NotIntegerException
133 4
	 * @throws Ex\InvalidTypeException
134
	 */
135 87
	public static function assertIsIntRange(string $var_name, $value, int $min, int $max): void
136
	{
137
		self::assertIsInt($var_name, $value);
138
139
		if ($min > $max) {
140
			throw new \InvalidArgumentException(
141
				\sprintf('%s: Invalid range for "%s". Ensure bound values are not swapped.', __FUNCTION__, $var_name));
142
		}
143
144
		if (($min > $value) || ($value > $max)) {
145
			throw new \OutOfBoundsException(
146
				\sprintf('Value of "%s" (%d) is out of bounds. Must be between %d-%d inclusive.', $var_name, $value, $min, $max));
147
		}
148 87
	}
149
150 87
	/**
151 87
	 * Checks if $item (of name $key) is of type that is include in $allowed_types (there's `OR` connection
152 13
	 * between specified types).
153 13
	 *
154 13
	 * @param string $var_name      Label or name of the variable to be used in exception message (if thrown).
155
	 * @param mixed  $value         Variable to be asserted.
156
	 * @param array  $allowed_types Array of allowed types for $value, i.e. [Type::INTEGER]
157 87
	 * @param string $ex_class      Name of exception class (which implements InvalidTypeExceptionContract) to
158
	 *                              be used when assertion fails. In that case object of that class will be
159
	 *                              instantiated and thrown.
160
	 *
161
	 * @return void
162
	 *
163
	 * @throws Ex\InvalidTypeException
164 10
	 */
165
	public static function assertIsType(string $var_name, $value, array $allowed_types,
166 10
	                                    string $ex_class = Ex\InvalidTypeException::class): void
167 10
	{
168 10
		// Type::EXISTING_CLASS is artificial type, so we need separate logic to handle it.
169 10
		$tmp = $allowed_types;
170
		$idx = array_search(Type::EXISTING_CLASS, $tmp, true);
171
		if ($idx !== false) {
172
			// Remove the type, so gettype() test loop won't see it.
173
			unset($tmp[$idx]);
174
			if (is_string($value) && class_exists($value)) {
175
				// It's existing class, no need to test further.
176 8
				return;
177
			}
178 8
		}
179 8
180 8
		$type = \gettype($value);
181
		if (!empty($tmp)) {
182
			if (!\in_array($type, $allowed_types, true)) {
183
				// FIXME we need to ensure $ex_class implements InvalidTypeExceptionContract at some point.
184
				throw new $ex_class($var_name, $type, $allowed_types);
185
			}
186
		} else {
187
			// FIXME we need to ensure $ex_class implements InvalidTypeExceptionContract at some point.
188
			throw new Ex\ClassNotFound($var_name, $type, $allowed_types);
189 4
		}
190
	}
191 4
192 1
	/**
193 1
	 * Ensures given $http_code is valid code for error response.
194
	 *
195
	 * @param int $http_code
196 3
	 *
197
	 * @throws Ex\InvalidTypeException
198
	 * @throws Ex\NotIntegerException
199
	 */
200
	public static function assertErrorHttpCode(int $http_code): void
201
	{
202
		self::assertIsInt('http_code', $http_code);
203
		self::assertIsIntRange('http_code', $http_code,
204
			RB::ERROR_HTTP_CODE_MIN, RB::ERROR_HTTP_CODE_MAX);
205
	}
206
207
	/**
208
	 * Ensures given $http_code is valid for response indicating sucessful operation.
209
	 *
210
	 * @param int $http_code
211
	 *
212
	 * @throws Ex\InvalidTypeException
213
	 * @throws Ex\NotIntegerException
214
	 */
215
	public static function assertOkHttpCode(int $http_code): void
216
	{
217
		self::assertIsInt('http_code', $http_code);
218
		self::assertIsIntRange('http_code', $http_code, 200, 299);
219
	}
220
221
	/**
222
	 * Ensures $obj (that is value coming from variable, which name is passed in $label) is instance of $cls class.
223
	 *
224
	 * @param string $var_name Name of variable that the $obj value is coming from. Used for exception message.
225
	 * @param object $obj      Object to check instance of
226
	 * @param string $cls      Target class we want to check $obj agains.
227
	 *
228
	 * @throws \InvalidArgumentException
229
	 */
230
	public static function assertInstanceOf(string $var_name, object $obj, string $cls): void
231
	{
232
		if (!($obj instanceof $cls)) {
233
			throw new \InvalidArgumentException(
234
				\sprintf('"%s" must be instance of "%s".', $var_name, $cls)
235
			);
236
		}
237
	}
238
239
	/**
240
	 * Ensure that we either have array with user provided keys i.e. ['foo'=>'bar'], which will then
241
	 * be turned into JSON object or array without user specified keys (['bar']) which we would return as JSON
242
	 * array. But you can't mix these two as the final JSON would not produce predictable results.
243
	 *
244
	 * @param array $data
245
	 *
246
	 * @throws Ex\ArrayWithMixedKeysException
247
	 */
248
	public static function assertArrayHasNoMixedKeys(array $data): void
249
	{
250
		$string_keys_cnt = 0;
251
		$int_keys_cnt = 0;
252
		foreach (\array_keys($data) as $key) {
253
			if (\is_int($key)) {
254
				if ($string_keys_cnt > 0) {
255
					throw new Ex\ArrayWithMixedKeysException();
256
				}
257
				$int_keys_cnt++;
258
			} else {
259
				if ($int_keys_cnt > 0) {
260
					throw new Ex\ArrayWithMixedKeysException();
261
				}
262
				$string_keys_cnt++;
263
			}
264
		}
265
	}
266
}
267