Issues (88)

src/Validation.php (3 issues)

1
<?php
2
3
/**
4
 * This file is part of Dimtrovich/Validation.
5
 *
6
 * (c) 2023 Dimitri Sitchet Tomkeu <[email protected]>
7
 *
8
 * For the full copyright and license information, please view
9
 * the LICENSE file that was distributed with this source code.
10
 */
11
12
namespace Dimtrovich\Validation;
13
14
use BlitzPHP\Utilities\Helpers;
15
use Dimtrovich\Validation\Exceptions\ValidationException;
16
use Dimtrovich\Validation\Rules\AbstractRule;
17
use InvalidArgumentException;
18
use Rakit\Validation\Rule;
0 ignored issues
show
This use statement conflicts with another class in this namespace, Dimtrovich\Validation\Rule. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
19
use Rakit\Validation\RuleNotFoundException;
20
use Rakit\Validation\Validation as RakitValidation;
21
use Rakit\Validation\Validator as RakitValidator;
22
use RuntimeException;
23
use Throwable;
24
25
class Validation
26
{
27
    protected RakitValidator $validator;
28
    private ?RakitValidation $validation = null;
29
30
    /**
31
     * The exception to throw on failure.
32
     */
33
    protected string $exception = ValidationException::class;
34
35
    /**
36
     * Validation messages
37
     *
38
     * @var array<string, string>
39
     */
40
    protected array $messages = [];
41
42
    /**
43
     * Translations on validation rules
44
     *
45
     * @var array<string, string>
46
     */
47
    protected array $translations = [];
48
49
    /**
50
     * Alias of keys of datas to validate
51
     *
52
     * @var array<string, string>
53
     */
54
    protected array $aliases = [];
55
56
    /**
57
     * Data to validate
58
     */
59
    private array $data = [];
60
61
    /**
62
     * validation rules
63
     *
64
     * @var array<string, mixed>
65
     */
66
    protected array $rules = [];
67
68
    /**
69
     * self instance for singleton
70
     */
71
    protected static ?self $_instance = null;
72
73
    /**
74
     * Constructor
75
     */
76
    public function __construct(protected string $locale = 'en')
77
    {
78 132
        $this->validator = new RakitValidator($this->translations);
79 132
        $this->validator->allowRuleOverride(true);
80
        $this->registerRules([
81
            Rules\AcceptedIf::class,
82
            Rules\ActiveURL::class,
83
            Rules\After::class,
84
            Rules\AfterOrEqual::class,
85
            Rules\Alpha::class,
86
            Rules\AlphaDash::class,
87
            Rules\AlphaNum::class,
88
            Rules\AnyOf::class,
89
            Rules\ArrayCanOnlyHaveKeys::class,
90
            Rules\ArrayMustHaveKeys::class,
91
            Rules\Ascii::class,
92
            Rules\Base64::class,
93
            Rules\Before::class,
94
            Rules\BeforeOrEqual::class,
95
            Rules\Between::class,
96
            Rules\Bic::class,
97
            Rules\BitcoinAddress::class,
98
            Rules\Camelcase::class,
99
            Rules\CapitalCharWithNumber::class,
100
            Rules\CarNumber::class,
101
            Rules\Cidr::class,
102
            Rules\Confirmed::class,
103
            Rules\Contains::class,
104
            Rules\ContainsAll::class,
105
            Rules\CreditCard::class,
106
            Rules\Currency::class,
107
            Rules\Date::class,
108
            Rules\DateEquals::class,
109
            Rules\Datetime::class,
110
            Rules\Decimal::class,
111
            Rules\Declined::class,
112
            Rules\DeclinedIf::class,
113
            Rules\Dimensions::class,
114
            Rules\DiscordUsername::class,
115
            Rules\Distinct::class,
116
            Rules\DoesntEndWith::class,
117
            Rules\DoesntStartWith::class,
118
            Rules\Domain::class,
119
            Rules\Duplicate::class,
120
            Rules\DuplicateCharacter::class,
121
            Rules\Ean::class,
122
            Rules\Email::class,
123
            Rules\EndWith::class,
124
            Rules\Enum::class,
125
            Rules\EvenNumber::class,
126
            Rules\Ext::class,
127
            Rules\File::class,
128
            Rules\Fullname::class,
129
            Rules\Gt::class,
130
            Rules\Gte::class,
131
            Rules\Gtin::class,
132
            Rules\Hash::class,
133
            Rules\Hashtag::class,
134
            Rules\Hex::class,
135
            Rules\Hexcolor::class,
136
            Rules\Htmlclean::class,
137
            Rules\Htmltag::class,
138
            Rules\Iban::class,
139
            Rules\Image::class,
140
            Rules\Imei::class,
141
            Rules\InArray::class,
142
            Rules\Isbn::class,
143
            Rules\Issn::class,
144
            Rules\Jwt::class,
145
            Rules\Kebabcase::class,
146
            Rules\Length::class,
147
            Rules\Lt::class,
148
            Rules\Lte::class,
149
            Rules\MacAddress::class,
150
            Rules\Max::class,
151
            Rules\Mimes::class,
152
            Rules\Mimetypes::class,
153
            Rules\Min::class,
154
            Rules\Missing::class,
155
            Rules\MissingIf::class,
156
            Rules\MissingUnless::class,
157
            Rules\MissingWith::class,
158
            Rules\MissingWithAll::class,
159
            Rules\MultipleOf::class,
160
            Rules\NotInArray::class,
161
            Rules\NotRegex::class,
162
            Rules\OddNumber::class,
163
            Rules\Pascalcase::class,
164
            Rules\Password::class,
165
            Rules\Pattern::class,
166
            Rules\Phone::class,
167
            Rules\Port::class,
168
            Rules\Postalcode::class,
169
            Rules\PresentIf::class,
170
            Rules\PresentUnless::class,
171
            Rules\PresentWith::class,
172
            Rules\PresentWithAll::class,
173
            Rules\Prohibited::class,
174
            Rules\ProhibitedIf::class,
175
            Rules\ProhibitedUnless::class,
176
            Rules\Prohibits::class,
177
            Rules\RequiredIfAccepted::class,
178
            Rules\RequiredIfDeclined::class,
179
            Rules\Semver::class,
180
            Rules\Size::class,
181
            Rules\SlashEndOfString::class,
182
            Rules\Slug::class,
183
            Rules\Snakecase::class,
184
            Rules\StartWith::class,
185
            Rules\Time::class,
186
            Rules\Timezone::class,
187
            Rules\Titlecase::class,
188
            Rules\TypeArray::class,
189
            Rules\TypeFloat::class,
190
            Rules\TypeInstanceOf::class,
191
            Rules\TypeString::class,
192
            Rules\Ulid::class,
193
            Rules\Username::class,
194
            Rules\Uuid::class,
195
            Rules\Vatid::class,
196 132
        ]);
197
    }
198
199
    /**
200
     * singleton constructor
201
     *
202
     * @return self
203
     */
204
    public static function instance()
205
    {
206
        if (static::$_instance === null) {
207 2
            static::$_instance = new static();
208
        }
209
210 20
        return static::$_instance;
211
    }
212
213
    /**
214
     * Get the actual validator instance
215
     *
216
     * if parameter $rule passed, return instance of $rule validator
217
     *
218
     * @return RakitValidator|Rule
219
     */
220
    public function getValidator(?string $rule = null)
221
    {
222
        if (null === $rule) {
223
            return $this->validator;
224
        }
225
226 20
        return $this->validator->getValidator($rule);
227
    }
228
229
    /**
230
     * Set the actual validation instance
231
     */
232
    public function setValidation(?RakitValidation $validation): self
233
    {
234
        $this->validation = $validation;
235
236
        return $this;
237
    }
238
239
    /**
240
     * Add rule validator
241
     */
242
    public function addValidator(string $name, AbstractRule $rule): self
243
    {
244 132
        $this->validator->addValidator($name, $rule->locale($this->locale));
245
246 132
        return $this;
247
    }
248
249
    /**
250
     * Check if validation fails
251
     */
252
    public function fails(): bool
253
    {
254 4
        return ! $this->passes();
255
    }
256
257
    /**
258
     * Check if validation passes
259
     */
260
    public function passes(): bool
261
    {
262 132
        $this->validation = $this->validator->make($this->data, $this->rules, $this->messages);
263 132
        $this->validation->setMessages($this->translations);
264 132
        $this->validation->setTranslations($this->translations);
265 132
        $this->validation->setAliases($this->aliases);
266 132
        $this->validation->validate();
267
268 132
        return $this->validation->passes();
269
    }
270
271
    /**
272
     * Run the validator's rules against its data.
273
     *
274
     * @throws ValidationException
275
     */
276
    public function validate(): array
277
    {
278
        $this->throwIf($this->fails());
279
280
        return $this->validated();
281
    }
282
283
    /**
284
     * Run the validator's rules against its data.
285
     *
286
     * @throws ValidationException
287
     */
288
    public function validateWithBag(string $errorBag): array
0 ignored issues
show
The parameter $errorBag is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

288
    public function validateWithBag(/** @scrutinizer ignore-unused */ string $errorBag): array

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
289
    {
290
        try {
291
            return $this->validate();
292
        } catch (ValidationException $e) {
293
            // $e->errorBag = $errorBag;
294
295
            throw $e;
296
        }
297
    }
298
299
    /**
300
     * Returns the data which was valid.
301
     */
302
    public function valid(): array
303
    {
304
        if (! $this->validation) {
305
            return [];
306
        }
307
308
        return $this->validation->getValidData();
309
    }
310
311
    /**
312
     * Get the attributes and values that have been validated.
313
     *
314
     * @throws ValidationException
315
     */
316
    public function validated(): array
317
    {
318
        $this->passes();
319
320
        $this->throwIf($this->invalid());
321
322
        return $this->validation->getValidatedData();
323
    }
324
325
    /**
326
     * Returns data that was invalid.
327
     */
328
    public function invalid(): array
329
    {
330
        if (! $this->validation) {
331
            return [];
332
        }
333
334
        return $this->validation->getInvalidData();
335
    }
336
337
    /**
338
     * Get a validated input container for the validated input.
339
     *
340
     * @return array|ValidatedInput
341
     */
342
    public function safe(?array $keys = null)
343
    {
344
        $input = new ValidatedInput($this->validated());
345
346
        return is_array($keys) ? $input->only($keys) : $input;
347
    }
348
349
    /**
350
     * Recover errors that occurred during validation
351
     */
352
    public function errors(): ErrorBag
353
    {
354
        if (! $this->validation) {
355 2
            throw new RuntimeException();
356
        }
357
358 2
        return ErrorBag::fromBase($this->validation->errors());
359
    }
360
361
    /**
362
     * Definition of the aliases of the data to be validated
363
     */
364
    public function alias(array|string $key, string $value = ''): self
365
    {
366
        if (is_array($key)) {
0 ignored issues
show
The condition is_array($key) is always true.
Loading history...
367 132
            $this->aliases = array_merge($this->aliases, $key);
368
        } elseif ('' === $value) {
369
            throw new InvalidArgumentException('Valeur non valide fournie');
370
        } else {
371
            $this->aliases[$key] = $value;
372
        }
373
374 132
        return $this;
375
    }
376
377
    /**
378
     * Definition of the data to be validated
379
     */
380
    public function data(array $data): self
381
    {
382 132
        $this->data = $data;
383
384 132
        return $this;
385
    }
386
387
    /**
388
     * Definition of locale of error messages
389
     */
390
    public function locale(string $locale): self
391
    {
392
        $this->locale = $locale;
393
394
        return $this;
395
    }
396
397
    /**
398
     * Definition of validation rules
399
     */
400
    public function rules(array $rules): self
401
    {
402
        foreach ($rules as &$value) {
403
            if ($value instanceof Rule) {
404 14
                $value = [$value];
405
            }
406
        }
407
408 132
        $this->rules = array_merge($this->rules, $rules);
409
410 132
        return $this;
411
    }
412
413
    /**
414
     * Definition of a validation rule
415
     */
416
    public function rule(string $key, mixed $rule): self
417
    {
418
        return $this->rules([$key => $rule]);
419
    }
420
421
    /**
422
     * Definition of messages associated with each validation rule
423
     */
424
    public function messages(array $messages): self
425
    {
426 132
        $this->messages = array_merge($this->messages, $messages);
427
428 132
        return $this;
429
    }
430
431
    /**
432
     * Definition of a message associated with a validation rule
433
     */
434
    public function message(string $key, string $message): self
435
    {
436
        return $this->messages([$key => $message]);
437
    }
438
439
    /**
440
     * Definition of rule translations
441
     */
442
    public function translations(array $translations): self
443
    {
444
        $this->translations = array_merge($this->translations, $translations);
445
446
        return $this;
447
    }
448
449
    /**
450
     * Definition of the translation of a rule
451
     */
452
    public function translation(string $key, string $value): self
453
    {
454
        if ('' === $value) {
455
            throw new InvalidArgumentException('Invalid value provided');
456
        }
457
458
        return $this->translations([$key => $value]);
459
    }
460
461
    /**
462
     * Register validation rules
463
     */
464
    protected function registerRules(array $rules): void
465
    {
466
        foreach ($rules as $key => $value) {
467
            if (is_int($key)) {
468 132
                $name = $value::name();
469 132
                $rule = $value;
470
            } else {
471
                $name = $value;
472
                $rule = $key;
473
            }
474
475 132
            $this->addValidator($name, new $rule());
476
        }
477
    }
478
479
    /**
480
     * Magic method to create a rule
481
     *
482
     * @throws ValidationException
483
     */
484
    public function __invoke(string $rule): Rule
485
    {
486
        try {
487
            return $this->validator->__invoke(...func_get_args());
488
        } catch (RuleNotFoundException $e) {
489
            throw ValidationException::ruleNotFound($rule);
490
        }
491
    }
492
493
    /**
494
     * Lève l'exception donnée si la condition donnée est vraie.
495
     *
496
     * @throws Throwable
497
     */
498
    private function throwIf(mixed $condition)
499
    {
500
        if (is_a($this->exception, ValidationException::class, true)) {
501
            Helpers::throwIf($condition, $this->exception, '', $this);
502
        } else {
503
            Helpers::throwIf($condition, $this->exception, ValidationException::summarize($this));
504
        }
505
    }
506
}
507