1<?php 2 3/* 4 * This file is part of the webmozart/assert package. 5 * 6 * (c) Bernhard Schussek <bschussek@gmail.com> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Webmozart\Assert; 13 14use ArrayAccess; 15use BadMethodCallException; 16use Closure; 17use Countable; 18use DateTime; 19use DateTimeImmutable; 20use Exception; 21use ResourceBundle; 22use SimpleXMLElement; 23use Throwable; 24use Traversable; 25 26/** 27 * Efficient assertions to validate the input/output of your methods. 28 * 29 * @since 1.0 30 * 31 * @author Bernhard Schussek <bschussek@gmail.com> 32 */ 33class Assert 34{ 35 use Mixin; 36 37 /** 38 * @psalm-pure 39 * @psalm-assert string $value 40 * 41 * @param mixed $value 42 * @param string $message 43 * 44 * @throws InvalidArgumentException 45 */ 46 public static function string($value, $message = '') 47 { 48 if (!\is_string($value)) { 49 static::reportInvalidArgument(\sprintf( 50 $message ?: 'Expected a string. Got: %s', 51 static::typeToString($value) 52 )); 53 } 54 } 55 56 /** 57 * @psalm-pure 58 * @psalm-assert non-empty-string $value 59 * 60 * @param mixed $value 61 * @param string $message 62 * 63 * @throws InvalidArgumentException 64 */ 65 public static function stringNotEmpty($value, $message = '') 66 { 67 static::string($value, $message); 68 static::notEq($value, '', $message); 69 } 70 71 /** 72 * @psalm-pure 73 * @psalm-assert int $value 74 * 75 * @param mixed $value 76 * @param string $message 77 * 78 * @throws InvalidArgumentException 79 */ 80 public static function integer($value, $message = '') 81 { 82 if (!\is_int($value)) { 83 static::reportInvalidArgument(\sprintf( 84 $message ?: 'Expected an integer. Got: %s', 85 static::typeToString($value) 86 )); 87 } 88 } 89 90 /** 91 * @psalm-pure 92 * @psalm-assert numeric $value 93 * 94 * @param mixed $value 95 * @param string $message 96 * 97 * @throws InvalidArgumentException 98 */ 99 public static function integerish($value, $message = '') 100 { 101 if (!\is_numeric($value) || $value != (int) $value) { 102 static::reportInvalidArgument(\sprintf( 103 $message ?: 'Expected an integerish value. Got: %s', 104 static::typeToString($value) 105 )); 106 } 107 } 108 109 /** 110 * @psalm-pure 111 * @psalm-assert positive-int $value 112 * 113 * @param mixed $value 114 * @param string $message 115 * 116 * @throws InvalidArgumentException 117 */ 118 public static function positiveInteger($value, $message = '') 119 { 120 if (!(\is_int($value) && $value > 0)) { 121 static::reportInvalidArgument(\sprintf( 122 $message ?: 'Expected a positive integer. Got: %s', 123 static::valueToString($value) 124 )); 125 } 126 } 127 128 /** 129 * @psalm-pure 130 * @psalm-assert float $value 131 * 132 * @param mixed $value 133 * @param string $message 134 * 135 * @throws InvalidArgumentException 136 */ 137 public static function float($value, $message = '') 138 { 139 if (!\is_float($value)) { 140 static::reportInvalidArgument(\sprintf( 141 $message ?: 'Expected a float. Got: %s', 142 static::typeToString($value) 143 )); 144 } 145 } 146 147 /** 148 * @psalm-pure 149 * @psalm-assert numeric $value 150 * 151 * @param mixed $value 152 * @param string $message 153 * 154 * @throws InvalidArgumentException 155 */ 156 public static function numeric($value, $message = '') 157 { 158 if (!\is_numeric($value)) { 159 static::reportInvalidArgument(\sprintf( 160 $message ?: 'Expected a numeric. Got: %s', 161 static::typeToString($value) 162 )); 163 } 164 } 165 166 /** 167 * @psalm-pure 168 * @psalm-assert positive-int|0 $value 169 * 170 * @param mixed $value 171 * @param string $message 172 * 173 * @throws InvalidArgumentException 174 */ 175 public static function natural($value, $message = '') 176 { 177 if (!\is_int($value) || $value < 0) { 178 static::reportInvalidArgument(\sprintf( 179 $message ?: 'Expected a non-negative integer. Got: %s', 180 static::valueToString($value) 181 )); 182 } 183 } 184 185 /** 186 * @psalm-pure 187 * @psalm-assert bool $value 188 * 189 * @param mixed $value 190 * @param string $message 191 * 192 * @throws InvalidArgumentException 193 */ 194 public static function boolean($value, $message = '') 195 { 196 if (!\is_bool($value)) { 197 static::reportInvalidArgument(\sprintf( 198 $message ?: 'Expected a boolean. Got: %s', 199 static::typeToString($value) 200 )); 201 } 202 } 203 204 /** 205 * @psalm-pure 206 * @psalm-assert scalar $value 207 * 208 * @param mixed $value 209 * @param string $message 210 * 211 * @throws InvalidArgumentException 212 */ 213 public static function scalar($value, $message = '') 214 { 215 if (!\is_scalar($value)) { 216 static::reportInvalidArgument(\sprintf( 217 $message ?: 'Expected a scalar. Got: %s', 218 static::typeToString($value) 219 )); 220 } 221 } 222 223 /** 224 * @psalm-pure 225 * @psalm-assert object $value 226 * 227 * @param mixed $value 228 * @param string $message 229 * 230 * @throws InvalidArgumentException 231 */ 232 public static function object($value, $message = '') 233 { 234 if (!\is_object($value)) { 235 static::reportInvalidArgument(\sprintf( 236 $message ?: 'Expected an object. Got: %s', 237 static::typeToString($value) 238 )); 239 } 240 } 241 242 /** 243 * @psalm-pure 244 * @psalm-assert resource $value 245 * 246 * @param mixed $value 247 * @param string|null $type type of resource this should be. @see https://www.php.net/manual/en/function.get-resource-type.php 248 * @param string $message 249 * 250 * @throws InvalidArgumentException 251 */ 252 public static function resource($value, $type = null, $message = '') 253 { 254 if (!\is_resource($value)) { 255 static::reportInvalidArgument(\sprintf( 256 $message ?: 'Expected a resource. Got: %s', 257 static::typeToString($value) 258 )); 259 } 260 261 if ($type && $type !== \get_resource_type($value)) { 262 static::reportInvalidArgument(\sprintf( 263 $message ?: 'Expected a resource of type %2$s. Got: %s', 264 static::typeToString($value), 265 $type 266 )); 267 } 268 } 269 270 /** 271 * @psalm-pure 272 * @psalm-assert callable $value 273 * 274 * @param mixed $value 275 * @param string $message 276 * 277 * @throws InvalidArgumentException 278 */ 279 public static function isCallable($value, $message = '') 280 { 281 if (!\is_callable($value)) { 282 static::reportInvalidArgument(\sprintf( 283 $message ?: 'Expected a callable. Got: %s', 284 static::typeToString($value) 285 )); 286 } 287 } 288 289 /** 290 * @psalm-pure 291 * @psalm-assert array $value 292 * 293 * @param mixed $value 294 * @param string $message 295 * 296 * @throws InvalidArgumentException 297 */ 298 public static function isArray($value, $message = '') 299 { 300 if (!\is_array($value)) { 301 static::reportInvalidArgument(\sprintf( 302 $message ?: 'Expected an array. Got: %s', 303 static::typeToString($value) 304 )); 305 } 306 } 307 308 /** 309 * @psalm-pure 310 * @psalm-assert iterable $value 311 * 312 * @deprecated use "isIterable" or "isInstanceOf" instead 313 * 314 * @param mixed $value 315 * @param string $message 316 * 317 * @throws InvalidArgumentException 318 */ 319 public static function isTraversable($value, $message = '') 320 { 321 @\trigger_error( 322 \sprintf( 323 'The "%s" assertion is deprecated. You should stop using it, as it will soon be removed in 2.0 version. Use "isIterable" or "isInstanceOf" instead.', 324 __METHOD__ 325 ), 326 \E_USER_DEPRECATED 327 ); 328 329 if (!\is_array($value) && !($value instanceof Traversable)) { 330 static::reportInvalidArgument(\sprintf( 331 $message ?: 'Expected a traversable. Got: %s', 332 static::typeToString($value) 333 )); 334 } 335 } 336 337 /** 338 * @psalm-pure 339 * @psalm-assert array|ArrayAccess $value 340 * 341 * @param mixed $value 342 * @param string $message 343 * 344 * @throws InvalidArgumentException 345 */ 346 public static function isArrayAccessible($value, $message = '') 347 { 348 if (!\is_array($value) && !($value instanceof ArrayAccess)) { 349 static::reportInvalidArgument(\sprintf( 350 $message ?: 'Expected an array accessible. Got: %s', 351 static::typeToString($value) 352 )); 353 } 354 } 355 356 /** 357 * @psalm-pure 358 * @psalm-assert countable $value 359 * 360 * @param mixed $value 361 * @param string $message 362 * 363 * @throws InvalidArgumentException 364 */ 365 public static function isCountable($value, $message = '') 366 { 367 if ( 368 !\is_array($value) 369 && !($value instanceof Countable) 370 && !($value instanceof ResourceBundle) 371 && !($value instanceof SimpleXMLElement) 372 ) { 373 static::reportInvalidArgument(\sprintf( 374 $message ?: 'Expected a countable. Got: %s', 375 static::typeToString($value) 376 )); 377 } 378 } 379 380 /** 381 * @psalm-pure 382 * @psalm-assert iterable $value 383 * 384 * @param mixed $value 385 * @param string $message 386 * 387 * @throws InvalidArgumentException 388 */ 389 public static function isIterable($value, $message = '') 390 { 391 if (!\is_array($value) && !($value instanceof Traversable)) { 392 static::reportInvalidArgument(\sprintf( 393 $message ?: 'Expected an iterable. Got: %s', 394 static::typeToString($value) 395 )); 396 } 397 } 398 399 /** 400 * @psalm-pure 401 * @psalm-template ExpectedType of object 402 * @psalm-param class-string<ExpectedType> $class 403 * @psalm-assert ExpectedType $value 404 * 405 * @param mixed $value 406 * @param string|object $class 407 * @param string $message 408 * 409 * @throws InvalidArgumentException 410 */ 411 public static function isInstanceOf($value, $class, $message = '') 412 { 413 if (!($value instanceof $class)) { 414 static::reportInvalidArgument(\sprintf( 415 $message ?: 'Expected an instance of %2$s. Got: %s', 416 static::typeToString($value), 417 $class 418 )); 419 } 420 } 421 422 /** 423 * @psalm-pure 424 * @psalm-template ExpectedType of object 425 * @psalm-param class-string<ExpectedType> $class 426 * @psalm-assert !ExpectedType $value 427 * 428 * @param mixed $value 429 * @param string|object $class 430 * @param string $message 431 * 432 * @throws InvalidArgumentException 433 */ 434 public static function notInstanceOf($value, $class, $message = '') 435 { 436 if ($value instanceof $class) { 437 static::reportInvalidArgument(\sprintf( 438 $message ?: 'Expected an instance other than %2$s. Got: %s', 439 static::typeToString($value), 440 $class 441 )); 442 } 443 } 444 445 /** 446 * @psalm-pure 447 * @psalm-param array<class-string> $classes 448 * 449 * @param mixed $value 450 * @param array<object|string> $classes 451 * @param string $message 452 * 453 * @throws InvalidArgumentException 454 */ 455 public static function isInstanceOfAny($value, array $classes, $message = '') 456 { 457 foreach ($classes as $class) { 458 if ($value instanceof $class) { 459 return; 460 } 461 } 462 463 static::reportInvalidArgument(\sprintf( 464 $message ?: 'Expected an instance of any of %2$s. Got: %s', 465 static::typeToString($value), 466 \implode(', ', \array_map(array('static', 'valueToString'), $classes)) 467 )); 468 } 469 470 /** 471 * @psalm-pure 472 * @psalm-template ExpectedType of object 473 * @psalm-param class-string<ExpectedType> $class 474 * @psalm-assert ExpectedType|class-string<ExpectedType> $value 475 * 476 * @param object|string $value 477 * @param string $class 478 * @param string $message 479 * 480 * @throws InvalidArgumentException 481 */ 482 public static function isAOf($value, $class, $message = '') 483 { 484 static::string($class, 'Expected class as a string. Got: %s'); 485 486 if (!\is_a($value, $class, \is_string($value))) { 487 static::reportInvalidArgument(sprintf( 488 $message ?: 'Expected an instance of this class or to this class among his parents %2$s. Got: %s', 489 static::typeToString($value), 490 $class 491 )); 492 } 493 } 494 495 /** 496 * @psalm-pure 497 * @psalm-template UnexpectedType of object 498 * @psalm-param class-string<UnexpectedType> $class 499 * @psalm-assert !UnexpectedType $value 500 * @psalm-assert !class-string<UnexpectedType> $value 501 * 502 * @param object|string $value 503 * @param string $class 504 * @param string $message 505 * 506 * @throws InvalidArgumentException 507 */ 508 public static function isNotA($value, $class, $message = '') 509 { 510 static::string($class, 'Expected class as a string. Got: %s'); 511 512 if (\is_a($value, $class, \is_string($value))) { 513 static::reportInvalidArgument(sprintf( 514 $message ?: 'Expected an instance of this class or to this class among his parents other than %2$s. Got: %s', 515 static::typeToString($value), 516 $class 517 )); 518 } 519 } 520 521 /** 522 * @psalm-pure 523 * @psalm-param array<class-string> $classes 524 * 525 * @param object|string $value 526 * @param string[] $classes 527 * @param string $message 528 * 529 * @throws InvalidArgumentException 530 */ 531 public static function isAnyOf($value, array $classes, $message = '') 532 { 533 foreach ($classes as $class) { 534 static::string($class, 'Expected class as a string. Got: %s'); 535 536 if (\is_a($value, $class, \is_string($value))) { 537 return; 538 } 539 } 540 541 static::reportInvalidArgument(sprintf( 542 $message ?: 'Expected an any of instance of this class or to this class among his parents other than %2$s. Got: %s', 543 static::typeToString($value), 544 \implode(', ', \array_map(array('static', 'valueToString'), $classes)) 545 )); 546 } 547 548 /** 549 * @psalm-pure 550 * @psalm-assert empty $value 551 * 552 * @param mixed $value 553 * @param string $message 554 * 555 * @throws InvalidArgumentException 556 */ 557 public static function isEmpty($value, $message = '') 558 { 559 if (!empty($value)) { 560 static::reportInvalidArgument(\sprintf( 561 $message ?: 'Expected an empty value. Got: %s', 562 static::valueToString($value) 563 )); 564 } 565 } 566 567 /** 568 * @psalm-pure 569 * @psalm-assert !empty $value 570 * 571 * @param mixed $value 572 * @param string $message 573 * 574 * @throws InvalidArgumentException 575 */ 576 public static function notEmpty($value, $message = '') 577 { 578 if (empty($value)) { 579 static::reportInvalidArgument(\sprintf( 580 $message ?: 'Expected a non-empty value. Got: %s', 581 static::valueToString($value) 582 )); 583 } 584 } 585 586 /** 587 * @psalm-pure 588 * @psalm-assert null $value 589 * 590 * @param mixed $value 591 * @param string $message 592 * 593 * @throws InvalidArgumentException 594 */ 595 public static function null($value, $message = '') 596 { 597 if (null !== $value) { 598 static::reportInvalidArgument(\sprintf( 599 $message ?: 'Expected null. Got: %s', 600 static::valueToString($value) 601 )); 602 } 603 } 604 605 /** 606 * @psalm-pure 607 * @psalm-assert !null $value 608 * 609 * @param mixed $value 610 * @param string $message 611 * 612 * @throws InvalidArgumentException 613 */ 614 public static function notNull($value, $message = '') 615 { 616 if (null === $value) { 617 static::reportInvalidArgument( 618 $message ?: 'Expected a value other than null.' 619 ); 620 } 621 } 622 623 /** 624 * @psalm-pure 625 * @psalm-assert true $value 626 * 627 * @param mixed $value 628 * @param string $message 629 * 630 * @throws InvalidArgumentException 631 */ 632 public static function true($value, $message = '') 633 { 634 if (true !== $value) { 635 static::reportInvalidArgument(\sprintf( 636 $message ?: 'Expected a value to be true. Got: %s', 637 static::valueToString($value) 638 )); 639 } 640 } 641 642 /** 643 * @psalm-pure 644 * @psalm-assert false $value 645 * 646 * @param mixed $value 647 * @param string $message 648 * 649 * @throws InvalidArgumentException 650 */ 651 public static function false($value, $message = '') 652 { 653 if (false !== $value) { 654 static::reportInvalidArgument(\sprintf( 655 $message ?: 'Expected a value to be false. Got: %s', 656 static::valueToString($value) 657 )); 658 } 659 } 660 661 /** 662 * @psalm-pure 663 * @psalm-assert !false $value 664 * 665 * @param mixed $value 666 * @param string $message 667 * 668 * @throws InvalidArgumentException 669 */ 670 public static function notFalse($value, $message = '') 671 { 672 if (false === $value) { 673 static::reportInvalidArgument( 674 $message ?: 'Expected a value other than false.' 675 ); 676 } 677 } 678 679 /** 680 * @param mixed $value 681 * @param string $message 682 * 683 * @throws InvalidArgumentException 684 */ 685 public static function ip($value, $message = '') 686 { 687 if (false === \filter_var($value, \FILTER_VALIDATE_IP)) { 688 static::reportInvalidArgument(\sprintf( 689 $message ?: 'Expected a value to be an IP. Got: %s', 690 static::valueToString($value) 691 )); 692 } 693 } 694 695 /** 696 * @param mixed $value 697 * @param string $message 698 * 699 * @throws InvalidArgumentException 700 */ 701 public static function ipv4($value, $message = '') 702 { 703 if (false === \filter_var($value, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) { 704 static::reportInvalidArgument(\sprintf( 705 $message ?: 'Expected a value to be an IPv4. Got: %s', 706 static::valueToString($value) 707 )); 708 } 709 } 710 711 /** 712 * @param mixed $value 713 * @param string $message 714 * 715 * @throws InvalidArgumentException 716 */ 717 public static function ipv6($value, $message = '') 718 { 719 if (false === \filter_var($value, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { 720 static::reportInvalidArgument(\sprintf( 721 $message ?: 'Expected a value to be an IPv6. Got: %s', 722 static::valueToString($value) 723 )); 724 } 725 } 726 727 /** 728 * @param mixed $value 729 * @param string $message 730 * 731 * @throws InvalidArgumentException 732 */ 733 public static function email($value, $message = '') 734 { 735 if (false === \filter_var($value, FILTER_VALIDATE_EMAIL)) { 736 static::reportInvalidArgument(\sprintf( 737 $message ?: 'Expected a value to be a valid e-mail address. Got: %s', 738 static::valueToString($value) 739 )); 740 } 741 } 742 743 /** 744 * Does non strict comparisons on the items, so ['3', 3] will not pass the assertion. 745 * 746 * @param array $values 747 * @param string $message 748 * 749 * @throws InvalidArgumentException 750 */ 751 public static function uniqueValues(array $values, $message = '') 752 { 753 $allValues = \count($values); 754 $uniqueValues = \count(\array_unique($values)); 755 756 if ($allValues !== $uniqueValues) { 757 $difference = $allValues - $uniqueValues; 758 759 static::reportInvalidArgument(\sprintf( 760 $message ?: 'Expected an array of unique values, but %s of them %s duplicated', 761 $difference, 762 (1 === $difference ? 'is' : 'are') 763 )); 764 } 765 } 766 767 /** 768 * @param mixed $value 769 * @param mixed $expect 770 * @param string $message 771 * 772 * @throws InvalidArgumentException 773 */ 774 public static function eq($value, $expect, $message = '') 775 { 776 if ($expect != $value) { 777 static::reportInvalidArgument(\sprintf( 778 $message ?: 'Expected a value equal to %2$s. Got: %s', 779 static::valueToString($value), 780 static::valueToString($expect) 781 )); 782 } 783 } 784 785 /** 786 * @param mixed $value 787 * @param mixed $expect 788 * @param string $message 789 * 790 * @throws InvalidArgumentException 791 */ 792 public static function notEq($value, $expect, $message = '') 793 { 794 if ($expect == $value) { 795 static::reportInvalidArgument(\sprintf( 796 $message ?: 'Expected a different value than %s.', 797 static::valueToString($expect) 798 )); 799 } 800 } 801 802 /** 803 * @psalm-pure 804 * 805 * @param mixed $value 806 * @param mixed $expect 807 * @param string $message 808 * 809 * @throws InvalidArgumentException 810 */ 811 public static function same($value, $expect, $message = '') 812 { 813 if ($expect !== $value) { 814 static::reportInvalidArgument(\sprintf( 815 $message ?: 'Expected a value identical to %2$s. Got: %s', 816 static::valueToString($value), 817 static::valueToString($expect) 818 )); 819 } 820 } 821 822 /** 823 * @psalm-pure 824 * 825 * @param mixed $value 826 * @param mixed $expect 827 * @param string $message 828 * 829 * @throws InvalidArgumentException 830 */ 831 public static function notSame($value, $expect, $message = '') 832 { 833 if ($expect === $value) { 834 static::reportInvalidArgument(\sprintf( 835 $message ?: 'Expected a value not identical to %s.', 836 static::valueToString($expect) 837 )); 838 } 839 } 840 841 /** 842 * @psalm-pure 843 * 844 * @param mixed $value 845 * @param mixed $limit 846 * @param string $message 847 * 848 * @throws InvalidArgumentException 849 */ 850 public static function greaterThan($value, $limit, $message = '') 851 { 852 if ($value <= $limit) { 853 static::reportInvalidArgument(\sprintf( 854 $message ?: 'Expected a value greater than %2$s. Got: %s', 855 static::valueToString($value), 856 static::valueToString($limit) 857 )); 858 } 859 } 860 861 /** 862 * @psalm-pure 863 * 864 * @param mixed $value 865 * @param mixed $limit 866 * @param string $message 867 * 868 * @throws InvalidArgumentException 869 */ 870 public static function greaterThanEq($value, $limit, $message = '') 871 { 872 if ($value < $limit) { 873 static::reportInvalidArgument(\sprintf( 874 $message ?: 'Expected a value greater than or equal to %2$s. Got: %s', 875 static::valueToString($value), 876 static::valueToString($limit) 877 )); 878 } 879 } 880 881 /** 882 * @psalm-pure 883 * 884 * @param mixed $value 885 * @param mixed $limit 886 * @param string $message 887 * 888 * @throws InvalidArgumentException 889 */ 890 public static function lessThan($value, $limit, $message = '') 891 { 892 if ($value >= $limit) { 893 static::reportInvalidArgument(\sprintf( 894 $message ?: 'Expected a value less than %2$s. Got: %s', 895 static::valueToString($value), 896 static::valueToString($limit) 897 )); 898 } 899 } 900 901 /** 902 * @psalm-pure 903 * 904 * @param mixed $value 905 * @param mixed $limit 906 * @param string $message 907 * 908 * @throws InvalidArgumentException 909 */ 910 public static function lessThanEq($value, $limit, $message = '') 911 { 912 if ($value > $limit) { 913 static::reportInvalidArgument(\sprintf( 914 $message ?: 'Expected a value less than or equal to %2$s. Got: %s', 915 static::valueToString($value), 916 static::valueToString($limit) 917 )); 918 } 919 } 920 921 /** 922 * Inclusive range, so Assert::(3, 3, 5) passes. 923 * 924 * @psalm-pure 925 * 926 * @param mixed $value 927 * @param mixed $min 928 * @param mixed $max 929 * @param string $message 930 * 931 * @throws InvalidArgumentException 932 */ 933 public static function range($value, $min, $max, $message = '') 934 { 935 if ($value < $min || $value > $max) { 936 static::reportInvalidArgument(\sprintf( 937 $message ?: 'Expected a value between %2$s and %3$s. Got: %s', 938 static::valueToString($value), 939 static::valueToString($min), 940 static::valueToString($max) 941 )); 942 } 943 } 944 945 /** 946 * A more human-readable alias of Assert::inArray(). 947 * 948 * @psalm-pure 949 * 950 * @param mixed $value 951 * @param array $values 952 * @param string $message 953 * 954 * @throws InvalidArgumentException 955 */ 956 public static function oneOf($value, array $values, $message = '') 957 { 958 static::inArray($value, $values, $message); 959 } 960 961 /** 962 * Does strict comparison, so Assert::inArray(3, ['3']) does not pass the assertion. 963 * 964 * @psalm-pure 965 * 966 * @param mixed $value 967 * @param array $values 968 * @param string $message 969 * 970 * @throws InvalidArgumentException 971 */ 972 public static function inArray($value, array $values, $message = '') 973 { 974 if (!\in_array($value, $values, true)) { 975 static::reportInvalidArgument(\sprintf( 976 $message ?: 'Expected one of: %2$s. Got: %s', 977 static::valueToString($value), 978 \implode(', ', \array_map(array('static', 'valueToString'), $values)) 979 )); 980 } 981 } 982 983 /** 984 * @psalm-pure 985 * 986 * @param string $value 987 * @param string $subString 988 * @param string $message 989 * 990 * @throws InvalidArgumentException 991 */ 992 public static function contains($value, $subString, $message = '') 993 { 994 if (false === \strpos($value, $subString)) { 995 static::reportInvalidArgument(\sprintf( 996 $message ?: 'Expected a value to contain %2$s. Got: %s', 997 static::valueToString($value), 998 static::valueToString($subString) 999 )); 1000 } 1001 } 1002 1003 /** 1004 * @psalm-pure 1005 * 1006 * @param string $value 1007 * @param string $subString 1008 * @param string $message 1009 * 1010 * @throws InvalidArgumentException 1011 */ 1012 public static function notContains($value, $subString, $message = '') 1013 { 1014 if (false !== \strpos($value, $subString)) { 1015 static::reportInvalidArgument(\sprintf( 1016 $message ?: '%2$s was not expected to be contained in a value. Got: %s', 1017 static::valueToString($value), 1018 static::valueToString($subString) 1019 )); 1020 } 1021 } 1022 1023 /** 1024 * @psalm-pure 1025 * 1026 * @param string $value 1027 * @param string $message 1028 * 1029 * @throws InvalidArgumentException 1030 */ 1031 public static function notWhitespaceOnly($value, $message = '') 1032 { 1033 if (\preg_match('/^\s*$/', $value)) { 1034 static::reportInvalidArgument(\sprintf( 1035 $message ?: 'Expected a non-whitespace string. Got: %s', 1036 static::valueToString($value) 1037 )); 1038 } 1039 } 1040 1041 /** 1042 * @psalm-pure 1043 * 1044 * @param string $value 1045 * @param string $prefix 1046 * @param string $message 1047 * 1048 * @throws InvalidArgumentException 1049 */ 1050 public static function startsWith($value, $prefix, $message = '') 1051 { 1052 if (0 !== \strpos($value, $prefix)) { 1053 static::reportInvalidArgument(\sprintf( 1054 $message ?: 'Expected a value to start with %2$s. Got: %s', 1055 static::valueToString($value), 1056 static::valueToString($prefix) 1057 )); 1058 } 1059 } 1060 1061 /** 1062 * @psalm-pure 1063 * 1064 * @param string $value 1065 * @param string $prefix 1066 * @param string $message 1067 * 1068 * @throws InvalidArgumentException 1069 */ 1070 public static function notStartsWith($value, $prefix, $message = '') 1071 { 1072 if (0 === \strpos($value, $prefix)) { 1073 static::reportInvalidArgument(\sprintf( 1074 $message ?: 'Expected a value not to start with %2$s. Got: %s', 1075 static::valueToString($value), 1076 static::valueToString($prefix) 1077 )); 1078 } 1079 } 1080 1081 /** 1082 * @psalm-pure 1083 * 1084 * @param mixed $value 1085 * @param string $message 1086 * 1087 * @throws InvalidArgumentException 1088 */ 1089 public static function startsWithLetter($value, $message = '') 1090 { 1091 static::string($value); 1092 1093 $valid = isset($value[0]); 1094 1095 if ($valid) { 1096 $locale = \setlocale(LC_CTYPE, 0); 1097 \setlocale(LC_CTYPE, 'C'); 1098 $valid = \ctype_alpha($value[0]); 1099 \setlocale(LC_CTYPE, $locale); 1100 } 1101 1102 if (!$valid) { 1103 static::reportInvalidArgument(\sprintf( 1104 $message ?: 'Expected a value to start with a letter. Got: %s', 1105 static::valueToString($value) 1106 )); 1107 } 1108 } 1109 1110 /** 1111 * @psalm-pure 1112 * 1113 * @param string $value 1114 * @param string $suffix 1115 * @param string $message 1116 * 1117 * @throws InvalidArgumentException 1118 */ 1119 public static function endsWith($value, $suffix, $message = '') 1120 { 1121 if ($suffix !== \substr($value, -\strlen($suffix))) { 1122 static::reportInvalidArgument(\sprintf( 1123 $message ?: 'Expected a value to end with %2$s. Got: %s', 1124 static::valueToString($value), 1125 static::valueToString($suffix) 1126 )); 1127 } 1128 } 1129 1130 /** 1131 * @psalm-pure 1132 * 1133 * @param string $value 1134 * @param string $suffix 1135 * @param string $message 1136 * 1137 * @throws InvalidArgumentException 1138 */ 1139 public static function notEndsWith($value, $suffix, $message = '') 1140 { 1141 if ($suffix === \substr($value, -\strlen($suffix))) { 1142 static::reportInvalidArgument(\sprintf( 1143 $message ?: 'Expected a value not to end with %2$s. Got: %s', 1144 static::valueToString($value), 1145 static::valueToString($suffix) 1146 )); 1147 } 1148 } 1149 1150 /** 1151 * @psalm-pure 1152 * 1153 * @param string $value 1154 * @param string $pattern 1155 * @param string $message 1156 * 1157 * @throws InvalidArgumentException 1158 */ 1159 public static function regex($value, $pattern, $message = '') 1160 { 1161 if (!\preg_match($pattern, $value)) { 1162 static::reportInvalidArgument(\sprintf( 1163 $message ?: 'The value %s does not match the expected pattern.', 1164 static::valueToString($value) 1165 )); 1166 } 1167 } 1168 1169 /** 1170 * @psalm-pure 1171 * 1172 * @param string $value 1173 * @param string $pattern 1174 * @param string $message 1175 * 1176 * @throws InvalidArgumentException 1177 */ 1178 public static function notRegex($value, $pattern, $message = '') 1179 { 1180 if (\preg_match($pattern, $value, $matches, PREG_OFFSET_CAPTURE)) { 1181 static::reportInvalidArgument(\sprintf( 1182 $message ?: 'The value %s matches the pattern %s (at offset %d).', 1183 static::valueToString($value), 1184 static::valueToString($pattern), 1185 $matches[0][1] 1186 )); 1187 } 1188 } 1189 1190 /** 1191 * @psalm-pure 1192 * 1193 * @param mixed $value 1194 * @param string $message 1195 * 1196 * @throws InvalidArgumentException 1197 */ 1198 public static function unicodeLetters($value, $message = '') 1199 { 1200 static::string($value); 1201 1202 if (!\preg_match('/^\p{L}+$/u', $value)) { 1203 static::reportInvalidArgument(\sprintf( 1204 $message ?: 'Expected a value to contain only Unicode letters. Got: %s', 1205 static::valueToString($value) 1206 )); 1207 } 1208 } 1209 1210 /** 1211 * @psalm-pure 1212 * 1213 * @param mixed $value 1214 * @param string $message 1215 * 1216 * @throws InvalidArgumentException 1217 */ 1218 public static function alpha($value, $message = '') 1219 { 1220 static::string($value); 1221 1222 $locale = \setlocale(LC_CTYPE, 0); 1223 \setlocale(LC_CTYPE, 'C'); 1224 $valid = !\ctype_alpha($value); 1225 \setlocale(LC_CTYPE, $locale); 1226 1227 if ($valid) { 1228 static::reportInvalidArgument(\sprintf( 1229 $message ?: 'Expected a value to contain only letters. Got: %s', 1230 static::valueToString($value) 1231 )); 1232 } 1233 } 1234 1235 /** 1236 * @psalm-pure 1237 * 1238 * @param string $value 1239 * @param string $message 1240 * 1241 * @throws InvalidArgumentException 1242 */ 1243 public static function digits($value, $message = '') 1244 { 1245 $locale = \setlocale(LC_CTYPE, 0); 1246 \setlocale(LC_CTYPE, 'C'); 1247 $valid = !\ctype_digit($value); 1248 \setlocale(LC_CTYPE, $locale); 1249 1250 if ($valid) { 1251 static::reportInvalidArgument(\sprintf( 1252 $message ?: 'Expected a value to contain digits only. Got: %s', 1253 static::valueToString($value) 1254 )); 1255 } 1256 } 1257 1258 /** 1259 * @psalm-pure 1260 * 1261 * @param string $value 1262 * @param string $message 1263 * 1264 * @throws InvalidArgumentException 1265 */ 1266 public static function alnum($value, $message = '') 1267 { 1268 $locale = \setlocale(LC_CTYPE, 0); 1269 \setlocale(LC_CTYPE, 'C'); 1270 $valid = !\ctype_alnum($value); 1271 \setlocale(LC_CTYPE, $locale); 1272 1273 if ($valid) { 1274 static::reportInvalidArgument(\sprintf( 1275 $message ?: 'Expected a value to contain letters and digits only. Got: %s', 1276 static::valueToString($value) 1277 )); 1278 } 1279 } 1280 1281 /** 1282 * @psalm-pure 1283 * @psalm-assert lowercase-string $value 1284 * 1285 * @param string $value 1286 * @param string $message 1287 * 1288 * @throws InvalidArgumentException 1289 */ 1290 public static function lower($value, $message = '') 1291 { 1292 $locale = \setlocale(LC_CTYPE, 0); 1293 \setlocale(LC_CTYPE, 'C'); 1294 $valid = !\ctype_lower($value); 1295 \setlocale(LC_CTYPE, $locale); 1296 1297 if ($valid) { 1298 static::reportInvalidArgument(\sprintf( 1299 $message ?: 'Expected a value to contain lowercase characters only. Got: %s', 1300 static::valueToString($value) 1301 )); 1302 } 1303 } 1304 1305 /** 1306 * @psalm-pure 1307 * @psalm-assert !lowercase-string $value 1308 * 1309 * @param string $value 1310 * @param string $message 1311 * 1312 * @throws InvalidArgumentException 1313 */ 1314 public static function upper($value, $message = '') 1315 { 1316 $locale = \setlocale(LC_CTYPE, 0); 1317 \setlocale(LC_CTYPE, 'C'); 1318 $valid = !\ctype_upper($value); 1319 \setlocale(LC_CTYPE, $locale); 1320 1321 if ($valid) { 1322 static::reportInvalidArgument(\sprintf( 1323 $message ?: 'Expected a value to contain uppercase characters only. Got: %s', 1324 static::valueToString($value) 1325 )); 1326 } 1327 } 1328 1329 /** 1330 * @psalm-pure 1331 * 1332 * @param string $value 1333 * @param int $length 1334 * @param string $message 1335 * 1336 * @throws InvalidArgumentException 1337 */ 1338 public static function length($value, $length, $message = '') 1339 { 1340 if ($length !== static::strlen($value)) { 1341 static::reportInvalidArgument(\sprintf( 1342 $message ?: 'Expected a value to contain %2$s characters. Got: %s', 1343 static::valueToString($value), 1344 $length 1345 )); 1346 } 1347 } 1348 1349 /** 1350 * Inclusive min. 1351 * 1352 * @psalm-pure 1353 * 1354 * @param string $value 1355 * @param int|float $min 1356 * @param string $message 1357 * 1358 * @throws InvalidArgumentException 1359 */ 1360 public static function minLength($value, $min, $message = '') 1361 { 1362 if (static::strlen($value) < $min) { 1363 static::reportInvalidArgument(\sprintf( 1364 $message ?: 'Expected a value to contain at least %2$s characters. Got: %s', 1365 static::valueToString($value), 1366 $min 1367 )); 1368 } 1369 } 1370 1371 /** 1372 * Inclusive max. 1373 * 1374 * @psalm-pure 1375 * 1376 * @param string $value 1377 * @param int|float $max 1378 * @param string $message 1379 * 1380 * @throws InvalidArgumentException 1381 */ 1382 public static function maxLength($value, $max, $message = '') 1383 { 1384 if (static::strlen($value) > $max) { 1385 static::reportInvalidArgument(\sprintf( 1386 $message ?: 'Expected a value to contain at most %2$s characters. Got: %s', 1387 static::valueToString($value), 1388 $max 1389 )); 1390 } 1391 } 1392 1393 /** 1394 * Inclusive , so Assert::lengthBetween('asd', 3, 5); passes the assertion. 1395 * 1396 * @psalm-pure 1397 * 1398 * @param string $value 1399 * @param int|float $min 1400 * @param int|float $max 1401 * @param string $message 1402 * 1403 * @throws InvalidArgumentException 1404 */ 1405 public static function lengthBetween($value, $min, $max, $message = '') 1406 { 1407 $length = static::strlen($value); 1408 1409 if ($length < $min || $length > $max) { 1410 static::reportInvalidArgument(\sprintf( 1411 $message ?: 'Expected a value to contain between %2$s and %3$s characters. Got: %s', 1412 static::valueToString($value), 1413 $min, 1414 $max 1415 )); 1416 } 1417 } 1418 1419 /** 1420 * Will also pass if $value is a directory, use Assert::file() instead if you need to be sure it is a file. 1421 * 1422 * @param mixed $value 1423 * @param string $message 1424 * 1425 * @throws InvalidArgumentException 1426 */ 1427 public static function fileExists($value, $message = '') 1428 { 1429 static::string($value); 1430 1431 if (!\file_exists($value)) { 1432 static::reportInvalidArgument(\sprintf( 1433 $message ?: 'The file %s does not exist.', 1434 static::valueToString($value) 1435 )); 1436 } 1437 } 1438 1439 /** 1440 * @param mixed $value 1441 * @param string $message 1442 * 1443 * @throws InvalidArgumentException 1444 */ 1445 public static function file($value, $message = '') 1446 { 1447 static::fileExists($value, $message); 1448 1449 if (!\is_file($value)) { 1450 static::reportInvalidArgument(\sprintf( 1451 $message ?: 'The path %s is not a file.', 1452 static::valueToString($value) 1453 )); 1454 } 1455 } 1456 1457 /** 1458 * @param mixed $value 1459 * @param string $message 1460 * 1461 * @throws InvalidArgumentException 1462 */ 1463 public static function directory($value, $message = '') 1464 { 1465 static::fileExists($value, $message); 1466 1467 if (!\is_dir($value)) { 1468 static::reportInvalidArgument(\sprintf( 1469 $message ?: 'The path %s is no directory.', 1470 static::valueToString($value) 1471 )); 1472 } 1473 } 1474 1475 /** 1476 * @param string $value 1477 * @param string $message 1478 * 1479 * @throws InvalidArgumentException 1480 */ 1481 public static function readable($value, $message = '') 1482 { 1483 if (!\is_readable($value)) { 1484 static::reportInvalidArgument(\sprintf( 1485 $message ?: 'The path %s is not readable.', 1486 static::valueToString($value) 1487 )); 1488 } 1489 } 1490 1491 /** 1492 * @param string $value 1493 * @param string $message 1494 * 1495 * @throws InvalidArgumentException 1496 */ 1497 public static function writable($value, $message = '') 1498 { 1499 if (!\is_writable($value)) { 1500 static::reportInvalidArgument(\sprintf( 1501 $message ?: 'The path %s is not writable.', 1502 static::valueToString($value) 1503 )); 1504 } 1505 } 1506 1507 /** 1508 * @psalm-assert class-string $value 1509 * 1510 * @param mixed $value 1511 * @param string $message 1512 * 1513 * @throws InvalidArgumentException 1514 */ 1515 public static function classExists($value, $message = '') 1516 { 1517 if (!\class_exists($value)) { 1518 static::reportInvalidArgument(\sprintf( 1519 $message ?: 'Expected an existing class name. Got: %s', 1520 static::valueToString($value) 1521 )); 1522 } 1523 } 1524 1525 /** 1526 * @psalm-pure 1527 * @psalm-template ExpectedType of object 1528 * @psalm-param class-string<ExpectedType> $class 1529 * @psalm-assert class-string<ExpectedType>|ExpectedType $value 1530 * 1531 * @param mixed $value 1532 * @param string|object $class 1533 * @param string $message 1534 * 1535 * @throws InvalidArgumentException 1536 */ 1537 public static function subclassOf($value, $class, $message = '') 1538 { 1539 if (!\is_subclass_of($value, $class)) { 1540 static::reportInvalidArgument(\sprintf( 1541 $message ?: 'Expected a sub-class of %2$s. Got: %s', 1542 static::valueToString($value), 1543 static::valueToString($class) 1544 )); 1545 } 1546 } 1547 1548 /** 1549 * @psalm-assert class-string $value 1550 * 1551 * @param mixed $value 1552 * @param string $message 1553 * 1554 * @throws InvalidArgumentException 1555 */ 1556 public static function interfaceExists($value, $message = '') 1557 { 1558 if (!\interface_exists($value)) { 1559 static::reportInvalidArgument(\sprintf( 1560 $message ?: 'Expected an existing interface name. got %s', 1561 static::valueToString($value) 1562 )); 1563 } 1564 } 1565 1566 /** 1567 * @psalm-pure 1568 * @psalm-template ExpectedType of object 1569 * @psalm-param class-string<ExpectedType> $interface 1570 * @psalm-assert class-string<ExpectedType> $value 1571 * 1572 * @param mixed $value 1573 * @param mixed $interface 1574 * @param string $message 1575 * 1576 * @throws InvalidArgumentException 1577 */ 1578 public static function implementsInterface($value, $interface, $message = '') 1579 { 1580 if (!\in_array($interface, \class_implements($value))) { 1581 static::reportInvalidArgument(\sprintf( 1582 $message ?: 'Expected an implementation of %2$s. Got: %s', 1583 static::valueToString($value), 1584 static::valueToString($interface) 1585 )); 1586 } 1587 } 1588 1589 /** 1590 * @psalm-pure 1591 * @psalm-param class-string|object $classOrObject 1592 * 1593 * @param string|object $classOrObject 1594 * @param mixed $property 1595 * @param string $message 1596 * 1597 * @throws InvalidArgumentException 1598 */ 1599 public static function propertyExists($classOrObject, $property, $message = '') 1600 { 1601 if (!\property_exists($classOrObject, $property)) { 1602 static::reportInvalidArgument(\sprintf( 1603 $message ?: 'Expected the property %s to exist.', 1604 static::valueToString($property) 1605 )); 1606 } 1607 } 1608 1609 /** 1610 * @psalm-pure 1611 * @psalm-param class-string|object $classOrObject 1612 * 1613 * @param string|object $classOrObject 1614 * @param mixed $property 1615 * @param string $message 1616 * 1617 * @throws InvalidArgumentException 1618 */ 1619 public static function propertyNotExists($classOrObject, $property, $message = '') 1620 { 1621 if (\property_exists($classOrObject, $property)) { 1622 static::reportInvalidArgument(\sprintf( 1623 $message ?: 'Expected the property %s to not exist.', 1624 static::valueToString($property) 1625 )); 1626 } 1627 } 1628 1629 /** 1630 * @psalm-pure 1631 * @psalm-param class-string|object $classOrObject 1632 * 1633 * @param string|object $classOrObject 1634 * @param mixed $method 1635 * @param string $message 1636 * 1637 * @throws InvalidArgumentException 1638 */ 1639 public static function methodExists($classOrObject, $method, $message = '') 1640 { 1641 if (!(\is_string($classOrObject) || \is_object($classOrObject)) || !\method_exists($classOrObject, $method)) { 1642 static::reportInvalidArgument(\sprintf( 1643 $message ?: 'Expected the method %s to exist.', 1644 static::valueToString($method) 1645 )); 1646 } 1647 } 1648 1649 /** 1650 * @psalm-pure 1651 * @psalm-param class-string|object $classOrObject 1652 * 1653 * @param string|object $classOrObject 1654 * @param mixed $method 1655 * @param string $message 1656 * 1657 * @throws InvalidArgumentException 1658 */ 1659 public static function methodNotExists($classOrObject, $method, $message = '') 1660 { 1661 if ((\is_string($classOrObject) || \is_object($classOrObject)) && \method_exists($classOrObject, $method)) { 1662 static::reportInvalidArgument(\sprintf( 1663 $message ?: 'Expected the method %s to not exist.', 1664 static::valueToString($method) 1665 )); 1666 } 1667 } 1668 1669 /** 1670 * @psalm-pure 1671 * 1672 * @param array $array 1673 * @param string|int $key 1674 * @param string $message 1675 * 1676 * @throws InvalidArgumentException 1677 */ 1678 public static function keyExists($array, $key, $message = '') 1679 { 1680 if (!(isset($array[$key]) || \array_key_exists($key, $array))) { 1681 static::reportInvalidArgument(\sprintf( 1682 $message ?: 'Expected the key %s to exist.', 1683 static::valueToString($key) 1684 )); 1685 } 1686 } 1687 1688 /** 1689 * @psalm-pure 1690 * 1691 * @param array $array 1692 * @param string|int $key 1693 * @param string $message 1694 * 1695 * @throws InvalidArgumentException 1696 */ 1697 public static function keyNotExists($array, $key, $message = '') 1698 { 1699 if (isset($array[$key]) || \array_key_exists($key, $array)) { 1700 static::reportInvalidArgument(\sprintf( 1701 $message ?: 'Expected the key %s to not exist.', 1702 static::valueToString($key) 1703 )); 1704 } 1705 } 1706 1707 /** 1708 * Checks if a value is a valid array key (int or string). 1709 * 1710 * @psalm-pure 1711 * @psalm-assert array-key $value 1712 * 1713 * @param mixed $value 1714 * @param string $message 1715 * 1716 * @throws InvalidArgumentException 1717 */ 1718 public static function validArrayKey($value, $message = '') 1719 { 1720 if (!(\is_int($value) || \is_string($value))) { 1721 static::reportInvalidArgument(\sprintf( 1722 $message ?: 'Expected string or integer. Got: %s', 1723 static::typeToString($value) 1724 )); 1725 } 1726 } 1727 1728 /** 1729 * Does not check if $array is countable, this can generate a warning on php versions after 7.2. 1730 * 1731 * @param Countable|array $array 1732 * @param int $number 1733 * @param string $message 1734 * 1735 * @throws InvalidArgumentException 1736 */ 1737 public static function count($array, $number, $message = '') 1738 { 1739 static::eq( 1740 \count($array), 1741 $number, 1742 \sprintf( 1743 $message ?: 'Expected an array to contain %d elements. Got: %d.', 1744 $number, 1745 \count($array) 1746 ) 1747 ); 1748 } 1749 1750 /** 1751 * Does not check if $array is countable, this can generate a warning on php versions after 7.2. 1752 * 1753 * @param Countable|array $array 1754 * @param int|float $min 1755 * @param string $message 1756 * 1757 * @throws InvalidArgumentException 1758 */ 1759 public static function minCount($array, $min, $message = '') 1760 { 1761 if (\count($array) < $min) { 1762 static::reportInvalidArgument(\sprintf( 1763 $message ?: 'Expected an array to contain at least %2$d elements. Got: %d', 1764 \count($array), 1765 $min 1766 )); 1767 } 1768 } 1769 1770 /** 1771 * Does not check if $array is countable, this can generate a warning on php versions after 7.2. 1772 * 1773 * @param Countable|array $array 1774 * @param int|float $max 1775 * @param string $message 1776 * 1777 * @throws InvalidArgumentException 1778 */ 1779 public static function maxCount($array, $max, $message = '') 1780 { 1781 if (\count($array) > $max) { 1782 static::reportInvalidArgument(\sprintf( 1783 $message ?: 'Expected an array to contain at most %2$d elements. Got: %d', 1784 \count($array), 1785 $max 1786 )); 1787 } 1788 } 1789 1790 /** 1791 * Does not check if $array is countable, this can generate a warning on php versions after 7.2. 1792 * 1793 * @param Countable|array $array 1794 * @param int|float $min 1795 * @param int|float $max 1796 * @param string $message 1797 * 1798 * @throws InvalidArgumentException 1799 */ 1800 public static function countBetween($array, $min, $max, $message = '') 1801 { 1802 $count = \count($array); 1803 1804 if ($count < $min || $count > $max) { 1805 static::reportInvalidArgument(\sprintf( 1806 $message ?: 'Expected an array to contain between %2$d and %3$d elements. Got: %d', 1807 $count, 1808 $min, 1809 $max 1810 )); 1811 } 1812 } 1813 1814 /** 1815 * @psalm-pure 1816 * @psalm-assert list $array 1817 * 1818 * @param mixed $array 1819 * @param string $message 1820 * 1821 * @throws InvalidArgumentException 1822 */ 1823 public static function isList($array, $message = '') 1824 { 1825 if (!\is_array($array) || $array !== \array_values($array)) { 1826 static::reportInvalidArgument( 1827 $message ?: 'Expected list - non-associative array.' 1828 ); 1829 } 1830 } 1831 1832 /** 1833 * @psalm-pure 1834 * @psalm-assert non-empty-list $array 1835 * 1836 * @param mixed $array 1837 * @param string $message 1838 * 1839 * @throws InvalidArgumentException 1840 */ 1841 public static function isNonEmptyList($array, $message = '') 1842 { 1843 static::isList($array, $message); 1844 static::notEmpty($array, $message); 1845 } 1846 1847 /** 1848 * @psalm-pure 1849 * @psalm-template T 1850 * @psalm-param mixed|array<T> $array 1851 * @psalm-assert array<string, T> $array 1852 * 1853 * @param mixed $array 1854 * @param string $message 1855 * 1856 * @throws InvalidArgumentException 1857 */ 1858 public static function isMap($array, $message = '') 1859 { 1860 if ( 1861 !\is_array($array) || 1862 \array_keys($array) !== \array_filter(\array_keys($array), '\is_string') 1863 ) { 1864 static::reportInvalidArgument( 1865 $message ?: 'Expected map - associative array with string keys.' 1866 ); 1867 } 1868 } 1869 1870 /** 1871 * @psalm-pure 1872 * @psalm-template T 1873 * @psalm-param mixed|array<T> $array 1874 * @psalm-assert array<string, T> $array 1875 * @psalm-assert !empty $array 1876 * 1877 * @param mixed $array 1878 * @param string $message 1879 * 1880 * @throws InvalidArgumentException 1881 */ 1882 public static function isNonEmptyMap($array, $message = '') 1883 { 1884 static::isMap($array, $message); 1885 static::notEmpty($array, $message); 1886 } 1887 1888 /** 1889 * @psalm-pure 1890 * 1891 * @param string $value 1892 * @param string $message 1893 * 1894 * @throws InvalidArgumentException 1895 */ 1896 public static function uuid($value, $message = '') 1897 { 1898 $value = \str_replace(array('urn:', 'uuid:', '{', '}'), '', $value); 1899 1900 // The nil UUID is special form of UUID that is specified to have all 1901 // 128 bits set to zero. 1902 if ('00000000-0000-0000-0000-000000000000' === $value) { 1903 return; 1904 } 1905 1906 if (!\preg_match('/^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/', $value)) { 1907 static::reportInvalidArgument(\sprintf( 1908 $message ?: 'Value %s is not a valid UUID.', 1909 static::valueToString($value) 1910 )); 1911 } 1912 } 1913 1914 /** 1915 * @psalm-param class-string<Throwable> $class 1916 * 1917 * @param Closure $expression 1918 * @param string $class 1919 * @param string $message 1920 * 1921 * @throws InvalidArgumentException 1922 */ 1923 public static function throws(Closure $expression, $class = 'Exception', $message = '') 1924 { 1925 static::string($class); 1926 1927 $actual = 'none'; 1928 1929 try { 1930 $expression(); 1931 } catch (Exception $e) { 1932 $actual = \get_class($e); 1933 if ($e instanceof $class) { 1934 return; 1935 } 1936 } catch (Throwable $e) { 1937 $actual = \get_class($e); 1938 if ($e instanceof $class) { 1939 return; 1940 } 1941 } 1942 1943 static::reportInvalidArgument($message ?: \sprintf( 1944 'Expected to throw "%s", got "%s"', 1945 $class, 1946 $actual 1947 )); 1948 } 1949 1950 /** 1951 * @throws BadMethodCallException 1952 */ 1953 public static function __callStatic($name, $arguments) 1954 { 1955 if ('nullOr' === \substr($name, 0, 6)) { 1956 if (null !== $arguments[0]) { 1957 $method = \lcfirst(\substr($name, 6)); 1958 \call_user_func_array(array('static', $method), $arguments); 1959 } 1960 1961 return; 1962 } 1963 1964 if ('all' === \substr($name, 0, 3)) { 1965 static::isIterable($arguments[0]); 1966 1967 $method = \lcfirst(\substr($name, 3)); 1968 $args = $arguments; 1969 1970 foreach ($arguments[0] as $entry) { 1971 $args[0] = $entry; 1972 1973 \call_user_func_array(array('static', $method), $args); 1974 } 1975 1976 return; 1977 } 1978 1979 throw new BadMethodCallException('No such method: '.$name); 1980 } 1981 1982 /** 1983 * @param mixed $value 1984 * 1985 * @return string 1986 */ 1987 protected static function valueToString($value) 1988 { 1989 if (null === $value) { 1990 return 'null'; 1991 } 1992 1993 if (true === $value) { 1994 return 'true'; 1995 } 1996 1997 if (false === $value) { 1998 return 'false'; 1999 } 2000 2001 if (\is_array($value)) { 2002 return 'array'; 2003 } 2004 2005 if (\is_object($value)) { 2006 if (\method_exists($value, '__toString')) { 2007 return \get_class($value).': '.self::valueToString($value->__toString()); 2008 } 2009 2010 if ($value instanceof DateTime || $value instanceof DateTimeImmutable) { 2011 return \get_class($value).': '.self::valueToString($value->format('c')); 2012 } 2013 2014 return \get_class($value); 2015 } 2016 2017 if (\is_resource($value)) { 2018 return 'resource'; 2019 } 2020 2021 if (\is_string($value)) { 2022 return '"'.$value.'"'; 2023 } 2024 2025 return (string) $value; 2026 } 2027 2028 /** 2029 * @param mixed $value 2030 * 2031 * @return string 2032 */ 2033 protected static function typeToString($value) 2034 { 2035 return \is_object($value) ? \get_class($value) : \gettype($value); 2036 } 2037 2038 protected static function strlen($value) 2039 { 2040 if (!\function_exists('mb_detect_encoding')) { 2041 return \strlen($value); 2042 } 2043 2044 if (false === $encoding = \mb_detect_encoding($value)) { 2045 return \strlen($value); 2046 } 2047 2048 return \mb_strlen($value, $encoding); 2049 } 2050 2051 /** 2052 * @param string $message 2053 * 2054 * @throws InvalidArgumentException 2055 * 2056 * @psalm-pure this method is not supposed to perform side-effects 2057 */ 2058 protected static function reportInvalidArgument($message) 2059 { 2060 throw new InvalidArgumentException($message); 2061 } 2062 2063 private function __construct() 2064 { 2065 } 2066} 2067