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:
12: namespace Webmozart\Assert;
13:
14: use ArrayAccess;
15: use BadMethodCallException;
16: use Closure;
17: use Countable;
18: use DateTime;
19: use DateTimeImmutable;
20: use Exception;
21: use InvalidArgumentException;
22: use ResourceBundle;
23: use SimpleXMLElement;
24: use Throwable;
25: use 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: */
36: class 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: