├── .editorconfig ├── .gitattributes ├── .gitignore ├── LICENSE.md ├── README.md ├── composer.json ├── phpcs.xml └── src ├── Attribute.php ├── ErrorBag.php ├── Helper.php ├── MimeTypeGuesser.php ├── MissingRequiredParameterException.php ├── Rule.php ├── RuleNotFoundException.php ├── RuleQuashException.php ├── Rules ├── Accepted.php ├── After.php ├── Alpha.php ├── AlphaDash.php ├── AlphaNum.php ├── AlphaSpaces.php ├── Before.php ├── Between.php ├── Boolean.php ├── Callback.php ├── Date.php ├── Defaults.php ├── Different.php ├── Digits.php ├── DigitsBetween.php ├── Email.php ├── Extension.php ├── In.php ├── Integer.php ├── Interfaces │ ├── BeforeValidate.php │ └── ModifyValue.php ├── Ip.php ├── Ipv4.php ├── Ipv6.php ├── Json.php ├── Lowercase.php ├── Max.php ├── Mimes.php ├── Min.php ├── NotIn.php ├── Nullable.php ├── Numeric.php ├── Present.php ├── Regex.php ├── Required.php ├── RequiredIf.php ├── RequiredUnless.php ├── RequiredWith.php ├── RequiredWithAll.php ├── RequiredWithout.php ├── RequiredWithoutAll.php ├── Same.php ├── Traits │ ├── DateUtilsTrait.php │ ├── FileTrait.php │ └── SizeTrait.php ├── TypeArray.php ├── UploadedFile.php ├── Uppercase.php └── Url.php ├── Traits ├── MessagesTrait.php └── TranslationsTrait.php ├── Validation.php └── Validator.php /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.php] 4 | indent_size = 4 5 | indent_style = space 6 | end_of_line = lf 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Exclude unused files 2 | /tests export-ignore 3 | /phpunit.xml.dist export-ignore 4 | /.travis.yml export-ignore 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | composer.lock 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-2019 Muhammad Syifa 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Rakit Validation - PHP Standalone Validation Library 2 | ====================================================== 3 | 4 | [![Build Status](https://img.shields.io/travis/rakit/validation.svg?style=flat-square)](https://travis-ci.org/rakit/validation) 5 | [![Coverage Status](https://coveralls.io/repos/github/rakit/validation/badge.svg?branch=setup_coveralls)](https://coveralls.io/github/rakit/validation) 6 | [![License](http://img.shields.io/:license-mit-blue.svg?style=flat-square)](http://doge.mit-license.org) 7 | 8 | 9 | PHP Standalone library for validating data. Inspired by `Illuminate\Validation` Laravel. 10 | 11 | ## Features 12 | 13 | * API like Laravel validation. 14 | * Array validation. 15 | * `$_FILES` validation with multiple file support. 16 | * Custom attribute aliases. 17 | * Custom validation messages. 18 | * Custom rule. 19 | 20 | ## Requirements 21 | 22 | * PHP 7.0 or higher 23 | * Composer for installation 24 | 25 | ## Quick Start 26 | 27 | #### Installation 28 | 29 | ``` 30 | composer require "rakit/validation" 31 | ``` 32 | 33 | #### Usage 34 | 35 | There are two ways to validating data with this library. Using `make` to make validation object, 36 | then validate it using `validate`. Or just use `validate`. 37 | Examples: 38 | 39 | Using `make`: 40 | 41 | ```php 42 | make($_POST + $_FILES, [ 52 | 'name' => 'required', 53 | 'email' => 'required|email', 54 | 'password' => 'required|min:6', 55 | 'confirm_password' => 'required|same:password', 56 | 'avatar' => 'required|uploaded_file:0,500K,png,jpeg', 57 | 'skills' => 'array', 58 | 'skills.*.id' => 'required|numeric', 59 | 'skills.*.percentage' => 'required|numeric' 60 | ]); 61 | 62 | // then validate 63 | $validation->validate(); 64 | 65 | if ($validation->fails()) { 66 | // handling errors 67 | $errors = $validation->errors(); 68 | echo "
";
  69 |     print_r($errors->firstOfAll());
  70 |     echo "
"; 71 | exit; 72 | } else { 73 | // validation passes 74 | echo "Success!"; 75 | } 76 | 77 | ``` 78 | 79 | or just `validate` it: 80 | 81 | ```php 82 | validate($_POST + $_FILES, [ 91 | 'name' => 'required', 92 | 'email' => 'required|email', 93 | 'password' => 'required|min:6', 94 | 'confirm_password' => 'required|same:password', 95 | 'avatar' => 'required|uploaded_file:0,500K,png,jpeg', 96 | 'skills' => 'array', 97 | 'skills.*.id' => 'required|numeric', 98 | 'skills.*.percentage' => 'required|numeric' 99 | ]); 100 | 101 | if ($validation->fails()) { 102 | // handling errors 103 | $errors = $validation->errors(); 104 | echo "
";
 105 | 	print_r($errors->firstOfAll());
 106 | 	echo "
"; 107 | exit; 108 | } else { 109 | // validation passes 110 | echo "Success!"; 111 | } 112 | 113 | ``` 114 | 115 | In this case, 2 examples above will output the same results. 116 | 117 | But with `make` you can setup something like custom invalid message, custom attribute alias, etc before validation running. 118 | 119 | ### Attribute Alias 120 | 121 | By default we will transform your attribute into more readable text. For example `confirm_password` will be displayed as `Confirm password`. 122 | But you can set it anything you want with `setAlias` or `setAliases` method. 123 | 124 | Example: 125 | 126 | ```php 127 | $validator = new Validator; 128 | 129 | // To set attribute alias, you should use `make` instead `validate`. 130 | $validation->make([ 131 | 'province_id' => $_POST['province_id'], 132 | 'district_id' => $_POST['district_id'] 133 | ], [ 134 | 'province_id' => 'required|numeric', 135 | 'district_id' => 'required|numeric' 136 | ]); 137 | 138 | // now you can set aliases using this way: 139 | $validation->setAlias('province_id', 'Province'); 140 | $validation->setAlias('district_id', 'District'); 141 | 142 | // or this way: 143 | $validation->setAliases([ 144 | 'province_id' => 'Province', 145 | 'district_id' => 'District' 146 | ]); 147 | 148 | // then validate it 149 | $validation->validate(); 150 | 151 | ``` 152 | 153 | Now if `province_id` value is empty, error message would be 'Province is required'. 154 | 155 | ## Custom Validation Message 156 | 157 | Before register/set custom messages, here are some variables you can use in your custom messages: 158 | 159 | * `:attribute`: will replaced into attribute alias. 160 | * `:value`: will replaced into stringify value of attribute. For array and object will replaced to json. 161 | 162 | And also there are several message variables depends on their rules. 163 | 164 | Here are some ways to register/set your custom message(s): 165 | 166 | #### Custom Messages for Validator 167 | 168 | With this way, anytime you make validation using `make` or `validate` it will set your custom messages for it. 169 | It is useful for localization. 170 | 171 | To do this, you can set custom messages as first argument constructor like this: 172 | 173 | ```php 174 | $validator = new Validator([ 175 | 'required' => ':attribute harus diisi', 176 | 'email' => ':email tidak valid', 177 | // etc 178 | ]); 179 | 180 | // then validation belows will use those custom messages 181 | $validation_a = $validator->validate($dataset_a, $rules_for_a); 182 | $validation_b = $validator->validate($dataset_b, $rules_for_b); 183 | 184 | ``` 185 | 186 | Or using `setMessages` method like this: 187 | 188 | ```php 189 | $validator = new Validator; 190 | $validator->setMessages([ 191 | 'required' => ':attribute harus diisi', 192 | 'email' => ':email tidak valid', 193 | // etc 194 | ]); 195 | 196 | // now validation belows will use those custom messages 197 | $validation_a = $validator->validate($dataset_a, $rules_for_dataset_a); 198 | $validation_b = $validator->validate($dataset_b, $rules_for_dataset_b); 199 | 200 | ``` 201 | 202 | #### Custom Messages for Validation 203 | 204 | Sometimes you may want to set custom messages for specific validation. 205 | To do this you can set your custom messages as 3rd argument of `$validator->make` or `$validator->validate` like this: 206 | 207 | ```php 208 | $validator = new Validator; 209 | 210 | $validation_a = $validator->validate($dataset_a, $rules_for_dataset_a, [ 211 | 'required' => ':attribute harus diisi', 212 | 'email' => ':email tidak valid', 213 | // etc 214 | ]); 215 | 216 | ``` 217 | 218 | Or you can use `$validation->setMessages` like this: 219 | 220 | ```php 221 | $validator = new Validator; 222 | 223 | $validation_a = $validator->make($dataset_a, $rules_for_dataset_a); 224 | $validation_a->setMessages([ 225 | 'required' => ':attribute harus diisi', 226 | 'email' => ':email tidak valid', 227 | // etc 228 | ]); 229 | 230 | ... 231 | 232 | $validation_a->validate(); 233 | ``` 234 | 235 | #### Custom Message for Specific Attribute Rule 236 | 237 | Sometimes you may want to set custom message for specific rule attribute. 238 | To do this you can use `:` as message separator or using chaining methods. 239 | 240 | Examples: 241 | 242 | ```php 243 | $validator = new Validator; 244 | 245 | $validation_a = $validator->make($dataset_a, [ 246 | 'age' => 'required|min:18' 247 | ]); 248 | 249 | $validation_a->setMessages([ 250 | 'age:min' => '18+ only', 251 | ]); 252 | 253 | $validation_a->validate(); 254 | ``` 255 | 256 | Or using chaining methods: 257 | 258 | ```php 259 | $validator = new Validator; 260 | 261 | $validation_a = $validator->make($dataset_a, [ 262 | 'photo' => [ 263 | 'required', 264 | $validator('uploaded_file')->fileTypes('jpeg|png')->message('Photo must be jpeg/png image') 265 | ] 266 | ]); 267 | 268 | $validation_a->validate(); 269 | ``` 270 | 271 | ## Translation 272 | 273 | Translation is different with custom messages. 274 | Translation may needed when you use custom message for rule `in`, `not_in`, `mimes`, and `uploaded_file`. 275 | 276 | For example if you use rule `in:1,2,3` we will set invalid message like "The Attribute only allows '1', '2', or '3'" 277 | where part "'1', '2', or '3'" is comes from ":allowed_values" tag. 278 | So if you have custom Indonesian message ":attribute hanya memperbolehkan :allowed_values", 279 | we will set invalid message like "Attribute hanya memperbolehkan '1', '2', or '3'" which is the "or" word is not part of Indonesian language. 280 | 281 | So, to solve this problem, we can use translation like this: 282 | 283 | ```php 284 | // Set translation for words 'and' and 'or'. 285 | $validator->setTranslations([ 286 | 'and' => 'dan', 287 | 'or' => 'atau' 288 | ]); 289 | 290 | // Set custom message for 'in' rule 291 | $validator->setMessage('in', ":attribute hanya memperbolehkan :allowed_values"); 292 | 293 | // Validate 294 | $validation = $validator->validate($inputs, [ 295 | 'nomor' => 'in:1,2,3' 296 | ]); 297 | 298 | $message = $validation->errors()->first('nomor'); // "Nomor hanya memperbolehkan '1', '2', atau '3'" 299 | ``` 300 | 301 | > Actually, our built-in rules only use words 'and' and 'or' that you may need to translates. 302 | 303 | ## Working with Error Message 304 | 305 | Errors messages are collected in `Rakit\Validation\ErrorBag` object that you can get it using `errors()` method. 306 | 307 | ```php 308 | $validation = $validator->validate($inputs, $rules); 309 | 310 | $errors = $validation->errors(); // << ErrorBag 311 | ``` 312 | 313 | Now you can use methods below to retrieves errors messages: 314 | 315 | #### `all(string $format = ':message')` 316 | 317 | Get all messages as flatten array. 318 | 319 | Examples: 320 | 321 | ```php 322 | $messages = $errors->all(); 323 | // [ 324 | // 'Email is not valid email', 325 | // 'Password minimum 6 character', 326 | // 'Password must contains capital letters' 327 | // ] 328 | 329 | $messages = $errors->all('
  • :message
  • '); 330 | // [ 331 | // '
  • Email is not valid email
  • ', 332 | // '
  • Password minimum 6 character
  • ', 333 | // '
  • Password must contains capital letters
  • ' 334 | // ] 335 | ``` 336 | 337 | #### `firstOfAll(string $format = ':message', bool $dotNotation = false)` 338 | 339 | Get only first message from all existing keys. 340 | 341 | Examples: 342 | 343 | ```php 344 | $messages = $errors->firstOfAll(); 345 | // [ 346 | // 'email' => Email is not valid email', 347 | // 'password' => 'Password minimum 6 character', 348 | // ] 349 | 350 | $messages = $errors->firstOfAll('
  • :message
  • '); 351 | // [ 352 | // 'email' => '
  • Email is not valid email
  • ', 353 | // 'password' => '
  • Password minimum 6 character
  • ', 354 | // ] 355 | ``` 356 | 357 | Argument `$dotNotation` is for array validation. 358 | If it is `false` it will return original array structure, if it `true` it will return flatten array with dot notation keys. 359 | 360 | For example: 361 | 362 | ```php 363 | $messages = $errors->firstOfAll(':message', false); 364 | // [ 365 | // 'contacts' => [ 366 | // 1 => [ 367 | // 'email' => 'Email is not valid email', 368 | // 'phone' => 'Phone is not valid phone number' 369 | // ], 370 | // ], 371 | // ] 372 | 373 | $messages = $errors->firstOfAll(':message', true); 374 | // [ 375 | // 'contacts.1.email' => 'Email is not valid email', 376 | // 'contacts.1.phone' => 'Email is not valid phone number', 377 | // ] 378 | ``` 379 | 380 | #### `first(string $key)` 381 | 382 | Get first message from given key. It will return `string` if key has any error message, or `null` if key has no errors. 383 | 384 | For example: 385 | 386 | ```php 387 | if ($emailError = $errors->first('email')) { 388 | echo $emailError; 389 | } 390 | ``` 391 | 392 | #### `toArray()` 393 | 394 | Get all messages grouped by it's keys. 395 | 396 | For example: 397 | 398 | ```php 399 | $messages = $errors->toArray(); 400 | // [ 401 | // 'email' => [ 402 | // 'Email is not valid email' 403 | // ], 404 | // 'password' => [ 405 | // 'Password minimum 6 character', 406 | // 'Password must contains capital letters' 407 | // ] 408 | // ] 409 | ``` 410 | 411 | #### `count()` 412 | 413 | Get count messages. 414 | 415 | #### `has(string $key)` 416 | 417 | Check if given key has an error. It returns `bool` if a key has an error, and otherwise. 418 | 419 | 420 | ## Getting Validated, Valid, and Invalid Data 421 | 422 | For example you have validation like this: 423 | 424 | ```php 425 | $validation = $validator->validate([ 426 | 'title' => 'Lorem Ipsum', 427 | 'body' => 'Lorem ipsum dolor sit amet ...', 428 | 'published' => null, 429 | 'something' => '-invalid-' 430 | ], [ 431 | 'title' => 'required', 432 | 'body' => 'required', 433 | 'published' => 'default:1|required|in:0,1', 434 | 'something' => 'required|numeric' 435 | ]); 436 | ``` 437 | 438 | You can get validated data, valid data, or invalid data using methods in example below: 439 | 440 | ```php 441 | $validatedData = $validation->getValidatedData(); 442 | // [ 443 | // 'title' => 'Lorem Ipsum', 444 | // 'body' => 'Lorem ipsum dolor sit amet ...', 445 | // 'published' => '1' // notice this 446 | // 'something' => '-invalid-' 447 | // ] 448 | 449 | $validData = $validation->getValidData(); 450 | // [ 451 | // 'title' => 'Lorem Ipsum', 452 | // 'body' => 'Lorem ipsum dolor sit amet ...', 453 | // 'published' => '1' 454 | // ] 455 | 456 | $invalidData = $validation->getInvalidData(); 457 | // [ 458 | // 'something' => '-invalid-' 459 | // ] 460 | ``` 461 | 462 | ## Available Rules 463 | 464 | > Click to show details. 465 | 466 |
    required 467 | 468 | The field under this validation must be present and not 'empty'. 469 | 470 | Here are some examples: 471 | 472 | | Value | Valid | 473 | | ------------- | ----- | 474 | | `'something'` | true | 475 | | `'0'` | true | 476 | | `0` | true | 477 | | `[0]` | true | 478 | | `[null]` | true | 479 | | null | false | 480 | | [] | false | 481 | | '' | false | 482 | 483 | For uploaded file, `$_FILES['key']['error']` must not `UPLOAD_ERR_NO_FILE`. 484 | 485 |
    486 | 487 |
    required_if:another_field,value_1,value_2,... 488 | 489 | The field under this rule must be present and not empty if the anotherfield field is equal to any value. 490 | 491 | For example `required_if:something,1,yes,on` will be required if `something` value is one of `1`, `'1'`, `'yes'`, or `'on'`. 492 | 493 |
    494 | 495 |
    required_unless:another_field,value_1,value_2,... 496 | 497 | The field under validation must be present and not empty unless the anotherfield field is equal to any value. 498 | 499 |
    500 | 501 |
    required_with:field_1,field_2,... 502 | 503 | The field under validation must be present and not empty only if any of the other specified fields are present. 504 | 505 |
    506 | 507 |
    required_without:field_1,field_2,... 508 | 509 | The field under validation must be present and not empty only when any of the other specified fields are not present. 510 | 511 |
    512 | 513 |
    required_with_all:field_1,field_2,... 514 | 515 | The field under validation must be present and not empty only if all of the other specified fields are present. 516 | 517 |
    518 | 519 |
    required_without_all:field_1,field_2,... 520 | 521 | The field under validation must be present and not empty only when all of the other specified fields are not present. 522 | 523 |
    524 | 525 |
    uploaded_file:min_size,max_size,extension_a,extension_b,... 526 | 527 | This rule will validate data from `$_FILES`. 528 | Field under this rule must be follows rules below to be valid: 529 | 530 | * `$_FILES['key']['error']` must be `UPLOAD_ERR_OK` or `UPLOAD_ERR_NO_FILE`. For `UPLOAD_ERR_NO_FILE` you can validate it with `required` rule. 531 | * If min size is given, uploaded file size **MUST NOT** be lower than min size. 532 | * If max size is given, uploaded file size **MUST NOT** be higher than max size. 533 | * If file types is given, mime type must be one of those given types. 534 | 535 | Here are some example definitions and explanations: 536 | 537 | * `uploaded_file`: uploaded file is optional. When it is not empty, it must be `ERR_UPLOAD_OK`. 538 | * `required|uploaded_file`: uploaded file is required, and it must be `ERR_UPLOAD_OK`. 539 | * `uploaded_file:0,1M`: uploaded file size must be between 0 - 1 MB, but uploaded file is optional. 540 | * `required|uploaded_file:0,1M,png,jpeg`: uploaded file size must be between 0 - 1MB and mime types must be `image/jpeg` or `image/png`. 541 | 542 | Optionally, if you want to have separate error message between size and type validation. 543 | You can use `mimes` rule to validate file types, and `min`, `max`, or `between` to validate it's size. 544 | 545 | For multiple file upload, PHP will give you undesirable array `$_FILES` structure ([here](http://php.net/manual/en/features.file-upload.multiple.php#53240) is the topic). So we make `uploaded_file` rule to automatically resolve your `$_FILES` value to be well-organized array structure. That means, you cannot only use `min`, `max`, `between`, or `mimes` rules to validate multiple file upload. You should put `uploaded_file` just to resolve it's value and make sure that value is correct uploaded file value. 546 | 547 | For example if you have input files like this: 548 | 549 | ```html 550 | 551 | 552 | 553 | ``` 554 | 555 | You can simply validate it like this: 556 | 557 | ```php 558 | $validation = $validator->validate($_FILES, [ 559 | 'photos.*' => 'uploaded_file:0,2M,jpeg,png' 560 | ]); 561 | 562 | // or 563 | 564 | $validation = $validator->validate($_FILES, [ 565 | 'photos.*' => 'uploaded_file|max:2M|mimes:jpeg,png' 566 | ]); 567 | ``` 568 | 569 | Or if you have input files like this: 570 | 571 | ```html 572 | 573 | 574 | ``` 575 | 576 | You can validate it like this: 577 | 578 | ```php 579 | $validation = $validator->validate($_FILES, [ 580 | 'images.*' => 'uploaded_file|max:2M|mimes:jpeg,png', 581 | ]); 582 | 583 | // or 584 | 585 | $validation = $validator->validate($_FILES, [ 586 | 'images.profile' => 'uploaded_file|max:2M|mimes:jpeg,png', 587 | 'images.cover' => 'uploaded_file|max:5M|mimes:jpeg,png', 588 | ]); 589 | ``` 590 | 591 | Now when you use `getValidData()` or `getInvalidData()` you will get well array structure just like single file upload. 592 | 593 |
    594 | 595 |
    mimes:extension_a,extension_b,... 596 | 597 | The `$_FILES` item under validation must have a MIME type corresponding to one of the listed extensions. 598 | 599 |
    600 | 601 |
    default/defaults 602 | 603 | This is special rule that doesn't validate anything. 604 | It just set default value to your attribute if that attribute is empty or not present. 605 | 606 | For example if you have validation like this 607 | 608 | ```php 609 | $validation = $validator->validate([ 610 | 'enabled' => null 611 | ], [ 612 | 'enabled' => 'default:1|required|in:0,1' 613 | 'published' => 'default:0|required|in:0,1' 614 | ]); 615 | 616 | $validation->passes(); // true 617 | 618 | // Get the valid/default data 619 | $valid_data = $validation->getValidData(); 620 | 621 | $enabled = $valid_data['enabled']; 622 | $published = $valid_data['published']; 623 | ``` 624 | 625 | Validation passes because we sets default value for `enabled` and `published` to `1` and `0` which is valid. Then we can get the valid/default data. 626 | 627 |
    628 | 629 |
    email 630 | 631 | The field under this validation must be valid email address. 632 | 633 |
    634 | 635 |
    uppercase 636 | 637 | The field under this validation must be valid uppercase. 638 | 639 |
    640 | 641 |
    lowercase 642 | 643 | The field under this validation must be valid lowercase. 644 | 645 |
    646 | 647 |
    json 648 | 649 | The field under this validation must be valid JSON string. 650 | 651 |
    652 | 653 |
    alpha 654 | 655 | The field under this rule must be entirely alphabetic characters. 656 | 657 |
    658 | 659 |
    numeric 660 | 661 | The field under this rule must be numeric. 662 | 663 |
    664 | 665 |
    alpha_num 666 | 667 | The field under this rule must be entirely alpha-numeric characters. 668 | 669 |
    670 | 671 |
    alpha_dash 672 | 673 | The field under this rule may have alpha-numeric characters, as well as dashes and underscores. 674 | 675 |
    676 | 677 |
    alpha_spaces 678 | 679 | The field under this rule may have alpha characters, as well as spaces. 680 | 681 |
    682 | 683 |
    in:value_1,value_2,... 684 | 685 | The field under this rule must be included in the given list of values. 686 | 687 | This rule is using `in_array` to check the value. 688 | By default `in_array` disable strict checking. 689 | So it doesn't check data type. 690 | If you want enable strict checking, you can invoke validator like this: 691 | 692 | ```php 693 | $validation = $validator->validate($data, [ 694 | 'enabled' => [ 695 | 'required', 696 | $validator('in', [true, 1])->strict() 697 | ] 698 | ]); 699 | ``` 700 | 701 | Then 'enabled' value should be boolean `true`, or int `1`. 702 | 703 |
    704 | 705 |
    not_in:value_1,value_2,... 706 | 707 | The field under this rule must not be included in the given list of values. 708 | 709 | This rule also using `in_array`. You can enable strict checking by invoking validator and call `strict()` like example in rule `in` above. 710 | 711 |
    712 | 713 |
    min:number 714 | 715 | The field under this rule must have a size greater or equal than the given number. 716 | 717 | For string value, size corresponds to the number of characters. For integer or float value, size corresponds to its numerical value. For an array, size corresponds to the count of the array. If your value is numeric string, you can put `numeric` rule to treat its size by numeric value instead of number of characters. 718 | 719 | You can also validate uploaded file using this rule to validate minimum size of uploaded file. 720 | For example: 721 | 722 | ```php 723 | $validation = $validator->validate([ 724 | 'photo' => $_FILES['photo'] 725 | ], [ 726 | 'photo' => 'required|min:1M' 727 | ]); 728 | ``` 729 | 730 |
    731 | 732 |
    max:number 733 | 734 | The field under this rule must have a size lower or equal than the given number. 735 | Value size calculated in same way like `min` rule. 736 | 737 | You can also validate uploaded file using this rule to validate maximum size of uploaded file. 738 | For example: 739 | 740 | ```php 741 | $validation = $validator->validate([ 742 | 'photo' => $_FILES['photo'] 743 | ], [ 744 | 'photo' => 'required|max:2M' 745 | ]); 746 | ``` 747 | 748 |
    749 | 750 |
    between:min,max 751 | 752 | The field under this rule must have a size between min and max params. 753 | Value size calculated in same way like `min` and `max` rule. 754 | 755 | You can also validate uploaded file using this rule to validate size of uploaded file. 756 | For example: 757 | 758 | ```php 759 | $validation = $validator->validate([ 760 | 'photo' => $_FILES['photo'] 761 | ], [ 762 | 'photo' => 'required|between:1M,2M' 763 | ]); 764 | ``` 765 | 766 |
    767 | 768 |
    digits:value 769 | 770 | The field under validation must be numeric and must have an exact length of `value`. 771 | 772 |
    773 | 774 |
    digits_between:min,max 775 | 776 | The field under validation must have a length between the given `min` and `max`. 777 | 778 |
    779 | 780 |
    url 781 | 782 | The field under this rule must be valid url format. 783 | By default it check common URL scheme format like `any_scheme://...`. 784 | But you can specify URL schemes if you want. 785 | 786 | For example: 787 | 788 | ```php 789 | $validation = $validator->validate($inputs, [ 790 | 'random_url' => 'url', // value can be `any_scheme://...` 791 | 'https_url' => 'url:http', // value must be started with `https://` 792 | 'http_url' => 'url:http,https', // value must be started with `http://` or `https://` 793 | 'ftp_url' => 'url:ftp', // value must be started with `ftp://` 794 | 'custom_url' => 'url:custom', // value must be started with `custom://` 795 | 'mailto_url' => 'url:mailto', // value must conatin valid mailto URL scheme like `mailto:a@mail.com,b@mail.com` 796 | 'jdbc_url' => 'url:jdbc', // value must contain valid jdbc URL scheme like `jdbc:mysql://localhost/dbname` 797 | ]); 798 | ``` 799 | 800 | > For common URL scheme and mailto, we combine `FILTER_VALIDATE_URL` to validate URL format and `preg_match` to validate it's scheme. 801 | Except for JDBC URL, currently it just check a valid JDBC scheme. 802 | 803 |
    804 | 805 |
    integer 806 | The field under t rule must be integer. 807 | 808 |
    809 | 810 |
    boolean 811 | 812 | The field under this rule must be boolean. Accepted input are `true`, `false`, `1`, `0`, `"1"`, and `"0"`. 813 | 814 |
    815 | 816 |
    ip 817 | 818 | The field under this rule must be valid ipv4 or ipv6. 819 | 820 |
    821 | 822 |
    ipv4 823 | 824 | The field under this rule must be valid ipv4. 825 | 826 |
    827 | 828 |
    ipv6 829 | 830 | The field under this rule must be valid ipv6. 831 | 832 |
    833 | 834 |
    extension:extension_a,extension_b,... 835 | 836 | The field under this rule must end with an extension corresponding to one of those listed. 837 | 838 | This is useful for validating a file type for a given a path or url. The `mimes` rule should be used for validating uploads. 839 | 840 |
    841 | 842 |
    array 843 | 844 | The field under this rule must be array. 845 | 846 |
    847 | 848 |
    same:another_field 849 | 850 | The field value under this rule must be same with `another_field` value. 851 | 852 |
    853 | 854 |
    regex:/your-regex/ 855 | 856 | The field under this rule must be match with given regex. 857 | 858 |
    859 | 860 |
    date:format 861 | 862 | The field under this rule must be valid date format. Parameter `format` is optional, default format is `Y-m-d`. 863 | 864 |
    865 | 866 |
    accepted 867 | 868 | The field under this rule must be one of `'on'`, `'yes'`, `'1'`, `'true'`, or `true`. 869 | 870 |
    871 | 872 |
    present 873 | 874 | The field under this rule must be exists, whatever the value is. 875 | 876 |
    877 | 878 |
    different:another_field 879 | 880 | Opposite of `same`. The field value under this rule must be different with `another_field` value. 881 | 882 |
    883 | 884 |
    after:tomorrow 885 | 886 | Anything that can be parsed by `strtotime` can be passed as a parameter to this rule. Valid examples include : 887 | - after:next week 888 | - after:2016-12-31 889 | - after:2016 890 | - after:2016-12-31 09:56:02 891 | 892 |
    893 | 894 |
    before:yesterday 895 | 896 | This also works the same way as the [after rule](#after). Pass anything that can be parsed by `strtotime` 897 | 898 |
    899 | 900 |
    callback 901 | 902 | You can use this rule to define your own validation rule. 903 | This rule can't be registered using string pipe. 904 | To use this rule, you should put Closure inside array of rules. 905 | 906 | For example: 907 | 908 | ```php 909 | $validation = $validator->validate($_POST, [ 910 | 'even_number' => [ 911 | 'required', 912 | function ($value) { 913 | // false = invalid 914 | return (is_numeric($value) AND $value % 2 === 0); 915 | } 916 | ] 917 | ]); 918 | ``` 919 | 920 | You can set invalid message by returning a string. 921 | For example, example above would be: 922 | 923 | ```php 924 | $validation = $validator->validate($_POST, [ 925 | 'even_number' => [ 926 | 'required', 927 | function ($value) { 928 | if (!is_numeric($value)) { 929 | return ":attribute must be numeric."; 930 | } 931 | if ($value % 2 !== 0) { 932 | return ":attribute is not even number."; 933 | } 934 | // you can return true or don't return anything if value is valid 935 | } 936 | ] 937 | ]); 938 | ``` 939 | 940 | > Note: `Rakit\Validation\Rules\Callback` instance is binded into your Closure. 941 | So you can access rule properties and methods using `$this`. 942 | 943 |
    944 | 945 |
    nullable 946 | 947 | Field under this rule may be empty. 948 | 949 |
    950 | 951 | ## Register/Override Rule 952 | 953 | Another way to use custom validation rule is to create a class extending `Rakit\Validation\Rule`. 954 | Then register it using `setValidator` or `addValidator`. 955 | 956 | For example, you want to create `unique` validator that check field availability from database. 957 | 958 | First, lets create `UniqueRule` class: 959 | 960 | ```php 961 | pdo = $pdo; 976 | } 977 | 978 | public function check($value): bool 979 | { 980 | // make sure required parameters exists 981 | $this->requireParameters(['table', 'column']); 982 | 983 | // getting parameters 984 | $column = $this->parameter('column'); 985 | $table = $this->parameter('table'); 986 | $except = $this->parameter('except'); 987 | 988 | if ($except AND $except == $value) { 989 | return true; 990 | } 991 | 992 | // do query 993 | $stmt = $this->pdo->prepare("select count(*) as count from `{$table}` where `{$column}` = :value"); 994 | $stmt->bindParam(':value', $value); 995 | $stmt->execute(); 996 | $data = $stmt->fetch(PDO::FETCH_ASSOC); 997 | 998 | // true for valid, false for invalid 999 | return intval($data['count']) === 0; 1000 | } 1001 | } 1002 | 1003 | ``` 1004 | 1005 | Then you need to register `UniqueRule` instance into validator like this: 1006 | 1007 | ```php 1008 | use Rakit\Validation\Validator; 1009 | 1010 | $validator = new Validator; 1011 | 1012 | $validator->addValidator('unique', new UniqueRule($pdo)); 1013 | ``` 1014 | 1015 | Now you can use it like this: 1016 | 1017 | ```php 1018 | $validation = $validator->validate($_POST, [ 1019 | 'email' => 'email|unique:users,email,exception@mail.com' 1020 | ]); 1021 | ``` 1022 | 1023 | In `UniqueRule` above, property `$message` is used for default invalid message. And property `$fillable_params` is used for `fillParameters` method (defined in `Rakit\Validation\Rule` class). By default `fillParameters` will fill parameters listed in `$fillable_params`. For example `unique:users,email,exception@mail.com` in example above, will set: 1024 | 1025 | ```php 1026 | $params['table'] = 'users'; 1027 | $params['column'] = 'email'; 1028 | $params['except'] = 'exception@mail.com'; 1029 | ``` 1030 | 1031 | > If you want your custom rule accept parameter list like `in`,`not_in`, or `uploaded_file` rules, 1032 | you just need to override `fillParameters(array $params)` method in your custom rule class. 1033 | 1034 | Note that `unique` rule that we created above also can be used like this: 1035 | 1036 | ```php 1037 | $validation = $validator->validate($_POST, [ 1038 | 'email' => [ 1039 | 'required', 'email', 1040 | $validator('unique', 'users', 'email')->message('Custom message') 1041 | ] 1042 | ]); 1043 | ``` 1044 | 1045 | So you can improve `UniqueRule` class above by adding some methods that returning its own instance like this: 1046 | 1047 | ```php 1048 | params['table'] = $table; 1059 | return $this; 1060 | } 1061 | 1062 | public function column($column) 1063 | { 1064 | $this->params['column'] = $column; 1065 | return $this; 1066 | } 1067 | 1068 | public function except($value) 1069 | { 1070 | $this->params['except'] = $value; 1071 | return $this; 1072 | } 1073 | 1074 | ... 1075 | } 1076 | 1077 | ``` 1078 | 1079 | Then you can use it in more funky way like this: 1080 | 1081 | ```php 1082 | $validation = $validator->validate($_POST, [ 1083 | 'email' => [ 1084 | 'required', 'email', 1085 | $validator('unique')->table('users')->column('email')->except('exception@mail.com')->message('Custom message') 1086 | ] 1087 | ]); 1088 | ``` 1089 | 1090 | #### Implicit Rule 1091 | 1092 | Implicit rule is a rule that if it's invalid, then next rules will be ignored. For example if attribute didn't pass `required*` rules, mostly it's next rules will also be invalids. So to prevent our next rules messages to get collected, we make `required*` rules to be implicit. 1093 | 1094 | To make your custom rule implicit, you can make `$implicit` property value to be `true`. For example: 1095 | 1096 | ```php 1097 | getAttribute(); // Rakit\Validation\Attribute instance 1159 | $validation = $this->validation; // Rakit\Validation\Validation instance 1160 | 1161 | // Do something with $attribute and $validation 1162 | // For example change attribute value 1163 | $validation->setValue($attribute->getKey(), "your custom value"); 1164 | } 1165 | 1166 | ... 1167 | } 1168 | ``` 1169 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rakit/validation", 3 | "description": "PHP Laravel like standalone validation library", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "email": "emsifa@gmail.com", 8 | "name": "Muhammad Syifa" 9 | } 10 | ], 11 | "autoload": { 12 | "psr-4": { 13 | "Rakit\\Validation\\": "src" 14 | } 15 | }, 16 | "autoload-dev": { 17 | "psr-4": { 18 | "Rakit\\Validation\\Tests\\": ["tests", "tests/Fixtures"] 19 | } 20 | }, 21 | "require": { 22 | "php": ">=7.0", 23 | "ext-mbstring": "*" 24 | }, 25 | "require-dev": { 26 | "phpunit/phpunit": "^6.5", 27 | "squizlabs/php_codesniffer": "^3", 28 | "php-coveralls/php-coveralls": "^2.2" 29 | }, 30 | "scripts": { 31 | "test": [ 32 | "phpunit", 33 | "phpcs" 34 | ] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Rakit validation coding standard 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | src 16 | tests 17 | -------------------------------------------------------------------------------- /src/Attribute.php: -------------------------------------------------------------------------------- 1 | validation = $validation; 48 | $this->alias = $alias; 49 | $this->key = $key; 50 | foreach ($rules as $rule) { 51 | $this->addRule($rule); 52 | } 53 | } 54 | 55 | /** 56 | * Set the primary attribute 57 | * 58 | * @param \Rakit\Validation\Attribute $primaryAttribute 59 | * @return void 60 | */ 61 | public function setPrimaryAttribute(Attribute $primaryAttribute) 62 | { 63 | $this->primaryAttribute = $primaryAttribute; 64 | } 65 | 66 | /** 67 | * Set key indexes 68 | * 69 | * @param array $keyIndexes 70 | * @return void 71 | */ 72 | public function setKeyIndexes(array $keyIndexes) 73 | { 74 | $this->keyIndexes = $keyIndexes; 75 | } 76 | 77 | /** 78 | * Get primary attributes 79 | * 80 | * @return \Rakit\Validation\Attribute|null 81 | */ 82 | public function getPrimaryAttribute() 83 | { 84 | return $this->primaryAttribute; 85 | } 86 | 87 | /** 88 | * Set other attributes 89 | * 90 | * @param array $otherAttributes 91 | * @return void 92 | */ 93 | public function setOtherAttributes(array $otherAttributes) 94 | { 95 | $this->otherAttributes = []; 96 | foreach ($otherAttributes as $otherAttribute) { 97 | $this->addOtherAttribute($otherAttribute); 98 | } 99 | } 100 | 101 | /** 102 | * Add other attributes 103 | * 104 | * @param \Rakit\Validation\Attribute $otherAttribute 105 | * @return void 106 | */ 107 | public function addOtherAttribute(Attribute $otherAttribute) 108 | { 109 | $this->otherAttributes[] = $otherAttribute; 110 | } 111 | 112 | /** 113 | * Get other attributes 114 | * 115 | * @return array 116 | */ 117 | public function getOtherAttributes(): array 118 | { 119 | return $this->otherAttributes; 120 | } 121 | 122 | /** 123 | * Add rule 124 | * 125 | * @param \Rakit\Validation\Rule $rule 126 | * @return void 127 | */ 128 | public function addRule(Rule $rule) 129 | { 130 | $rule->setAttribute($this); 131 | $rule->setValidation($this->validation); 132 | $this->rules[$rule->getKey()] = $rule; 133 | } 134 | 135 | /** 136 | * Get rule 137 | * 138 | * @param string $ruleKey 139 | * @return void 140 | */ 141 | public function getRule(string $ruleKey) 142 | { 143 | return $this->hasRule($ruleKey)? $this->rules[$ruleKey] : null; 144 | } 145 | 146 | /** 147 | * Get rules 148 | * 149 | * @return array 150 | */ 151 | public function getRules(): array 152 | { 153 | return $this->rules; 154 | } 155 | 156 | /** 157 | * Check the $ruleKey has in the rule 158 | * 159 | * @param string $ruleKey 160 | * @return bool 161 | */ 162 | public function hasRule(string $ruleKey): bool 163 | { 164 | return isset($this->rules[$ruleKey]); 165 | } 166 | 167 | /** 168 | * Set required 169 | * 170 | * @param boolean $required 171 | * @return void 172 | */ 173 | public function setRequired(bool $required) 174 | { 175 | $this->required = $required; 176 | } 177 | 178 | /** 179 | * Set rule is required 180 | * 181 | * @return boolean 182 | */ 183 | public function isRequired(): bool 184 | { 185 | return $this->required; 186 | } 187 | 188 | /** 189 | * Get key 190 | * 191 | * @return string 192 | */ 193 | public function getKey(): string 194 | { 195 | return $this->key; 196 | } 197 | 198 | /** 199 | * Get key indexes 200 | * 201 | * @return array 202 | */ 203 | public function getKeyIndexes(): array 204 | { 205 | return $this->keyIndexes; 206 | } 207 | 208 | /** 209 | * Get value 210 | * 211 | * @param string|null $key 212 | * @return mixed 213 | */ 214 | public function getValue(string $key = null) 215 | { 216 | if ($key && $this->isArrayAttribute()) { 217 | $key = $this->resolveSiblingKey($key); 218 | } 219 | 220 | if (!$key) { 221 | $key = $this->getKey(); 222 | } 223 | 224 | return $this->validation->getValue($key); 225 | } 226 | 227 | /** 228 | * Get that is array attribute 229 | * 230 | * @return boolean 231 | */ 232 | public function isArrayAttribute(): bool 233 | { 234 | return count($this->getKeyIndexes()) > 0; 235 | } 236 | 237 | /** 238 | * Check this attribute is using dot notation 239 | * 240 | * @return boolean 241 | */ 242 | public function isUsingDotNotation(): bool 243 | { 244 | return strpos($this->getKey(), '.') !== false; 245 | } 246 | 247 | /** 248 | * Resolve sibling key 249 | * 250 | * @param string $key 251 | * @return string 252 | */ 253 | public function resolveSiblingKey(string $key): string 254 | { 255 | $indexes = $this->getKeyIndexes(); 256 | $keys = explode("*", $key); 257 | $countAsterisks = count($keys) - 1; 258 | if (count($indexes) < $countAsterisks) { 259 | $indexes = array_merge($indexes, array_fill(0, $countAsterisks - count($indexes), "*")); 260 | } 261 | $args = array_merge([str_replace("*", "%s", $key)], $indexes); 262 | return call_user_func_array('sprintf', $args); 263 | } 264 | 265 | /** 266 | * Get humanize key 267 | * 268 | * @return string 269 | */ 270 | public function getHumanizedKey() 271 | { 272 | $primaryAttribute = $this->getPrimaryAttribute(); 273 | $key = str_replace('_', ' ', $this->key); 274 | 275 | // Resolve key from array validation 276 | if ($primaryAttribute) { 277 | $split = explode('.', $key); 278 | $key = implode(' ', array_map(function ($word) { 279 | if (is_numeric($word)) { 280 | $word = $word + 1; 281 | } 282 | return Helper::snakeCase($word, ' '); 283 | }, $split)); 284 | } 285 | 286 | return ucfirst($key); 287 | } 288 | 289 | /** 290 | * Set alias 291 | * 292 | * @param string $alias 293 | * @return void 294 | */ 295 | public function setAlias(string $alias) 296 | { 297 | $this->alias = $alias; 298 | } 299 | 300 | /** 301 | * Get alias 302 | * 303 | * @return string|null 304 | */ 305 | public function getAlias() 306 | { 307 | return $this->alias; 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /src/ErrorBag.php: -------------------------------------------------------------------------------- 1 | messages = $messages; 20 | } 21 | 22 | /** 23 | * Add message for given key and rule 24 | * 25 | * @param string $key 26 | * @param string $rule 27 | * @param string $message 28 | * @return void 29 | */ 30 | public function add(string $key, string $rule, string $message) 31 | { 32 | if (!isset($this->messages[$key])) { 33 | $this->messages[$key] = []; 34 | } 35 | 36 | $this->messages[$key][$rule] = $message; 37 | } 38 | 39 | /** 40 | * Get messages count 41 | * 42 | * @return int 43 | */ 44 | public function count(): int 45 | { 46 | return count($this->all()); 47 | } 48 | 49 | /** 50 | * Check given key is existed 51 | * 52 | * @param string $key 53 | * @return bool 54 | */ 55 | public function has(string $key): bool 56 | { 57 | list($key, $ruleName) = $this->parsekey($key); 58 | if ($this->isWildcardKey($key)) { 59 | $messages = $this->filterMessagesForWildcardKey($key, $ruleName); 60 | return count(Helper::arrayDot($messages)) > 0; 61 | } else { 62 | $messages = isset($this->messages[$key])? $this->messages[$key] : null; 63 | 64 | if (!$ruleName) { 65 | return !empty($messages); 66 | } else { 67 | return !empty($messages) and isset($messages[$ruleName]); 68 | } 69 | } 70 | } 71 | 72 | /** 73 | * Get the first value of array 74 | * 75 | * @param string $key 76 | * @return mixed 77 | */ 78 | public function first(string $key) 79 | { 80 | list($key, $ruleName) = $this->parsekey($key); 81 | if ($this->isWildcardKey($key)) { 82 | $messages = $this->filterMessagesForWildcardKey($key, $ruleName); 83 | $flattenMessages = Helper::arrayDot($messages); 84 | return array_shift($flattenMessages); 85 | } else { 86 | $keyMessages = isset($this->messages[$key])? $this->messages[$key] : []; 87 | 88 | if (empty($keyMessages)) { 89 | return null; 90 | } 91 | 92 | if ($ruleName) { 93 | return isset($keyMessages[$ruleName])? $keyMessages[$ruleName] : null; 94 | } else { 95 | return array_shift($keyMessages); 96 | } 97 | } 98 | } 99 | 100 | /** 101 | * Get messages from given key, can be use custom format 102 | * 103 | * @param string $key 104 | * @param string $format 105 | * @return array 106 | */ 107 | public function get(string $key, string $format = ':message'): array 108 | { 109 | list($key, $ruleName) = $this->parsekey($key); 110 | $results = []; 111 | if ($this->isWildcardKey($key)) { 112 | $messages = $this->filterMessagesForWildcardKey($key, $ruleName); 113 | foreach ($messages as $explicitKey => $keyMessages) { 114 | foreach ($keyMessages as $rule => $message) { 115 | $results[$explicitKey][$rule] = $this->formatMessage($message, $format); 116 | } 117 | } 118 | } else { 119 | $keyMessages = isset($this->messages[$key])? $this->messages[$key] : []; 120 | foreach ($keyMessages as $rule => $message) { 121 | if ($ruleName and $ruleName != $rule) { 122 | continue; 123 | } 124 | $results[$rule] = $this->formatMessage($message, $format); 125 | } 126 | } 127 | 128 | return $results; 129 | } 130 | 131 | /** 132 | * Get all messages 133 | * 134 | * @param string $format 135 | * @return array 136 | */ 137 | public function all(string $format = ':message'): array 138 | { 139 | $messages = $this->messages; 140 | $results = []; 141 | foreach ($messages as $key => $keyMessages) { 142 | foreach ($keyMessages as $message) { 143 | $results[] = $this->formatMessage($message, $format); 144 | } 145 | } 146 | return $results; 147 | } 148 | 149 | /** 150 | * Get the first message from existing keys 151 | * 152 | * @param string $format 153 | * @param boolean $dotNotation 154 | * @return array 155 | */ 156 | public function firstOfAll(string $format = ':message', bool $dotNotation = false): array 157 | { 158 | $messages = $this->messages; 159 | $results = []; 160 | foreach ($messages as $key => $keyMessages) { 161 | if ($dotNotation) { 162 | $results[$key] = $this->formatMessage(array_shift($messages[$key]), $format); 163 | } else { 164 | Helper::arraySet($results, $key, $this->formatMessage(array_shift($messages[$key]), $format)); 165 | } 166 | } 167 | return $results; 168 | } 169 | 170 | /** 171 | * Get plain array messages 172 | * 173 | * @return array 174 | */ 175 | public function toArray(): array 176 | { 177 | return $this->messages; 178 | } 179 | 180 | /** 181 | * Parse $key to get the array of $key and $ruleName 182 | * 183 | * @param string $key 184 | * @return array 185 | */ 186 | protected function parseKey(string $key): array 187 | { 188 | $expl = explode(':', $key, 2); 189 | $key = $expl[0]; 190 | $ruleName = isset($expl[1])? $expl[1] : null; 191 | return [$key, $ruleName]; 192 | } 193 | 194 | /** 195 | * Check the $key is wildcard 196 | * 197 | * @param mixed $key 198 | * @return bool 199 | */ 200 | protected function isWildcardKey(string $key): bool 201 | { 202 | return false !== strpos($key, '*'); 203 | } 204 | 205 | /** 206 | * Filter messages with wildcard key 207 | * 208 | * @param string $key 209 | * @param mixed $ruleName 210 | * @return array 211 | */ 212 | protected function filterMessagesForWildcardKey(string $key, $ruleName = null): array 213 | { 214 | $messages = $this->messages; 215 | $pattern = preg_quote($key, '#'); 216 | $pattern = str_replace('\*', '.*', $pattern); 217 | 218 | $filteredMessages = []; 219 | 220 | foreach ($messages as $k => $keyMessages) { 221 | if ((bool) preg_match('#^'.$pattern.'\z#u', $k) === false) { 222 | continue; 223 | } 224 | 225 | foreach ($keyMessages as $rule => $message) { 226 | if ($ruleName and $rule != $ruleName) { 227 | continue; 228 | } 229 | $filteredMessages[$k][$rule] = $message; 230 | } 231 | } 232 | 233 | return $filteredMessages; 234 | } 235 | 236 | /** 237 | * Get formatted message 238 | * 239 | * @param string $message 240 | * @param string $format 241 | * @return string 242 | */ 243 | protected function formatMessage(string $message, string $format): string 244 | { 245 | return str_replace(':message', $message, $format); 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/Helper.php: -------------------------------------------------------------------------------- 1 | $value) { 100 | if (is_array($value) && ! empty($value)) { 101 | $results = array_merge($results, static::arrayDot($value, $prepend.$key.'.')); 102 | } else { 103 | $results[$prepend.$key] = $value; 104 | } 105 | } 106 | 107 | return $results; 108 | } 109 | 110 | /** 111 | * Set an item on an array or object using dot notation. 112 | * Adapted from: https://github.com/illuminate/support/blob/v5.3.23/helpers.php#L437 113 | * 114 | * @param mixed $target 115 | * @param string|array|null $key 116 | * @param mixed $value 117 | * @param bool $overwrite 118 | * @return mixed 119 | */ 120 | public static function arraySet(&$target, $key, $value, $overwrite = true): array 121 | { 122 | if (is_null($key)) { 123 | if ($overwrite) { 124 | return $target = array_merge($target, $value); 125 | } 126 | return $target = array_merge($value, $target); 127 | } 128 | 129 | $segments = is_array($key) ? $key : explode('.', $key); 130 | 131 | if (($segment = array_shift($segments)) === '*') { 132 | if (! is_array($target)) { 133 | $target = []; 134 | } 135 | 136 | if ($segments) { 137 | foreach ($target as &$inner) { 138 | static::arraySet($inner, $segments, $value, $overwrite); 139 | } 140 | } elseif ($overwrite) { 141 | foreach ($target as &$inner) { 142 | $inner = $value; 143 | } 144 | } 145 | } elseif (is_array($target)) { 146 | if ($segments) { 147 | if (! array_key_exists($segment, $target)) { 148 | $target[$segment] = []; 149 | } 150 | 151 | static::arraySet($target[$segment], $segments, $value, $overwrite); 152 | } elseif ($overwrite || ! array_key_exists($segment, $target)) { 153 | $target[$segment] = $value; 154 | } 155 | } else { 156 | $target = []; 157 | 158 | if ($segments) { 159 | static::arraySet($target[$segment], $segments, $value, $overwrite); 160 | } elseif ($overwrite) { 161 | $target[$segment] = $value; 162 | } 163 | } 164 | 165 | return $target; 166 | } 167 | 168 | /** 169 | * Unset an item on an array or object using dot notation. 170 | * 171 | * @param mixed $target 172 | * @param string|array $key 173 | * @return mixed 174 | */ 175 | public static function arrayUnset(&$target, $key) 176 | { 177 | if (!is_array($target)) { 178 | return $target; 179 | } 180 | 181 | $segments = is_array($key) ? $key : explode('.', $key); 182 | $segment = array_shift($segments); 183 | 184 | if ($segment == '*') { 185 | $target = []; 186 | } elseif ($segments) { 187 | if (array_key_exists($segment, $target)) { 188 | static::arrayUnset($target[$segment], $segments); 189 | } 190 | } elseif (array_key_exists($segment, $target)) { 191 | unset($target[$segment]); 192 | } 193 | 194 | return $target; 195 | } 196 | 197 | /** 198 | * Get snake_case format from given string 199 | * 200 | * @param string $value 201 | * @param string $delimiter 202 | * @return string 203 | */ 204 | public static function snakeCase(string $value, string $delimiter = '_'): string 205 | { 206 | if (! ctype_lower($value)) { 207 | $value = preg_replace('/\s+/u', '', ucwords($value)); 208 | $value = strtolower(preg_replace('/(.)(?=[A-Z])/u', '$1'.$delimiter, $value)); 209 | } 210 | 211 | return $value; 212 | } 213 | 214 | /** 215 | * Join string[] to string with given $separator and $lastSeparator. 216 | * 217 | * @param array $pieces 218 | * @param string $separator 219 | * @param string|null $lastSeparator 220 | * @return string 221 | */ 222 | public static function join(array $pieces, string $separator, string $lastSeparator = null): string 223 | { 224 | if (is_null($lastSeparator)) { 225 | $lastSeparator = $separator; 226 | } 227 | 228 | $last = array_pop($pieces); 229 | 230 | switch (count($pieces)) { 231 | case 0: 232 | return $last ?: ''; 233 | case 1: 234 | return $pieces[0] . $lastSeparator . $last; 235 | default: 236 | return implode($separator, $pieces) . $lastSeparator . $last; 237 | } 238 | } 239 | 240 | /** 241 | * Wrap string[] by given $prefix and $suffix 242 | * 243 | * @param array $strings 244 | * @param string $prefix 245 | * @param string|null $suffix 246 | * @return array 247 | */ 248 | public static function wraps(array $strings, string $prefix, string $suffix = null): array 249 | { 250 | if (is_null($suffix)) { 251 | $suffix = $prefix; 252 | } 253 | 254 | return array_map(function ($str) use ($prefix, $suffix) { 255 | return $prefix . $str . $suffix; 256 | }, $strings); 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /src/MimeTypeGuesser.php: -------------------------------------------------------------------------------- 1 | 'ez', 11 | 'application/applixware' => 'aw', 12 | 'application/atom+xml' => 'atom', 13 | 'application/atomcat+xml' => 'atomcat', 14 | 'application/atomsvc+xml' => 'atomsvc', 15 | 'application/ccxml+xml' => 'ccxml', 16 | 'application/cdmi-capability' => 'cdmia', 17 | 'application/cdmi-container' => 'cdmic', 18 | 'application/cdmi-domain' => 'cdmid', 19 | 'application/cdmi-object' => 'cdmio', 20 | 'application/cdmi-queue' => 'cdmiq', 21 | 'application/cu-seeme' => 'cu', 22 | 'application/davmount+xml' => 'davmount', 23 | 'application/docbook+xml' => 'dbk', 24 | 'application/dssc+der' => 'dssc', 25 | 'application/dssc+xml' => 'xdssc', 26 | 'application/ecmascript' => 'ecma', 27 | 'application/emma+xml' => 'emma', 28 | 'application/epub+zip' => 'epub', 29 | 'application/exi' => 'exi', 30 | 'application/font-tdpfr' => 'pfr', 31 | 'application/gml+xml' => 'gml', 32 | 'application/gpx+xml' => 'gpx', 33 | 'application/gxf' => 'gxf', 34 | 'application/hyperstudio' => 'stk', 35 | 'application/inkml+xml' => 'ink', 36 | 'application/ipfix' => 'ipfix', 37 | 'application/java-archive' => 'jar', 38 | 'application/java-serialized-object' => 'ser', 39 | 'application/java-vm' => 'class', 40 | 'application/javascript' => 'js', 41 | 'application/json' => 'json', 42 | 'application/jsonml+json' => 'jsonml', 43 | 'application/lost+xml' => 'lostxml', 44 | 'application/mac-binhex40' => 'hqx', 45 | 'application/mac-compactpro' => 'cpt', 46 | 'application/mads+xml' => 'mads', 47 | 'application/marc' => 'mrc', 48 | 'application/marcxml+xml' => 'mrcx', 49 | 'application/mathematica' => 'ma', 50 | 'application/mathml+xml' => 'mathml', 51 | 'application/mbox' => 'mbox', 52 | 'application/mediaservercontrol+xml' => 'mscml', 53 | 'application/metalink+xml' => 'metalink', 54 | 'application/metalink4+xml' => 'meta4', 55 | 'application/mets+xml' => 'mets', 56 | 'application/mods+xml' => 'mods', 57 | 'application/mp21' => 'm21', 58 | 'application/mp4' => 'mp4s', 59 | 'application/msword' => 'doc', 60 | 'application/mxf' => 'mxf', 61 | 'application/octet-stream' => 'bin', 62 | 'application/oda' => 'oda', 63 | 'application/oebps-package+xml' => 'opf', 64 | 'application/ogg' => 'ogx', 65 | 'application/omdoc+xml' => 'omdoc', 66 | 'application/onenote' => 'onetoc', 67 | 'application/oxps' => 'oxps', 68 | 'application/patch-ops-error+xml' => 'xer', 69 | 'application/pdf' => 'pdf', 70 | 'application/pgp-encrypted' => 'pgp', 71 | 'application/pgp-signature' => 'asc', 72 | 'application/pics-rules' => 'prf', 73 | 'application/pkcs10' => 'p10', 74 | 'application/pkcs7-mime' => 'p7m', 75 | 'application/pkcs7-signature' => 'p7s', 76 | 'application/pkcs8' => 'p8', 77 | 'application/pkix-attr-cert' => 'ac', 78 | 'application/pkix-cert' => 'cer', 79 | 'application/pkix-crl' => 'crl', 80 | 'application/pkix-pkipath' => 'pkipath', 81 | 'application/pkixcmp' => 'pki', 82 | 'application/pls+xml' => 'pls', 83 | 'application/postscript' => 'ai', 84 | 'application/prs.cww' => 'cww', 85 | 'application/pskc+xml' => 'pskcxml', 86 | 'application/rdf+xml' => 'rdf', 87 | 'application/reginfo+xml' => 'rif', 88 | 'application/relax-ng-compact-syntax' => 'rnc', 89 | 'application/resource-lists+xml' => 'rl', 90 | 'application/resource-lists-diff+xml' => 'rld', 91 | 'application/rls-services+xml' => 'rs', 92 | 'application/rpki-ghostbusters' => 'gbr', 93 | 'application/rpki-manifest' => 'mft', 94 | 'application/rpki-roa' => 'roa', 95 | 'application/rsd+xml' => 'rsd', 96 | 'application/rss+xml' => 'rss', 97 | 'application/rtf' => 'rtf', 98 | 'application/sbml+xml' => 'sbml', 99 | 'application/scvp-cv-request' => 'scq', 100 | 'application/scvp-cv-response' => 'scs', 101 | 'application/scvp-vp-request' => 'spq', 102 | 'application/scvp-vp-response' => 'spp', 103 | 'application/sdp' => 'sdp', 104 | 'application/set-payment-initiation' => 'setpay', 105 | 'application/set-registration-initiation' => 'setreg', 106 | 'application/shf+xml' => 'shf', 107 | 'application/smil+xml' => 'smi', 108 | 'application/sparql-query' => 'rq', 109 | 'application/sparql-results+xml' => 'srx', 110 | 'application/srgs' => 'gram', 111 | 'application/srgs+xml' => 'grxml', 112 | 'application/sru+xml' => 'sru', 113 | 'application/ssdl+xml' => 'ssdl', 114 | 'application/ssml+xml' => 'ssml', 115 | 'application/tei+xml' => 'tei', 116 | 'application/thraud+xml' => 'tfi', 117 | 'application/timestamped-data' => 'tsd', 118 | 'application/vnd.3gpp.pic-bw-large' => 'plb', 119 | 'application/vnd.3gpp.pic-bw-small' => 'psb', 120 | 'application/vnd.3gpp.pic-bw-var' => 'pvb', 121 | 'application/vnd.3gpp2.tcap' => 'tcap', 122 | 'application/vnd.3m.post-it-notes' => 'pwn', 123 | 'application/vnd.accpac.simply.aso' => 'aso', 124 | 'application/vnd.accpac.simply.imp' => 'imp', 125 | 'application/vnd.acucobol' => 'acu', 126 | 'application/vnd.acucorp' => 'atc', 127 | 'application/vnd.adobe.air-application-installer-package+zip' => 'air', 128 | 'application/vnd.adobe.formscentral.fcdt' => 'fcdt', 129 | 'application/vnd.adobe.fxp' => 'fxp', 130 | 'application/vnd.adobe.xdp+xml' => 'xdp', 131 | 'application/vnd.adobe.xfdf' => 'xfdf', 132 | 'application/vnd.ahead.space' => 'ahead', 133 | 'application/vnd.airzip.filesecure.azf' => 'azf', 134 | 'application/vnd.airzip.filesecure.azs' => 'azs', 135 | 'application/vnd.amazon.ebook' => 'azw', 136 | 'application/vnd.americandynamics.acc' => 'acc', 137 | 'application/vnd.amiga.ami' => 'ami', 138 | 'application/vnd.android.package-archive' => 'apk', 139 | 'application/vnd.anser-web-certificate-issue-initiation' => 'cii', 140 | 'application/vnd.anser-web-funds-transfer-initiation' => 'fti', 141 | 'application/vnd.antix.game-component' => 'atx', 142 | 'application/vnd.apple.installer+xml' => 'mpkg', 143 | 'application/vnd.apple.mpegurl' => 'm3u8', 144 | 'application/vnd.aristanetworks.swi' => 'swi', 145 | 'application/vnd.astraea-software.iota' => 'iota', 146 | 'application/vnd.audiograph' => 'aep', 147 | 'application/vnd.blueice.multipass' => 'mpm', 148 | 'application/vnd.bmi' => 'bmi', 149 | 'application/vnd.businessobjects' => 'rep', 150 | 'application/vnd.chemdraw+xml' => 'cdxml', 151 | 'application/vnd.chipnuts.karaoke-mmd' => 'mmd', 152 | 'application/vnd.cinderella' => 'cdy', 153 | 'application/vnd.claymore' => 'cla', 154 | 'application/vnd.cloanto.rp9' => 'rp9', 155 | 'application/vnd.clonk.c4group' => 'c4g', 156 | 'application/vnd.cluetrust.cartomobile-config' => 'c11amc', 157 | 'application/vnd.cluetrust.cartomobile-config-pkg' => 'c11amz', 158 | 'application/vnd.commonspace' => 'csp', 159 | 'application/vnd.contact.cmsg' => 'cdbcmsg', 160 | 'application/vnd.cosmocaller' => 'cmc', 161 | 'application/vnd.crick.clicker' => 'clkx', 162 | 'application/vnd.crick.clicker.keyboard' => 'clkk', 163 | 'application/vnd.crick.clicker.palette' => 'clkp', 164 | 'application/vnd.crick.clicker.template' => 'clkt', 165 | 'application/vnd.crick.clicker.wordbank' => 'clkw', 166 | 'application/vnd.criticaltools.wbs+xml' => 'wbs', 167 | 'application/vnd.ctc-posml' => 'pml', 168 | 'application/vnd.cups-ppd' => 'ppd', 169 | 'application/vnd.curl.car' => 'car', 170 | 'application/vnd.curl.pcurl' => 'pcurl', 171 | 'application/vnd.dart' => 'dart', 172 | 'application/vnd.data-vision.rdz' => 'rdz', 173 | 'application/vnd.dece.data' => 'uvf', 174 | 'application/vnd.dece.ttml+xml' => 'uvt', 175 | 'application/vnd.dece.unspecified' => 'uvx', 176 | 'application/vnd.dece.zip' => 'uvz', 177 | 'application/vnd.denovo.fcselayout-link' => 'fe_launch', 178 | 'application/vnd.dna' => 'dna', 179 | 'application/vnd.dolby.mlp' => 'mlp', 180 | 'application/vnd.dpgraph' => 'dpg', 181 | 'application/vnd.dreamfactory' => 'dfac', 182 | 'application/vnd.ds-keypoint' => 'kpxx', 183 | 'application/vnd.dvb.ait' => 'ait', 184 | 'application/vnd.dvb.service' => 'svc', 185 | 'application/vnd.dynageo' => 'geo', 186 | 'application/vnd.ecowin.chart' => 'mag', 187 | 'application/vnd.enliven' => 'nml', 188 | 'application/vnd.epson.esf' => 'esf', 189 | 'application/vnd.epson.msf' => 'msf', 190 | 'application/vnd.epson.quickanime' => 'qam', 191 | 'application/vnd.epson.salt' => 'slt', 192 | 'application/vnd.epson.ssf' => 'ssf', 193 | 'application/vnd.eszigno3+xml' => 'es3', 194 | 'application/vnd.ezpix-album' => 'ez2', 195 | 'application/vnd.ezpix-package' => 'ez3', 196 | 'application/vnd.fdf' => 'fdf', 197 | 'application/vnd.fdsn.mseed' => 'mseed', 198 | 'application/vnd.fdsn.seed' => 'seed', 199 | 'application/vnd.flographit' => 'gph', 200 | 'application/vnd.fluxtime.clip' => 'ftc', 201 | 'application/vnd.framemaker' => 'fm', 202 | 'application/vnd.frogans.fnc' => 'fnc', 203 | 'application/vnd.frogans.ltf' => 'ltf', 204 | 'application/vnd.fsc.weblaunch' => 'fsc', 205 | 'application/vnd.fujitsu.oasys' => 'oas', 206 | 'application/vnd.fujitsu.oasys2' => 'oa2', 207 | 'application/vnd.fujitsu.oasys3' => 'oa3', 208 | 'application/vnd.fujitsu.oasysgp' => 'fg5', 209 | 'application/vnd.fujitsu.oasysprs' => 'bh2', 210 | 'application/vnd.fujixerox.ddd' => 'ddd', 211 | 'application/vnd.fujixerox.docuworks' => 'xdw', 212 | 'application/vnd.fujixerox.docuworks.binder' => 'xbd', 213 | 'application/vnd.fuzzysheet' => 'fzs', 214 | 'application/vnd.genomatix.tuxedo' => 'txd', 215 | 'application/vnd.geogebra.file' => 'ggb', 216 | 'application/vnd.geogebra.tool' => 'ggt', 217 | 'application/vnd.geometry-explorer' => 'gex', 218 | 'application/vnd.geonext' => 'gxt', 219 | 'application/vnd.geoplan' => 'g2w', 220 | 'application/vnd.geospace' => 'g3w', 221 | 'application/vnd.gmx' => 'gmx', 222 | 'application/vnd.google-earth.kml+xml' => 'kml', 223 | 'application/vnd.google-earth.kmz' => 'kmz', 224 | 'application/vnd.grafeq' => 'gqf', 225 | 'application/vnd.groove-account' => 'gac', 226 | 'application/vnd.groove-help' => 'ghf', 227 | 'application/vnd.groove-identity-message' => 'gim', 228 | 'application/vnd.groove-injector' => 'grv', 229 | 'application/vnd.groove-tool-message' => 'gtm', 230 | 'application/vnd.groove-tool-template' => 'tpl', 231 | 'application/vnd.groove-vcard' => 'vcg', 232 | 'application/vnd.hal+xml' => 'hal', 233 | 'application/vnd.handheld-entertainment+xml' => 'zmm', 234 | 'application/vnd.hbci' => 'hbci', 235 | 'application/vnd.hhe.lesson-player' => 'les', 236 | 'application/vnd.hp-hpgl' => 'hpgl', 237 | 'application/vnd.hp-hpid' => 'hpid', 238 | 'application/vnd.hp-hps' => 'hps', 239 | 'application/vnd.hp-jlyt' => 'jlt', 240 | 'application/vnd.hp-pcl' => 'pcl', 241 | 'application/vnd.hp-pclxl' => 'pclxl', 242 | 'application/vnd.hydrostatix.sof-data' => 'sfd-hdstx', 243 | 'application/vnd.ibm.minipay' => 'mpy', 244 | 'application/vnd.ibm.modcap' => 'afp', 245 | 'application/vnd.ibm.rights-management' => 'irm', 246 | 'application/vnd.ibm.secure-container' => 'sc', 247 | 'application/vnd.iccprofile' => 'icc', 248 | 'application/vnd.igloader' => 'igl', 249 | 'application/vnd.immervision-ivp' => 'ivp', 250 | 'application/vnd.immervision-ivu' => 'ivu', 251 | 'application/vnd.insors.igm' => 'igm', 252 | 'application/vnd.intercon.formnet' => 'xpw', 253 | 'application/vnd.intergeo' => 'i2g', 254 | 'application/vnd.intu.qbo' => 'qbo', 255 | 'application/vnd.intu.qfx' => 'qfx', 256 | 'application/vnd.ipunplugged.rcprofile' => 'rcprofile', 257 | 'application/vnd.irepository.package+xml' => 'irp', 258 | 'application/vnd.is-xpr' => 'xpr', 259 | 'application/vnd.isac.fcs' => 'fcs', 260 | 'application/vnd.jam' => 'jam', 261 | 'application/vnd.jcp.javame.midlet-rms' => 'rms', 262 | 'application/vnd.jisp' => 'jisp', 263 | 'application/vnd.joost.joda-archive' => 'joda', 264 | 'application/vnd.kahootz' => 'ktz', 265 | 'application/vnd.kde.karbon' => 'karbon', 266 | 'application/vnd.kde.kchart' => 'chrt', 267 | 'application/vnd.kde.kformula' => 'kfo', 268 | 'application/vnd.kde.kivio' => 'flw', 269 | 'application/vnd.kde.kontour' => 'kon', 270 | 'application/vnd.kde.kpresenter' => 'kpr', 271 | 'application/vnd.kde.kspread' => 'ksp', 272 | 'application/vnd.kde.kword' => 'kwd', 273 | 'application/vnd.kenameaapp' => 'htke', 274 | 'application/vnd.kidspiration' => 'kia', 275 | 'application/vnd.kinar' => 'kne', 276 | 'application/vnd.koan' => 'skp', 277 | 'application/vnd.kodak-descriptor' => 'sse', 278 | 'application/vnd.las.las+xml' => 'lasxml', 279 | 'application/vnd.llamagraphics.life-balance.desktop' => 'lbd', 280 | 'application/vnd.llamagraphics.life-balance.exchange+xml' => 'lbe', 281 | 'application/vnd.lotus-1-2-3' => '123', 282 | 'application/vnd.lotus-approach' => 'apr', 283 | 'application/vnd.lotus-freelance' => 'pre', 284 | 'application/vnd.lotus-notes' => 'nsf', 285 | 'application/vnd.lotus-organizer' => 'org', 286 | 'application/vnd.lotus-screencam' => 'scm', 287 | 'application/vnd.lotus-wordpro' => 'lwp', 288 | 'application/vnd.macports.portpkg' => 'portpkg', 289 | 'application/vnd.mcd' => 'mcd', 290 | 'application/vnd.medcalcdata' => 'mc1', 291 | 'application/vnd.mediastation.cdkey' => 'cdkey', 292 | 'application/vnd.mfer' => 'mwf', 293 | 'application/vnd.mfmp' => 'mfm', 294 | 'application/vnd.micrografx.flo' => 'flo', 295 | 'application/vnd.micrografx.igx' => 'igx', 296 | 'application/vnd.mif' => 'mif', 297 | 'application/vnd.mobius.daf' => 'daf', 298 | 'application/vnd.mobius.dis' => 'dis', 299 | 'application/vnd.mobius.mbk' => 'mbk', 300 | 'application/vnd.mobius.mqy' => 'mqy', 301 | 'application/vnd.mobius.msl' => 'msl', 302 | 'application/vnd.mobius.plc' => 'plc', 303 | 'application/vnd.mobius.txf' => 'txf', 304 | 'application/vnd.mophun.application' => 'mpn', 305 | 'application/vnd.mophun.certificate' => 'mpc', 306 | 'application/vnd.mozilla.xul+xml' => 'xul', 307 | 'application/vnd.ms-artgalry' => 'cil', 308 | 'application/vnd.ms-cab-compressed' => 'cab', 309 | 'application/vnd.ms-excel' => 'xls', 310 | 'application/vnd.ms-excel.addin.macroenabled.12' => 'xlam', 311 | 'application/vnd.ms-excel.sheet.binary.macroenabled.12' => 'xlsb', 312 | 'application/vnd.ms-excel.sheet.macroenabled.12' => 'xlsm', 313 | 'application/vnd.ms-excel.template.macroenabled.12' => 'xltm', 314 | 'application/vnd.ms-fontobject' => 'eot', 315 | 'application/vnd.ms-htmlhelp' => 'chm', 316 | 'application/vnd.ms-ims' => 'ims', 317 | 'application/vnd.ms-lrm' => 'lrm', 318 | 'application/vnd.ms-officetheme' => 'thmx', 319 | 'application/vnd.ms-pki.seccat' => 'cat', 320 | 'application/vnd.ms-pki.stl' => 'stl', 321 | 'application/vnd.ms-powerpoint' => 'ppt', 322 | 'application/vnd.ms-powerpoint.addin.macroenabled.12' => 'ppam', 323 | 'application/vnd.ms-powerpoint.presentation.macroenabled.12' => 'pptm', 324 | 'application/vnd.ms-powerpoint.slide.macroenabled.12' => 'sldm', 325 | 'application/vnd.ms-powerpoint.slideshow.macroenabled.12' => 'ppsm', 326 | 'application/vnd.ms-powerpoint.template.macroenabled.12' => 'potm', 327 | 'application/vnd.ms-project' => 'mpp', 328 | 'application/vnd.ms-word.document.macroenabled.12' => 'docm', 329 | 'application/vnd.ms-word.template.macroenabled.12' => 'dotm', 330 | 'application/vnd.ms-works' => 'wps', 331 | 'application/vnd.ms-wpl' => 'wpl', 332 | 'application/vnd.ms-xpsdocument' => 'xps', 333 | 'application/vnd.mseq' => 'mseq', 334 | 'application/vnd.musician' => 'mus', 335 | 'application/vnd.muvee.style' => 'msty', 336 | 'application/vnd.mynfc' => 'taglet', 337 | 'application/vnd.neurolanguage.nlu' => 'nlu', 338 | 'application/vnd.nitf' => 'ntf', 339 | 'application/vnd.noblenet-directory' => 'nnd', 340 | 'application/vnd.noblenet-sealer' => 'nns', 341 | 'application/vnd.noblenet-web' => 'nnw', 342 | 'application/vnd.nokia.n-gage.data' => 'ngdat', 343 | 'application/vnd.nokia.n-gage.symbian.install' => 'n-gage', 344 | 'application/vnd.nokia.radio-preset' => 'rpst', 345 | 'application/vnd.nokia.radio-presets' => 'rpss', 346 | 'application/vnd.novadigm.edm' => 'edm', 347 | 'application/vnd.novadigm.edx' => 'edx', 348 | 'application/vnd.novadigm.ext' => 'ext', 349 | 'application/vnd.oasis.opendocument.chart' => 'odc', 350 | 'application/vnd.oasis.opendocument.chart-template' => 'otc', 351 | 'application/vnd.oasis.opendocument.database' => 'odb', 352 | 'application/vnd.oasis.opendocument.formula' => 'odf', 353 | 'application/vnd.oasis.opendocument.formula-template' => 'odft', 354 | 'application/vnd.oasis.opendocument.graphics' => 'odg', 355 | 'application/vnd.oasis.opendocument.graphics-template' => 'otg', 356 | 'application/vnd.oasis.opendocument.image' => 'odi', 357 | 'application/vnd.oasis.opendocument.image-template' => 'oti', 358 | 'application/vnd.oasis.opendocument.presentation' => 'odp', 359 | 'application/vnd.oasis.opendocument.presentation-template' => 'otp', 360 | 'application/vnd.oasis.opendocument.spreadsheet' => 'ods', 361 | 'application/vnd.oasis.opendocument.spreadsheet-template' => 'ots', 362 | 'application/vnd.oasis.opendocument.text' => 'odt', 363 | 'application/vnd.oasis.opendocument.text-master' => 'odm', 364 | 'application/vnd.oasis.opendocument.text-template' => 'ott', 365 | 'application/vnd.oasis.opendocument.text-web' => 'oth', 366 | 'application/vnd.olpc-sugar' => 'xo', 367 | 'application/vnd.oma.dd2+xml' => 'dd2', 368 | 'application/vnd.openofficeorg.extension' => 'oxt', 369 | 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx', 370 | 'application/vnd.openxmlformats-officedocument.presentationml.slide' => 'sldx', 371 | 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => 'ppsx', 372 | 'application/vnd.openxmlformats-officedocument.presentationml.template' => 'potx', 373 | 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx', 374 | 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => 'xltx', 375 | 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx', 376 | 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => 'dotx', 377 | 'application/vnd.osgeo.mapguide.package' => 'mgp', 378 | 'application/vnd.osgi.dp' => 'dp', 379 | 'application/vnd.osgi.subsystem' => 'esa', 380 | 'application/vnd.palm' => 'pdb', 381 | 'application/vnd.pawaafile' => 'paw', 382 | 'application/vnd.pg.format' => 'str', 383 | 'application/vnd.pg.osasli' => 'ei6', 384 | 'application/vnd.picsel' => 'efif', 385 | 'application/vnd.pmi.widget' => 'wg', 386 | 'application/vnd.pocketlearn' => 'plf', 387 | 'application/vnd.powerbuilder6' => 'pbd', 388 | 'application/vnd.previewsystems.box' => 'box', 389 | 'application/vnd.proteus.magazine' => 'mgz', 390 | 'application/vnd.publishare-delta-tree' => 'qps', 391 | 'application/vnd.pvi.ptid1' => 'ptid', 392 | 'application/vnd.quark.quarkxpress' => 'qxd', 393 | 'application/vnd.realvnc.bed' => 'bed', 394 | 'application/vnd.recordare.musicxml' => 'mxl', 395 | 'application/vnd.recordare.musicxml+xml' => 'musicxml', 396 | 'application/vnd.rig.cryptonote' => 'cryptonote', 397 | 'application/vnd.rim.cod' => 'cod', 398 | 'application/vnd.rn-realmedia' => 'rm', 399 | 'application/vnd.rn-realmedia-vbr' => 'rmvb', 400 | 'application/vnd.route66.link66+xml' => 'link66', 401 | 'application/vnd.sailingtracker.track' => 'st', 402 | 'application/vnd.seemail' => 'see', 403 | 'application/vnd.sema' => 'sema', 404 | 'application/vnd.semd' => 'semd', 405 | 'application/vnd.semf' => 'semf', 406 | 'application/vnd.shana.informed.formdata' => 'ifm', 407 | 'application/vnd.shana.informed.formtemplate' => 'itp', 408 | 'application/vnd.shana.informed.interchange' => 'iif', 409 | 'application/vnd.shana.informed.package' => 'ipk', 410 | 'application/vnd.simtech-mindmapper' => 'twd', 411 | 'application/vnd.smaf' => 'mmf', 412 | 'application/vnd.smart.teacher' => 'teacher', 413 | 'application/vnd.solent.sdkm+xml' => 'sdkm', 414 | 'application/vnd.spotfire.dxp' => 'dxp', 415 | 'application/vnd.spotfire.sfs' => 'sfs', 416 | 'application/vnd.stardivision.calc' => 'sdc', 417 | 'application/vnd.stardivision.draw' => 'sda', 418 | 'application/vnd.stardivision.impress' => 'sdd', 419 | 'application/vnd.stardivision.math' => 'smf', 420 | 'application/vnd.stardivision.writer' => 'sdw', 421 | 'application/vnd.stardivision.writer-global' => 'sgl', 422 | 'application/vnd.stepmania.package' => 'smzip', 423 | 'application/vnd.stepmania.stepchart' => 'sm', 424 | 'application/vnd.sun.xml.calc' => 'sxc', 425 | 'application/vnd.sun.xml.calc.template' => 'stc', 426 | 'application/vnd.sun.xml.draw' => 'sxd', 427 | 'application/vnd.sun.xml.draw.template' => 'std', 428 | 'application/vnd.sun.xml.impress' => 'sxi', 429 | 'application/vnd.sun.xml.impress.template' => 'sti', 430 | 'application/vnd.sun.xml.math' => 'sxm', 431 | 'application/vnd.sun.xml.writer' => 'sxw', 432 | 'application/vnd.sun.xml.writer.global' => 'sxg', 433 | 'application/vnd.sun.xml.writer.template' => 'stw', 434 | 'application/vnd.sus-calendar' => 'sus', 435 | 'application/vnd.svd' => 'svd', 436 | 'application/vnd.symbian.install' => 'sis', 437 | 'application/vnd.syncml+xml' => 'xsm', 438 | 'application/vnd.syncml.dm+wbxml' => 'bdm', 439 | 'application/vnd.syncml.dm+xml' => 'xdm', 440 | 'application/vnd.tao.intent-module-archive' => 'tao', 441 | 'application/vnd.tcpdump.pcap' => 'pcap', 442 | 'application/vnd.tmobile-livetv' => 'tmo', 443 | 'application/vnd.trid.tpt' => 'tpt', 444 | 'application/vnd.triscape.mxs' => 'mxs', 445 | 'application/vnd.trueapp' => 'tra', 446 | 'application/vnd.ufdl' => 'ufd', 447 | 'application/vnd.uiq.theme' => 'utz', 448 | 'application/vnd.umajin' => 'umj', 449 | 'application/vnd.unity' => 'unityweb', 450 | 'application/vnd.uoml+xml' => 'uoml', 451 | 'application/vnd.vcx' => 'vcx', 452 | 'application/vnd.visio' => 'vsd', 453 | 'application/vnd.visionary' => 'vis', 454 | 'application/vnd.vsf' => 'vsf', 455 | 'application/vnd.wap.wbxml' => 'wbxml', 456 | 'application/vnd.wap.wmlc' => 'wmlc', 457 | 'application/vnd.wap.wmlscriptc' => 'wmlsc', 458 | 'application/vnd.webturbo' => 'wtb', 459 | 'application/vnd.wolfram.player' => 'nbp', 460 | 'application/vnd.wordperfect' => 'wpd', 461 | 'application/vnd.wqd' => 'wqd', 462 | 'application/vnd.wt.stf' => 'stf', 463 | 'application/vnd.xara' => 'xar', 464 | 'application/vnd.xfdl' => 'xfdl', 465 | 'application/vnd.yamaha.hv-dic' => 'hvd', 466 | 'application/vnd.yamaha.hv-script' => 'hvs', 467 | 'application/vnd.yamaha.hv-voice' => 'hvp', 468 | 'application/vnd.yamaha.openscoreformat' => 'osf', 469 | 'application/vnd.yamaha.openscoreformat.osfpvg+xml' => 'osfpvg', 470 | 'application/vnd.yamaha.smaf-audio' => 'saf', 471 | 'application/vnd.yamaha.smaf-phrase' => 'spf', 472 | 'application/vnd.yellowriver-custom-menu' => 'cmp', 473 | 'application/vnd.zul' => 'zir', 474 | 'application/vnd.zzazz.deck+xml' => 'zaz', 475 | 'application/voicexml+xml' => 'vxml', 476 | 'application/widget' => 'wgt', 477 | 'application/winhlp' => 'hlp', 478 | 'application/wsdl+xml' => 'wsdl', 479 | 'application/wspolicy+xml' => 'wspolicy', 480 | 'application/x-7z-compressed' => '7z', 481 | 'application/x-abiword' => 'abw', 482 | 'application/x-ace-compressed' => 'ace', 483 | 'application/x-apple-diskimage' => 'dmg', 484 | 'application/x-authorware-bin' => 'aab', 485 | 'application/x-authorware-map' => 'aam', 486 | 'application/x-authorware-seg' => 'aas', 487 | 'application/x-bcpio' => 'bcpio', 488 | 'application/x-bittorrent' => 'torrent', 489 | 'application/x-blorb' => 'blb', 490 | 'application/x-bzip' => 'bz', 491 | 'application/x-bzip2' => 'bz2', 492 | 'application/x-cbr' => 'cbr', 493 | 'application/x-cdlink' => 'vcd', 494 | 'application/x-cfs-compressed' => 'cfs', 495 | 'application/x-chat' => 'chat', 496 | 'application/x-chess-pgn' => 'pgn', 497 | 'application/x-conference' => 'nsc', 498 | 'application/x-cpio' => 'cpio', 499 | 'application/x-csh' => 'csh', 500 | 'application/x-debian-package' => 'deb', 501 | 'application/x-dgc-compressed' => 'dgc', 502 | 'application/x-director' => 'dir', 503 | 'application/x-doom' => 'wad', 504 | 'application/x-dtbncx+xml' => 'ncx', 505 | 'application/x-dtbook+xml' => 'dtb', 506 | 'application/x-dtbresource+xml' => 'res', 507 | 'application/x-dvi' => 'dvi', 508 | 'application/x-envoy' => 'evy', 509 | 'application/x-eva' => 'eva', 510 | 'application/x-font-bdf' => 'bdf', 511 | 'application/x-font-ghostscript' => 'gsf', 512 | 'application/x-font-linux-psf' => 'psf', 513 | 'application/x-font-otf' => 'otf', 514 | 'application/x-font-pcf' => 'pcf', 515 | 'application/x-font-snf' => 'snf', 516 | 'application/x-font-ttf' => 'ttf', 517 | 'application/x-font-type1' => 'pfa', 518 | 'application/x-font-woff' => 'woff', 519 | 'application/x-freearc' => 'arc', 520 | 'application/x-futuresplash' => 'spl', 521 | 'application/x-gca-compressed' => 'gca', 522 | 'application/x-glulx' => 'ulx', 523 | 'application/x-gnumeric' => 'gnumeric', 524 | 'application/x-gramps-xml' => 'gramps', 525 | 'application/x-gtar' => 'gtar', 526 | 'application/x-hdf' => 'hdf', 527 | 'application/x-install-instructions' => 'install', 528 | 'application/x-iso9660-image' => 'iso', 529 | 'application/x-java-jnlp-file' => 'jnlp', 530 | 'application/x-latex' => 'latex', 531 | 'application/x-lzh-compressed' => 'lzh', 532 | 'application/x-mie' => 'mie', 533 | 'application/x-mobipocket-ebook' => 'prc', 534 | 'application/x-ms-application' => 'application', 535 | 'application/x-ms-shortcut' => 'lnk', 536 | 'application/x-ms-wmd' => 'wmd', 537 | 'application/x-ms-wmz' => 'wmz', 538 | 'application/x-ms-xbap' => 'xbap', 539 | 'application/x-msaccess' => 'mdb', 540 | 'application/x-msbinder' => 'obd', 541 | 'application/x-mscardfile' => 'crd', 542 | 'application/x-msclip' => 'clp', 543 | 'application/x-msdownload' => 'exe', 544 | 'application/x-msmediaview' => 'mvb', 545 | 'application/x-msmetafile' => 'wmf', 546 | 'application/x-msmoney' => 'mny', 547 | 'application/x-mspublisher' => 'pub', 548 | 'application/x-msschedule' => 'scd', 549 | 'application/x-msterminal' => 'trm', 550 | 'application/x-mswrite' => 'wri', 551 | 'application/x-netcdf' => 'nc', 552 | 'application/x-nzb' => 'nzb', 553 | 'application/x-pkcs12' => 'p12', 554 | 'application/x-pkcs7-certificates' => 'p7b', 555 | 'application/x-pkcs7-certreqresp' => 'p7r', 556 | 'application/x-rar-compressed' => 'rar', 557 | 'application/x-rar' => 'rar', 558 | 'application/x-research-info-systems' => 'ris', 559 | 'application/x-sh' => 'sh', 560 | 'application/x-shar' => 'shar', 561 | 'application/x-shockwave-flash' => 'swf', 562 | 'application/x-silverlight-app' => 'xap', 563 | 'application/x-sql' => 'sql', 564 | 'application/x-stuffit' => 'sit', 565 | 'application/x-stuffitx' => 'sitx', 566 | 'application/x-subrip' => 'srt', 567 | 'application/x-sv4cpio' => 'sv4cpio', 568 | 'application/x-sv4crc' => 'sv4crc', 569 | 'application/x-t3vm-image' => 't3', 570 | 'application/x-tads' => 'gam', 571 | 'application/x-tar' => 'tar', 572 | 'application/x-tcl' => 'tcl', 573 | 'application/x-tex' => 'tex', 574 | 'application/x-tex-tfm' => 'tfm', 575 | 'application/x-texinfo' => 'texinfo', 576 | 'application/x-tgif' => 'obj', 577 | 'application/x-ustar' => 'ustar', 578 | 'application/x-wais-source' => 'src', 579 | 'application/x-x509-ca-cert' => 'der', 580 | 'application/x-xfig' => 'fig', 581 | 'application/x-xliff+xml' => 'xlf', 582 | 'application/x-xpinstall' => 'xpi', 583 | 'application/x-xz' => 'xz', 584 | 'application/x-zmachine' => 'z1', 585 | 'application/xaml+xml' => 'xaml', 586 | 'application/xcap-diff+xml' => 'xdf', 587 | 'application/xenc+xml' => 'xenc', 588 | 'application/xhtml+xml' => 'xhtml', 589 | 'application/xml' => 'xml', 590 | 'application/xml-dtd' => 'dtd', 591 | 'application/xop+xml' => 'xop', 592 | 'application/xproc+xml' => 'xpl', 593 | 'application/xslt+xml' => 'xslt', 594 | 'application/xspf+xml' => 'xspf', 595 | 'application/xv+xml' => 'mxml', 596 | 'application/yang' => 'yang', 597 | 'application/yin+xml' => 'yin', 598 | 'application/zip' => 'zip', 599 | 'audio/adpcm' => 'adp', 600 | 'audio/basic' => 'au', 601 | 'audio/midi' => 'mid', 602 | 'audio/mp3' => 'mp3', 603 | 'audio/mp4' => 'mp4a', 604 | 'audio/mpeg' => 'mpga', 605 | 'audio/ogg' => 'oga', 606 | 'audio/s3m' => 's3m', 607 | 'audio/silk' => 'sil', 608 | 'audio/vnd.dece.audio' => 'uva', 609 | 'audio/vnd.digital-winds' => 'eol', 610 | 'audio/vnd.dra' => 'dra', 611 | 'audio/vnd.dts' => 'dts', 612 | 'audio/vnd.dts.hd' => 'dtshd', 613 | 'audio/vnd.lucent.voice' => 'lvp', 614 | 'audio/vnd.ms-playready.media.pya' => 'pya', 615 | 'audio/vnd.nuera.ecelp4800' => 'ecelp4800', 616 | 'audio/vnd.nuera.ecelp7470' => 'ecelp7470', 617 | 'audio/vnd.nuera.ecelp9600' => 'ecelp9600', 618 | 'audio/vnd.rip' => 'rip', 619 | 'audio/webm' => 'weba', 620 | 'audio/x-aac' => 'aac', 621 | 'audio/x-aiff' => 'aif', 622 | 'audio/x-caf' => 'caf', 623 | 'audio/x-flac' => 'flac', 624 | 'audio/x-matroska' => 'mka', 625 | 'audio/x-mpegurl' => 'm3u', 626 | 'audio/x-ms-wax' => 'wax', 627 | 'audio/x-ms-wma' => 'wma', 628 | 'audio/x-pn-realaudio' => 'ram', 629 | 'audio/x-pn-realaudio-plugin' => 'rmp', 630 | 'audio/x-wav' => 'wav', 631 | 'audio/xm' => 'xm', 632 | 'chemical/x-cdx' => 'cdx', 633 | 'chemical/x-cif' => 'cif', 634 | 'chemical/x-cmdf' => 'cmdf', 635 | 'chemical/x-cml' => 'cml', 636 | 'chemical/x-csml' => 'csml', 637 | 'chemical/x-xyz' => 'xyz', 638 | 'image/bmp' => 'bmp', 639 | 'image/x-ms-bmp' => 'bmp', 640 | 'image/cgm' => 'cgm', 641 | 'image/g3fax' => 'g3', 642 | 'image/gif' => 'gif', 643 | 'image/ief' => 'ief', 644 | 'image/jpeg' => 'jpeg', 645 | 'image/pjpeg' => 'jpeg', 646 | 'image/ktx' => 'ktx', 647 | 'image/png' => 'png', 648 | 'image/prs.btif' => 'btif', 649 | 'image/sgi' => 'sgi', 650 | 'image/svg+xml' => 'svg', 651 | 'image/tiff' => 'tiff', 652 | 'image/vnd.adobe.photoshop' => 'psd', 653 | 'image/vnd.dece.graphic' => 'uvi', 654 | 'image/vnd.dvb.subtitle' => 'sub', 655 | 'image/vnd.djvu' => 'djvu', 656 | 'image/vnd.dwg' => 'dwg', 657 | 'image/vnd.dxf' => 'dxf', 658 | 'image/vnd.fastbidsheet' => 'fbs', 659 | 'image/vnd.fpx' => 'fpx', 660 | 'image/vnd.fst' => 'fst', 661 | 'image/vnd.fujixerox.edmics-mmr' => 'mmr', 662 | 'image/vnd.fujixerox.edmics-rlc' => 'rlc', 663 | 'image/vnd.ms-modi' => 'mdi', 664 | 'image/vnd.ms-photo' => 'wdp', 665 | 'image/vnd.net-fpx' => 'npx', 666 | 'image/vnd.wap.wbmp' => 'wbmp', 667 | 'image/vnd.xiff' => 'xif', 668 | 'image/webp' => 'webp', 669 | 'image/x-3ds' => '3ds', 670 | 'image/x-cmu-raster' => 'ras', 671 | 'image/x-cmx' => 'cmx', 672 | 'image/x-freehand' => 'fh', 673 | 'image/x-icon' => 'ico', 674 | 'image/x-mrsid-image' => 'sid', 675 | 'image/x-pcx' => 'pcx', 676 | 'image/x-pict' => 'pic', 677 | 'image/x-portable-anymap' => 'pnm', 678 | 'image/x-portable-bitmap' => 'pbm', 679 | 'image/x-portable-graymap' => 'pgm', 680 | 'image/x-portable-pixmap' => 'ppm', 681 | 'image/x-rgb' => 'rgb', 682 | 'image/x-tga' => 'tga', 683 | 'image/x-xbitmap' => 'xbm', 684 | 'image/x-xpixmap' => 'xpm', 685 | 'image/x-xwindowdump' => 'xwd', 686 | 'message/rfc822' => 'eml', 687 | 'model/iges' => 'igs', 688 | 'model/mesh' => 'msh', 689 | 'model/vnd.collada+xml' => 'dae', 690 | 'model/vnd.dwf' => 'dwf', 691 | 'model/vnd.gdl' => 'gdl', 692 | 'model/vnd.gtw' => 'gtw', 693 | 'model/vnd.mts' => 'mts', 694 | 'model/vnd.vtu' => 'vtu', 695 | 'model/vrml' => 'wrl', 696 | 'model/x3d+binary' => 'x3db', 697 | 'model/x3d+vrml' => 'x3dv', 698 | 'model/x3d+xml' => 'x3d', 699 | 'text/cache-manifest' => 'appcache', 700 | 'text/calendar' => 'ics', 701 | 'text/css' => 'css', 702 | 'text/csv' => 'csv', 703 | 'text/html' => 'html', 704 | 'text/n3' => 'n3', 705 | 'text/plain' => 'txt', 706 | 'text/prs.lines.tag' => 'dsc', 707 | 'text/richtext' => 'rtx', 708 | 'text/rtf' => 'rtf', 709 | 'text/sgml' => 'sgml', 710 | 'text/tab-separated-values' => 'tsv', 711 | 'text/troff' => 't', 712 | 'text/turtle' => 'ttl', 713 | 'text/uri-list' => 'uri', 714 | 'text/vcard' => 'vcard', 715 | 'text/vnd.curl' => 'curl', 716 | 'text/vnd.curl.dcurl' => 'dcurl', 717 | 'text/vnd.curl.scurl' => 'scurl', 718 | 'text/vnd.curl.mcurl' => 'mcurl', 719 | 'text/vnd.dvb.subtitle' => 'sub', 720 | 'text/vnd.fly' => 'fly', 721 | 'text/vnd.fmi.flexstor' => 'flx', 722 | 'text/vnd.graphviz' => 'gv', 723 | 'text/vnd.in3d.3dml' => '3dml', 724 | 'text/vnd.in3d.spot' => 'spot', 725 | 'text/vnd.sun.j2me.app-descriptor' => 'jad', 726 | 'text/vnd.wap.wml' => 'wml', 727 | 'text/vnd.wap.wmlscript' => 'wmls', 728 | 'text/x-asm' => 's', 729 | 'text/x-c' => 'c', 730 | 'text/x-fortran' => 'f', 731 | 'text/x-pascal' => 'p', 732 | 'text/x-java-source' => 'java', 733 | 'text/x-opml' => 'opml', 734 | 'text/x-nfo' => 'nfo', 735 | 'text/x-setext' => 'etx', 736 | 'text/x-sfv' => 'sfv', 737 | 'text/x-uuencode' => 'uu', 738 | 'text/x-vcalendar' => 'vcs', 739 | 'text/x-vcard' => 'vcf', 740 | 'video/3gpp' => '3gp', 741 | 'video/3gpp2' => '3g2', 742 | 'video/h261' => 'h261', 743 | 'video/h263' => 'h263', 744 | 'video/h264' => 'h264', 745 | 'video/jpeg' => 'jpgv', 746 | 'video/jpm' => 'jpm', 747 | 'video/mj2' => 'mj2', 748 | 'video/mp4' => 'mp4', 749 | 'video/mpeg' => 'mpeg', 750 | 'video/ogg' => 'ogv', 751 | 'video/quicktime' => 'qt', 752 | 'video/vnd.dece.hd' => 'uvh', 753 | 'video/vnd.dece.mobile' => 'uvm', 754 | 'video/vnd.dece.pd' => 'uvp', 755 | 'video/vnd.dece.sd' => 'uvs', 756 | 'video/vnd.dece.video' => 'uvv', 757 | 'video/vnd.dvb.file' => 'dvb', 758 | 'video/vnd.fvt' => 'fvt', 759 | 'video/vnd.mpegurl' => 'mxu', 760 | 'video/vnd.ms-playready.media.pyv' => 'pyv', 761 | 'video/vnd.uvvu.mp4' => 'uvu', 762 | 'video/vnd.vivo' => 'viv', 763 | 'video/webm' => 'webm', 764 | 'video/x-f4v' => 'f4v', 765 | 'video/x-fli' => 'fli', 766 | 'video/x-flv' => 'flv', 767 | 'video/x-m4v' => 'm4v', 768 | 'video/x-matroska' => 'mkv', 769 | 'video/x-mng' => 'mng', 770 | 'video/x-ms-asf' => 'asf', 771 | 'video/x-ms-vob' => 'vob', 772 | 'video/x-ms-wm' => 'wm', 773 | 'video/x-ms-wmv' => 'wmv', 774 | 'video/x-ms-wmx' => 'wmx', 775 | 'video/x-ms-wvx' => 'wvx', 776 | 'video/x-msvideo' => 'avi', 777 | 'video/x-sgi-movie' => 'movie', 778 | 'video/x-smv' => 'smv', 779 | 'x-conference/x-cooltalk' => 'ice' 780 | ]; 781 | 782 | /** 783 | * Get extension by mime type 784 | * 785 | * @param string $mimeType 786 | * @return string|null 787 | */ 788 | public function getExtension(string $mimeType) 789 | { 790 | return isset($this->mimeTypes[$mimeType])? $this->mimeTypes[$mimeType] : null; 791 | } 792 | 793 | /** 794 | * Get mime type by extension 795 | * 796 | * @param string $extension 797 | * @return string|null 798 | */ 799 | public function getMimeType(string $extension) 800 | { 801 | $key = array_search($extension, $this->mimeTypes); 802 | return $key ?: null; 803 | } 804 | } 805 | -------------------------------------------------------------------------------- /src/MissingRequiredParameterException.php: -------------------------------------------------------------------------------- 1 | validation = $validation; 44 | } 45 | 46 | /** 47 | * Set key 48 | * 49 | * @param string $key 50 | * @return void 51 | */ 52 | public function setKey(string $key) 53 | { 54 | $this->key = $key; 55 | } 56 | 57 | /** 58 | * Get key 59 | * 60 | * @return string 61 | */ 62 | public function getKey() 63 | { 64 | return $this->key ?: get_class($this); 65 | } 66 | 67 | /** 68 | * Set attribute 69 | * 70 | * @param \Rakit\Validation\Attribute $attribute 71 | * @return void 72 | */ 73 | public function setAttribute(Attribute $attribute) 74 | { 75 | $this->attribute = $attribute; 76 | } 77 | 78 | /** 79 | * Get attribute 80 | * 81 | * @return \Rakit\Validation\Attribute|null 82 | */ 83 | public function getAttribute() 84 | { 85 | return $this->attribute; 86 | } 87 | 88 | /** 89 | * Get parameters 90 | * 91 | * @return array 92 | */ 93 | public function getParameters(): array 94 | { 95 | return $this->params; 96 | } 97 | 98 | /** 99 | * Set params 100 | * 101 | * @param array $params 102 | * @return \Rakit\Validation\Rule 103 | */ 104 | public function setParameters(array $params): Rule 105 | { 106 | $this->params = array_merge($this->params, $params); 107 | return $this; 108 | } 109 | 110 | /** 111 | * Set parameters 112 | * 113 | * @param string $key 114 | * @param mixed $value 115 | * @return \Rakit\Validation\Rule 116 | */ 117 | public function setParameter(string $key, $value): Rule 118 | { 119 | $this->params[$key] = $value; 120 | return $this; 121 | } 122 | 123 | /** 124 | * Fill $params to $this->params 125 | * 126 | * @param array $params 127 | * @return \Rakit\Validation\Rule 128 | */ 129 | public function fillParameters(array $params): Rule 130 | { 131 | foreach ($this->fillableParams as $key) { 132 | if (empty($params)) { 133 | break; 134 | } 135 | $this->params[$key] = array_shift($params); 136 | } 137 | return $this; 138 | } 139 | 140 | /** 141 | * Get parameter from given $key, return null if it not exists 142 | * 143 | * @param string $key 144 | * @return mixed 145 | */ 146 | public function parameter(string $key) 147 | { 148 | return isset($this->params[$key])? $this->params[$key] : null; 149 | } 150 | 151 | /** 152 | * Set parameter text that can be displayed in error message using ':param_key' 153 | * 154 | * @param string $key 155 | * @param string $text 156 | * @return void 157 | */ 158 | public function setParameterText(string $key, string $text) 159 | { 160 | $this->paramsTexts[$key] = $text; 161 | } 162 | 163 | /** 164 | * Get $paramsTexts 165 | * 166 | * @return array 167 | */ 168 | public function getParametersTexts(): array 169 | { 170 | return $this->paramsTexts; 171 | } 172 | 173 | /** 174 | * Check whether this rule is implicit 175 | * 176 | * @return boolean 177 | */ 178 | public function isImplicit(): bool 179 | { 180 | return $this->implicit; 181 | } 182 | 183 | /** 184 | * Just alias of setMessage 185 | * 186 | * @param string $message 187 | * @return \Rakit\Validation\Rule 188 | */ 189 | public function message(string $message): Rule 190 | { 191 | return $this->setMessage($message); 192 | } 193 | 194 | /** 195 | * Set message 196 | * 197 | * @param string $message 198 | * @return \Rakit\Validation\Rule 199 | */ 200 | public function setMessage(string $message): Rule 201 | { 202 | $this->message = $message; 203 | return $this; 204 | } 205 | 206 | /** 207 | * Get message 208 | * 209 | * @return string 210 | */ 211 | public function getMessage(): string 212 | { 213 | return $this->message; 214 | } 215 | 216 | /** 217 | * Check given $params must be exists 218 | * 219 | * @param array $params 220 | * @return void 221 | * @throws \Rakit\Validation\MissingRequiredParameterException 222 | */ 223 | protected function requireParameters(array $params) 224 | { 225 | foreach ($params as $param) { 226 | if (!isset($this->params[$param])) { 227 | $rule = $this->getKey(); 228 | throw new MissingRequiredParameterException("Missing required parameter '{$param}' on rule '{$rule}'"); 229 | } 230 | } 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/RuleNotFoundException.php: -------------------------------------------------------------------------------- 1 | requireParameters($this->fillableParams); 28 | $time = $this->parameter('time'); 29 | 30 | if (!$this->isValidDate($value)) { 31 | throw $this->throwException($value); 32 | } 33 | 34 | if (!$this->isValidDate($time)) { 35 | throw $this->throwException($time); 36 | } 37 | 38 | return $this->getTimeStamp($time) < $this->getTimeStamp($value); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Rules/Alpha.php: -------------------------------------------------------------------------------- 1 | 0; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Rules/AlphaNum.php: -------------------------------------------------------------------------------- 1 | 0; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Rules/AlphaSpaces.php: -------------------------------------------------------------------------------- 1 | 0; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Rules/Before.php: -------------------------------------------------------------------------------- 1 | requireParameters($this->fillableParams); 27 | $time = $this->parameter('time'); 28 | 29 | if (!$this->isValidDate($value)) { 30 | throw $this->throwException($value); 31 | } 32 | 33 | if (!$this->isValidDate($time)) { 34 | throw $this->throwException($time); 35 | } 36 | 37 | return $this->getTimeStamp($time) > $this->getTimeStamp($value); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Rules/Between.php: -------------------------------------------------------------------------------- 1 | requireParameters($this->fillableParams); 26 | 27 | $min = $this->getBytesSize($this->parameter('min')); 28 | $max = $this->getBytesSize($this->parameter('max')); 29 | 30 | $valueSize = $this->getValueSize($value); 31 | 32 | if (!is_numeric($valueSize)) { 33 | return false; 34 | } 35 | 36 | return ($valueSize >= $min && $valueSize <= $max); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Rules/Boolean.php: -------------------------------------------------------------------------------- 1 | setParameter('callback', $callback); 27 | } 28 | 29 | /** 30 | * Check the $value is valid 31 | * 32 | * @param mixed $value 33 | * @return bool 34 | * @throws \Exception 35 | */ 36 | public function check($value): bool 37 | { 38 | $this->requireParameters($this->fillableParams); 39 | 40 | $callback = $this->parameter('callback'); 41 | if (false === $callback instanceof Closure) { 42 | $key = $this->attribute->getKey(); 43 | throw new InvalidArgumentException("Callback rule for '{$key}' is not callable."); 44 | } 45 | 46 | $callback = $callback->bindTo($this); 47 | $invalidMessage = $callback($value); 48 | 49 | if (is_string($invalidMessage)) { 50 | $this->setMessage($invalidMessage); 51 | return false; 52 | } elseif (false === $invalidMessage) { 53 | return false; 54 | } 55 | 56 | return true; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Rules/Date.php: -------------------------------------------------------------------------------- 1 | 'Y-m-d' 19 | ]; 20 | 21 | /** 22 | * Check the $value is valid 23 | * 24 | * @param mixed $value 25 | * @return bool 26 | */ 27 | public function check($value): bool 28 | { 29 | $this->requireParameters($this->fillableParams); 30 | 31 | $format = $this->parameter('format'); 32 | return date_create_from_format($format, $value) !== false; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Rules/Defaults.php: -------------------------------------------------------------------------------- 1 | requireParameters($this->fillableParams); 26 | 27 | $default = $this->parameter('default'); 28 | return true; 29 | } 30 | 31 | /** 32 | * {@inheritDoc} 33 | */ 34 | public function modifyValue($value) 35 | { 36 | return $this->isEmptyValue($value) ? $this->parameter('default') : $value; 37 | } 38 | 39 | /** 40 | * Check $value is empty value 41 | * 42 | * @param mixed $value 43 | * @return boolean 44 | */ 45 | protected function isEmptyValue($value): bool 46 | { 47 | $requiredValidator = new Required; 48 | return false === $requiredValidator->check($value, []); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Rules/Different.php: -------------------------------------------------------------------------------- 1 | requireParameters($this->fillableParams); 25 | 26 | $field = $this->parameter('field'); 27 | $anotherValue = $this->validation->getValue($field); 28 | 29 | return $value != $anotherValue; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Rules/Digits.php: -------------------------------------------------------------------------------- 1 | requireParameters($this->fillableParams); 25 | 26 | $length = (int) $this->parameter('length'); 27 | 28 | return ! preg_match('/[^0-9]/', $value) 29 | && strlen((string) $value) == $length; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Rules/DigitsBetween.php: -------------------------------------------------------------------------------- 1 | requireParameters($this->fillableParams); 25 | 26 | $min = (int) $this->parameter('min'); 27 | $max = (int) $this->parameter('max'); 28 | 29 | $length = strlen((string) $value); 30 | 31 | return ! preg_match('/[^0-9]/', $value) 32 | && $length >= $min && $length <= $max; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Rules/Email.php: -------------------------------------------------------------------------------- 1 | params 16 | * 17 | * @param array $params 18 | * @return self 19 | */ 20 | public function fillParameters(array $params): Rule 21 | { 22 | if (count($params) == 1 && is_array($params[0])) { 23 | $params = $params[0]; 24 | } 25 | $this->params['allowed_extensions'] = $params; 26 | return $this; 27 | } 28 | 29 | /** 30 | * Check the $value is valid 31 | * 32 | * @param mixed $value 33 | * @return bool 34 | */ 35 | public function check($value): bool 36 | { 37 | $this->requireParameters(['allowed_extensions']); 38 | $allowedExtensions = $this->parameter('allowed_extensions'); 39 | foreach ($allowedExtensions as $key => $ext) { 40 | $allowedExtensions[$key] = ltrim($ext, '.'); 41 | } 42 | 43 | $or = $this->validation ? $this->validation->getTranslation('or') : 'or'; 44 | $allowedExtensionsText = Helper::join(Helper::wraps($allowedExtensions, ".", ""), ', ', ", {$or} "); 45 | $this->setParameterText('allowed_extensions', $allowedExtensionsText); 46 | 47 | $ext = strtolower(pathinfo($value, PATHINFO_EXTENSION)); 48 | return ($ext && in_array($ext, $allowedExtensions)) ? true : false; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Rules/In.php: -------------------------------------------------------------------------------- 1 | params 19 | * 20 | * @param array $params 21 | * @return self 22 | */ 23 | public function fillParameters(array $params): Rule 24 | { 25 | if (count($params) == 1 && is_array($params[0])) { 26 | $params = $params[0]; 27 | } 28 | $this->params['allowed_values'] = $params; 29 | return $this; 30 | } 31 | 32 | /** 33 | * Set strict value 34 | * 35 | * @param bool $strict 36 | * @return void 37 | */ 38 | public function strict(bool $strict = true) 39 | { 40 | $this->strict = $strict; 41 | } 42 | 43 | /** 44 | * Check $value is existed 45 | * 46 | * @param mixed $value 47 | * @return bool 48 | */ 49 | public function check($value): bool 50 | { 51 | $this->requireParameters(['allowed_values']); 52 | 53 | $allowedValues = $this->parameter('allowed_values'); 54 | 55 | $or = $this->validation ? $this->validation->getTranslation('or') : 'or'; 56 | $allowedValuesText = Helper::join(Helper::wraps($allowedValues, "'"), ', ', ", {$or} "); 57 | $this->setParameterText('allowed_values', $allowedValuesText); 58 | 59 | return in_array($value, $allowedValues, $this->strict); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Rules/Integer.php: -------------------------------------------------------------------------------- 1 | requireParameters($this->fillableParams); 26 | 27 | $max = $this->getBytesSize($this->parameter('max')); 28 | $valueSize = $this->getValueSize($value); 29 | 30 | if (!is_numeric($valueSize)) { 31 | return false; 32 | } 33 | 34 | return $valueSize <= $max; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Rules/Mimes.php: -------------------------------------------------------------------------------- 1 | params 27 | * 28 | * @param array $params 29 | * @return self 30 | */ 31 | public function fillParameters(array $params): Rule 32 | { 33 | $this->allowTypes($params); 34 | return $this; 35 | } 36 | 37 | /** 38 | * Given $types and assign $this->params 39 | * 40 | * @param mixed $types 41 | * @return self 42 | */ 43 | public function allowTypes($types): Rule 44 | { 45 | if (is_string($types)) { 46 | $types = explode('|', $types); 47 | } 48 | 49 | $this->params['allowed_types'] = $types; 50 | 51 | return $this; 52 | } 53 | 54 | /** 55 | * Check the $value is valid 56 | * 57 | * @param mixed $value 58 | * @return bool 59 | */ 60 | public function check($value): bool 61 | { 62 | $allowedTypes = $this->parameter('allowed_types'); 63 | 64 | if ($allowedTypes) { 65 | $or = $this->validation ? $this->validation->getTranslation('or') : 'or'; 66 | $this->setParameterText('allowed_types', Helper::join(Helper::wraps($allowedTypes, "'"), ', ', ", {$or} ")); 67 | } 68 | 69 | // below is Required rule job 70 | if (!$this->isValueFromUploadedFiles($value) or $value['error'] == UPLOAD_ERR_NO_FILE) { 71 | return true; 72 | } 73 | 74 | if (!$this->isUploadedFile($value)) { 75 | return false; 76 | } 77 | 78 | // just make sure there is no error 79 | if ($value['error']) { 80 | return false; 81 | } 82 | 83 | if (!empty($allowedTypes)) { 84 | $guesser = new MimeTypeGuesser; 85 | $ext = $guesser->getExtension($value['type']); 86 | unset($guesser); 87 | 88 | if (!in_array($ext, $allowedTypes)) { 89 | return false; 90 | } 91 | } 92 | 93 | return true; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Rules/Min.php: -------------------------------------------------------------------------------- 1 | requireParameters($this->fillableParams); 26 | 27 | $min = $this->getBytesSize($this->parameter('min')); 28 | $valueSize = $this->getValueSize($value); 29 | 30 | if (!is_numeric($valueSize)) { 31 | return false; 32 | } 33 | 34 | return $valueSize >= $min; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Rules/NotIn.php: -------------------------------------------------------------------------------- 1 | params 19 | * 20 | * @param array $params 21 | * @return self 22 | */ 23 | public function fillParameters(array $params): Rule 24 | { 25 | if (count($params) == 1 and is_array($params[0])) { 26 | $params = $params[0]; 27 | } 28 | $this->params['disallowed_values'] = $params; 29 | return $this; 30 | } 31 | 32 | /** 33 | * Set strict value 34 | * 35 | * @param bool $strict 36 | * @return void 37 | */ 38 | public function strict($strict = true) 39 | { 40 | $this->strict = $strict; 41 | } 42 | 43 | /** 44 | * Check the $value is valid 45 | * 46 | * @param mixed $value 47 | * @return bool 48 | */ 49 | public function check($value): bool 50 | { 51 | $this->requireParameters(['disallowed_values']); 52 | 53 | $disallowedValues = (array) $this->parameter('disallowed_values'); 54 | 55 | $and = $this->validation ? $this->validation->getTranslation('and') : 'and'; 56 | $disallowedValuesText = Helper::join(Helper::wraps($disallowedValues, "'"), ', ', ", {$and} "); 57 | $this->setParameterText('disallowed_values', $disallowedValuesText); 58 | 59 | return !in_array($value, $disallowedValues, $this->strict); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Rules/Nullable.php: -------------------------------------------------------------------------------- 1 | setAttributeAsRequired(); 24 | 25 | return $this->validation->hasValue($this->attribute->getKey()); 26 | } 27 | 28 | /** 29 | * Set attribute is required if $this->attribute is set 30 | * 31 | * @return void 32 | */ 33 | protected function setAttributeAsRequired() 34 | { 35 | if ($this->attribute) { 36 | $this->attribute->setRequired(true); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Rules/Regex.php: -------------------------------------------------------------------------------- 1 | requireParameters($this->fillableParams); 25 | $regex = $this->parameter('regex'); 26 | return preg_match($regex, $value) > 0; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Rules/Required.php: -------------------------------------------------------------------------------- 1 | setAttributeAsRequired(); 26 | 27 | if ($this->attribute and $this->attribute->hasRule('uploaded_file')) { 28 | return $this->isValueFromUploadedFiles($value) and $value['error'] != UPLOAD_ERR_NO_FILE; 29 | } 30 | 31 | if (is_string($value)) { 32 | return mb_strlen(trim($value), 'UTF-8') > 0; 33 | } 34 | if (is_array($value)) { 35 | return count($value) > 0; 36 | } 37 | return !is_null($value); 38 | } 39 | 40 | /** 41 | * Set attribute is required if $this->attribute is set 42 | * 43 | * @return void 44 | */ 45 | protected function setAttributeAsRequired() 46 | { 47 | if ($this->attribute) { 48 | $this->attribute->setRequired(true); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Rules/RequiredIf.php: -------------------------------------------------------------------------------- 1 | params 17 | * 18 | * @param array $params 19 | * @return self 20 | */ 21 | public function fillParameters(array $params): Rule 22 | { 23 | $this->params['field'] = array_shift($params); 24 | $this->params['values'] = $params; 25 | return $this; 26 | } 27 | 28 | /** 29 | * Check the $value is valid 30 | * 31 | * @param mixed $value 32 | * @return bool 33 | */ 34 | public function check($value): bool 35 | { 36 | $this->requireParameters(['field', 'values']); 37 | 38 | $anotherAttribute = $this->parameter('field'); 39 | $definedValues = $this->parameter('values'); 40 | $anotherValue = $this->getAttribute()->getValue($anotherAttribute); 41 | 42 | $validator = $this->validation->getValidator(); 43 | $requiredValidator = $validator('required'); 44 | 45 | if (in_array($anotherValue, $definedValues)) { 46 | $this->setAttributeAsRequired(); 47 | return $requiredValidator->check($value, []); 48 | } 49 | 50 | return true; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Rules/RequiredUnless.php: -------------------------------------------------------------------------------- 1 | params 17 | * 18 | * @param array $params 19 | * @return self 20 | */ 21 | public function fillParameters(array $params): Rule 22 | { 23 | $this->params['field'] = array_shift($params); 24 | $this->params['values'] = $params; 25 | return $this; 26 | } 27 | 28 | /** 29 | * Check the $value is valid 30 | * 31 | * @param mixed $value 32 | * @return bool 33 | */ 34 | public function check($value): bool 35 | { 36 | $this->requireParameters(['field', 'values']); 37 | 38 | $anotherAttribute = $this->parameter('field'); 39 | $definedValues = $this->parameter('values'); 40 | $anotherValue = $this->getAttribute()->getValue($anotherAttribute); 41 | 42 | $validator = $this->validation->getValidator(); 43 | $requiredValidator = $validator('required'); 44 | 45 | if (!in_array($anotherValue, $definedValues)) { 46 | $this->setAttributeAsRequired(); 47 | return $requiredValidator->check($value, []); 48 | } 49 | 50 | return true; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Rules/RequiredWith.php: -------------------------------------------------------------------------------- 1 | params 17 | * 18 | * @param array $params 19 | * @return self 20 | */ 21 | public function fillParameters(array $params): Rule 22 | { 23 | $this->params['fields'] = $params; 24 | return $this; 25 | } 26 | 27 | /** 28 | * Check the $value is valid 29 | * 30 | * @param mixed $value 31 | * @return bool 32 | */ 33 | public function check($value): bool 34 | { 35 | $this->requireParameters(['fields']); 36 | $fields = $this->parameter('fields'); 37 | $validator = $this->validation->getValidator(); 38 | $requiredValidator = $validator('required'); 39 | 40 | foreach ($fields as $field) { 41 | if ($this->validation->hasValue($field)) { 42 | $this->setAttributeAsRequired(); 43 | return $requiredValidator->check($value, []); 44 | } 45 | } 46 | 47 | return true; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Rules/RequiredWithAll.php: -------------------------------------------------------------------------------- 1 | params 17 | * 18 | * @param array $params 19 | * @return self 20 | */ 21 | public function fillParameters(array $params): Rule 22 | { 23 | $this->params['fields'] = $params; 24 | return $this; 25 | } 26 | 27 | /** 28 | * Check the $value is valid 29 | * 30 | * @param mixed $value 31 | * @return bool 32 | */ 33 | public function check($value): bool 34 | { 35 | $this->requireParameters(['fields']); 36 | $fields = $this->parameter('fields'); 37 | $validator = $this->validation->getValidator(); 38 | $requiredValidator = $validator('required'); 39 | 40 | foreach ($fields as $field) { 41 | if (!$this->validation->hasValue($field)) { 42 | return true; 43 | } 44 | } 45 | 46 | $this->setAttributeAsRequired(); 47 | return $requiredValidator->check($value, []); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Rules/RequiredWithout.php: -------------------------------------------------------------------------------- 1 | params 17 | * 18 | * @param array $params 19 | * @return self 20 | */ 21 | public function fillParameters(array $params): Rule 22 | { 23 | $this->params['fields'] = $params; 24 | return $this; 25 | } 26 | 27 | /** 28 | * Check the $value is valid 29 | * 30 | * @param mixed $value 31 | * @return bool 32 | */ 33 | public function check($value): bool 34 | { 35 | $this->requireParameters(['fields']); 36 | $fields = $this->parameter('fields'); 37 | $validator = $this->validation->getValidator(); 38 | $requiredValidator = $validator('required'); 39 | 40 | foreach ($fields as $field) { 41 | if (!$this->validation->hasValue($field)) { 42 | $this->setAttributeAsRequired(); 43 | return $requiredValidator->check($value, []); 44 | } 45 | } 46 | 47 | return true; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Rules/RequiredWithoutAll.php: -------------------------------------------------------------------------------- 1 | params 17 | * 18 | * @param array $params 19 | * @return self 20 | */ 21 | public function fillParameters(array $params): Rule 22 | { 23 | $this->params['fields'] = $params; 24 | return $this; 25 | } 26 | 27 | /** 28 | * Check the $value is valid 29 | * 30 | * @param mixed $value 31 | * @return bool 32 | */ 33 | public function check($value): bool 34 | { 35 | $this->requireParameters(['fields']); 36 | $fields = $this->parameter('fields'); 37 | $validator = $this->validation->getValidator(); 38 | $requiredValidator = $validator('required'); 39 | 40 | foreach ($fields as $field) { 41 | if ($this->validation->hasValue($field)) { 42 | return true; 43 | } 44 | } 45 | 46 | $this->setAttributeAsRequired(); 47 | return $requiredValidator->check($value, []); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Rules/Same.php: -------------------------------------------------------------------------------- 1 | requireParameters($this->fillableParams); 25 | 26 | $field = $this->parameter('field'); 27 | $anotherValue = $this->getAttribute()->getValue($field); 28 | 29 | return $value == $anotherValue; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Rules/Traits/DateUtilsTrait.php: -------------------------------------------------------------------------------- 1 | isValueFromUploadedFiles($value) && is_uploaded_file($value['tmp_name']); 42 | } 43 | 44 | /** 45 | * Resolve uploaded file value 46 | * 47 | * @param mixed $value 48 | * @return array|null 49 | */ 50 | public function resolveUploadedFileValue($value) 51 | { 52 | if (!$this->isValueFromUploadedFiles($value)) { 53 | return null; 54 | } 55 | 56 | // Here $value should be an array: 57 | // [ 58 | // 'name' => string|array, 59 | // 'type' => string|array, 60 | // 'size' => int|array, 61 | // 'tmp_name' => string|array, 62 | // 'error' => string|array, 63 | // ] 64 | 65 | // Flatten $value to it's array dot format, 66 | // so our array must be something like: 67 | // ['name' => string, 'type' => string, 'size' => int, ...] 68 | // or for multiple values: 69 | // ['name.0' => string, 'name.1' => string, 'type.0' => string, 'type.1' => string, ...] 70 | // or for nested array: 71 | // ['name.foo.bar' => string, 'name.foo.baz' => string, 'type.foo.bar' => string, 'type.foo.baz' => string, ...] 72 | $arrayDots = Helper::arrayDot($value); 73 | 74 | $results = []; 75 | foreach ($arrayDots as $key => $val) { 76 | // Move first key to last key 77 | // name.foo.bar -> foo.bar.name 78 | $splits = explode(".", $key); 79 | $firstKey = array_shift($splits); 80 | $key = count($splits) ? implode(".", $splits) . ".{$firstKey}" : $firstKey; 81 | 82 | Helper::arraySet($results, $key, $val); 83 | } 84 | return $results; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Rules/Traits/SizeTrait.php: -------------------------------------------------------------------------------- 1 | getAttribute() 19 | && ($this->getAttribute()->hasRule('numeric') || $this->getAttribute()->hasRule('integer')) 20 | && is_numeric($value) 21 | ) { 22 | $value = (float) $value; 23 | } 24 | 25 | if (is_int($value) || is_float($value)) { 26 | return (float) $value; 27 | } elseif (is_string($value)) { 28 | return (float) mb_strlen($value, 'UTF-8'); 29 | } elseif ($this->isUploadedFileValue($value)) { 30 | return (float) $value['size']; 31 | } elseif (is_array($value)) { 32 | return (float) count($value); 33 | } else { 34 | return false; 35 | } 36 | } 37 | 38 | /** 39 | * Given $size and get the bytes 40 | * 41 | * @param string|int $size 42 | * @return float 43 | * @throws InvalidArgumentException 44 | */ 45 | protected function getBytesSize($size) 46 | { 47 | if (is_numeric($size)) { 48 | return (float) $size; 49 | } 50 | 51 | if (!is_string($size)) { 52 | throw new InvalidArgumentException("Size must be string or numeric Bytes", 1); 53 | } 54 | 55 | if (!preg_match("/^(?((\d+)?\.)?\d+)(?(B|K|M|G|T|P)B?)?$/i", $size, $match)) { 56 | throw new InvalidArgumentException("Size is not valid format", 1); 57 | } 58 | 59 | $number = (float) $match['number']; 60 | $format = isset($match['format']) ? $match['format'] : ''; 61 | 62 | switch (strtoupper($format)) { 63 | case "KB": 64 | case "K": 65 | return $number * 1024; 66 | 67 | case "MB": 68 | case "M": 69 | return $number * pow(1024, 2); 70 | 71 | case "GB": 72 | case "G": 73 | return $number * pow(1024, 3); 74 | 75 | case "TB": 76 | case "T": 77 | return $number * pow(1024, 4); 78 | 79 | case "PB": 80 | case "P": 81 | return $number * pow(1024, 5); 82 | 83 | default: 84 | return $number; 85 | } 86 | } 87 | 88 | /** 89 | * Check whether value is from $_FILES 90 | * 91 | * @param mixed $value 92 | * @return bool 93 | */ 94 | public function isUploadedFileValue($value): bool 95 | { 96 | if (!is_array($value)) { 97 | return false; 98 | } 99 | 100 | $keys = ['name', 'type', 'tmp_name', 'size', 'error']; 101 | foreach ($keys as $key) { 102 | if (!array_key_exists($key, $value)) { 103 | return false; 104 | } 105 | } 106 | 107 | return true; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/Rules/TypeArray.php: -------------------------------------------------------------------------------- 1 | params 28 | * 29 | * @param array $params 30 | * @return self 31 | */ 32 | public function fillParameters(array $params): Rule 33 | { 34 | $this->minSize(array_shift($params)); 35 | $this->maxSize(array_shift($params)); 36 | $this->fileTypes($params); 37 | 38 | return $this; 39 | } 40 | 41 | /** 42 | * Given $size and set the max size 43 | * 44 | * @param string|int $size 45 | * @return self 46 | */ 47 | public function maxSize($size): Rule 48 | { 49 | $this->params['max_size'] = $size; 50 | return $this; 51 | } 52 | 53 | /** 54 | * Given $size and set the min size 55 | * 56 | * @param string|int $size 57 | * @return self 58 | */ 59 | public function minSize($size): Rule 60 | { 61 | $this->params['min_size'] = $size; 62 | return $this; 63 | } 64 | 65 | /** 66 | * Given $min and $max then set the range size 67 | * 68 | * @param string|int $min 69 | * @param string|int $max 70 | * @return self 71 | */ 72 | public function sizeBetween($min, $max): Rule 73 | { 74 | $this->minSize($min); 75 | $this->maxSize($max); 76 | 77 | return $this; 78 | } 79 | 80 | /** 81 | * Given $types and assign $this->params 82 | * 83 | * @param mixed $types 84 | * @return self 85 | */ 86 | public function fileTypes($types): Rule 87 | { 88 | if (is_string($types)) { 89 | $types = explode('|', $types); 90 | } 91 | 92 | $this->params['allowed_types'] = $types; 93 | 94 | return $this; 95 | } 96 | 97 | /** 98 | * {@inheritDoc} 99 | */ 100 | public function beforeValidate() 101 | { 102 | $attribute = $this->getAttribute(); 103 | 104 | // We only resolve uploaded file value 105 | // from complex attribute such as 'files.photo', 'images.*', 'images.foo.bar', etc. 106 | if (!$attribute->isUsingDotNotation()) { 107 | return; 108 | } 109 | 110 | $keys = explode(".", $attribute->getKey()); 111 | $firstKey = array_shift($keys); 112 | $firstKeyValue = $this->validation->getValue($firstKey); 113 | 114 | $resolvedValue = $this->resolveUploadedFileValue($firstKeyValue); 115 | 116 | // Return original value if $value can't be resolved as uploaded file value 117 | if (!$resolvedValue) { 118 | return; 119 | } 120 | 121 | $this->validation->setValue($firstKey, $resolvedValue); 122 | } 123 | 124 | /** 125 | * Check the $value is valid 126 | * 127 | * @param mixed $value 128 | * @return bool 129 | */ 130 | public function check($value): bool 131 | { 132 | $minSize = $this->parameter('min_size'); 133 | $maxSize = $this->parameter('max_size'); 134 | $allowedTypes = $this->parameter('allowed_types'); 135 | 136 | if ($allowedTypes) { 137 | $or = $this->validation ? $this->validation->getTranslation('or') : 'or'; 138 | $this->setParameterText('allowed_types', Helper::join(Helper::wraps($allowedTypes, "'"), ', ', ", {$or} ")); 139 | } 140 | 141 | // below is Required rule job 142 | if (!$this->isValueFromUploadedFiles($value) or $value['error'] == UPLOAD_ERR_NO_FILE) { 143 | return true; 144 | } 145 | 146 | if (!$this->isUploadedFile($value)) { 147 | return false; 148 | } 149 | 150 | // just make sure there is no error 151 | if ($value['error']) { 152 | return false; 153 | } 154 | 155 | if ($minSize) { 156 | $bytesMinSize = $this->getBytesSize($minSize); 157 | if ($value['size'] < $bytesMinSize) { 158 | $this->setMessage('The :attribute file is too small, minimum size is :min_size'); 159 | return false; 160 | } 161 | } 162 | 163 | if ($maxSize) { 164 | $bytesMaxSize = $this->getBytesSize($maxSize); 165 | if ($value['size'] > $bytesMaxSize) { 166 | $this->setMessage('The :attribute file is too large, maximum size is :max_size'); 167 | return false; 168 | } 169 | } 170 | 171 | if (!empty($allowedTypes)) { 172 | $guesser = new MimeTypeGuesser; 173 | $ext = $guesser->getExtension($value['type']); 174 | unset($guesser); 175 | 176 | if (!in_array($ext, $allowedTypes)) { 177 | $this->setMessage('The :attribute file type must be :allowed_types'); 178 | return false; 179 | } 180 | } 181 | 182 | return true; 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/Rules/Uppercase.php: -------------------------------------------------------------------------------- 1 | params 15 | * 16 | * @param array $params 17 | * @return self 18 | */ 19 | public function fillParameters(array $params): Rule 20 | { 21 | if (count($params) == 1 and is_array($params[0])) { 22 | $params = $params[0]; 23 | } 24 | return $this->forScheme($params); 25 | } 26 | 27 | /** 28 | * Given $schemes and assign $this->params 29 | * 30 | * @param array $schemes 31 | * @return self 32 | */ 33 | public function forScheme($schemes): Rule 34 | { 35 | $this->params['schemes'] = (array) $schemes; 36 | return $this; 37 | } 38 | 39 | /** 40 | * Check the $value is valid 41 | * 42 | * @param mixed $value 43 | * @return bool 44 | */ 45 | public function check($value): bool 46 | { 47 | $schemes = $this->parameter('schemes'); 48 | 49 | if (!$schemes) { 50 | return $this->validateCommonScheme($value); 51 | } else { 52 | foreach ($schemes as $scheme) { 53 | $method = 'validate' . ucfirst($scheme) .'Scheme'; 54 | if (method_exists($this, $method)) { 55 | if ($this->{$method}($value)) { 56 | return true; 57 | } 58 | } elseif ($this->validateCommonScheme($value, $scheme)) { 59 | return true; 60 | } 61 | } 62 | 63 | return false; 64 | } 65 | } 66 | 67 | /** 68 | * Validate $value is valid URL format 69 | * 70 | * @param mixed $value 71 | * @return bool 72 | */ 73 | public function validateBasic($value): bool 74 | { 75 | return filter_var($value, FILTER_VALIDATE_URL) !== false; 76 | } 77 | 78 | /** 79 | * Validate $value is correct $scheme format 80 | * 81 | * @param mixed $value 82 | * @param null $scheme 83 | * @return bool 84 | */ 85 | public function validateCommonScheme($value, $scheme = null): bool 86 | { 87 | if (!$scheme) { 88 | return $this->validateBasic($value) && (bool) preg_match("/^\w+:\/\//i", $value); 89 | } else { 90 | return $this->validateBasic($value) && (bool) preg_match("/^{$scheme}:\/\//", $value); 91 | } 92 | } 93 | 94 | /** 95 | * Validate the $value is mailto scheme format 96 | * 97 | * @param mixed $value 98 | * @return bool 99 | */ 100 | public function validateMailtoScheme($value): bool 101 | { 102 | return $this->validateBasic($value) && preg_match("/^mailto:/", $value); 103 | } 104 | 105 | /** 106 | * Validate the $value is jdbc scheme format 107 | * 108 | * @param mixed $value 109 | * @return bool 110 | */ 111 | public function validateJdbcScheme($value): bool 112 | { 113 | return (bool) preg_match("/^jdbc:\w+:\/\//", $value); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Traits/MessagesTrait.php: -------------------------------------------------------------------------------- 1 | messages[$key] = $message; 21 | } 22 | 23 | /** 24 | * Given $messages and set multiple messages 25 | * 26 | * @param array $messages 27 | * @return void 28 | */ 29 | public function setMessages(array $messages) 30 | { 31 | $this->messages = array_merge($this->messages, $messages); 32 | } 33 | 34 | /** 35 | * Given message from given $key 36 | * 37 | * @param string $key 38 | * @return string 39 | */ 40 | public function getMessage(string $key): string 41 | { 42 | return array_key_exists($key, $this->messages) ? $this->messages[$key] : $key; 43 | } 44 | 45 | /** 46 | * Get all $messages 47 | * 48 | * @return array 49 | */ 50 | public function getMessages(): array 51 | { 52 | return $this->messages; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Traits/TranslationsTrait.php: -------------------------------------------------------------------------------- 1 | translations[$key] = $translation; 21 | } 22 | 23 | /** 24 | * Given $translations and set multiple translations 25 | * 26 | * @param array $translations 27 | * @return void 28 | */ 29 | public function setTranslations(array $translations) 30 | { 31 | $this->translations = array_merge($this->translations, $translations); 32 | } 33 | 34 | /** 35 | * Given translation from given $key 36 | * 37 | * @param string $key 38 | * @return string 39 | */ 40 | public function getTranslation(string $key): string 41 | { 42 | return array_key_exists($key, $this->translations) ? $this->translations[$key] : $key; 43 | } 44 | 45 | /** 46 | * Get all $translations 47 | * 48 | * @return array 49 | */ 50 | public function getTranslations(): array 51 | { 52 | return $this->translations; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Validation.php: -------------------------------------------------------------------------------- 1 | validator = $validator; 54 | $this->inputs = $this->resolveInputAttributes($inputs); 55 | $this->messages = $messages; 56 | $this->errors = new ErrorBag; 57 | foreach ($rules as $attributeKey => $rules) { 58 | $this->addAttribute($attributeKey, $rules); 59 | } 60 | } 61 | 62 | /** 63 | * Add attribute rules 64 | * 65 | * @param string $attributeKey 66 | * @param string|array $rules 67 | * @return void 68 | */ 69 | public function addAttribute(string $attributeKey, $rules) 70 | { 71 | $resolvedRules = $this->resolveRules($rules); 72 | $attribute = new Attribute($this, $attributeKey, $this->getAlias($attributeKey), $resolvedRules); 73 | $this->attributes[$attributeKey] = $attribute; 74 | } 75 | 76 | /** 77 | * Get attribute by key 78 | * 79 | * @param string $attributeKey 80 | * @return null|\Rakit\Validation\Attribute 81 | */ 82 | public function getAttribute(string $attributeKey) 83 | { 84 | return isset($this->attributes[$attributeKey])? $this->attributes[$attributeKey] : null; 85 | } 86 | 87 | /** 88 | * Run validation 89 | * 90 | * @param array $inputs 91 | * @return void 92 | */ 93 | public function validate(array $inputs = []) 94 | { 95 | $this->errors = new ErrorBag; // reset error bag 96 | $this->inputs = array_merge($this->inputs, $this->resolveInputAttributes($inputs)); 97 | 98 | // Before validation hooks 99 | foreach ($this->attributes as $attributeKey => $attribute) { 100 | foreach ($attribute->getRules() as $rule) { 101 | if ($rule instanceof BeforeValidate) { 102 | $rule->beforeValidate(); 103 | } 104 | } 105 | } 106 | 107 | foreach ($this->attributes as $attributeKey => $attribute) { 108 | $this->validateAttribute($attribute); 109 | } 110 | } 111 | 112 | /** 113 | * Get ErrorBag instance 114 | * 115 | * @return \Rakit\Validation\ErrorBag 116 | */ 117 | public function errors(): ErrorBag 118 | { 119 | return $this->errors; 120 | } 121 | 122 | /** 123 | * Validate attribute 124 | * 125 | * @param \Rakit\Validation\Attribute $attribute 126 | * @return void 127 | */ 128 | protected function validateAttribute(Attribute $attribute) 129 | { 130 | if ($this->isArrayAttribute($attribute)) { 131 | $attributes = $this->parseArrayAttribute($attribute); 132 | foreach ($attributes as $i => $attr) { 133 | $this->validateAttribute($attr); 134 | } 135 | return; 136 | } 137 | 138 | $attributeKey = $attribute->getKey(); 139 | $rules = $attribute->getRules(); 140 | 141 | $value = $this->getValue($attributeKey); 142 | $isEmptyValue = $this->isEmptyValue($value); 143 | 144 | if ($attribute->hasRule('nullable') && $isEmptyValue) { 145 | $rules = []; 146 | } 147 | 148 | $isValid = true; 149 | foreach ($rules as $ruleValidator) { 150 | $ruleValidator->setAttribute($attribute); 151 | 152 | if ($ruleValidator instanceof ModifyValue) { 153 | $value = $ruleValidator->modifyValue($value); 154 | $isEmptyValue = $this->isEmptyValue($value); 155 | } 156 | 157 | $valid = $ruleValidator->check($value); 158 | 159 | if ($isEmptyValue and $this->ruleIsOptional($attribute, $ruleValidator)) { 160 | continue; 161 | } 162 | 163 | if (!$valid) { 164 | $isValid = false; 165 | $this->addError($attribute, $value, $ruleValidator); 166 | if ($ruleValidator->isImplicit()) { 167 | break; 168 | } 169 | } 170 | } 171 | 172 | if ($isValid) { 173 | $this->setValidData($attribute, $value); 174 | } else { 175 | $this->setInvalidData($attribute, $value); 176 | } 177 | } 178 | 179 | /** 180 | * Check whether given $attribute is array attribute 181 | * 182 | * @param \Rakit\Validation\Attribute $attribute 183 | * @return bool 184 | */ 185 | protected function isArrayAttribute(Attribute $attribute): bool 186 | { 187 | $key = $attribute->getKey(); 188 | return strpos($key, '*') !== false; 189 | } 190 | 191 | /** 192 | * Parse array attribute into it's child attributes 193 | * 194 | * @param \Rakit\Validation\Attribute $attribute 195 | * @return array 196 | */ 197 | protected function parseArrayAttribute(Attribute $attribute): array 198 | { 199 | $attributeKey = $attribute->getKey(); 200 | $data = Helper::arrayDot($this->initializeAttributeOnData($attributeKey)); 201 | 202 | $pattern = str_replace('\*', '([^\.]+)', preg_quote($attributeKey)); 203 | 204 | $data = array_merge($data, $this->extractValuesForWildcards( 205 | $data, 206 | $attributeKey 207 | )); 208 | 209 | $attributes = []; 210 | 211 | foreach ($data as $key => $value) { 212 | if ((bool) preg_match('/^'.$pattern.'\z/', $key, $match)) { 213 | $attr = new Attribute($this, $key, null, $attribute->getRules()); 214 | $attr->setPrimaryAttribute($attribute); 215 | $attr->setKeyIndexes(array_slice($match, 1)); 216 | $attributes[] = $attr; 217 | } 218 | } 219 | 220 | // set other attributes to each attributes 221 | foreach ($attributes as $i => $attr) { 222 | $otherAttributes = $attributes; 223 | unset($otherAttributes[$i]); 224 | $attr->setOtherAttributes($otherAttributes); 225 | } 226 | 227 | return $attributes; 228 | } 229 | 230 | /** 231 | * Gather a copy of the attribute data filled with any missing attributes. 232 | * Adapted from: https://github.com/illuminate/validation/blob/v5.3.23/Validator.php#L334 233 | * 234 | * @param string $attribute 235 | * @return array 236 | */ 237 | protected function initializeAttributeOnData(string $attributeKey): array 238 | { 239 | $explicitPath = $this->getLeadingExplicitAttributePath($attributeKey); 240 | 241 | $data = $this->extractDataFromPath($explicitPath); 242 | 243 | $asteriskPos = strpos($attributeKey, '*'); 244 | 245 | if (false === $asteriskPos || $asteriskPos === (mb_strlen($attributeKey, 'UTF-8') - 1)) { 246 | return $data; 247 | } 248 | 249 | return Helper::arraySet($data, $attributeKey, null, true); 250 | } 251 | 252 | /** 253 | * Get all of the exact attribute values for a given wildcard attribute. 254 | * Adapted from: https://github.com/illuminate/validation/blob/v5.3.23/Validator.php#L354 255 | * 256 | * @param array $data 257 | * @param string $attributeKey 258 | * @return array 259 | */ 260 | public function extractValuesForWildcards(array $data, string $attributeKey): array 261 | { 262 | $keys = []; 263 | 264 | $pattern = str_replace('\*', '[^\.]+', preg_quote($attributeKey)); 265 | 266 | foreach ($data as $key => $value) { 267 | if ((bool) preg_match('/^'.$pattern.'/', $key, $matches)) { 268 | $keys[] = $matches[0]; 269 | } 270 | } 271 | 272 | $keys = array_unique($keys); 273 | 274 | $data = []; 275 | 276 | foreach ($keys as $key) { 277 | $data[$key] = Helper::arrayGet($this->inputs, $key); 278 | } 279 | 280 | return $data; 281 | } 282 | 283 | /** 284 | * Get the explicit part of the attribute name. 285 | * Adapted from: https://github.com/illuminate/validation/blob/v5.3.23/Validator.php#L2817 286 | * 287 | * E.g. 'foo.bar.*.baz' -> 'foo.bar' 288 | * 289 | * Allows us to not spin through all of the flattened data for some operations. 290 | * 291 | * @param string $attributeKey 292 | * @return string|null null when root wildcard 293 | */ 294 | protected function getLeadingExplicitAttributePath(string $attributeKey) 295 | { 296 | return rtrim(explode('*', $attributeKey)[0], '.') ?: null; 297 | } 298 | 299 | /** 300 | * Extract data based on the given dot-notated path. 301 | * Adapted from: https://github.com/illuminate/validation/blob/v5.3.23/Validator.php#L2830 302 | * 303 | * Used to extract a sub-section of the data for faster iteration. 304 | * 305 | * @param string|null $attributeKey 306 | * @return array 307 | */ 308 | protected function extractDataFromPath($attributeKey): array 309 | { 310 | $results = []; 311 | 312 | $value = Helper::arrayGet($this->inputs, $attributeKey, '__missing__'); 313 | 314 | if ($value != '__missing__') { 315 | Helper::arraySet($results, $attributeKey, $value); 316 | } 317 | 318 | return $results; 319 | } 320 | 321 | /** 322 | * Add error to the $this->errors 323 | * 324 | * @param \Rakit\Validation\Attribute $attribute 325 | * @param mixed $value 326 | * @param \Rakit\Validation\Rule $ruleValidator 327 | * @return void 328 | */ 329 | protected function addError(Attribute $attribute, $value, Rule $ruleValidator) 330 | { 331 | $ruleName = $ruleValidator->getKey(); 332 | $message = $this->resolveMessage($attribute, $value, $ruleValidator); 333 | 334 | $this->errors->add($attribute->getKey(), $ruleName, $message); 335 | } 336 | 337 | /** 338 | * Check $value is empty value 339 | * 340 | * @param mixed $value 341 | * @return boolean 342 | */ 343 | protected function isEmptyValue($value): bool 344 | { 345 | $requiredValidator = new Required; 346 | return false === $requiredValidator->check($value, []); 347 | } 348 | 349 | /** 350 | * Check the rule is optional 351 | * 352 | * @param \Rakit\Validation\Attribute $attribute 353 | * @param \Rakit\Validation\Rule $rule 354 | * @return bool 355 | */ 356 | protected function ruleIsOptional(Attribute $attribute, Rule $rule): bool 357 | { 358 | return false === $attribute->isRequired() and 359 | false === $rule->isImplicit() and 360 | false === $rule instanceof Required; 361 | } 362 | 363 | /** 364 | * Resolve attribute name 365 | * 366 | * @param \Rakit\Validation\Attribute $attribute 367 | * @return string 368 | */ 369 | protected function resolveAttributeName(Attribute $attribute): string 370 | { 371 | $primaryAttribute = $attribute->getPrimaryAttribute(); 372 | if (isset($this->aliases[$attribute->getKey()])) { 373 | return $this->aliases[$attribute->getKey()]; 374 | } elseif ($primaryAttribute and isset($this->aliases[$primaryAttribute->getKey()])) { 375 | return $this->aliases[$primaryAttribute->getKey()]; 376 | } elseif ($this->validator->isUsingHumanizedKey()) { 377 | return $attribute->getHumanizedKey(); 378 | } else { 379 | return $attribute->getKey(); 380 | } 381 | } 382 | 383 | /** 384 | * Resolve message 385 | * 386 | * @param \Rakit\Validation\Attribute $attribute 387 | * @param mixed $value 388 | * @param \Rakit\Validation\Rule $validator 389 | * @return mixed 390 | */ 391 | protected function resolveMessage(Attribute $attribute, $value, Rule $validator): string 392 | { 393 | $primaryAttribute = $attribute->getPrimaryAttribute(); 394 | $params = array_merge($validator->getParameters(), $validator->getParametersTexts()); 395 | $attributeKey = $attribute->getKey(); 396 | $ruleKey = $validator->getKey(); 397 | $alias = $attribute->getAlias() ?: $this->resolveAttributeName($attribute); 398 | $message = $validator->getMessage(); // default rule message 399 | $messageKeys = [ 400 | $attributeKey.$this->messageSeparator.$ruleKey, 401 | $attributeKey, 402 | $ruleKey 403 | ]; 404 | 405 | if ($primaryAttribute) { 406 | // insert primaryAttribute keys 407 | // $messageKeys = [ 408 | // $attributeKey.$this->messageSeparator.$ruleKey, 409 | // >> here [1] << 410 | // $attributeKey, 411 | // >> and here [3] << 412 | // $ruleKey 413 | // ]; 414 | $primaryAttributeKey = $primaryAttribute->getKey(); 415 | array_splice($messageKeys, 1, 0, $primaryAttributeKey.$this->messageSeparator.$ruleKey); 416 | array_splice($messageKeys, 3, 0, $primaryAttributeKey); 417 | } 418 | 419 | foreach ($messageKeys as $key) { 420 | if (isset($this->messages[$key])) { 421 | $message = $this->messages[$key]; 422 | break; 423 | } 424 | } 425 | 426 | // Replace message params 427 | $vars = array_merge($params, [ 428 | 'attribute' => $alias, 429 | 'value' => $value, 430 | ]); 431 | 432 | foreach ($vars as $key => $value) { 433 | $value = $this->stringify($value); 434 | $message = str_replace(':'.$key, $value, $message); 435 | } 436 | 437 | // Replace key indexes 438 | $keyIndexes = $attribute->getKeyIndexes(); 439 | foreach ($keyIndexes as $pathIndex => $index) { 440 | $replacers = [ 441 | "[{$pathIndex}]" => $index, 442 | ]; 443 | 444 | if (is_numeric($index)) { 445 | $replacers["{{$pathIndex}}"] = $index + 1; 446 | } 447 | 448 | $message = str_replace(array_keys($replacers), array_values($replacers), $message); 449 | } 450 | 451 | return $message; 452 | } 453 | 454 | /** 455 | * Stringify $value 456 | * 457 | * @param mixed $value 458 | * @return string 459 | */ 460 | protected function stringify($value): string 461 | { 462 | if (is_string($value) || is_numeric($value)) { 463 | return $value; 464 | } elseif (is_array($value) || is_object($value)) { 465 | return json_encode($value); 466 | } else { 467 | return ''; 468 | } 469 | } 470 | 471 | /** 472 | * Resolve $rules 473 | * 474 | * @param mixed $rules 475 | * @return array 476 | */ 477 | protected function resolveRules($rules): array 478 | { 479 | if (is_string($rules)) { 480 | $rules = explode('|', $rules); 481 | } 482 | 483 | $resolvedRules = []; 484 | $validatorFactory = $this->getValidator(); 485 | 486 | foreach ($rules as $i => $rule) { 487 | if (empty($rule)) { 488 | continue; 489 | } 490 | $params = []; 491 | 492 | if (is_string($rule)) { 493 | list($rulename, $params) = $this->parseRule($rule); 494 | $validator = call_user_func_array($validatorFactory, array_merge([$rulename], $params)); 495 | } elseif ($rule instanceof Rule) { 496 | $validator = $rule; 497 | } elseif ($rule instanceof Closure) { 498 | $validator = call_user_func_array($validatorFactory, ['callback', $rule]); 499 | } else { 500 | $ruleName = is_object($rule) ? get_class($rule) : gettype($rule); 501 | $message = "Rule must be a string, Closure or '".Rule::class."' instance. ".$ruleName." given"; 502 | throw new \Exception(); 503 | } 504 | 505 | $resolvedRules[] = $validator; 506 | } 507 | 508 | return $resolvedRules; 509 | } 510 | 511 | /** 512 | * Parse $rule 513 | * 514 | * @param string $rule 515 | * @return array 516 | */ 517 | protected function parseRule(string $rule): array 518 | { 519 | $exp = explode(':', $rule, 2); 520 | $rulename = $exp[0]; 521 | if ($rulename !== 'regex') { 522 | $params = isset($exp[1])? explode(',', $exp[1]) : []; 523 | } else { 524 | $params = [$exp[1]]; 525 | } 526 | 527 | return [$rulename, $params]; 528 | } 529 | 530 | /** 531 | * Given $attributeKey and $alias then assign alias 532 | * 533 | * @param mixed $attributeKey 534 | * @param mixed $alias 535 | * @return void 536 | */ 537 | public function setAlias(string $attributeKey, string $alias) 538 | { 539 | $this->aliases[$attributeKey] = $alias; 540 | } 541 | 542 | /** 543 | * Get attribute alias from given key 544 | * 545 | * @param mixed $attributeKey 546 | * @return string|null 547 | */ 548 | public function getAlias(string $attributeKey) 549 | { 550 | return isset($this->aliases[$attributeKey])? $this->aliases[$attributeKey] : null; 551 | } 552 | 553 | /** 554 | * Set attributes aliases 555 | * 556 | * @param array $aliases 557 | * @return void 558 | */ 559 | public function setAliases(array $aliases) 560 | { 561 | $this->aliases = array_merge($this->aliases, $aliases); 562 | } 563 | 564 | /** 565 | * Check validations are passed 566 | * 567 | * @return bool 568 | */ 569 | public function passes(): bool 570 | { 571 | return $this->errors->count() == 0; 572 | } 573 | 574 | /** 575 | * Check validations are failed 576 | * 577 | * @return bool 578 | */ 579 | public function fails(): bool 580 | { 581 | return !$this->passes(); 582 | } 583 | 584 | /** 585 | * Given $key and get value 586 | * 587 | * @param string $key 588 | * @return mixed 589 | */ 590 | public function getValue(string $key) 591 | { 592 | return Helper::arrayGet($this->inputs, $key); 593 | } 594 | 595 | /** 596 | * Set input value 597 | * 598 | * @param string $key 599 | * @param mixed $value 600 | * @return void 601 | */ 602 | public function setValue(string $key, $value) 603 | { 604 | Helper::arraySet($this->inputs, $key, $value); 605 | } 606 | 607 | /** 608 | * Given $key and check value is exsited 609 | * 610 | * @param string $key 611 | * @return boolean 612 | */ 613 | public function hasValue(string $key): bool 614 | { 615 | return Helper::arrayHas($this->inputs, $key); 616 | } 617 | 618 | /** 619 | * Get Validator class instance 620 | * 621 | * @return \Rakit\Validation\Validator 622 | */ 623 | public function getValidator(): Validator 624 | { 625 | return $this->validator; 626 | } 627 | 628 | /** 629 | * Given $inputs and resolve input attributes 630 | * 631 | * @param array $inputs 632 | * @return array 633 | */ 634 | protected function resolveInputAttributes(array $inputs): array 635 | { 636 | $resolvedInputs = []; 637 | foreach ($inputs as $key => $rules) { 638 | $exp = explode(':', $key); 639 | 640 | if (count($exp) > 1) { 641 | // set attribute alias 642 | $this->aliases[$exp[0]] = $exp[1]; 643 | } 644 | 645 | $resolvedInputs[$exp[0]] = $rules; 646 | } 647 | 648 | return $resolvedInputs; 649 | } 650 | 651 | /** 652 | * Get validated data 653 | * 654 | * @return array 655 | */ 656 | public function getValidatedData(): array 657 | { 658 | return array_merge($this->validData, $this->invalidData); 659 | } 660 | 661 | /** 662 | * Set valid data 663 | * 664 | * @param \Rakit\Validation\Attribute $attribute 665 | * @param mixed $value 666 | * @return void 667 | */ 668 | protected function setValidData(Attribute $attribute, $value) 669 | { 670 | $key = $attribute->getKey(); 671 | if ($attribute->isArrayAttribute() || $attribute->isUsingDotNotation()) { 672 | Helper::arraySet($this->validData, $key, $value); 673 | Helper::arrayUnset($this->invalidData, $key); 674 | } else { 675 | $this->validData[$key] = $value; 676 | } 677 | } 678 | 679 | /** 680 | * Get valid data 681 | * 682 | * @return array 683 | */ 684 | public function getValidData(): array 685 | { 686 | return $this->validData; 687 | } 688 | 689 | /** 690 | * Set invalid data 691 | * 692 | * @param \Rakit\Validation\Attribute $attribute 693 | * @param mixed $value 694 | * @return void 695 | */ 696 | protected function setInvalidData(Attribute $attribute, $value) 697 | { 698 | $key = $attribute->getKey(); 699 | if ($attribute->isArrayAttribute() || $attribute->isUsingDotNotation()) { 700 | Helper::arraySet($this->invalidData, $key, $value); 701 | Helper::arrayUnset($this->validData, $key); 702 | } else { 703 | $this->invalidData[$key] = $value; 704 | } 705 | } 706 | 707 | /** 708 | * Get invalid data 709 | * 710 | * @return void 711 | */ 712 | public function getInvalidData(): array 713 | { 714 | return $this->invalidData; 715 | } 716 | } 717 | -------------------------------------------------------------------------------- /src/Validator.php: -------------------------------------------------------------------------------- 1 | messages = $messages; 30 | $this->registerBaseValidators(); 31 | } 32 | 33 | /** 34 | * Register or override existing validator 35 | * 36 | * @param mixed $key 37 | * @param \Rakit\Validation\Rule $rule 38 | * @return void 39 | */ 40 | public function setValidator(string $key, Rule $rule) 41 | { 42 | $this->validators[$key] = $rule; 43 | $rule->setKey($key); 44 | } 45 | 46 | /** 47 | * Get validator object from given $key 48 | * 49 | * @param mixed $key 50 | * @return mixed 51 | */ 52 | public function getValidator($key) 53 | { 54 | return isset($this->validators[$key]) ? $this->validators[$key] : null; 55 | } 56 | 57 | /** 58 | * Validate $inputs 59 | * 60 | * @param array $inputs 61 | * @param array $rules 62 | * @param array $messages 63 | * @return Validation 64 | */ 65 | public function validate(array $inputs, array $rules, array $messages = []): Validation 66 | { 67 | $validation = $this->make($inputs, $rules, $messages); 68 | $validation->validate(); 69 | return $validation; 70 | } 71 | 72 | /** 73 | * Given $inputs, $rules and $messages to make the Validation class instance 74 | * 75 | * @param array $inputs 76 | * @param array $rules 77 | * @param array $messages 78 | * @return Validation 79 | */ 80 | public function make(array $inputs, array $rules, array $messages = []): Validation 81 | { 82 | $messages = array_merge($this->messages, $messages); 83 | $validation = new Validation($this, $inputs, $rules, $messages); 84 | $validation->setTranslations($this->getTranslations()); 85 | 86 | return $validation; 87 | } 88 | 89 | /** 90 | * Magic invoke method to make Rule instance 91 | * 92 | * @param string $rule 93 | * @return Rule 94 | * @throws RuleNotFoundException 95 | */ 96 | public function __invoke(string $rule): Rule 97 | { 98 | $args = func_get_args(); 99 | $rule = array_shift($args); 100 | $params = $args; 101 | $validator = $this->getValidator($rule); 102 | if (!$validator) { 103 | throw new RuleNotFoundException("Validator '{$rule}' is not registered", 1); 104 | } 105 | 106 | $clonedValidator = clone $validator; 107 | $clonedValidator->fillParameters($params); 108 | 109 | return $clonedValidator; 110 | } 111 | 112 | /** 113 | * Initialize base validators array 114 | * 115 | * @return void 116 | */ 117 | protected function registerBaseValidators() 118 | { 119 | $baseValidator = [ 120 | 'required' => new Rules\Required, 121 | 'required_if' => new Rules\RequiredIf, 122 | 'required_unless' => new Rules\RequiredUnless, 123 | 'required_with' => new Rules\RequiredWith, 124 | 'required_without' => new Rules\RequiredWithout, 125 | 'required_with_all' => new Rules\RequiredWithAll, 126 | 'required_without_all' => new Rules\RequiredWithoutAll, 127 | 'email' => new Rules\Email, 128 | 'alpha' => new Rules\Alpha, 129 | 'numeric' => new Rules\Numeric, 130 | 'alpha_num' => new Rules\AlphaNum, 131 | 'alpha_dash' => new Rules\AlphaDash, 132 | 'alpha_spaces' => new Rules\AlphaSpaces, 133 | 'in' => new Rules\In, 134 | 'not_in' => new Rules\NotIn, 135 | 'min' => new Rules\Min, 136 | 'max' => new Rules\Max, 137 | 'between' => new Rules\Between, 138 | 'url' => new Rules\Url, 139 | 'integer' => new Rules\Integer, 140 | 'boolean' => new Rules\Boolean, 141 | 'ip' => new Rules\Ip, 142 | 'ipv4' => new Rules\Ipv4, 143 | 'ipv6' => new Rules\Ipv6, 144 | 'extension' => new Rules\Extension, 145 | 'array' => new Rules\TypeArray, 146 | 'same' => new Rules\Same, 147 | 'regex' => new Rules\Regex, 148 | 'date' => new Rules\Date, 149 | 'accepted' => new Rules\Accepted, 150 | 'present' => new Rules\Present, 151 | 'different' => new Rules\Different, 152 | 'uploaded_file' => new Rules\UploadedFile, 153 | 'mimes' => new Rules\Mimes, 154 | 'callback' => new Rules\Callback, 155 | 'before' => new Rules\Before, 156 | 'after' => new Rules\After, 157 | 'lowercase' => new Rules\Lowercase, 158 | 'uppercase' => new Rules\Uppercase, 159 | 'json' => new Rules\Json, 160 | 'digits' => new Rules\Digits, 161 | 'digits_between' => new Rules\DigitsBetween, 162 | 'defaults' => new Rules\Defaults, 163 | 'default' => new Rules\Defaults, // alias of defaults 164 | 'nullable' => new Rules\Nullable, 165 | ]; 166 | 167 | foreach ($baseValidator as $key => $validator) { 168 | $this->setValidator($key, $validator); 169 | } 170 | } 171 | 172 | /** 173 | * Given $ruleName and $rule to add new validator 174 | * 175 | * @param string $ruleName 176 | * @param \Rakit\Validation\Rule $rule 177 | * @return void 178 | */ 179 | public function addValidator(string $ruleName, Rule $rule) 180 | { 181 | if (!$this->allowRuleOverride && array_key_exists($ruleName, $this->validators)) { 182 | throw new RuleQuashException( 183 | "You cannot override a built in rule. You have to rename your rule" 184 | ); 185 | } 186 | 187 | $this->setValidator($ruleName, $rule); 188 | } 189 | 190 | /** 191 | * Set rule can allow to be overrided 192 | * 193 | * @param boolean $status 194 | * @return void 195 | */ 196 | public function allowRuleOverride(bool $status = false) 197 | { 198 | $this->allowRuleOverride = $status; 199 | } 200 | 201 | /** 202 | * Set this can use humanize keys 203 | * 204 | * @param boolean $useHumanizedKeys 205 | * @return void 206 | */ 207 | public function setUseHumanizedKeys(bool $useHumanizedKeys = true) 208 | { 209 | $this->useHumanizedKeys = $useHumanizedKeys; 210 | } 211 | 212 | /** 213 | * Get $this->useHumanizedKeys value 214 | * 215 | * @return void 216 | */ 217 | public function isUsingHumanizedKey(): bool 218 | { 219 | return $this->useHumanizedKeys; 220 | } 221 | } 222 | --------------------------------------------------------------------------------