Subversion-Projekte lars-tiefland.laravel_shop

Revision

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