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