Completed
Push — master ( c0879b...402bf1 )
by Terry
03:18
created

Assert::ipAddress()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 12
nc 4
nop 2
dl 0
loc 19
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
namespace Terah\Assert;
4
5
    /**
6
     * Assert
7
     *
8
     * LICENSE
9
     *
10
     * This source file is subject to the new BSD license that is bundled
11
     * with this package in the file LICENSE.txt.
12
     * If you did not receive a copy of the license and are unable to
13
     * obtain it through the world-wide-web, please send an email
14
     * to [email protected] so I can send you a copy immediately.
15
     */
16
17
/**
18
 * Assert library
19
 *
20
 * @author Benjamin Eberlei <[email protected]>
21
 * @author Terry Cullen <[email protected]>
22
 *
23
 */
24
class Assert
25
{
26
    const INVALID_FLOAT = 9;
27
    const INVALID_INTEGER = 10;
28
    const INVALID_DIGIT = 11;
29
    const INVALID_INTEGERISH = 12;
30
    const INVALID_BOOLEAN = 13;
31
    const VALUE_EMPTY = 14;
32
    const VALUE_NULL = 15;
33
    const INVALID_STRING = 16;
34
    const INVALID_REGEX = 17;
35
    const INVALID_MIN_LENGTH = 18;
36
    const INVALID_MAX_LENGTH = 19;
37
    const INVALID_STRING_START = 20;
38
    const INVALID_STRING_CONTAINS = 21;
39
    const INVALID_CHOICE = 22;
40
    const INVALID_NUMERIC = 23;
41
    const INVALID_ARRAY = 24;
42
    const INVALID_KEY_EXISTS = 26;
43
    const INVALID_NOT_BLANK = 27;
44
    const INVALID_INSTANCE_OF = 28;
45
    const INVALID_SUBCLASS_OF = 29;
46
    const INVALID_RANGE = 30;
47
    const INVALID_ALNUM = 31;
48
    const INVALID_TRUE = 32;
49
    const INVALID_EQ = 33;
50
    const INVALID_SAME = 34;
51
    const INVALID_MIN = 35;
52
    const INVALID_MAX = 36;
53
    const INVALID_LENGTH = 37;
54
    const INVALID_FALSE = 38;
55
    const INVALID_STRING_END = 39;
56
    const INVALID_UUID = 40;
57
    const INVALID_COUNT = 41;
58
    const INVALID_NOT_EQ = 42;
59
    const INVALID_NOT_SAME = 43;
60
    const INVALID_TRAVERSABLE = 44;
61
    const INVALID_ARRAY_ACCESSIBLE = 45;
62
    const INVALID_KEY_ISSET = 46;
63
    const INVALID_DIRECTORY = 101;
64
    const INVALID_FILE = 102;
65
    const INVALID_READABLE = 103;
66
    const INVALID_WRITEABLE = 104;
67
    const INVALID_CLASS = 105;
68
    const INVALID_EMAIL = 201;
69
    const INTERFACE_NOT_IMPLEMENTED = 202;
70
    const INVALID_URL = 203;
71
    const INVALID_NOT_INSTANCE_OF = 204;
72
    const VALUE_NOT_EMPTY = 205;
73
    const INVALID_JSON_STRING = 206;
74
    const INVALID_OBJECT = 207;
75
    const INVALID_METHOD = 208;
76
    const INVALID_SCALAR = 209;
77
    const INVALID_DATE = 210;
78
    const INVALID_CALLABLE = 211;
79
    const INVALID_KEYS_EXIST = 300;
80
    const INVALID_PROPERTY_EXISTS = 301;
81
    const INVALID_PROPERTIES_EXIST = 302;
82
    const INVALID_UTF8 = 303;
83
    const INVALID_DOMAIN_NAME = 304;
84
    const INVALID_NOT_FALSE = 305;
85
    const INVALID_FILE_OR_DIR = 306;
86
    const INVALID_ASCII = 307;
87
    const INVALID_NOT_REGEX = 308;
88
    const INVALID_GREATER_THAN = 309;
89
    const INVALID_LESS_THAN = 310;
90
    const INVALID_GREATER_THAN_OR_EQ = 311;
91
    const INVALID_LESS_THAN_OR_EQ = 312;
92
    const INVALID_IP_ADDRESS = 313;
93
94
    /** @var bool */
95
    protected $nullOr       = false;
96
97
    /** @var bool */
98
    protected $emptyOr     = false;
99
100
    /** @var mixed */
101
    protected $value        = null;
102
103
    /** @var bool */
104
    protected $all          = false;
105
106
    /** @var null|string */
107
    protected $propertyPath = null;
108
109
    /** @var int */
110
    protected $overrideCode  = null;
111
112
    /** @var string */
113
    protected $overrideError  = '';
114
    /**
115
     * Exception to throw when an assertion failed.
116
     *
117
     * @var string
118
     */
119
    protected $exceptionClass = 'Terah\Assert\AssertionFailedException';
120
121
    /**
122
     * @param mixed $value
123
     */
124
    public function __construct($value)
125
    {
126
        $this->value($value);
127
    }
128
129
    /**
130
     * @param $value
131
     * @return Assert
132
     */
133
    public static function that($value)
134
    {
135
        return new Assert($value);
136
    }
137
138
    /**
139
     * @param mixed $value
140
     * @return Assert
141
     */
142
    public function reset($value)
143
    {
144
        return $this->all(false)->nullOr(false)->value($value);
145
    }
146
147
    /**
148
     * @param mixed $value
149
     * @return Assert
150
     */
151
    public function value($value)
152
    {
153
        $this->value = $value;
154
        return $this;
155
    }
156
157
    /**
158
     * @param bool $nullOr
159
     * @return Assert
160
     */
161
    public function nullOr($nullOr = true)
162
    {
163
        $this->nullOr = $nullOr;
164
        return $this;
165
    }
166
167
    /**
168
     * @param bool $emptyOr
169
     * @return Assert
170
     */
171
    public function emptyOr($emptyOr = true)
172
    {
173
        $this->emptyOr = $emptyOr;
174
        return $this;
175
    }
176
177
    /**
178
     * @param bool $all
179
     * @return Assert
180
     */
181
    public function all($all = true)
182
    {
183
        $this->all = $all;
184
        return $this;
185
    }
186
187
    /**
188
     * Helper method that handles building the assertion failure exceptions.
189
     * They are returned from this method so that the stack trace still shows
190
     * the assertions method.
191
     *
192
     * @param string $message
193
     * @param int    $code
194
     * @param string $propertyPath
195
     * @param array  $constraints
196
     * @return AssertionFailedException
197
     */
198
    protected function createException($message, $code, $propertyPath, array $constraints = [])
199
    {
200
        $exceptionClass = $this->exceptionClass;
201
        $propertyPath   = is_null($propertyPath) ? $this->propertyPath : $propertyPath;
202
        return new $exceptionClass($message, $code, $propertyPath, $this->value, $constraints);
203
    }
204
205
    /**
206
     * @param $exceptionClass
207
     * @return Assert
208
     */
209
    public function setExceptionClass($exceptionClass)
210
    {
211
        $this->exceptionClass = $exceptionClass;
212
        return $this;
213
    }
214
215
    /**
216
     * @param int $code
217
     * @return Assert
218
     */
219
    public function code($code)
220
    {
221
        $this->overrideCode = $code;
222
        return $this;
223
    }
224
225
    /**
226
     * @param string $error
227
     * @return Assert
228
     */
229
    public function error($error)
230
    {
231
        $this->overrideError = $error;
232
        return $this;
233
    }
234
235
    /**
236
     * @param string $name
237
     * @return Assert
238
     */
239
    public function name($name)
240
    {
241
        $this->propertyPath = $name;
242
        return $this;
243
    }
244
245
    /**
246
     * Assert that two values are equal (using == ).
247
     *
248
     * @param mixed       $value2
249
     * @param string|null $message
250
     * @param string|null $propertyPath
251
     * @return Assert
252
     * @throws AssertionFailedException
253
     */
254
    public function eq($value2, $message = null, $propertyPath = null)
255
    {
256
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
257
        {
258
            return $this;
259
        }
260
        if ( $this->value != $value2 )
261
        {
262
            $message = $message ?: $this->overrideError;
263
            $message = sprintf(
264
                $message ?: 'Value "%s" does not equal expected value "%s".',
265
                $this->stringify($this->value),
266
                $this->stringify($value2)
267
            );
268
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_EQ, $propertyPath, ['expected' => $value2]);
269
        }
270
        return $this;
271
    }
272
273
    /**
274
     *
275
     * @param mixed       $value2
276
     * @param string|null $message
277
     * @param string|null $propertyPath
278
     * @return Assert
279
     * @throws AssertionFailedException
280
     */
281
    public function greaterThan($value2, $message = null, $propertyPath = null)
282
    {
283
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
284
        {
285
            return $this;
286
        }
287
        if ( ! ( $this->value > $value2 ) )
288
        {
289
            $message = $message ?: $this->overrideError;
290
            $message = sprintf(
291
                $message ?: 'Value "%s" does not greater then expected value "%s".',
292
                $this->stringify($this->value),
293
                $this->stringify($value2)
294
            );
295
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_EQ, $propertyPath, ['expected' => $value2]);
296
        }
297
        return $this;
298
    }
299
300
    /**
301
     *
302
     * @param mixed       $value2
303
     * @param string|null $message
304
     * @param string|null $propertyPath
305
     * @return Assert
306
     * @throws AssertionFailedException
307
     */
308
    public function greaterThanOrEq($value2, $message = null, $propertyPath = null)
309
    {
310
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
311
        {
312
            return $this;
313
        }
314
        if ( ! ( $this->value >= $value2 ) )
315
        {
316
            $message = $message ?: $this->overrideError;
317
            $message = sprintf(
318
                $message ?: 'Value "%s" does not greater than or equal to expected value "%s".',
319
                $this->stringify($this->value),
320
                $this->stringify($value2)
321
            );
322
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_EQ, $propertyPath, ['expected' => $value2]);
323
        }
324
        return $this;
325
    }
326
327
    /**
328
     *
329
     * @param mixed       $value2
330
     * @param string|null $message
331
     * @param string|null $propertyPath
332
     * @return Assert
333
     * @throws AssertionFailedException
334
     */
335
    public function lessThan($value2, $message = null, $propertyPath = null)
336
    {
337
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
338
        {
339
            return $this;
340
        }
341
        if ( ! ( $this->value < $value2 ) )
342
        {
343
            $message = $message ?: $this->overrideError;
344
            $message = sprintf(
345
                $message ?: 'Value "%s" does not less then expected value "%s".',
346
                $this->stringify($this->value),
347
                $this->stringify($value2)
348
            );
349
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_LESS_THAN, $propertyPath, ['expected' => $value2]);
350
        }
351
        return $this;
352
    }
353
354
    /**
355
     *
356
     * @param mixed       $value2
357
     * @param string|null $message
358
     * @param string|null $propertyPath
359
     * @return Assert
360
     * @throws AssertionFailedException
361
     */
362
    public function lessThanOrEq($value2, $message = null, $propertyPath = null)
363
    {
364
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
365
        {
366
            return $this;
367
        }
368
        if ( ! ( $this->value <= $value2 ) )
369
        {
370
            $message = $message ?: $this->overrideError;
371
            $message = sprintf(
372
                $message ?: 'Value "%s" does not less than or equal to expected value "%s".',
373
                $this->stringify($this->value),
374
                $this->stringify($value2)
375
            );
376
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_LESS_THAN_OR_EQ, $propertyPath, ['expected' => $value2]);
377
        }
378
        return $this;
379
    }
380
381
    /**
382
     * Assert that two values are the same (using ===).
383
     *
384
     * @param mixed       $value2
385
     * @param string|null $message
386
     * @param string|null $propertyPath
387
     * @return Assert
388
     * @throws AssertionFailedException
389
     */
390
    public function same($value2, $message = null, $propertyPath = null)
391
    {
392
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
393
        {
394
            return $this;
395
        }
396
        if ( $this->value !== $value2 )
397
        {
398
            $message = $message ?: $this->overrideError;
399
            $message = sprintf(
400
                $message ?: 'Value "%s" is not the same as expected value "%s".',
401
                $this->stringify($this->value),
402
                $this->stringify($value2)
403
            );
404
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_SAME, $propertyPath, ['expected' => $value2]);
405
        }
406
        return $this;
407
    }
408
409
    /**
410
     * Assert that two values are not equal (using == ).
411
     *
412
     * @param mixed       $value2
413
     * @param string|null $message
414
     * @param string|null $propertyPath
415
     * @return Assert
416
     * @throws AssertionFailedException
417
     */
418
    public function notEq($value2, $message = null, $propertyPath = null)
419
    {
420
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
421
        {
422
            return $this;
423
        }
424
        if ( $this->value == $value2 )
425
        {
426
            $message = $message ?: $this->overrideError;
427
            $message = sprintf(
428
                $message ?: 'Value "%s" is equal to expected value "%s".',
429
                $this->stringify($this->value),
430
                $this->stringify($value2)
431
            );
432
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_NOT_EQ, $propertyPath, ['expected' => $value2]);
433
        }
434
        return $this;
435
    }
436
437
    /**
438
     * @param string|null $message
439
     * @param string|null        $propertyPath
440
     *
441
     * @return $this
442
     * @throws AssertionFailedException
443
     */
444
    public function isCallable($message = null, $propertyPath = null)
445
    {
446
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
447
        {
448
            return $this;
449
        }
450
        if ( !is_callable($this->value) )
451
        {
452
            $message = $message ?: $this->overrideError;
453
            $message = sprintf(
454
                $message ?: 'Value "%s" is not callable.',
455
                $this->stringify($this->value)
456
            );
457
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_NOT_EQ, $propertyPath);
458
        }
459
        return $this;
460
    }
461
462
    /**
463
     * Assert that two values are not the same (using === ).
464
     *
465
     * @param mixed       $value2
466
     * @param string|null $message
467
     * @param string|null $propertyPath
468
     * @return Assert
469
     * @throws AssertionFailedException
470
     */
471
    public function notSame($value2, $message = null, $propertyPath = null)
472
    {
473
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
474
        {
475
            return $this;
476
        }
477
        if ( $this->value === $value2 )
478
        {
479
            $message = $message ?: $this->overrideError;
480
            $message = sprintf(
481
                $message ?: 'Value "%s" is the same as expected value "%s".',
482
                $this->stringify($this->value),
483
                $this->stringify($value2)
484
            );
485
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_NOT_SAME, $propertyPath, ['expected' => $value2]);
486
        }
487
        return $this;
488
    }
489
490
    /**
491
     * @param string|null $message
492
     * @param string|null $propertyPath
493
     * @return Assert
494
     * @throws AssertionFailedException
495
     */
496
    public function id($message = null, $propertyPath = null)
497
    {
498
        $message = $message ?: $this->overrideError;
499
        $message = $message ?: 'Value "%s" is not an integer id.';
500
        return $this->nonEmptyInt($message, $propertyPath)->range(1, PHP_INT_MAX);
501
    }
502
503
    /**
504
     * @param string|null $message
505
     * @param string|null $propertyPath
506
     * @return Assert
507
     * @throws AssertionFailedException
508
     */
509
    public function flag($message = null, $propertyPath = null)
510
    {
511
        $message = $message ?: $this->overrideError;
512
        $message = $message ?: 'Value "%s" is not a 0 or 1.';
513
        return $this->range(0, 1, $message, $propertyPath);
514
    }
515
516
    /**
517
     * @param string|null $message
518
     * @param string|null $propertyPath
519
     * @return Assert
520
     * @throws AssertionFailedException
521
     */
522
    public function status($message = null, $propertyPath = null)
523
    {
524
        $message = $message ?: $this->overrideError;
525
        $message = $message ?: 'Value "%s" is not a valid status.';
526
        return $this->integer($message, $propertyPath)->inArray([-1, 0, 1]);
527
    }
528
529
    /**
530
     * @param string|null $message
531
     * @param string|null $propertyPath
532
     * @return Assert
533
     */
534
    public function nullOrId($message = null, $propertyPath = null)
535
    {
536
        return $this->nullOr()->id($message, $propertyPath);
537
    }
538
539
    /**
540
     * @param string|null $message
541
     * @param string|null $propertyPath
542
     * @return Assert
543
     */
544
    public function allIds($message = null, $propertyPath = null)
545
    {
546
        return $this->all()->id($message, $propertyPath);
547
    }
548
549
    /**
550
     * @param string|null $message
551
     * @param string|null $propertyPath
552
     * @return Assert
553
     * @throws AssertionFailedException
554
     */
555
    public function int($message = null, $propertyPath = null)
556
    {
557
        return $this->integer($message, $propertyPath);
558
    }
559
560
    /**
561
     * Assert that value is a php integer.
562
     *
563
     * @param string|null $message
564
     * @param string|null $propertyPath
565
     * @return Assert
566
     * @throws AssertionFailedException
567
     */
568
    public function integer($message = null, $propertyPath = null)
569
    {
570
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
571
        {
572
            return $this;
573
        }
574
        if ( !is_int($this->value) )
575
        {
576
            $message = $message ?: $this->overrideError;
577
            $message = sprintf(
578
                $message ?: 'Value "%s" is not an integer.',
579
                $this->stringify($this->value)
580
            );
581
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_INTEGER, $propertyPath);
582
        }
583
        return $this;
584
    }
585
586
    /**
587
     * Assert that value is a php float.
588
     *
589
     * @param string|null $message
590
     * @param string|null $propertyPath
591
     * @return Assert
592
     * @throws AssertionFailedException
593
     */
594
    public function float($message = null, $propertyPath = null)
595
    {
596
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
597
        {
598
            return $this;
599
        }
600
        if ( ! is_float($this->value) )
601
        {
602
            $message = $message ?: $this->overrideError;
603
            $message = sprintf(
604
                $message ?: 'Value "%s" is not a float.',
605
                $this->stringify($this->value)
606
            );
607
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_FLOAT, $propertyPath);
608
        }
609
        return $this;
610
    }
611
612
    /**
613
     * Validates if an integer or integerish is a digit.
614
     *
615
     * @param string|null $message
616
     * @param string|null $propertyPath
617
     * @return Assert
618
     * @throws AssertionFailedException
619
     */
620
    public function digit($message = null, $propertyPath = null)
621
    {
622
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
623
        {
624
            return $this;
625
        }
626
        if ( ! ctype_digit((string)$this->value) )
627
        {
628
            $message = $message ?: $this->overrideError;
629
            $message = sprintf(
630
                $message ?: 'Value "%s" is not a digit.',
631
                $this->stringify($this->value)
632
            );
633
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_DIGIT, $propertyPath);
634
        }
635
        return $this;
636
    }
637
638
    /**
639
     * Validates if an string is a date .
640
     *
641
     * @param string|null $message
642
     * @param string|null $propertyPath
643
     * @return Assert
644
     * @throws AssertionFailedException
645
     */
646
    public function date($message = null, $propertyPath = null)
647
    {
648
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
649
        {
650
            return $this;
651
        }
652
        $this->notEmpty($message, $propertyPath);
653
        if ( strtotime($this->value) === false )
654
        {
655
            $message = $message ?: $this->overrideError;
656
            $message = sprintf(
657
                $message ?: 'Value "%s" is not a date.',
658
                $this->stringify($this->value)
659
            );
660
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_DATE, $propertyPath);
661
        }
662
        return $this;
663
    }
664
665
    /**
666
     * Assert that value is a php integer'ish.
667
     *
668
     * @param string|null $message
669
     * @param string|null $propertyPath
670
     * @return Assert
671
     * @throws AssertionFailedException
672
     */
673
    public function integerish($message = null, $propertyPath = null)
674
    {
675
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
676
        {
677
            return $this;
678
        }
679
        if ( is_object($this->value) || strval(intval($this->value)) != $this->value || is_bool($this->value) || is_null($this->value) )
680
        {
681
            $message = $message ?: $this->overrideError;
682
            $message = sprintf(
683
                $message ?: 'Value "%s" is not an integer or a number castable to integer.',
684
                $this->stringify($this->value)
685
            );
686
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_INTEGERISH, $propertyPath);
687
        }
688
        return $this;
689
    }
690
691
    /**
692
     * Assert that value is php boolean
693
     *
694
     * @param string|null $message
695
     * @param string|null $propertyPath
696
     * @return Assert
697
     * @throws AssertionFailedException
698
     */
699
    public function boolean($message = null, $propertyPath = null)
700
    {
701
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
702
        {
703
            return $this;
704
        }
705
        if ( ! is_bool($this->value) )
706
        {
707
            $message = $message ?: $this->overrideError;
708
            $message = sprintf(
709
                $message ?: 'Value "%s" is not a boolean.',
710
                $this->stringify($this->value)
711
            );
712
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_BOOLEAN, $propertyPath);
713
        }
714
        return $this;
715
    }
716
717
    /**
718
     * Assert that value is a PHP scalar
719
     *
720
     * @param string|null $message
721
     * @param string|null $propertyPath
722
     * @return Assert
723
     * @throws AssertionFailedException
724
     */
725
    public function scalar($message = null, $propertyPath = null)
726
    {
727
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
728
        {
729
            return $this;
730
        }
731
        if ( !is_scalar($this->value) )
732
        {
733
            $message = $message ?: $this->overrideError;
734
            $message = sprintf(
735
                $message ?: 'Value "%s" is not a scalar.',
736
                $this->stringify($this->value)
737
            );
738
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_SCALAR, $propertyPath);
739
        }
740
        return $this;
741
    }
742
743
    /**
744
     * Assert that value is not empty
745
     *
746
     * @param string|null $message
747
     * @param string|null $propertyPath
748
     * @return Assert
749
     * @throws AssertionFailedException
750
     */
751 View Code Duplication
    public function notEmpty($message = null, $propertyPath = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
752
    {
753
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
754
        {
755
            return $this;
756
        }
757
        if ( ( is_object($this->value) && empty((array)$this->value) ) || empty($this->value) )
758
        {
759
            $message = $message ?: $this->overrideError;
760
            $message = sprintf(
761
                $message ?: 'Value "%s" is empty, but non empty value was expected.',
762
                $this->stringify($this->value)
763
            );
764
            throw $this->createException($message, $this->overrideCode ?: self::VALUE_EMPTY, $propertyPath);
765
        }
766
        return $this;
767
    }
768
769
    /**
770
     * Assert that value is empty
771
     *
772
     * @param string|null $message
773
     * @param string|null $propertyPath
774
     * @return Assert
775
     * @throws AssertionFailedException
776
     */
777
    public function noContent($message = null, $propertyPath = null)
778
    {
779
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
780
        {
781
            return $this;
782
        }
783
        if ( !empty( $this->value ) )
784
        {
785
            $message = $message ?: $this->overrideError;
786
            $message = sprintf(
787
                $message ?: 'Value "%s" is not empty, but empty value was expected.',
788
                $this->stringify($this->value)
789
            );
790
            throw $this->createException($message, $this->overrideCode ?: self::VALUE_NOT_EMPTY, $propertyPath);
791
        }
792
        return $this;
793
    }
794
795
    /**
796
     * Assert that value is not null
797
     *
798
     * @param string|null $message
799
     * @param string|null $propertyPath
800
     * @return Assert
801
     * @throws AssertionFailedException
802
     */
803
    public function notNull($message = null, $propertyPath = null)
804
    {
805
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
806
        {
807
            return $this;
808
        }
809
        if ( $this->value === null )
810
        {
811
            $message = $message ?: $this->overrideError;
812
            $message = sprintf(
813
                $message ?: 'Value "%s" is null, but non null value was expected.',
814
                $this->stringify($this->value)
815
            );
816
            throw $this->createException($message, $this->overrideCode ?: self::VALUE_NULL, $propertyPath);
817
        }
818
        return $this;
819
    }
820
821
    /**
822
     * Assert that value is a string
823
     *
824
     * @param string|null $message
825
     * @param string|null $propertyPath
826
     * @return Assert
827
     * @throws AssertionFailedException
828
     */
829
    public function string($message = null, $propertyPath = null)
830
    {
831
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
832
        {
833
            return $this;
834
        }
835
        if ( !is_string($this->value) )
836
        {
837
            $message = $message ?: $this->overrideError;
838
            $message = sprintf(
839
                $message ?: 'Value "%s" expected to be string, type %s given.',
840
                $this->stringify($this->value),
841
                gettype($this->value)
842
            );
843
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_STRING, $propertyPath);
844
        }
845
        return $this;
846
    }
847
848
    /**
849
     * Assert that value matches a regex
850
     *
851
     * @param string      $pattern
852
     * @param string|null $message
853
     * @param string|null $propertyPath
854
     * @return Assert
855
     * @throws AssertionFailedException
856
     */
857
    public function regex($pattern, $message=null, $propertyPath=null)
858
    {
859
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
860
        {
861
            return $this;
862
        }
863
        $this->string($message, $propertyPath);
864
        if ( ! preg_match($pattern, $this->value) )
865
        {
866
            $message = $message ?: $this->overrideError;
867
            $message = sprintf(
868
                $message ?: 'Value "%s" does not match expression.',
869
                $this->stringify($this->value)
870
            );
871
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_REGEX, $propertyPath, ['pattern' => $pattern]);
872
        }
873
        return $this;
874
    }
875
876
    /**
877
     * @param string $message
878
     * @param string $propertyPath
879
     * @return $this
880
     * @throws AssertionFailedException
881
     */
882
    public function ipAddress($message = null, $propertyPath = null)
883
    {
884
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
885
        {
886
            return $this;
887
        }
888
        $this->string($message, $propertyPath);
889
        $pattern   = '/^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/';
890
        if ( ! preg_match($pattern, $this->value) )
891
        {
892
            $message = $message ?: $this->overrideError;
893
            $message = sprintf(
894
                $message ?: 'Value "%s" was expected to be a valid IP Address',
895
                $this->stringify($this->value)
896
            );
897
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_IP_ADDRESS, $propertyPath);
898
        }
899
        return $this;
900
    }
901
902
    /**
903
     * @param string $pattern
904
     * @param string|null $message
905
     * @param string|null $propertyPath
906
     * @return $this
907
     * @throws AssertionFailedException
908
     */
909
    public function notRegex($pattern, $message = null, $propertyPath = null)
910
    {
911
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
912
        {
913
            return $this;
914
        }
915
        $this->string($message, $propertyPath);
916
        if ( preg_match($pattern, $this->value) )
917
        {
918
            $message = $message ?: $this->overrideError;
919
            $message = sprintf(
920
                $message ?: 'Value "%s" does not match expression.',
921
                $this->stringify($this->value)
922
            );
923
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_REGEX, $propertyPath, ['pattern' => $pattern]);
924
        }
925
        return $this;
926
    }
927
928
    /**
929
     * Assert that string has a given length.
930
     *
931
     * @param int         $length
932
     * @param string|null $message
933
     * @param string|null $propertyPath
934
     * @param string      $encoding
935
     * @return Assert
936
     * @throws AssertionFailedException
937
     */
938
    public function length($length, $message = null, $propertyPath = null, $encoding = 'utf8')
939
    {
940
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
941
        {
942
            return $this;
943
        }
944
        $this->string($message, $propertyPath);
945
        if ( mb_strlen($this->value, $encoding) !== $length )
946
        {
947
            $message    = $message ?: $this->overrideError;
948
            $message    = sprintf(
949
                $message ?: 'Value "%s" has to be %d exactly characters long, but length is %d.',
950
                $this->stringify($this->value),
951
                $length,
952
                mb_strlen($this->value, $encoding)
953
            );
954
            $constraints = ['length' => $length, 'encoding' => $encoding];
955
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_LENGTH, $propertyPath, $constraints);
956
        }
957
        return $this;
958
    }
959
960
    /**
961
     * Assert that a string is at least $minLength chars long.
962
     *
963
     * @param int         $minLength
964
     * @param string|null $message
965
     * @param string|null $propertyPath
966
     * @param string      $encoding
967
     * @return Assert
968
     * @throws AssertionFailedException
969
     */
970
    public function minLength($minLength, $message = null, $propertyPath = null, $encoding = 'utf8')
971
    {
972
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
973
        {
974
            return $this;
975
        }
976
        $this->string($message, $propertyPath);
977
        if ( mb_strlen($this->value, $encoding) < $minLength )
978
        {
979
            $message = $message ?: $this->overrideError;
980
            $message     = sprintf(
981
                $message
982
                    ?: 'Value "%s" is too short, it should have more than %d characters, but only has %d characters.',
983
                $this->stringify($this->value),
984
                $minLength,
985
                mb_strlen($this->value, $encoding)
986
            );
987
            $constraints = ['min_length' => $minLength, 'encoding' => $encoding];
988
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_MIN_LENGTH, $propertyPath, $constraints);
989
        }
990
        return $this;
991
    }
992
993
    /**
994
     * Assert that string value is not longer than $maxLength chars.
995
     *
996
     * @param integer     $maxLength
997
     * @param string|null $message
998
     * @param string|null $propertyPath
999
     * @param string      $encoding
1000
     * @return Assert
1001
     * @throws AssertionFailedException
1002
     */
1003
    public function maxLength($maxLength, $message = null, $propertyPath = null, $encoding = 'utf8')
1004
    {
1005
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1006
        {
1007
            return $this;
1008
        }
1009
        $this->string($message, $propertyPath);
1010
        if ( mb_strlen($this->value, $encoding) > $maxLength )
1011
        {
1012
            $message = $message ?: $this->overrideError;
1013
            $message     = sprintf(
1014
                $message ?: 'Value "%s" is too long, it should have no more than %d characters, but has %d characters.',
1015
                $this->stringify($this->value),
1016
                $maxLength,
1017
                mb_strlen($this->value, $encoding)
1018
            );
1019
            $constraints = ['max_length' => $maxLength, 'encoding' => $encoding];
1020
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_MAX_LENGTH, $propertyPath, $constraints);
1021
        }
1022
        return $this;
1023
    }
1024
1025
    /**
1026
     * Assert that string length is between min,max lengths.
1027
     *
1028
     * @param integer     $minLength
1029
     * @param integer     $maxLength
1030
     * @param string|null $message
1031
     * @param string|null $propertyPath
1032
     * @param string      $encoding
1033
     * @return Assert
1034
     * @throws AssertionFailedException
1035
     */
1036
    public function betweenLength($minLength, $maxLength, $message = null, $propertyPath = null, $encoding = 'utf8')
1037
    {
1038
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1039
        {
1040
            return $this;
1041
        }
1042
        $this->string($message, $propertyPath);
1043
        if ( mb_strlen($this->value, $encoding) < $minLength )
1044
        {
1045
            $message = $message ?: $this->overrideError;
1046
            $message     = sprintf(
1047
                $message
1048
                    ?: 'Value "%s" is too short, it should have more than %d characters, but only has %d characters.',
1049
                $this->stringify($this->value),
1050
                $minLength,
1051
                mb_strlen($this->value, $encoding)
1052
            );
1053
            $constraints = ['min_length' => $minLength, 'encoding' => $encoding];
1054
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_MIN_LENGTH, $propertyPath, $constraints);
1055
        }
1056
        if ( mb_strlen($this->value, $encoding) > $maxLength )
1057
        {
1058
            $message = $message ?: $this->overrideError;
1059
            $message     = sprintf(
1060
                $message ?: 'Value "%s" is too long, it should have no more than %d characters, but has %d characters.',
1061
                $this->stringify($this->value),
1062
                $maxLength,
1063
                mb_strlen($this->value, $encoding)
1064
            );
1065
            $constraints = ['max_length' => $maxLength, 'encoding' => $encoding];
1066
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_MAX_LENGTH, $propertyPath, $constraints);
1067
        }
1068
        return $this;
1069
    }
1070
1071
    /**
1072
     * Assert that string starts with a sequence of chars.
1073
     *
1074
     * @param string      $needle
1075
     * @param string|null $message
1076
     * @param string|null $propertyPath
1077
     * @param string      $encoding
1078
     * @return Assert
1079
     * @throws AssertionFailedException
1080
     */
1081 View Code Duplication
    public function startsWith($needle, $message = null, $propertyPath = null, $encoding = 'utf8')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1082
    {
1083
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1084
        {
1085
            return $this;
1086
        }
1087
        $this->string($message, $propertyPath);
1088
        if ( mb_strpos($this->value, $needle, null, $encoding) !== 0 )
1089
        {
1090
            $message = $message ?: $this->overrideError;
1091
            $message     = sprintf(
1092
                $message ?: 'Value "%s" does not start with "%s".',
1093
                $this->stringify($this->value),
1094
                $this->stringify($needle)
1095
            );
1096
            $constraints = ['needle' => $needle, 'encoding' => $encoding];
1097
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_STRING_START, $propertyPath, $constraints);
1098
        }
1099
        return $this;
1100
    }
1101
1102
    /**
1103
     * Assert that string ends with a sequence of chars.
1104
     *
1105
     * @param string      $needle
1106
     * @param string|null $message
1107
     * @param string|null $propertyPath
1108
     * @param string      $encoding
1109
     * @return Assert
1110
     * @throws AssertionFailedException
1111
     */
1112
    public function endsWith($needle, $message = null, $propertyPath = null, $encoding = 'utf8')
1113
    {
1114
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1115
        {
1116
            return $this;
1117
        }
1118
        $this->string($message, $propertyPath);
1119
        $stringPosition = mb_strlen($this->value, $encoding) - mb_strlen($needle, $encoding);
1120
        if ( mb_strripos($this->value, $needle, null, $encoding) !== $stringPosition )
1121
        {
1122
            $message = $message ?: $this->overrideError;
1123
            $message     = sprintf(
1124
                $message ?: 'Value "%s" does not end with "%s".',
1125
                $this->stringify($this->value),
1126
                $this->stringify($needle)
1127
            );
1128
            $constraints = ['needle' => $needle, 'encoding' => $encoding];
1129
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_STRING_END, $propertyPath, $constraints);
1130
        }
1131
        return $this;
1132
    }
1133
1134
    /**
1135
     * Assert that string contains a sequence of chars.
1136
     *
1137
     * @param string      $needle
1138
     * @param string|null $message
1139
     * @param string|null $propertyPath
1140
     * @param string      $encoding
1141
     * @return Assert
1142
     * @throws AssertionFailedException
1143
     */
1144 View Code Duplication
    public function contains($needle, $message = null, $propertyPath = null, $encoding = 'utf8')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1145
    {
1146
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1147
        {
1148
            return $this;
1149
        }
1150
        $this->string($message, $propertyPath);
1151
        if ( mb_strpos($this->value, $needle, null, $encoding) === false )
1152
        {
1153
            $message = $message ?: $this->overrideError;
1154
            $message     = sprintf(
1155
                $message ?: 'Value "%s" does not contain "%s".',
1156
                $this->stringify($this->value),
1157
                $this->stringify($needle)
1158
            );
1159
            $constraints = ['needle' => $needle, 'encoding' => $encoding];
1160
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_STRING_CONTAINS, $propertyPath, $constraints);
1161
        }
1162
        return $this;
1163
    }
1164
1165
    /**
1166
     * Assert that value is in array of choices.
1167
     *
1168
     * @param array       $choices
1169
     * @param string|null $message
1170
     * @param string|null $propertyPath
1171
     * @return Assert
1172
     * @throws AssertionFailedException
1173
     */
1174
    public function choice(array $choices, $message = null, $propertyPath = null)
1175
    {
1176
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1177
        {
1178
            return $this;
1179
        }
1180
        if ( !in_array($this->value, $choices, true) )
1181
        {
1182
            $message = $message ?: $this->overrideError;
1183
            $message = sprintf(
1184
                $message ?: 'Value "%s" is not an element of the valid values: %s',
1185
                $this->stringify($this->value),
1186
                implode(", ", array_map('Terah\Assert\Assert::stringify', $choices))
1187
            );
1188
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_CHOICE, $propertyPath, ['choices' => $choices]);
1189
        }
1190
        return $this;
1191
    }
1192
1193
    /**
1194
     * Alias of {@see choice()}
1195
     *
1196
     * @throws AssertionFailedException
1197
     *
1198
     * @param array $choices
1199
     * @param string|null $message
1200
     * @param string|null $propertyPath
1201
     * @return $this
1202
     */
1203
    public function inArray(array $choices, $message = null, $propertyPath = null)
1204
    {
1205
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1206
        {
1207
            return $this;
1208
        }
1209
        $this->choice($choices, $message, $propertyPath);
1210
        return $this;
1211
    }
1212
1213
    /**
1214
     * Assert that value is numeric.
1215
     *
1216
     * @param string|null $message
1217
     * @param string|null $propertyPath
1218
     * @return Assert
1219
     * @throws AssertionFailedException
1220
     */
1221
    public function numeric($message = null, $propertyPath = null)
1222
    {
1223
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1224
        {
1225
            return $this;
1226
        }
1227
        if ( ! is_numeric($this->value) )
1228
        {
1229
            $message = $message ?: $this->overrideError;
1230
            $message = sprintf(
1231
                $message ?: 'Value "%s" is not numeric.',
1232
                $this->stringify($this->value)
1233
            );
1234
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_NUMERIC, $propertyPath);
1235
        }
1236
        return $this;
1237
    }
1238
1239
    /**
1240
     * @param string|null $message
1241
     * @param string|null $propertyPath
1242
     * @return Assert
1243
     * @throws AssertionFailedException
1244
     */
1245
    public function nonEmptyArray($message = null, $propertyPath = null)
1246
    {
1247
        $message = $message ?: 'Value "%s" is not a non-empty array.';
1248
        return $this->isArray($message, $propertyPath)->notEmpty($message, $propertyPath);
1249
    }
1250
1251
    /**
1252
     * @param string|null $message
1253
     * @param string|null $propertyPath
1254
     * @return Assert
1255
     * @throws AssertionFailedException
1256
     */
1257
    public function nonEmptyInt($message = null, $propertyPath = null)
1258
    {
1259
        $message = $message ?: 'Value "%s" is not a non-empty integer.';
1260
        return $this->integer($message, $propertyPath)->notEmpty($message, $propertyPath);
1261
    }
1262
1263
    /**
1264
     * @param string|null $message
1265
     * @param string|null $propertyPath
1266
     * @return Assert
1267
     * @throws AssertionFailedException
1268
     */
1269
    public function nonEmptyString($message = null, $propertyPath = null)
1270
    {
1271
        $message = $message ?: 'Value "%s" is not a non-empty string.';
1272
        return $this->integer($message, $propertyPath)->notEmpty($message, $propertyPath);
1273
    }
1274
1275
    /**
1276
     * Assert that value is an array.
1277
     *
1278
     * @param string|null $message
1279
     * @param string|null $propertyPath
1280
     * @return Assert
1281
     * @throws AssertionFailedException
1282
     */
1283
    public function isArray($message = null, $propertyPath = null)
1284
    {
1285
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1286
        {
1287
            return $this;
1288
        }
1289
        if ( !is_array($this->value) )
1290
        {
1291
            $message = $message ?: $this->overrideError;
1292
            $message = sprintf(
1293
                $message ?: 'Value "%s" is not an array.',
1294
                $this->stringify($this->value)
1295
            );
1296
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_ARRAY, $propertyPath);
1297
        }
1298
        return $this;
1299
    }
1300
1301
    /**
1302
     * Assert that value is an array or a traversable object.
1303
     *
1304
     * @param string|null $message
1305
     * @param string|null $propertyPath
1306
     * @return Assert
1307
     * @throws AssertionFailedException
1308
     */
1309
    public function isTraversable($message = null, $propertyPath = null)
1310
    {
1311
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1312
        {
1313
            return $this;
1314
        }
1315
        if ( !is_array($this->value) && !$this->value instanceof \Traversable )
1316
        {
1317
            $message = $message ?: $this->overrideError;
1318
            $message = sprintf(
1319
                $message ?: 'Value "%s" is not an array and does not implement Traversable.',
1320
                $this->stringify($this->value)
1321
            );
1322
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_TRAVERSABLE, $propertyPath);
1323
        }
1324
        return $this;
1325
    }
1326
1327
    /**
1328
     * Assert that value is an array or an array-accessible object.
1329
     *
1330
     * @param string|null $message
1331
     * @param string|null $propertyPath
1332
     * @return Assert
1333
     * @throws AssertionFailedException
1334
     */
1335
    public function isArrayAccessible($message = null, $propertyPath = null)
1336
    {
1337
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1338
        {
1339
            return $this;
1340
        }
1341
        if ( !is_array($this->value) && !$this->value instanceof \ArrayAccess )
1342
        {
1343
            $message = $message ?: $this->overrideError;
1344
            $message = sprintf(
1345
                $message ?: 'Value "%s" is not an array and does not implement ArrayAccess.',
1346
                $this->stringify($this->value)
1347
            );
1348
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_ARRAY_ACCESSIBLE, $propertyPath);
1349
        }
1350
        return $this;
1351
    }
1352
1353
    /**
1354
     * Assert that key exists in an array
1355
     *
1356
     * @param string|integer $key
1357
     * @param string|null    $message
1358
     * @param string|null    $propertyPath
1359
     * @return Assert
1360
     * @throws AssertionFailedException
1361
     */
1362
    public function keyExists($key, $message = null, $propertyPath = null)
1363
    {
1364
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1365
        {
1366
            return $this;
1367
        }
1368
        $this->isArray($message, $propertyPath);
1369
        if ( !array_key_exists($key, $this->value) )
1370
        {
1371
            $message = $message ?: $this->overrideError;
1372
            $message = sprintf(
1373
                $message ?: 'Array does not contain an element with key "%s"',
1374
                $this->stringify($key)
1375
            );
1376
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_KEY_EXISTS, $propertyPath, ['key' => $key]);
1377
        }
1378
        return $this;
1379
    }
1380
1381
    /**
1382
     * Assert that keys exist in array
1383
     *
1384
     * @param array       $keys
1385
     * @param string|null $message
1386
     * @param string|null $propertyPath
1387
     * @return Assert
1388
     * @throws AssertionFailedException
1389
     */
1390
    public function keysExist($keys, $message = null, $propertyPath = null)
1391
    {
1392
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1393
        {
1394
            return $this;
1395
        }
1396
        $this->isArray($message, $propertyPath);
1397
        foreach ( $keys as $key )
1398
        {
1399
            if ( !array_key_exists($key, $this->value) )
1400
            {
1401
                $message = $message
1402
                    ?: sprintf(
1403
                        'Array does not contain an element with key "%s"',
1404
                        $this->stringify($key)
1405
                    );
1406
                throw $this->createException($message, $this->overrideCode ?: self::INVALID_KEYS_EXIST, $propertyPath, ['key' => $key]);
1407
            }
1408
        }
1409
        return $this;
1410
    }
1411
1412
    /**
1413
     * Assert that property exists in array
1414
     *
1415
     * @param string|integer $key
1416
     * @param string|null    $message
1417
     * @param string|null    $propertyPath
1418
     * @return Assert
1419
     * @throws AssertionFailedException
1420
     */
1421
    public function propertyExists($key, $message = null, $propertyPath = null)
1422
    {
1423
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1424
        {
1425
            return $this;
1426
        }
1427
        $this->isObject($message, $propertyPath);
1428
        if ( !property_exists($this->value, $key) && !isset( $this->value->{$key} ) )
1429
        {
1430
            $message = $message
1431
                ?: sprintf(
1432
                    'Object does not contain a property with key "%s"',
1433
                    $this->stringify($key)
1434
                );
1435
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_PROPERTY_EXISTS, $propertyPath, ['key' => $key]);
1436
        }
1437
        return $this;
1438
    }
1439
1440
    /**
1441
     * Assert that properties exists in array
1442
     *
1443
     * @param array       $keys
1444
     * @param string|null $message
1445
     * @param string|null $propertyPath
1446
     * @return Assert
1447
     * @throws AssertionFailedException
1448
     */
1449
    public function propertiesExist(array $keys, $message = null, $propertyPath = null)
1450
    {
1451
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1452
        {
1453
            return $this;
1454
        }
1455
        $this->isObject($message, $propertyPath);
1456
        foreach ( $keys as $key )
1457
        {
1458
            // Using isset to allow resolution of magically defined properties
1459
            if ( !property_exists($this->value, $key) && !isset( $this->value->{$key} ) )
1460
            {
1461
                $message = $message
1462
                    ?: sprintf(
1463
                        'Object does not contain a property with key "%s"',
1464
                        $this->stringify($key)
1465
                    );
1466
                throw $this->createException($message, $this->overrideCode ?: self::INVALID_PROPERTIES_EXIST, $propertyPath, ['key' => $key]);
1467
            }
1468
        }
1469
        return $this;
1470
    }
1471
1472
    /**
1473
     * Assert that string is valid utf8
1474
     *
1475
     * @param string|null $message
1476
     * @param string|null $propertyPath
1477
     * @return Assert
1478
     * @throws AssertionFailedException
1479
     */
1480 View Code Duplication
    public function utf8($message = null, $propertyPath = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1481
    {
1482
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1483
        {
1484
            return $this;
1485
        }
1486
        $this->string($message, $propertyPath);
1487
        if ( mb_detect_encoding($this->value, 'UTF-8', true) !== 'UTF-8' )
1488
        {
1489
            $message = $message
1490
                ?: sprintf(
1491
                    'Value "%s" was expected to be a valid UTF8 string',
1492
                    $this->stringify($this->value)
1493
                );
1494
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_UTF8, $propertyPath);
1495
        }
1496
        return $this;
1497
    }
1498
1499
1500
    /**
1501
     * Assert that string is valid utf8
1502
     *
1503
     * @param string|null $message
1504
     * @param string|null $propertyPath
1505
     * @return Assert
1506
     * @throws AssertionFailedException
1507
     */
1508 View Code Duplication
    public function ascii($message = null, $propertyPath = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1509
    {
1510
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1511
        {
1512
            return $this;
1513
        }
1514
        $this->string($message, $propertyPath);
1515
        if ( ! preg_match('/^[ -~]+$/', $this->value) )
1516
        {
1517
            $message = $message
1518
                ?: sprintf(
1519
                    'Value "%s" was expected to be a valid ASCII string',
1520
                    $this->stringify($this->value)
1521
                );
1522
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_ASCII, $propertyPath);
1523
        }
1524
        return $this;
1525
    }
1526
1527
    /**
1528
     * Assert that key exists in an array/array-accessible object using isset()
1529
     *
1530
     * @param string|integer $key
1531
     * @param string|null    $message
1532
     * @param string|null    $propertyPath
1533
     * @return Assert
1534
     * @throws AssertionFailedException
1535
     */
1536
    public function keyIsset($key, $message = null, $propertyPath = null)
1537
    {
1538
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1539
        {
1540
            return $this;
1541
        }
1542
        $this->isArrayAccessible($message, $propertyPath);
1543
        if ( !isset( $this->value[$key] ) )
1544
        {
1545
            $message = $message ?: $this->overrideError;
1546
            $message = sprintf(
1547
                $message ?: 'The element with key "%s" was not found',
1548
                $this->stringify($key)
1549
            );
1550
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_KEY_ISSET, $propertyPath, ['key' => $key]);
1551
        }
1552
        return $this;
1553
    }
1554
1555
    /**
1556
     * Assert that key exists in an array/array-accessible object and it's value is not empty.
1557
     *
1558
     * @param string|integer $key
1559
     * @param string|null    $message
1560
     * @param string|null    $propertyPath
1561
     * @return Assert
1562
     * @throws AssertionFailedException
1563
     */
1564
    public function notEmptyKey($key, $message = null, $propertyPath = null)
1565
    {
1566
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1567
        {
1568
            return $this;
1569
        }
1570
        $this->keyIsset($key, $message, $propertyPath);
1571
        (new Assert($this->value[$key]))->setExceptionClass($this->exceptionClass)->notEmpty($message, $propertyPath);
1572
        return $this;
1573
    }
1574
1575
    /**
1576
     * Assert that value is not blank
1577
     *
1578
     * @param string|null $message
1579
     * @param string|null $propertyPath
1580
     * @return Assert
1581
     * @throws AssertionFailedException
1582
     */
1583 View Code Duplication
    public function notBlank($message = null, $propertyPath = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1584
    {
1585
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1586
        {
1587
            return $this;
1588
        }
1589
        if ( false === $this->value || ( empty( $this->value ) && '0' != $this->value ) )
1590
        {
1591
            $message = $message ?: $this->overrideError;
1592
            $message = sprintf(
1593
                $message ?: 'Value "%s" is blank, but was expected to contain a value.',
1594
                $this->stringify($this->value)
1595
            );
1596
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_NOT_BLANK, $propertyPath);
1597
        }
1598
        return $this;
1599
    }
1600
1601
    /**
1602
     * Assert that value is instance of given class-name.
1603
     *
1604
     * @param string      $className
1605
     * @param string|null $message
1606
     * @param string|null $propertyPath
1607
     * @return Assert
1608
     * @throws AssertionFailedException
1609
     */
1610
    public function isInstanceOf($className, $message = null, $propertyPath = null)
1611
    {
1612
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1613
        {
1614
            return $this;
1615
        }
1616
        if ( !( $this->value instanceof $className ) )
1617
        {
1618
            $message = $message ?: $this->overrideError;
1619
            $message = sprintf(
1620
                $message ?: 'Class "%s" was expected to be instanceof of "%s" but is not.',
1621
                $this->stringify($this->value),
1622
                $className
1623
            );
1624
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_INSTANCE_OF, $propertyPath, ['class' => $className]);
1625
        }
1626
        return $this;
1627
    }
1628
1629
    /**
1630
     * Assert that value is not instance of given class-name.
1631
     *
1632
     * @param string      $className
1633
     * @param string|null $message
1634
     * @param string|null $propertyPath
1635
     * @return Assert
1636
     * @throws AssertionFailedException
1637
     */
1638
    public function notIsInstanceOf($className, $message = null, $propertyPath = null)
1639
    {
1640
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1641
        {
1642
            return $this;
1643
        }
1644
        if ( $this->value instanceof $className )
1645
        {
1646
            $message = $message ?: $this->overrideError;
1647
            $message = sprintf(
1648
                $message ?: 'Class "%s" was not expected to be instanceof of "%s".',
1649
                $this->stringify($this->value),
1650
                $className
1651
            );
1652
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_NOT_INSTANCE_OF, $propertyPath, ['class' => $className]);
1653
        }
1654
        return $this;
1655
    }
1656
1657
    /**
1658
     * Assert that value is subclass of given class-name.
1659
     *
1660
     * @param string      $className
1661
     * @param string|null $message
1662
     * @param string|null $propertyPath
1663
     * @return Assert
1664
     * @throws AssertionFailedException
1665
     */
1666
    public function subclassOf($className, $message = null, $propertyPath = null)
1667
    {
1668
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1669
        {
1670
            return $this;
1671
        }
1672
        if ( !is_subclass_of($this->value, $className) )
1673
        {
1674
            $message = $message ?: $this->overrideError;
1675
            $message = sprintf(
1676
                $message ?: 'Class "%s" was expected to be subclass of "%s".',
1677
                $this->stringify($this->value),
1678
                $className
1679
            );
1680
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_SUBCLASS_OF, $propertyPath, ['class' => $className]);
1681
        }
1682
        return $this;
1683
    }
1684
1685
    /**
1686
     * Assert that value is in range of numbers.
1687
     *
1688
     * @param integer     $minValue
1689
     * @param integer     $maxValue
1690
     * @param string|null $message
1691
     * @param string|null $propertyPath
1692
     * @return Assert
1693
     * @throws AssertionFailedException
1694
     */
1695
    public function range($minValue, $maxValue, $message = null, $propertyPath = null)
1696
    {
1697
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1698
        {
1699
            return $this;
1700
        }
1701
        $this->numeric($message, $propertyPath);
1702
        if ( $this->value < $minValue || $this->value > $maxValue )
1703
        {
1704
            $message = $message ?: $this->overrideError;
1705
            $message = sprintf(
1706
                $message ?: 'Number "%s" was expected to be at least "%d" and at most "%d".',
1707
                $this->stringify($this->value),
1708
                $this->stringify($minValue),
1709
                $this->stringify($maxValue)
1710
            );
1711
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_RANGE, $propertyPath, [
1712
                'min' => $minValue,
1713
                'max' => $maxValue
1714
            ]);
1715
        }
1716
        return $this;
1717
    }
1718
1719
    /**
1720
     * Assert that a value is at least as big as a given limit
1721
     *
1722
     * @param mixed       $minValue
1723
     * @param string|null $message
1724
     * @param string|null $propertyPath
1725
     * @return Assert
1726
     * @throws AssertionFailedException
1727
     */
1728
    public function min($minValue, $message = null, $propertyPath = null)
1729
    {
1730
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1731
        {
1732
            return $this;
1733
        }
1734
        $this->numeric($message, $propertyPath);
1735
        if ( $this->value < $minValue )
1736
        {
1737
            $message = $message ?: $this->overrideError;
1738
            $message = sprintf(
1739
                $message ?: 'Number "%s" was expected to be at least "%d".',
1740
                $this->stringify($this->value),
1741
                $this->stringify($minValue)
1742
            );
1743
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_MIN, $propertyPath, ['min' => $minValue]);
1744
        }
1745
        return $this;
1746
    }
1747
1748
    /**
1749
     * Assert that a number is smaller as a given limit
1750
     *
1751
     * @param mixed       $maxValue
1752
     * @param string|null $message
1753
     * @param string|null $propertyPath
1754
     * @return Assert
1755
     * @throws AssertionFailedException
1756
     */
1757
    public function max($maxValue, $message = null, $propertyPath = null)
1758
    {
1759
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1760
        {
1761
            return $this;
1762
        }
1763
        $this->numeric($message, $propertyPath);
1764
        if ( $this->value > $maxValue )
1765
        {
1766
            $message = $message ?: $this->overrideError;
1767
            $message = sprintf(
1768
                $message ?: 'Number "%s" was expected to be at most "%d".',
1769
                $this->stringify($this->value),
1770
                $this->stringify($maxValue)
1771
            );
1772
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_MAX, $propertyPath, ['max' => $maxValue]);
1773
        }
1774
        return $this;
1775
    }
1776
1777
    /**
1778
     * Assert that a file exists
1779
     *
1780
     * @param string|null $message
1781
     * @param string|null $propertyPath
1782
     * @return Assert
1783
     * @throws AssertionFailedException
1784
     */
1785
    public function file($message = null, $propertyPath = null)
1786
    {
1787
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1788
        {
1789
            return $this;
1790
        }
1791
        $this->string($message, $propertyPath);
1792
        $this->notEmpty($message, $propertyPath);
1793
        if ( !is_file($this->value) )
1794
        {
1795
            $message = $message ?: $this->overrideError;
1796
            $message = sprintf(
1797
                $message ?: 'File "%s" was expected to exist.',
1798
                $this->stringify($this->value)
1799
            );
1800
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_FILE, $propertyPath);
1801
        }
1802
        return $this;
1803
    }
1804
1805
    /**
1806
     * @param string|null $message
1807
     * @param string|null $propertyPath
1808
     * @return $this
1809
     * @throws AssertionFailedException
1810
     */
1811
    public function fileExists($message = null, $propertyPath = null)
1812
    {
1813
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1814
        {
1815
            return $this;
1816
        }
1817
        $this->string($message, $propertyPath);
1818
        $this->notEmpty($message, $propertyPath);
1819
        if ( ! file_exists($this->value) )
1820
        {
1821
            $message = $message ?: $this->overrideError;
1822
            $message = sprintf(
1823
                $message ?: 'File or directory "%s" was expected to exist.',
1824
                $this->stringify($this->value)
1825
            );
1826
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_FILE_OR_DIR, $propertyPath);
1827
        }
1828
        return $this;
1829
    }
1830
1831
    /**
1832
     * Assert that a directory exists
1833
     *
1834
     * @param string|null $message
1835
     * @param string|null $propertyPath
1836
     * @return Assert
1837
     * @throws AssertionFailedException
1838
     */
1839 View Code Duplication
    public function directory($message = null, $propertyPath = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1840
    {
1841
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1842
        {
1843
            return $this;
1844
        }
1845
        $this->string($message, $propertyPath);
1846
        if ( !is_dir($this->value) )
1847
        {
1848
            $message = $message ?: $this->overrideError;
1849
            $message = sprintf(
1850
                $message ?: 'Path "%s" was expected to be a directory.',
1851
                $this->stringify($this->value)
1852
            );
1853
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_DIRECTORY, $propertyPath);
1854
        }
1855
        return $this;
1856
    }
1857
1858
    /**
1859
     * Assert that the value is something readable
1860
     *
1861
     * @param string|null $message
1862
     * @param string|null $propertyPath
1863
     * @return Assert
1864
     * @throws AssertionFailedException
1865
     */
1866 View Code Duplication
    public function readable($message = null, $propertyPath = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1867
    {
1868
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1869
        {
1870
            return $this;
1871
        }
1872
        $this->string($message, $propertyPath);
1873
        if ( !is_readable($this->value) )
1874
        {
1875
            $message = $message ?: $this->overrideError;
1876
            $message = sprintf(
1877
                $message ?: 'Path "%s" was expected to be readable.',
1878
                $this->stringify($this->value)
1879
            );
1880
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_READABLE, $propertyPath);
1881
        }
1882
        return $this;
1883
    }
1884
1885
    /**
1886
     * Assert that the value is something writeable
1887
     *
1888
     * @param string|null $message
1889
     * @param string|null $propertyPath
1890
     * @return Assert
1891
     * @throws AssertionFailedException
1892
     */
1893 View Code Duplication
    public function writeable($message = null, $propertyPath = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1894
    {
1895
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1896
        {
1897
            return $this;
1898
        }
1899
        $this->string($message, $propertyPath);
1900
        if ( !is_writeable($this->value) )
1901
        {
1902
            $message = $message ?: $this->overrideError;
1903
            $message = sprintf(
1904
                $message ?: 'Path "%s" was expected to be writeable.',
1905
                $this->stringify($this->value)
1906
            );
1907
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_WRITEABLE, $propertyPath);
1908
        }
1909
        return $this;
1910
    }
1911
1912
    /**
1913
     * Assert that value is an email adress (using
1914
     * input_filter/FILTER_VALIDATE_EMAIL).
1915
     *
1916
     * @param string|null $message
1917
     * @param string|null $propertyPath
1918
     * @return Assert
1919
     * @throws AssertionFailedException
1920
     */
1921
    public function email($message = null, $propertyPath = null)
1922
    {
1923
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1924
        {
1925
            return $this;
1926
        }
1927
        $this->string($message, $propertyPath);
1928
        if ( ! filter_var($this->value, FILTER_VALIDATE_EMAIL) )
1929
        {
1930
            $message = $message ?: $this->overrideError;
1931
            $message = sprintf(
1932
                $message ?: 'Value "%s" was expected to be a valid e-mail address.',
1933
                $this->stringify($this->value)
1934
            );
1935
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_EMAIL, $propertyPath);
1936
        }
1937
        else
1938
        {
1939
            $host = substr($this->value, strpos($this->value, '@') + 1);
1940
            // Likely not a FQDN, bug in PHP FILTER_VALIDATE_EMAIL prior to PHP 5.3.3
1941
            if ( version_compare(PHP_VERSION, '5.3.3', '<') && strpos($host, '.') === false )
1942
            {
1943
                $message = $message ?: $this->overrideError;
1944
                $message = sprintf(
1945
                    $message ?: 'Value "%s" was expected to be a valid e-mail address.',
1946
                    $this->stringify($this->value)
1947
                );
1948
                throw $this->createException($message, $this->overrideCode ?: self::INVALID_EMAIL, $propertyPath);
1949
            }
1950
        }
1951
        return $this;
1952
    }
1953
1954
    /**
1955
     * @param null $message
1956
     * @param null $propertyPath
1957
     * @return Assert
1958
     * @throws AssertionFailedException
1959
     */
1960
    public function emailPrefix($message = null, $propertyPath = null)
1961
    {
1962
        $this->value($this->value . '@example.com');
1963
        return $this->email($message, $propertyPath);
1964
    }
1965
1966
    /**
1967
     * Assert that value is an URL.
1968
     *
1969
     * This code snipped was taken from the Symfony project and modified to the special demands of this method.
1970
     *
1971
     * @param string|null $message
1972
     * @param string|null $propertyPath
1973
     * @return Assert
1974
     * @throws AssertionFailedException
1975
     *
1976
     *
1977
     * @link https://github.com/symfony/Validator/blob/master/Constraints/UrlValidator.php
1978
     * @link https://github.com/symfony/Validator/blob/master/Constraints/Url.php
1979
     */
1980
    public function url($message = null, $propertyPath = null)
1981
    {
1982
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
1983
        {
1984
            return $this;
1985
        }
1986
        $this->string($message, $propertyPath);
1987
        $protocols = ['http', 'https'];
1988
        $pattern   = '~^
1989
            (%s)://                                 # protocol
1990
            (
1991
                ([\pL\pN\pS-]+\.)+[\pL]+                   # a domain name
1992
                    |                                     #  or
1993
                \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}      # a IP address
1994
                    |                                     #  or
1995
                \[
1996
                    (?:(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-f]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,1}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,2}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,3}(?:(?:[0-9a-f]{1,4})))?::(?:(?:[0-9a-f]{1,4})):)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,4}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,5}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,6}(?:(?:[0-9a-f]{1,4})))?::))))
1997
                \]  # a IPv6 address
1998
            )
1999
            (:[0-9]+)?                              # a port (optional)
2000
            (/?|/\S+)                               # a /, nothing or a / with something
2001
        $~ixu';
2002
        $pattern   = sprintf($pattern, implode('|', $protocols));
2003
        if ( !preg_match($pattern, $this->value) )
2004
        {
2005
            $message = $message ?: $this->overrideError;
2006
            $message = sprintf(
2007
                $message ?: 'Value "%s" was expected to be a valid URL starting with http or https',
2008
                $this->stringify($this->value)
2009
            );
2010
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_URL, $propertyPath);
2011
        }
2012
        return $this;
2013
    }
2014
2015
    /**
2016
     * Assert that value is domain name.
2017
     *
2018
     * This code snipped was taken from the Symfony project and modified to the special demands of this method.
2019
     *
2020
     * @param string|null $message
2021
     * @param string|null $propertyPath
2022
     * @return Assert
2023
     * @throws AssertionFailedException
2024
     *
2025
     */
2026
    public function domainName($message = null, $propertyPath = null)
2027
    {
2028
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
2029
        {
2030
            return $this;
2031
        }
2032
        $this->string($message, $propertyPath);
2033
        $pattern   = '/^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6}$/';
2034
        if ( ! preg_match($pattern, $this->value) )
2035
        {
2036
            $message = $message ?: $this->overrideError;
2037
            $message = sprintf(
2038
                $message ?: 'Value "%s" was expected to be a valid domain name',
2039
                $this->stringify($this->value)
2040
            );
2041
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_DOMAIN_NAME, $propertyPath);
2042
        }
2043
        return $this;
2044
    }
2045
2046
    /**
2047
     * Assert that value is alphanumeric.
2048
     *
2049
     * @param string|null $message
2050
     * @param string|null $propertyPath
2051
     * @return Assert
2052
     * @throws AssertionFailedException
2053
     */
2054 View Code Duplication
    public function alnum($message = null, $propertyPath = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2055
    {
2056
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
2057
        {
2058
            return $this;
2059
        }
2060
        try
2061
        {
2062
            $this->regex('(^([a-zA-Z]{1}[a-zA-Z0-9]*)$)', $message, $propertyPath);
2063
        }
2064
        catch (AssertionFailedException $e)
2065
        {
2066
            $message = $message ?: $this->overrideError;
2067
            $message = sprintf(
2068
                $message
2069
                    ?: 'Value "%s" is not alphanumeric, starting with letters and containing only letters and numbers.',
2070
                $this->stringify($this->value)
2071
            );
2072
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_ALNUM, $propertyPath);
2073
        }
2074
        return $this;
2075
    }
2076
2077
    /**
2078
     * Assert that the value is boolean True.
2079
     *
2080
     * @param string|null $message
2081
     * @param string|null $propertyPath
2082
     * @return Assert
2083
     * @throws AssertionFailedException
2084
     */
2085
    public function true($message = null, $propertyPath = null)
2086
    {
2087
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
2088
        {
2089
            return $this;
2090
        }
2091
        if ( $this->value !== true )
2092
        {
2093
            $message = $message ?: $this->overrideError;
2094
            $message = sprintf(
2095
                $message ?: 'Value "%s" is not TRUE.',
2096
                $this->stringify($this->value)
2097
            );
2098
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_TRUE, $propertyPath);
2099
        }
2100
        return $this;
2101
    }
2102
2103
    /**
2104
     * Assert that the value is boolean True.
2105
     *
2106
     * @param string|null $message
2107
     * @param string|null $propertyPath
2108
     * @return Assert
2109
     * @throws AssertionFailedException
2110
     */
2111
    public function truthy($message = null, $propertyPath = null)
2112
    {
2113
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
2114
        {
2115
            return $this;
2116
        }
2117
        if ( ! $this->value )
2118
        {
2119
            $message = $message ?: $this->overrideError;
2120
            $message = sprintf(
2121
                $message ?: 'Value "%s" is not truthy.',
2122
                $this->stringify($this->value)
2123
            );
2124
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_TRUE, $propertyPath);
2125
        }
2126
        return $this;
2127
    }
2128
2129
    /**
2130
     * Assert that the value is boolean False.
2131
     *
2132
     * @param string|null $message
2133
     * @param string|null $propertyPath
2134
     * @return Assert
2135
     * @throws AssertionFailedException
2136
     */
2137
    public function false($message = null, $propertyPath = null)
2138
    {
2139
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
2140
        {
2141
            return $this;
2142
        }
2143
        if ( $this->value !== false )
2144
        {
2145
            $message = $message ?: $this->overrideError;
2146
            $message = sprintf(
2147
                $message ?: 'Value "%s" is not FALSE.',
2148
                $this->stringify($this->value)
2149
            );
2150
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_FALSE, $propertyPath);
2151
        }
2152
        return $this;
2153
    }
2154
2155
    /**
2156
     * Assert that the value is not boolean False.
2157
     *
2158
     * @param string|null $message
2159
     * @param string|null $propertyPath
2160
     * @return Assert
2161
     * @throws AssertionFailedException
2162
     */
2163
    public function notFalse($message = null, $propertyPath = null)
2164
    {
2165
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
2166
        {
2167
            return $this;
2168
        }
2169
        if ( $this->value === false )
2170
        {
2171
            $message = $message ?: $this->overrideError;
2172
            $message = sprintf(
2173
                $message ?: 'Value "%s" is not FALSE.',
2174
                $this->stringify($this->value)
2175
            );
2176
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_NOT_FALSE, $propertyPath);
2177
        }
2178
        return $this;
2179
    }
2180
2181
    /**
2182
     * Assert that the class exists.
2183
     *
2184
     * @param string|null $message
2185
     * @param string|null $propertyPath
2186
     * @return Assert
2187
     * @throws AssertionFailedException
2188
     */
2189
    public function classExists($message = null, $propertyPath = null)
2190
    {
2191
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
2192
        {
2193
            return $this;
2194
        }
2195
        if ( !class_exists($this->value) )
2196
        {
2197
            $message = $message ?: $this->overrideError;
2198
            $message = sprintf(
2199
                $message ?: 'Class "%s" does not exist.',
2200
                $this->stringify($this->value)
2201
            );
2202
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_CLASS, $propertyPath);
2203
        }
2204
        return $this;
2205
    }
2206
2207
    /**
2208
     * Assert that the class implements the interface
2209
     *
2210
     * @param string      $interfaceName
2211
     * @param string|null $message
2212
     * @param string|null $propertyPath
2213
     * @return Assert
2214
     * @throws AssertionFailedException
2215
     */
2216
    public function implementsInterface($interfaceName, $message = null, $propertyPath = null)
2217
    {
2218
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
2219
        {
2220
            return $this;
2221
        }
2222
        $reflection = new \ReflectionClass($this->value);
2223
        if ( !$reflection->implementsInterface($interfaceName) )
2224
        {
2225
            $message = $message ?: $this->overrideError;
2226
            $message = sprintf(
2227
                $message ?: 'Class "%s" does not implement interface "%s".',
2228
                $this->stringify($this->value),
2229
                $this->stringify($interfaceName)
2230
            );
2231
            throw $this->createException($message, self::INTERFACE_NOT_IMPLEMENTED, $propertyPath, ['interface' => $interfaceName]);
2232
        }
2233
        return $this;
2234
    }
2235
2236
    /**
2237
     * Assert that the given string is a valid json string.
2238
     *
2239
     * NOTICE:
2240
     * Since this does a json_decode to determine its validity
2241
     * you probably should consider, when using the variable
2242
     * content afterwards, just to decode and check for yourself instead
2243
     * of using this assertion.
2244
     *
2245
     * @param string|null $message
2246
     * @param string|null $propertyPath
2247
     * @return Assert
2248
     * @throws AssertionFailedException
2249
     */
2250
    public function isJsonString($message = null, $propertyPath = null)
2251
    {
2252
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
2253
        {
2254
            return $this;
2255
        }
2256
        if ( null === json_decode($this->value) && JSON_ERROR_NONE !== json_last_error() )
2257
        {
2258
            $message = $message ?: $this->overrideError;
2259
            $message = sprintf(
2260
                $message ?: 'Value "%s" is not a valid JSON string.',
2261
                $this->stringify($this->value)
2262
            );
2263
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_JSON_STRING, $propertyPath);
2264
        }
2265
        return $this;
2266
    }
2267
2268
    /**
2269
     * Assert that the given string is a valid UUID
2270
     *
2271
     * Uses code from {@link https://github.com/ramsey/uuid} that is MIT licensed.
2272
     *
2273
     * @param string|null $message
2274
     * @param string|null $propertyPath
2275
     * @return Assert
2276
     * @throws AssertionFailedException
2277
     */
2278
    public function uuid($message = null, $propertyPath = null)
2279
    {
2280
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
2281
        {
2282
            return $this;
2283
        }
2284
        $this->value = str_replace(['urn:', 'uuid:', '{', '}'], '', $this->value);
2285
        if ( $this->value === '00000000-0000-0000-0000-000000000000' )
2286
        {
2287
            return $this;
2288
        }
2289
        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}$/', $this->value) )
2290
        {
2291
            $message = $message ?: $this->overrideError;
2292
            $message = sprintf(
2293
                $message ?: 'Value "%s" is not a valid UUID.',
2294
                $this->stringify($this->value)
2295
            );
2296
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_UUID, $propertyPath);
2297
        }
2298
        return $this;
2299
    }
2300
2301
    /**
2302
     * Assert that the count of countable is equal to count.
2303
     *
2304
     * @param int    $count
2305
     * @param string $message
2306
     * @param string $propertyPath
2307
     * @return Assert
2308
     * @throws AssertionFailedException
2309
     */
2310
    public function count($count, $message = null, $propertyPath = null)
2311
    {
2312
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
2313
        {
2314
            return $this;
2315
        }
2316
        if ( $count !== count($this->value) )
2317
        {
2318
            $message = $message ?: $this->overrideError;
2319
            $message = sprintf(
2320
                $message ?: 'List does not contain exactly "%d" elements.',
2321
                $this->stringify($this->value),
2322
                $this->stringify($count)
2323
            );
2324
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_COUNT, $propertyPath, ['count' => $count]);
2325
        }
2326
        return $this;
2327
    }
2328
2329
    /**
2330
     * @param $func
2331
     * @param $args
2332
     * @return bool
2333
     * @throws AssertionFailedException
2334
     */
2335
    protected function doAllOrNullOr($func, $args)
2336
    {
2337
        if ( $this->nullOr && is_null($this->value) )
2338
        {
2339
            return true;
2340
        }
2341
        if ( $this->emptyOr && empty($this->value) )
2342
        {
2343
            return true;
2344
        }
2345
        if ( $this->all && (new Assert($this->value))->setExceptionClass($this->exceptionClass)->isTraversable() )
2346
        {
2347
            foreach ( $this->value as $idx => $value )
2348
            {
2349
                $object = (new Assert($value))->setExceptionClass($this->exceptionClass);
2350
                call_user_func_array([$object, $func], $args);
2351
            }
2352
            return true;
2353
        }
2354
        return ( $this->nullOr && is_null($this->value) ) || ( $this->emptyOr && empty($this->value) ) ? true : false;
2355
    }
2356
2357
    /**
2358
     * Determines if the values array has every choice as key and that this choice has content.
2359
     *
2360
     * @param array $choices
2361
     * @param string|null $message
2362
     * @param string|null $propertyPath
2363
     * @return $this
2364
     */
2365
    public function choicesNotEmpty(array $choices, $message = null, $propertyPath = null)
2366
    {
2367
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
2368
        {
2369
            return $this;
2370
        }
2371
        $this->notEmpty($message, $propertyPath);
2372
        foreach ( $choices as $choice )
2373
        {
2374
            $this->notEmptyKey($choice, $message, $propertyPath);
2375
        }
2376
        return $this;
2377
    }
2378
2379
    /**
2380
     * Determines that the named method is defined in the provided object.
2381
     *
2382
     * @param mixed $object
2383
     * @param string|null $message
2384
     * @param string|null $propertyPath
2385
     * @returns Assert
2386
     * @throws
2387
     */
2388
    public function methodExists($object, $message = null, $propertyPath = null)
2389
    {
2390
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
2391
        {
2392
            return $this;
2393
        }
2394
        (new Assert($object))->setExceptionClass($this->exceptionClass)->isObject($message, $propertyPath);
2395
        if ( !method_exists($object, $this->value) )
2396
        {
2397
            $message = $message ?: $this->overrideError;
2398
            $message = sprintf(
2399
                $message ?: 'Expected "%s" does not a exist in provided object.',
2400
                $this->stringify($this->value)
2401
            );
2402
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_METHOD, $propertyPath);
2403
        }
2404
        return $this;
2405
    }
2406
2407
    /**
2408
     * Determines that the provided value is an object.
2409
     *
2410
     * @param string|null $message
2411
     * @param string|null $propertyPath
2412
     * @return $this
2413
     * @throws AssertionFailedException
2414
     */
2415
    public function isObject($message = null, $propertyPath = null)
2416
    {
2417
        if ( $this->doAllOrNullOr(__FUNCTION__, func_get_args()) )
2418
        {
2419
            return $this;
2420
        }
2421
        if ( !is_object($this->value) )
2422
        {
2423
            $message = $message ?: $this->overrideError;
2424
            $message = sprintf(
2425
                $message ?: 'Provided "%s" is not a valid object.',
2426
                $this->stringify($this->value)
2427
            );
2428
            throw $this->createException($message, $this->overrideCode ?: self::INVALID_OBJECT, $propertyPath);
2429
        }
2430
        return $this;
2431
    }
2432
2433
    /**
2434
     * Make a string version of a value.
2435
     *
2436
     * @param $value
2437
     * @return string
2438
     */
2439
    private function stringify($value)
2440
    {
2441
        if ( is_bool($value) )
2442
        {
2443
            return $value ? '<TRUE>' : '<FALSE>';
2444
        }
2445
        if ( is_scalar($value) )
2446
        {
2447
            $val = (string)$value;
2448
            if ( strlen($val) > 100 )
2449
            {
2450
                $val = substr($val, 0, 97) . '...';
2451
            }
2452
            return $val;
2453
        }
2454
        if ( is_array($value) )
2455
        {
2456
            return '<ARRAY>';
2457
        }
2458
        if ( is_object($value) )
2459
        {
2460
            return get_class($value);
2461
        }
2462
        if ( is_resource($value) )
2463
        {
2464
            return '<RESOURCE>';
2465
        }
2466
        if ( $value === null )
2467
        {
2468
            return '<NULL>';
2469
        }
2470
        return 'unknown';
2471
    }
2472
}
2473
2474