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