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