Subversion-Projekte lars-tiefland.laravel_shop

Revision

Zur aktuellen Revision | Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Illuminate\Validation;
4
 
5
use BadMethodCallException;
6
use Illuminate\Contracts\Container\Container;
7
use Illuminate\Contracts\Translation\Translator;
8
use Illuminate\Contracts\Validation\DataAwareRule;
9
use Illuminate\Contracts\Validation\ImplicitRule;
10
use Illuminate\Contracts\Validation\Rule as RuleContract;
11
use Illuminate\Contracts\Validation\Validator as ValidatorContract;
12
use Illuminate\Contracts\Validation\ValidatorAwareRule;
13
use Illuminate\Support\Arr;
14
use Illuminate\Support\Fluent;
15
use Illuminate\Support\MessageBag;
16
use Illuminate\Support\Str;
17
use Illuminate\Support\ValidatedInput;
18
use InvalidArgumentException;
19
use RuntimeException;
20
use stdClass;
21
use Symfony\Component\HttpFoundation\File\UploadedFile;
22
 
23
class Validator implements ValidatorContract
24
{
25
    use Concerns\FormatsMessages,
26
        Concerns\ValidatesAttributes;
27
 
28
    /**
29
     * The Translator implementation.
30
     *
31
     * @var \Illuminate\Contracts\Translation\Translator
32
     */
33
    protected $translator;
34
 
35
    /**
36
     * The container instance.
37
     *
38
     * @var \Illuminate\Contracts\Container\Container
39
     */
40
    protected $container;
41
 
42
    /**
43
     * The Presence Verifier implementation.
44
     *
45
     * @var \Illuminate\Validation\PresenceVerifierInterface
46
     */
47
    protected $presenceVerifier;
48
 
49
    /**
50
     * The failed validation rules.
51
     *
52
     * @var array
53
     */
54
    protected $failedRules = [];
55
 
56
    /**
57
     * Attributes that should be excluded from the validated data.
58
     *
59
     * @var array
60
     */
61
    protected $excludeAttributes = [];
62
 
63
    /**
64
     * The message bag instance.
65
     *
66
     * @var \Illuminate\Support\MessageBag
67
     */
68
    protected $messages;
69
 
70
    /**
71
     * The data under validation.
72
     *
73
     * @var array
74
     */
75
    protected $data;
76
 
77
    /**
78
     * The initial rules provided.
79
     *
80
     * @var array
81
     */
82
    protected $initialRules;
83
 
84
    /**
85
     * The rules to be applied to the data.
86
     *
87
     * @var array
88
     */
89
    protected $rules;
90
 
91
    /**
92
     * The current rule that is validating.
93
     *
94
     * @var string
95
     */
96
    protected $currentRule;
97
 
98
    /**
99
     * The array of wildcard attributes with their asterisks expanded.
100
     *
101
     * @var array
102
     */
103
    protected $implicitAttributes = [];
104
 
105
    /**
106
     * The callback that should be used to format the attribute.
107
     *
108
     * @var callable|null
109
     */
110
    protected $implicitAttributesFormatter;
111
 
112
    /**
113
     * The cached data for the "distinct" rule.
114
     *
115
     * @var array
116
     */
117
    protected $distinctValues = [];
118
 
119
    /**
120
     * All of the registered "after" callbacks.
121
     *
122
     * @var array
123
     */
124
    protected $after = [];
125
 
126
    /**
127
     * The array of custom error messages.
128
     *
129
     * @var array
130
     */
131
    public $customMessages = [];
132
 
133
    /**
134
     * The array of fallback error messages.
135
     *
136
     * @var array
137
     */
138
    public $fallbackMessages = [];
139
 
140
    /**
141
     * The array of custom attribute names.
142
     *
143
     * @var array
144
     */
145
    public $customAttributes = [];
146
 
147
    /**
148
     * The array of custom displayable values.
149
     *
150
     * @var array
151
     */
152
    public $customValues = [];
153
 
154
    /**
155
     * Indicates if the validator should stop on the first rule failure.
156
     *
157
     * @var bool
158
     */
159
    protected $stopOnFirstFailure = false;
160
 
161
    /**
162
     * Indicates that unvalidated array keys should be excluded, even if the parent array was validated.
163
     *
164
     * @var bool
165
     */
166
    public $excludeUnvalidatedArrayKeys = false;
167
 
168
    /**
169
     * All of the custom validator extensions.
170
     *
171
     * @var array
172
     */
173
    public $extensions = [];
174
 
175
    /**
176
     * All of the custom replacer extensions.
177
     *
178
     * @var array
179
     */
180
    public $replacers = [];
181
 
182
    /**
183
     * The validation rules that may be applied to files.
184
     *
185
     * @var string[]
186
     */
187
    protected $fileRules = [
188
        'Between',
189
        'Dimensions',
190
        'File',
191
        'Image',
192
        'Max',
193
        'Mimes',
194
        'Mimetypes',
195
        'Min',
196
        'Size',
197
    ];
198
 
199
    /**
200
     * The validation rules that imply the field is required.
201
     *
202
     * @var string[]
203
     */
204
    protected $implicitRules = [
205
        'Accepted',
206
        'AcceptedIf',
207
        'Declined',
208
        'DeclinedIf',
209
        'Filled',
210
        'Present',
211
        'Required',
212
        'RequiredIf',
213
        'RequiredIfAccepted',
214
        'RequiredUnless',
215
        'RequiredWith',
216
        'RequiredWithAll',
217
        'RequiredWithout',
218
        'RequiredWithoutAll',
219
    ];
220
 
221
    /**
222
     * The validation rules which depend on other fields as parameters.
223
     *
224
     * @var string[]
225
     */
226
    protected $dependentRules = [
227
        'After',
228
        'AfterOrEqual',
229
        'Before',
230
        'BeforeOrEqual',
231
        'Confirmed',
232
        'Different',
233
        'ExcludeIf',
234
        'ExcludeUnless',
235
        'ExcludeWith',
236
        'ExcludeWithout',
237
        'Gt',
238
        'Gte',
239
        'Lt',
240
        'Lte',
241
        'AcceptedIf',
242
        'DeclinedIf',
243
        'RequiredIf',
244
        'RequiredIfAccepted',
245
        'RequiredUnless',
246
        'RequiredWith',
247
        'RequiredWithAll',
248
        'RequiredWithout',
249
        'RequiredWithoutAll',
250
        'Prohibited',
251
        'ProhibitedIf',
252
        'ProhibitedUnless',
253
        'Prohibits',
254
        'Same',
255
        'Unique',
256
    ];
257
 
258
    /**
259
     * The validation rules that can exclude an attribute.
260
     *
261
     * @var string[]
262
     */
263
    protected $excludeRules = ['Exclude', 'ExcludeIf', 'ExcludeUnless', 'ExcludeWith', 'ExcludeWithout'];
264
 
265
    /**
266
     * The size related validation rules.
267
     *
268
     * @var string[]
269
     */
270
    protected $sizeRules = ['Size', 'Between', 'Min', 'Max', 'Gt', 'Lt', 'Gte', 'Lte'];
271
 
272
    /**
273
     * The numeric related validation rules.
274
     *
275
     * @var string[]
276
     */
277
    protected $numericRules = ['Numeric', 'Integer', 'Decimal'];
278
 
279
    /**
280
     * The current placeholder for dots in rule keys.
281
     *
282
     * @var string
283
     */
284
    protected $dotPlaceholder;
285
 
286
    /**
287
     * The exception to throw upon failure.
288
     *
289
     * @var string
290
     */
291
    protected $exception = ValidationException::class;
292
 
293
    /**
294
     * Create a new Validator instance.
295
     *
296
     * @param  \Illuminate\Contracts\Translation\Translator  $translator
297
     * @param  array  $data
298
     * @param  array  $rules
299
     * @param  array  $messages
300
     * @param  array  $customAttributes
301
     * @return void
302
     */
303
    public function __construct(Translator $translator, array $data, array $rules,
304
                                array $messages = [], array $customAttributes = [])
305
    {
306
        $this->dotPlaceholder = Str::random();
307
 
308
        $this->initialRules = $rules;
309
        $this->translator = $translator;
310
        $this->customMessages = $messages;
311
        $this->data = $this->parseData($data);
312
        $this->customAttributes = $customAttributes;
313
 
314
        $this->setRules($rules);
315
    }
316
 
317
    /**
318
     * Parse the data array, converting dots and asterisks.
319
     *
320
     * @param  array  $data
321
     * @return array
322
     */
323
    public function parseData(array $data)
324
    {
325
        $newData = [];
326
 
327
        foreach ($data as $key => $value) {
328
            if (is_array($value)) {
329
                $value = $this->parseData($value);
330
            }
331
 
332
            $key = str_replace(
333
                ['.', '*'],
334
                [$this->dotPlaceholder, '__asterisk__'],
335
                $key
336
            );
337
 
338
            $newData[$key] = $value;
339
        }
340
 
341
        return $newData;
342
    }
343
 
344
    /**
345
     * Replace the placeholders used in data keys.
346
     *
347
     * @param  array  $data
348
     * @return array
349
     */
350
    protected function replacePlaceholders($data)
351
    {
352
        $originalData = [];
353
 
354
        foreach ($data as $key => $value) {
355
            $originalData[$this->replacePlaceholderInString($key)] = is_array($value)
356
                        ? $this->replacePlaceholders($value)
357
                        : $value;
358
        }
359
 
360
        return $originalData;
361
    }
362
 
363
    /**
364
     * Replace the placeholders in the given string.
365
     *
366
     * @param  string  $value
367
     * @return string
368
     */
369
    protected function replacePlaceholderInString(string $value)
370
    {
371
        return str_replace(
372
            [$this->dotPlaceholder, '__asterisk__'],
373
            ['.', '*'],
374
            $value
375
        );
376
    }
377
 
378
    /**
379
     * Add an after validation callback.
380
     *
381
     * @param  callable|string  $callback
382
     * @return $this
383
     */
384
    public function after($callback)
385
    {
386
        $this->after[] = fn () => $callback($this);
387
 
388
        return $this;
389
    }
390
 
391
    /**
392
     * Determine if the data passes the validation rules.
393
     *
394
     * @return bool
395
     */
396
    public function passes()
397
    {
398
        $this->messages = new MessageBag;
399
 
400
        [$this->distinctValues, $this->failedRules] = [[], []];
401
 
402
        // We'll spin through each rule, validating the attributes attached to that
403
        // rule. Any error messages will be added to the containers with each of
404
        // the other error messages, returning true if we don't have messages.
405
        foreach ($this->rules as $attribute => $rules) {
406
            if ($this->shouldBeExcluded($attribute)) {
407
                $this->removeAttribute($attribute);
408
 
409
                continue;
410
            }
411
 
412
            if ($this->stopOnFirstFailure && $this->messages->isNotEmpty()) {
413
                break;
414
            }
415
 
416
            foreach ($rules as $rule) {
417
                $this->validateAttribute($attribute, $rule);
418
 
419
                if ($this->shouldBeExcluded($attribute)) {
420
                    $this->removeAttribute($attribute);
421
 
422
                    break;
423
                }
424
 
425
                if ($this->shouldStopValidating($attribute)) {
426
                    break;
427
                }
428
            }
429
        }
430
 
431
        // Here we will spin through all of the "after" hooks on this validator and
432
        // fire them off. This gives the callbacks a chance to perform all kinds
433
        // of other validation that needs to get wrapped up in this operation.
434
        foreach ($this->after as $after) {
435
            $after();
436
        }
437
 
438
        return $this->messages->isEmpty();
439
    }
440
 
441
    /**
442
     * Determine if the data fails the validation rules.
443
     *
444
     * @return bool
445
     */
446
    public function fails()
447
    {
448
        return ! $this->passes();
449
    }
450
 
451
    /**
452
     * Determine if the attribute should be excluded.
453
     *
454
     * @param  string  $attribute
455
     * @return bool
456
     */
457
    protected function shouldBeExcluded($attribute)
458
    {
459
        foreach ($this->excludeAttributes as $excludeAttribute) {
460
            if ($attribute === $excludeAttribute ||
461
                Str::startsWith($attribute, $excludeAttribute.'.')) {
462
                return true;
463
            }
464
        }
465
 
466
        return false;
467
    }
468
 
469
    /**
470
     * Remove the given attribute.
471
     *
472
     * @param  string  $attribute
473
     * @return void
474
     */
475
    protected function removeAttribute($attribute)
476
    {
477
        Arr::forget($this->data, $attribute);
478
        Arr::forget($this->rules, $attribute);
479
    }
480
 
481
    /**
482
     * Run the validator's rules against its data.
483
     *
484
     * @return array
485
     *
486
     * @throws \Illuminate\Validation\ValidationException
487
     */
488
    public function validate()
489
    {
490
        throw_if($this->fails(), $this->exception, $this);
491
 
492
        return $this->validated();
493
    }
494
 
495
    /**
496
     * Run the validator's rules against its data.
497
     *
498
     * @param  string  $errorBag
499
     * @return array
500
     *
501
     * @throws \Illuminate\Validation\ValidationException
502
     */
503
    public function validateWithBag(string $errorBag)
504
    {
505
        try {
506
            return $this->validate();
507
        } catch (ValidationException $e) {
508
            $e->errorBag = $errorBag;
509
 
510
            throw $e;
511
        }
512
    }
513
 
514
    /**
515
     * Get a validated input container for the validated input.
516
     *
517
     * @param  array|null  $keys
518
     * @return \Illuminate\Support\ValidatedInput|array
519
     */
520
    public function safe(array $keys = null)
521
    {
522
        return is_array($keys)
523
                ? (new ValidatedInput($this->validated()))->only($keys)
524
                : new ValidatedInput($this->validated());
525
    }
526
 
527
    /**
528
     * Get the attributes and values that were validated.
529
     *
530
     * @return array
531
     *
532
     * @throws \Illuminate\Validation\ValidationException
533
     */
534
    public function validated()
535
    {
536
        throw_if($this->invalid(), $this->exception, $this);
537
 
538
        $results = [];
539
 
540
        $missingValue = new stdClass;
541
 
542
        foreach ($this->getRules() as $key => $rules) {
543
            if ($this->excludeUnvalidatedArrayKeys &&
544
                in_array('array', $rules) &&
545
                ! empty(preg_grep('/^'.preg_quote($key, '/').'\.+/', array_keys($this->getRules())))) {
546
                continue;
547
            }
548
 
549
            $value = data_get($this->getData(), $key, $missingValue);
550
 
551
            if ($value !== $missingValue) {
552
                Arr::set($results, $key, $value);
553
            }
554
        }
555
 
556
        return $this->replacePlaceholders($results);
557
    }
558
 
559
    /**
560
     * Validate a given attribute against a rule.
561
     *
562
     * @param  string  $attribute
563
     * @param  string  $rule
564
     * @return void
565
     */
566
    protected function validateAttribute($attribute, $rule)
567
    {
568
        $this->currentRule = $rule;
569
 
570
        [$rule, $parameters] = ValidationRuleParser::parse($rule);
571
 
572
        if ($rule === '') {
573
            return;
574
        }
575
 
576
        // First we will get the correct keys for the given attribute in case the field is nested in
577
        // an array. Then we determine if the given rule accepts other field names as parameters.
578
        // If so, we will replace any asterisks found in the parameters with the correct keys.
579
        if ($this->dependsOnOtherFields($rule)) {
580
            $parameters = $this->replaceDotInParameters($parameters);
581
 
582
            if ($keys = $this->getExplicitKeys($attribute)) {
583
                $parameters = $this->replaceAsterisksInParameters($parameters, $keys);
584
            }
585
        }
586
 
587
        $value = $this->getValue($attribute);
588
 
589
        // If the attribute is a file, we will verify that the file upload was actually successful
590
        // and if it wasn't we will add a failure for the attribute. Files may not successfully
591
        // upload if they are too large based on PHP's settings so we will bail in this case.
592
        if ($value instanceof UploadedFile && ! $value->isValid() &&
593
            $this->hasRule($attribute, array_merge($this->fileRules, $this->implicitRules))
594
        ) {
595
            return $this->addFailure($attribute, 'uploaded', []);
596
        }
597
 
598
        // If we have made it this far we will make sure the attribute is validatable and if it is
599
        // we will call the validation method with the attribute. If a method returns false the
600
        // attribute is invalid and we will add a failure message for this failing attribute.
601
        $validatable = $this->isValidatable($rule, $attribute, $value);
602
 
603
        if ($rule instanceof RuleContract) {
604
            return $validatable
605
                    ? $this->validateUsingCustomRule($attribute, $value, $rule)
606
                    : null;
607
        }
608
 
609
        $method = "validate{$rule}";
610
 
611
        if ($validatable && ! $this->$method($attribute, $value, $parameters, $this)) {
612
            $this->addFailure($attribute, $rule, $parameters);
613
        }
614
    }
615
 
616
    /**
617
     * Determine if the given rule depends on other fields.
618
     *
619
     * @param  string  $rule
620
     * @return bool
621
     */
622
    protected function dependsOnOtherFields($rule)
623
    {
624
        return in_array($rule, $this->dependentRules);
625
    }
626
 
627
    /**
628
     * Get the explicit keys from an attribute flattened with dot notation.
629
     *
630
     * E.g. 'foo.1.bar.spark.baz' -> [1, 'spark'] for 'foo.*.bar.*.baz'
631
     *
632
     * @param  string  $attribute
633
     * @return array
634
     */
635
    protected function getExplicitKeys($attribute)
636
    {
637
        $pattern = str_replace('\*', '([^\.]+)', preg_quote($this->getPrimaryAttribute($attribute), '/'));
638
 
639
        if (preg_match('/^'.$pattern.'/', $attribute, $keys)) {
640
            array_shift($keys);
641
 
642
            return $keys;
643
        }
644
 
645
        return [];
646
    }
647
 
648
    /**
649
     * Get the primary attribute name.
650
     *
651
     * For example, if "name.0" is given, "name.*" will be returned.
652
     *
653
     * @param  string  $attribute
654
     * @return string
655
     */
656
    protected function getPrimaryAttribute($attribute)
657
    {
658
        foreach ($this->implicitAttributes as $unparsed => $parsed) {
659
            if (in_array($attribute, $parsed, true)) {
660
                return $unparsed;
661
            }
662
        }
663
 
664
        return $attribute;
665
    }
666
 
667
    /**
668
     * Replace each field parameter which has an escaped dot with the dot placeholder.
669
     *
670
     * @param  array  $parameters
671
     * @return array
672
     */
673
    protected function replaceDotInParameters(array $parameters)
674
    {
675
        return array_map(function ($field) {
676
            return str_replace('\.', $this->dotPlaceholder, $field);
677
        }, $parameters);
678
    }
679
 
680
    /**
681
     * Replace each field parameter which has asterisks with the given keys.
682
     *
683
     * @param  array  $parameters
684
     * @param  array  $keys
685
     * @return array
686
     */
687
    protected function replaceAsterisksInParameters(array $parameters, array $keys)
688
    {
689
        return array_map(function ($field) use ($keys) {
690
            return vsprintf(str_replace('*', '%s', $field), $keys);
691
        }, $parameters);
692
    }
693
 
694
    /**
695
     * Determine if the attribute is validatable.
696
     *
697
     * @param  object|string  $rule
698
     * @param  string  $attribute
699
     * @param  mixed  $value
700
     * @return bool
701
     */
702
    protected function isValidatable($rule, $attribute, $value)
703
    {
704
        if (in_array($rule, $this->excludeRules)) {
705
            return true;
706
        }
707
 
708
        return $this->presentOrRuleIsImplicit($rule, $attribute, $value) &&
709
               $this->passesOptionalCheck($attribute) &&
710
               $this->isNotNullIfMarkedAsNullable($rule, $attribute) &&
711
               $this->hasNotFailedPreviousRuleIfPresenceRule($rule, $attribute);
712
    }
713
 
714
    /**
715
     * Determine if the field is present, or the rule implies required.
716
     *
717
     * @param  object|string  $rule
718
     * @param  string  $attribute
719
     * @param  mixed  $value
720
     * @return bool
721
     */
722
    protected function presentOrRuleIsImplicit($rule, $attribute, $value)
723
    {
724
        if (is_string($value) && trim($value) === '') {
725
            return $this->isImplicit($rule);
726
        }
727
 
728
        return $this->validatePresent($attribute, $value) ||
729
               $this->isImplicit($rule);
730
    }
731
 
732
    /**
733
     * Determine if a given rule implies the attribute is required.
734
     *
735
     * @param  object|string  $rule
736
     * @return bool
737
     */
738
    protected function isImplicit($rule)
739
    {
740
        return $rule instanceof ImplicitRule ||
741
               in_array($rule, $this->implicitRules);
742
    }
743
 
744
    /**
745
     * Determine if the attribute passes any optional check.
746
     *
747
     * @param  string  $attribute
748
     * @return bool
749
     */
750
    protected function passesOptionalCheck($attribute)
751
    {
752
        if (! $this->hasRule($attribute, ['Sometimes'])) {
753
            return true;
754
        }
755
 
756
        $data = ValidationData::initializeAndGatherData($attribute, $this->data);
757
 
758
        return array_key_exists($attribute, $data)
759
            || array_key_exists($attribute, $this->data);
760
    }
761
 
762
    /**
763
     * Determine if the attribute fails the nullable check.
764
     *
765
     * @param  string  $rule
766
     * @param  string  $attribute
767
     * @return bool
768
     */
769
    protected function isNotNullIfMarkedAsNullable($rule, $attribute)
770
    {
771
        if ($this->isImplicit($rule) || ! $this->hasRule($attribute, ['Nullable'])) {
772
            return true;
773
        }
774
 
775
        return ! is_null(Arr::get($this->data, $attribute, 0));
776
    }
777
 
778
    /**
779
     * Determine if it's a necessary presence validation.
780
     *
781
     * This is to avoid possible database type comparison errors.
782
     *
783
     * @param  string  $rule
784
     * @param  string  $attribute
785
     * @return bool
786
     */
787
    protected function hasNotFailedPreviousRuleIfPresenceRule($rule, $attribute)
788
    {
789
        return in_array($rule, ['Unique', 'Exists']) ? ! $this->messages->has($attribute) : true;
790
    }
791
 
792
    /**
793
     * Validate an attribute using a custom rule object.
794
     *
795
     * @param  string  $attribute
796
     * @param  mixed  $value
797
     * @param  \Illuminate\Contracts\Validation\Rule  $rule
798
     * @return void
799
     */
800
    protected function validateUsingCustomRule($attribute, $value, $rule)
801
    {
802
        $attribute = $this->replacePlaceholderInString($attribute);
803
 
804
        $value = is_array($value) ? $this->replacePlaceholders($value) : $value;
805
 
806
        if ($rule instanceof ValidatorAwareRule) {
807
            $rule->setValidator($this);
808
        }
809
 
810
        if ($rule instanceof DataAwareRule) {
811
            $rule->setData($this->data);
812
        }
813
 
814
        if (! $rule->passes($attribute, $value)) {
815
            $ruleClass = $rule instanceof InvokableValidationRule ?
816
                get_class($rule->invokable()) :
817
                get_class($rule);
818
 
819
            $this->failedRules[$attribute][$ruleClass] = [];
820
 
821
            $messages = $this->getFromLocalArray($attribute, $ruleClass) ?? $rule->message();
822
 
823
            $messages = $messages ? (array) $messages : [$ruleClass];
824
 
825
            foreach ($messages as $key => $message) {
826
                $key = is_string($key) ? $key : $attribute;
827
 
828
                $this->messages->add($key, $this->makeReplacements(
829
                    $message, $key, $ruleClass, []
830
                ));
831
            }
832
        }
833
    }
834
 
835
    /**
836
     * Check if we should stop further validations on a given attribute.
837
     *
838
     * @param  string  $attribute
839
     * @return bool
840
     */
841
    protected function shouldStopValidating($attribute)
842
    {
843
        $cleanedAttribute = $this->replacePlaceholderInString($attribute);
844
 
845
        if ($this->hasRule($attribute, ['Bail'])) {
846
            return $this->messages->has($cleanedAttribute);
847
        }
848
 
849
        if (isset($this->failedRules[$cleanedAttribute]) &&
850
            array_key_exists('uploaded', $this->failedRules[$cleanedAttribute])) {
851
            return true;
852
        }
853
 
854
        // In case the attribute has any rule that indicates that the field is required
855
        // and that rule already failed then we should stop validation at this point
856
        // as now there is no point in calling other rules with this field empty.
857
        return $this->hasRule($attribute, $this->implicitRules) &&
858
               isset($this->failedRules[$cleanedAttribute]) &&
859
               array_intersect(array_keys($this->failedRules[$cleanedAttribute]), $this->implicitRules);
860
    }
861
 
862
    /**
863
     * Add a failed rule and error message to the collection.
864
     *
865
     * @param  string  $attribute
866
     * @param  string  $rule
867
     * @param  array  $parameters
868
     * @return void
869
     */
870
    public function addFailure($attribute, $rule, $parameters = [])
871
    {
872
        if (! $this->messages) {
873
            $this->passes();
874
        }
875
 
876
        $attributeWithPlaceholders = $attribute;
877
 
878
        $attribute = $this->replacePlaceholderInString($attribute);
879
 
880
        if (in_array($rule, $this->excludeRules)) {
881
            return $this->excludeAttribute($attribute);
882
        }
883
 
884
        $this->messages->add($attribute, $this->makeReplacements(
885
            $this->getMessage($attributeWithPlaceholders, $rule), $attribute, $rule, $parameters
886
        ));
887
 
888
        $this->failedRules[$attribute][$rule] = $parameters;
889
    }
890
 
891
    /**
892
     * Add the given attribute to the list of excluded attributes.
893
     *
894
     * @param  string  $attribute
895
     * @return void
896
     */
897
    protected function excludeAttribute(string $attribute)
898
    {
899
        $this->excludeAttributes[] = $attribute;
900
 
901
        $this->excludeAttributes = array_unique($this->excludeAttributes);
902
    }
903
 
904
    /**
905
     * Returns the data which was valid.
906
     *
907
     * @return array
908
     */
909
    public function valid()
910
    {
911
        if (! $this->messages) {
912
            $this->passes();
913
        }
914
 
915
        return array_diff_key(
916
            $this->data, $this->attributesThatHaveMessages()
917
        );
918
    }
919
 
920
    /**
921
     * Returns the data which was invalid.
922
     *
923
     * @return array
924
     */
925
    public function invalid()
926
    {
927
        if (! $this->messages) {
928
            $this->passes();
929
        }
930
 
931
        $invalid = array_intersect_key(
932
            $this->data, $this->attributesThatHaveMessages()
933
        );
934
 
935
        $result = [];
936
 
937
        $failed = Arr::only(Arr::dot($invalid), array_keys($this->failed()));
938
 
939
        foreach ($failed as $key => $failure) {
940
            Arr::set($result, $key, $failure);
941
        }
942
 
943
        return $result;
944
    }
945
 
946
    /**
947
     * Generate an array of all attributes that have messages.
948
     *
949
     * @return array
950
     */
951
    protected function attributesThatHaveMessages()
952
    {
953
        return collect($this->messages()->toArray())->map(function ($message, $key) {
954
            return explode('.', $key)[0];
955
        })->unique()->flip()->all();
956
    }
957
 
958
    /**
959
     * Get the failed validation rules.
960
     *
961
     * @return array
962
     */
963
    public function failed()
964
    {
965
        return $this->failedRules;
966
    }
967
 
968
    /**
969
     * Get the message container for the validator.
970
     *
971
     * @return \Illuminate\Support\MessageBag
972
     */
973
    public function messages()
974
    {
975
        if (! $this->messages) {
976
            $this->passes();
977
        }
978
 
979
        return $this->messages;
980
    }
981
 
982
    /**
983
     * An alternative more semantic shortcut to the message container.
984
     *
985
     * @return \Illuminate\Support\MessageBag
986
     */
987
    public function errors()
988
    {
989
        return $this->messages();
990
    }
991
 
992
    /**
993
     * Get the messages for the instance.
994
     *
995
     * @return \Illuminate\Support\MessageBag
996
     */
997
    public function getMessageBag()
998
    {
999
        return $this->messages();
1000
    }
1001
 
1002
    /**
1003
     * Determine if the given attribute has a rule in the given set.
1004
     *
1005
     * @param  string  $attribute
1006
     * @param  string|array  $rules
1007
     * @return bool
1008
     */
1009
    public function hasRule($attribute, $rules)
1010
    {
1011
        return ! is_null($this->getRule($attribute, $rules));
1012
    }
1013
 
1014
    /**
1015
     * Get a rule and its parameters for a given attribute.
1016
     *
1017
     * @param  string  $attribute
1018
     * @param  string|array  $rules
1019
     * @return array|null
1020
     */
1021
    protected function getRule($attribute, $rules)
1022
    {
1023
        if (! array_key_exists($attribute, $this->rules)) {
1024
            return;
1025
        }
1026
 
1027
        $rules = (array) $rules;
1028
 
1029
        foreach ($this->rules[$attribute] as $rule) {
1030
            [$rule, $parameters] = ValidationRuleParser::parse($rule);
1031
 
1032
            if (in_array($rule, $rules)) {
1033
                return [$rule, $parameters];
1034
            }
1035
        }
1036
    }
1037
 
1038
    /**
1039
     * Get the data under validation.
1040
     *
1041
     * @return array
1042
     */
1043
    public function attributes()
1044
    {
1045
        return $this->getData();
1046
    }
1047
 
1048
    /**
1049
     * Get the data under validation.
1050
     *
1051
     * @return array
1052
     */
1053
    public function getData()
1054
    {
1055
        return $this->data;
1056
    }
1057
 
1058
    /**
1059
     * Set the data under validation.
1060
     *
1061
     * @param  array  $data
1062
     * @return $this
1063
     */
1064
    public function setData(array $data)
1065
    {
1066
        $this->data = $this->parseData($data);
1067
 
1068
        $this->setRules($this->initialRules);
1069
 
1070
        return $this;
1071
    }
1072
 
1073
    /**
1074
     * Get the value of a given attribute.
1075
     *
1076
     * @param  string  $attribute
1077
     * @return mixed
1078
     */
1079
    protected function getValue($attribute)
1080
    {
1081
        return Arr::get($this->data, $attribute);
1082
    }
1083
 
1084
    /**
1085
     * Get the validation rules.
1086
     *
1087
     * @return array
1088
     */
1089
    public function getRules()
1090
    {
1091
        return $this->rules;
1092
    }
1093
 
1094
    /**
1095
     * Get the validation rules with key placeholders removed.
1096
     *
1097
     * @return array
1098
     */
1099
    public function getRulesWithoutPlaceholders()
1100
    {
1101
        return collect($this->rules)
1102
            ->mapWithKeys(fn ($value, $key) => [
1103
                str_replace($this->dotPlaceholder, '\\.', $key) => $value,
1104
            ])
1105
            ->all();
1106
    }
1107
 
1108
    /**
1109
     * Set the validation rules.
1110
     *
1111
     * @param  array  $rules
1112
     * @return $this
1113
     */
1114
    public function setRules(array $rules)
1115
    {
1116
        $rules = collect($rules)->mapWithKeys(function ($value, $key) {
1117
            return [str_replace('\.', $this->dotPlaceholder, $key) => $value];
1118
        })->toArray();
1119
 
1120
        $this->initialRules = $rules;
1121
 
1122
        $this->rules = [];
1123
 
1124
        $this->addRules($rules);
1125
 
1126
        return $this;
1127
    }
1128
 
1129
    /**
1130
     * Parse the given rules and merge them into current rules.
1131
     *
1132
     * @param  array  $rules
1133
     * @return void
1134
     */
1135
    public function addRules($rules)
1136
    {
1137
        // The primary purpose of this parser is to expand any "*" rules to the all
1138
        // of the explicit rules needed for the given data. For example the rule
1139
        // names.* would get expanded to names.0, names.1, etc. for this data.
1140
        $response = (new ValidationRuleParser($this->data))
1141
                            ->explode(ValidationRuleParser::filterConditionalRules($rules, $this->data));
1142
 
1143
        $this->rules = array_merge_recursive(
1144
            $this->rules, $response->rules
1145
        );
1146
 
1147
        $this->implicitAttributes = array_merge(
1148
            $this->implicitAttributes, $response->implicitAttributes
1149
        );
1150
    }
1151
 
1152
    /**
1153
     * Add conditions to a given field based on a Closure.
1154
     *
1155
     * @param  string|array  $attribute
1156
     * @param  string|array  $rules
1157
     * @param  callable  $callback
1158
     * @return $this
1159
     */
1160
    public function sometimes($attribute, $rules, callable $callback)
1161
    {
1162
        $payload = new Fluent($this->data);
1163
 
1164
        foreach ((array) $attribute as $key) {
1165
            $response = (new ValidationRuleParser($this->data))->explode([$key => $rules]);
1166
 
1167
            $this->implicitAttributes = array_merge($response->implicitAttributes, $this->implicitAttributes);
1168
 
1169
            foreach ($response->rules as $ruleKey => $ruleValue) {
1170
                if ($callback($payload, $this->dataForSometimesIteration($ruleKey, ! str_ends_with($key, '.*')))) {
1171
                    $this->addRules([$ruleKey => $ruleValue]);
1172
                }
1173
            }
1174
        }
1175
 
1176
        return $this;
1177
    }
1178
 
1179
    /**
1180
     * Get the data that should be injected into the iteration of a wildcard "sometimes" callback.
1181
     *
1182
     * @param  string  $attribute
1183
     * @return \Illuminate\Support\Fluent|array|mixed
1184
     */
1185
    private function dataForSometimesIteration(string $attribute, $removeLastSegmentOfAttribute)
1186
    {
1187
        $lastSegmentOfAttribute = strrchr($attribute, '.');
1188
 
1189
        $attribute = $lastSegmentOfAttribute && $removeLastSegmentOfAttribute
1190
                    ? Str::replaceLast($lastSegmentOfAttribute, '', $attribute)
1191
                    : $attribute;
1192
 
1193
        return is_array($data = data_get($this->data, $attribute))
1194
            ? new Fluent($data)
1195
            : $data;
1196
    }
1197
 
1198
    /**
1199
     * Instruct the validator to stop validating after the first rule failure.
1200
     *
1201
     * @param  bool  $stopOnFirstFailure
1202
     * @return $this
1203
     */
1204
    public function stopOnFirstFailure($stopOnFirstFailure = true)
1205
    {
1206
        $this->stopOnFirstFailure = $stopOnFirstFailure;
1207
 
1208
        return $this;
1209
    }
1210
 
1211
    /**
1212
     * Register an array of custom validator extensions.
1213
     *
1214
     * @param  array  $extensions
1215
     * @return void
1216
     */
1217
    public function addExtensions(array $extensions)
1218
    {
1219
        if ($extensions) {
1220
            $keys = array_map([Str::class, 'snake'], array_keys($extensions));
1221
 
1222
            $extensions = array_combine($keys, array_values($extensions));
1223
        }
1224
 
1225
        $this->extensions = array_merge($this->extensions, $extensions);
1226
    }
1227
 
1228
    /**
1229
     * Register an array of custom implicit validator extensions.
1230
     *
1231
     * @param  array  $extensions
1232
     * @return void
1233
     */
1234
    public function addImplicitExtensions(array $extensions)
1235
    {
1236
        $this->addExtensions($extensions);
1237
 
1238
        foreach ($extensions as $rule => $extension) {
1239
            $this->implicitRules[] = Str::studly($rule);
1240
        }
1241
    }
1242
 
1243
    /**
1244
     * Register an array of custom dependent validator extensions.
1245
     *
1246
     * @param  array  $extensions
1247
     * @return void
1248
     */
1249
    public function addDependentExtensions(array $extensions)
1250
    {
1251
        $this->addExtensions($extensions);
1252
 
1253
        foreach ($extensions as $rule => $extension) {
1254
            $this->dependentRules[] = Str::studly($rule);
1255
        }
1256
    }
1257
 
1258
    /**
1259
     * Register a custom validator extension.
1260
     *
1261
     * @param  string  $rule
1262
     * @param  \Closure|string  $extension
1263
     * @return void
1264
     */
1265
    public function addExtension($rule, $extension)
1266
    {
1267
        $this->extensions[Str::snake($rule)] = $extension;
1268
    }
1269
 
1270
    /**
1271
     * Register a custom implicit validator extension.
1272
     *
1273
     * @param  string  $rule
1274
     * @param  \Closure|string  $extension
1275
     * @return void
1276
     */
1277
    public function addImplicitExtension($rule, $extension)
1278
    {
1279
        $this->addExtension($rule, $extension);
1280
 
1281
        $this->implicitRules[] = Str::studly($rule);
1282
    }
1283
 
1284
    /**
1285
     * Register a custom dependent validator extension.
1286
     *
1287
     * @param  string  $rule
1288
     * @param  \Closure|string  $extension
1289
     * @return void
1290
     */
1291
    public function addDependentExtension($rule, $extension)
1292
    {
1293
        $this->addExtension($rule, $extension);
1294
 
1295
        $this->dependentRules[] = Str::studly($rule);
1296
    }
1297
 
1298
    /**
1299
     * Register an array of custom validator message replacers.
1300
     *
1301
     * @param  array  $replacers
1302
     * @return void
1303
     */
1304
    public function addReplacers(array $replacers)
1305
    {
1306
        if ($replacers) {
1307
            $keys = array_map([Str::class, 'snake'], array_keys($replacers));
1308
 
1309
            $replacers = array_combine($keys, array_values($replacers));
1310
        }
1311
 
1312
        $this->replacers = array_merge($this->replacers, $replacers);
1313
    }
1314
 
1315
    /**
1316
     * Register a custom validator message replacer.
1317
     *
1318
     * @param  string  $rule
1319
     * @param  \Closure|string  $replacer
1320
     * @return void
1321
     */
1322
    public function addReplacer($rule, $replacer)
1323
    {
1324
        $this->replacers[Str::snake($rule)] = $replacer;
1325
    }
1326
 
1327
    /**
1328
     * Set the custom messages for the validator.
1329
     *
1330
     * @param  array  $messages
1331
     * @return $this
1332
     */
1333
    public function setCustomMessages(array $messages)
1334
    {
1335
        $this->customMessages = array_merge($this->customMessages, $messages);
1336
 
1337
        return $this;
1338
    }
1339
 
1340
    /**
1341
     * Set the custom attributes on the validator.
1342
     *
1343
     * @param  array  $attributes
1344
     * @return $this
1345
     */
1346
    public function setAttributeNames(array $attributes)
1347
    {
1348
        $this->customAttributes = $attributes;
1349
 
1350
        return $this;
1351
    }
1352
 
1353
    /**
1354
     * Add custom attributes to the validator.
1355
     *
1356
     * @param  array  $customAttributes
1357
     * @return $this
1358
     */
1359
    public function addCustomAttributes(array $customAttributes)
1360
    {
1361
        $this->customAttributes = array_merge($this->customAttributes, $customAttributes);
1362
 
1363
        return $this;
1364
    }
1365
 
1366
    /**
1367
     * Set the callback that used to format an implicit attribute.
1368
     *
1369
     * @param  callable|null  $formatter
1370
     * @return $this
1371
     */
1372
    public function setImplicitAttributesFormatter(callable $formatter = null)
1373
    {
1374
        $this->implicitAttributesFormatter = $formatter;
1375
 
1376
        return $this;
1377
    }
1378
 
1379
    /**
1380
     * Set the custom values on the validator.
1381
     *
1382
     * @param  array  $values
1383
     * @return $this
1384
     */
1385
    public function setValueNames(array $values)
1386
    {
1387
        $this->customValues = $values;
1388
 
1389
        return $this;
1390
    }
1391
 
1392
    /**
1393
     * Add the custom values for the validator.
1394
     *
1395
     * @param  array  $customValues
1396
     * @return $this
1397
     */
1398
    public function addCustomValues(array $customValues)
1399
    {
1400
        $this->customValues = array_merge($this->customValues, $customValues);
1401
 
1402
        return $this;
1403
    }
1404
 
1405
    /**
1406
     * Set the fallback messages for the validator.
1407
     *
1408
     * @param  array  $messages
1409
     * @return void
1410
     */
1411
    public function setFallbackMessages(array $messages)
1412
    {
1413
        $this->fallbackMessages = $messages;
1414
    }
1415
 
1416
    /**
1417
     * Get the Presence Verifier implementation.
1418
     *
1419
     * @param  string|null  $connection
1420
     * @return \Illuminate\Validation\PresenceVerifierInterface
1421
     *
1422
     * @throws \RuntimeException
1423
     */
1424
    public function getPresenceVerifier($connection = null)
1425
    {
1426
        if (! isset($this->presenceVerifier)) {
1427
            throw new RuntimeException('Presence verifier has not been set.');
1428
        }
1429
 
1430
        if ($this->presenceVerifier instanceof DatabasePresenceVerifierInterface) {
1431
            $this->presenceVerifier->setConnection($connection);
1432
        }
1433
 
1434
        return $this->presenceVerifier;
1435
    }
1436
 
1437
    /**
1438
     * Set the Presence Verifier implementation.
1439
     *
1440
     * @param  \Illuminate\Validation\PresenceVerifierInterface  $presenceVerifier
1441
     * @return void
1442
     */
1443
    public function setPresenceVerifier(PresenceVerifierInterface $presenceVerifier)
1444
    {
1445
        $this->presenceVerifier = $presenceVerifier;
1446
    }
1447
 
1448
    /**
1449
     * Set the exception to throw upon failed validation.
1450
     *
1451
     * @param  string  $exception
1452
     * @return $this
1453
     *
1454
     * @throws \InvalidArgumentException
1455
     */
1456
    public function setException($exception)
1457
    {
1458
        if (! is_a($exception, ValidationException::class, true)) {
1459
            throw new InvalidArgumentException(
1460
                sprintf('Exception [%s] is invalid. It must extend [%s].', $exception, ValidationException::class)
1461
            );
1462
        }
1463
 
1464
        $this->exception = $exception;
1465
 
1466
        return $this;
1467
    }
1468
 
1469
    /**
1470
     * Get the Translator implementation.
1471
     *
1472
     * @return \Illuminate\Contracts\Translation\Translator
1473
     */
1474
    public function getTranslator()
1475
    {
1476
        return $this->translator;
1477
    }
1478
 
1479
    /**
1480
     * Set the Translator implementation.
1481
     *
1482
     * @param  \Illuminate\Contracts\Translation\Translator  $translator
1483
     * @return void
1484
     */
1485
    public function setTranslator(Translator $translator)
1486
    {
1487
        $this->translator = $translator;
1488
    }
1489
 
1490
    /**
1491
     * Set the IoC container instance.
1492
     *
1493
     * @param  \Illuminate\Contracts\Container\Container  $container
1494
     * @return void
1495
     */
1496
    public function setContainer(Container $container)
1497
    {
1498
        $this->container = $container;
1499
    }
1500
 
1501
    /**
1502
     * Call a custom validator extension.
1503
     *
1504
     * @param  string  $rule
1505
     * @param  array  $parameters
1506
     * @return bool|null
1507
     */
1508
    protected function callExtension($rule, $parameters)
1509
    {
1510
        $callback = $this->extensions[$rule];
1511
 
1512
        if (is_callable($callback)) {
1513
            return $callback(...array_values($parameters));
1514
        } elseif (is_string($callback)) {
1515
            return $this->callClassBasedExtension($callback, $parameters);
1516
        }
1517
    }
1518
 
1519
    /**
1520
     * Call a class based validator extension.
1521
     *
1522
     * @param  string  $callback
1523
     * @param  array  $parameters
1524
     * @return bool
1525
     */
1526
    protected function callClassBasedExtension($callback, $parameters)
1527
    {
1528
        [$class, $method] = Str::parseCallback($callback, 'validate');
1529
 
1530
        return $this->container->make($class)->{$method}(...array_values($parameters));
1531
    }
1532
 
1533
    /**
1534
     * Handle dynamic calls to class methods.
1535
     *
1536
     * @param  string  $method
1537
     * @param  array  $parameters
1538
     * @return mixed
1539
     *
1540
     * @throws \BadMethodCallException
1541
     */
1542
    public function __call($method, $parameters)
1543
    {
1544
        $rule = Str::snake(substr($method, 8));
1545
 
1546
        if (isset($this->extensions[$rule])) {
1547
            return $this->callExtension($rule, $parameters);
1548
        }
1549
 
1550
        throw new BadMethodCallException(sprintf(
1551
            'Method %s::%s does not exist.', static::class, $method
1552
        ));
1553
    }
1554
}