@@ -17,6 +17,16 @@ class File implements Rule, DataAwareRule, ValidatorAwareRule
17
17
{
18
18
use Conditionable, Macroable;
19
19
20
+ /**
21
+ * Binary units flag used for size validation.
22
+ */
23
+ public const BINARY = 'binary ' ;
24
+
25
+ /**
26
+ * International units flag used for size validation.
27
+ */
28
+ public const INTERNATIONAL = 'international ' ;
29
+
20
30
/**
21
31
* The MIME types that the given file should match. This array may also contain file extensions.
22
32
*
@@ -45,6 +55,11 @@ class File implements Rule, DataAwareRule, ValidatorAwareRule
45
55
*/
46
56
protected $ maximumFileSize = null ;
47
57
58
+ /**
59
+ * The units used for size validation.
60
+ */
61
+ protected string $ units = self ::INTERNATIONAL ;
62
+
48
63
/**
49
64
* An array of custom rules that will be merged into the validation rules.
50
65
*
@@ -150,83 +165,138 @@ public function extensions($extensions)
150
165
return $ this ;
151
166
}
152
167
168
+ /**
169
+ * Set the units for size validation to binary (instance method).
170
+ */
171
+ public function binary (): static
172
+ {
173
+ $ this ->units = self ::BINARY ;
174
+ return $ this ;
175
+ }
176
+
177
+ /**
178
+ * Set the units for size validation to international (instance method).
179
+ */
180
+ public function international (): static
181
+ {
182
+ $ this ->units = self ::INTERNATIONAL ;
183
+ return $ this ;
184
+ }
185
+
186
+
187
+
153
188
/**
154
189
* Indicate that the uploaded file should be exactly a certain size in kilobytes.
155
- *
156
- * @param string|int $size
157
- * @return $this
158
190
*/
159
- public function size ($ size)
191
+ public function size (string | int $ size, ? string $ units = null ): static
160
192
{
161
- $ this ->minimumFileSize = $ this ->toKilobytes ($ size );
193
+ $ this ->minimumFileSize = $ this ->toKilobytes ($ size, $ this -> units ( $ units ) );
162
194
$ this ->maximumFileSize = $ this ->minimumFileSize ;
163
195
164
196
return $ this ;
165
197
}
166
198
167
199
/**
168
200
* Indicate that the uploaded file should be between a minimum and maximum size in kilobytes.
169
- *
170
- * @param string|int $minSize
171
- * @param string|int $maxSize
172
- * @return $this
173
201
*/
174
- public function between ($ minSize , $ maxSize)
202
+ public function between (string | int $ minSize , string | int $ maxSize, ? string $ units = null ): static
175
203
{
176
- $ this ->minimumFileSize = $ this ->toKilobytes ($ minSize );
177
- $ this ->maximumFileSize = $ this ->toKilobytes ($ maxSize );
204
+ $ this ->minimumFileSize = $ this ->toKilobytes ($ minSize, $ this -> units ( $ units ) );
205
+ $ this ->maximumFileSize = $ this ->toKilobytes ($ maxSize, $ this -> units ( $ units ) );
178
206
179
207
return $ this ;
180
208
}
181
209
182
210
/**
183
211
* Indicate that the uploaded file should be no less than the given number of kilobytes.
184
- *
185
- * @param string|int $size
186
- * @return $this
187
212
*/
188
- public function min ($ size)
213
+ public function min (string | int $ size, ? string $ units = null ): static
189
214
{
190
- $ this ->minimumFileSize = $ this ->toKilobytes ($ size );
215
+ $ this ->minimumFileSize = $ this ->toKilobytes ($ size, $ this -> units ( $ units ) );
191
216
192
217
return $ this ;
193
218
}
194
219
195
220
/**
196
221
* Indicate that the uploaded file should be no more than the given number of kilobytes.
197
- *
198
- * @param string|int $size
199
- * @return $this
200
222
*/
201
- public function max ($ size)
223
+ public function max (string | int $ size, ? string $ units = null ): static
202
224
{
203
- $ this ->maximumFileSize = $ this ->toKilobytes ($ size );
225
+ $ this ->maximumFileSize = $ this ->toKilobytes ($ size, $ this -> units ( $ units ) );
204
226
205
227
return $ this ;
206
228
}
207
229
230
+ protected function units (?string $ units = null ): string
231
+ {
232
+ return $ units ?? $ this ->units ;
233
+ }
234
+
235
+ /**
236
+ * Converts whole numbers to integers for exact arithmetic while keeping
237
+ * fractional numbers as floats; also provides overflow protection by
238
+ * falling back to float arithmetic for values too large for integer range.
239
+ */
240
+ protected function prepareValueForPrecision (float $ value ): float |int
241
+ {
242
+ return $ value > PHP_INT_MAX || $ value < PHP_INT_MIN || ((float ) (int ) $ value ) !== $ value
243
+ ? $ value
244
+ : (int ) $ value ;
245
+ }
246
+
208
247
/**
209
248
* Convert a potentially human-friendly file size to kilobytes.
210
- *
211
- * @param string|int $size
212
- * @return mixed
213
249
*/
214
- protected function toKilobytes ($ size)
250
+ protected function toKilobytes (string | int $ size, string $ units ): float | int
215
251
{
216
252
if (! is_string ($ size )) {
217
253
return $ size ;
218
254
}
219
255
220
256
$ size = strtolower (trim ($ size ));
221
257
222
- $ value = floatval ($ size );
258
+ $ value = (float ) $ size ;
259
+
260
+ return $ units === self ::BINARY
261
+ ? $ this ->toBinaryKilobytes ($ size , $ value )
262
+ : $ this ->toInternationalKilobytes ($ size , $ value );
263
+ }
264
+
265
+ /**
266
+ * Convert a human-friendly file size to kilobytes using the International System.
267
+ */
268
+ protected function toInternationalKilobytes (string $ size , float $ value ): float |int
269
+ {
270
+ $ value = $ this ->prepareValueForPrecision ($ value );
223
271
224
272
return round (match (true ) {
225
- Str::endsWith ($ size , 'kb ' ) => $ value * 1 ,
273
+ is_numeric ($ size ) => $ value ,
274
+ Str::endsWith ($ size , 'kb ' ) => $ value ,
226
275
Str::endsWith ($ size , 'mb ' ) => $ value * 1_000 ,
227
276
Str::endsWith ($ size , 'gb ' ) => $ value * 1_000_000 ,
228
277
Str::endsWith ($ size , 'tb ' ) => $ value * 1_000_000_000 ,
229
- default => throw new InvalidArgumentException ('Invalid file size suffix. ' ),
278
+ default => throw new InvalidArgumentException (
279
+ 'Invalid file size suffix. Valid suffixes are: KB, MB, GB, TB (case insensitive). '
280
+ ),
281
+ });
282
+ }
283
+
284
+ /**
285
+ * Convert a human-friendly file size to kilobytes using the Binary System.
286
+ */
287
+ protected function toBinaryKilobytes (string $ size , float $ value ): float |int
288
+ {
289
+ $ value = $ this ->prepareValueForPrecision ($ value );
290
+
291
+ return round (match (true ) {
292
+ is_numeric ($ size ) => $ value ,
293
+ Str::endsWith ($ size , 'kb ' ) => $ value ,
294
+ Str::endsWith ($ size , 'mb ' ) => $ value * 1_024 ,
295
+ Str::endsWith ($ size , 'gb ' ) => $ value * 1_048_576 ,
296
+ Str::endsWith ($ size , 'tb ' ) => $ value * 1_073_741_824 ,
297
+ default => throw new InvalidArgumentException (
298
+ 'Invalid file size suffix. Valid suffixes are: KB, MB, GB, TB (case insensitive). '
299
+ ),
230
300
});
231
301
}
232
302
@@ -283,14 +353,18 @@ protected function buildValidationRules()
283
353
$ rules [] = 'extensions: ' .implode (', ' , array_map (strtolower (...), $ this ->allowedExtensions ));
284
354
}
285
355
286
- $ rules [] = match (true ) {
287
- is_null ( $ this ->minimumFileSize ) && is_null ( $ this ->maximumFileSize ) => null ,
288
- is_null ( $ this ->maximumFileSize ) => "min: {$ this ->minimumFileSize }" ,
289
- is_null ( $ this ->minimumFileSize ) => "max: {$ this ->maximumFileSize }" ,
290
- $ this ->minimumFileSize !== $ this ->maximumFileSize => "between : {$ this ->minimumFileSize } , { $ this -> maximumFileSize }" ,
291
- default => "size : {$ this ->minimumFileSize }" ,
356
+ $ rule = match (true ) {
357
+ $ this ->minimumFileSize === null && $ this ->maximumFileSize === null => null ,
358
+ $ this ->maximumFileSize === null => "min: {$ this ->minimumFileSize }" ,
359
+ $ this ->minimumFileSize === null => "max: {$ this ->maximumFileSize }" ,
360
+ $ this ->minimumFileSize === $ this ->maximumFileSize => "size : {$ this ->minimumFileSize }" ,
361
+ default => "between : {$ this ->minimumFileSize } , { $ this -> maximumFileSize }" ,
292
362
};
293
363
364
+ if ($ rule ) {
365
+ $ rules [] = $ rule ;
366
+ }
367
+
294
368
return array_merge (array_filter ($ rules ), $ this ->customRules );
295
369
}
296
370
0 commit comments