├── resources └── lang │ ├── en │ └── validation.php │ └── de │ └── validation.php ├── phpunit-10.xml ├── src ├── ModelValidationServiceProvider.php └── Rules │ ├── ExistsEloquent.php │ └── UniqueEloquent.php ├── license.md ├── composer.json └── readme.md /resources/lang/en/validation.php: -------------------------------------------------------------------------------- 1 | 'The resource does not exist.', 5 | 'unique_model' => 'The resource already exists.', 6 | ]; 7 | -------------------------------------------------------------------------------- /resources/lang/de/validation.php: -------------------------------------------------------------------------------- 1 | 'Die Ressource existiert nicht.', 5 | 'unique_model' => 'Die Ressource existiert bereits.', 6 | ]; 7 | -------------------------------------------------------------------------------- /phpunit-10.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | src/ 9 | 10 | 11 | 12 | 13 | tests/Feature 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/ModelValidationServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishes([ 27 | __DIR__ . '/../resources/lang' => resource_path('lang/vendor/modelValidationRules'), 28 | ]); 29 | $this->loadTranslationsFrom(__DIR__ . '/../resources/lang/', 'modelValidationRules'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright © `2023` `korridor` 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the “Software”), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "korridor/laravel-model-validation-rules", 3 | "description": "A laravel validation rule that uses eloquent to validate if a model exists", 4 | "keywords": [ 5 | "validation", 6 | "laravel", 7 | "rule", 8 | "model", 9 | "exist", 10 | "eloquent" 11 | ], 12 | "homepage": "https://github.com/korridor/laravel-model-validation-rules", 13 | "authors": [ 14 | { 15 | "name": "korridor", 16 | "email": "26689068+korridor@users.noreply.github.com" 17 | } 18 | ], 19 | "license": "MIT", 20 | "require": { 21 | "php": ">=8.1", 22 | "illuminate/support": "^10|^11|^12.0", 23 | "illuminate/database": "^10|^11|^12.0" 24 | }, 25 | "require-dev": { 26 | "orchestra/testbench": "^8.0|^9.0|^10.0", 27 | "phpunit/phpunit": "^10|^11.5.3", 28 | "friendsofphp/php-cs-fixer": "^3.6", 29 | "squizlabs/php_codesniffer": "^3.5" 30 | }, 31 | "autoload": { 32 | "psr-4": { 33 | "Korridor\\LaravelModelValidationRules\\": "src" 34 | } 35 | }, 36 | "autoload-dev": { 37 | "psr-4": { 38 | "Korridor\\LaravelModelValidationRules\\Tests\\": "tests/" 39 | } 40 | }, 41 | "scripts": { 42 | "test": "vendor/bin/phpunit", 43 | "test-coverage": "XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-text --coverage-html coverage", 44 | "fix": "./vendor/bin/php-cs-fixer fix", 45 | "lint": "./vendor/bin/phpcs --extensions=php" 46 | }, 47 | "extra": { 48 | "laravel": { 49 | "providers": [ 50 | "Korridor\\LaravelModelValidationRules\\ModelValidationServiceProvider" 51 | ] 52 | } 53 | }, 54 | "config": { 55 | "sort-packages": true 56 | }, 57 | "minimum-stability": "dev", 58 | "prefer-stable": true 59 | } 60 | -------------------------------------------------------------------------------- /src/Rules/ExistsEloquent.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | private string $model; 21 | 22 | /** 23 | * Relevant key in the model. 24 | * 25 | * @var string|null 26 | */ 27 | private ?string $key; 28 | 29 | /** 30 | * Closure that can extend the eloquent builder. 31 | * 32 | * @var Closure|null 33 | */ 34 | private ?Closure $builderClosure; 35 | 36 | /** 37 | * Custom validation message. 38 | * 39 | * @var string|null 40 | */ 41 | private ?string $customMessage = null; 42 | 43 | /** 44 | * Custom translation key for message. 45 | * 46 | * @var string|null 47 | */ 48 | private ?string $customMessageTranslationKey = null; 49 | 50 | /** 51 | * Include soft deleted models in the query. 52 | * 53 | * @var bool 54 | */ 55 | private bool $includeSoftDeleted = false; 56 | 57 | /** 58 | * @var bool Whether the key field is of type UUID 59 | */ 60 | private bool $isFieldUuid = false; 61 | 62 | /** 63 | * Create a new rule instance. 64 | * 65 | * @param class-string $model Class name of model 66 | * @param string|null $key Relevant key in the model 67 | * @param Closure|null $builderClosure Closure that can extend the eloquent builder 68 | */ 69 | public function __construct(string $model, ?string $key = null, ?Closure $builderClosure = null) 70 | { 71 | $this->model = $model; 72 | $this->key = $key; 73 | $this->setBuilderClosure($builderClosure); 74 | } 75 | 76 | /** 77 | * Create a new rule instance. 78 | * 79 | * @param class-string $model Class name of model 80 | * @param string|null $key Relevant key in the model 81 | * @param Closure|null $builderClosure Closure that can extend the eloquent builder 82 | */ 83 | public static function make(string $model, ?string $key = null, ?Closure $builderClosure = null): self 84 | { 85 | return new self($model, $key, $builderClosure); 86 | } 87 | 88 | /** 89 | * Set a custom validation message. 90 | * 91 | * @param string $message 92 | * @return $this 93 | */ 94 | public function withMessage(string $message): self 95 | { 96 | $this->customMessage = $message; 97 | 98 | return $this; 99 | } 100 | 101 | /** 102 | * Set a translated custom validation message. 103 | * 104 | * @param string $translationKey 105 | * @return $this 106 | */ 107 | public function withCustomTranslation(string $translationKey): self 108 | { 109 | $this->customMessageTranslationKey = $translationKey; 110 | 111 | return $this; 112 | } 113 | 114 | /** 115 | * The field has the data type UUID. 116 | * If the field is not a UUID, the validation will fail, before the query is executed. 117 | * This is useful for example for Postgres databases where queries fail if a field with UUID data type is queried with a non-UUID value. 118 | * 119 | * @return $this 120 | */ 121 | public function uuid(): self 122 | { 123 | $this->isFieldUuid = true; 124 | 125 | return $this; 126 | } 127 | 128 | /** 129 | * Determine if the validation rule passes. 130 | * 131 | * @param string $attribute 132 | * @param mixed $value 133 | * @param Closure $fail 134 | * 135 | * @return void 136 | */ 137 | public function validate(string $attribute, mixed $value, Closure $fail): void 138 | { 139 | if ($this->isFieldUuid) { 140 | if (!is_string($value) || !Str::isUuid($value)) { 141 | $this->fail($attribute, $value, $fail); 142 | return; 143 | } 144 | } 145 | /** @var Model|Builder $builder */ 146 | $builder = new $this->model(); 147 | $modelKeyName = $builder->getKeyName(); 148 | if (null === $this->key) { 149 | $builder = $builder->where($modelKeyName, $value); 150 | } else { 151 | $builder = $builder->where($this->key, $value); 152 | } 153 | if (null !== $this->builderClosure) { 154 | $builderClosure = $this->builderClosure; 155 | $builder = $builderClosure($builder); 156 | } 157 | 158 | if ($this->includeSoftDeleted) { 159 | $builder = $builder->withTrashed(); 160 | } 161 | 162 | if ($builder->doesntExist()) { 163 | $this->fail($attribute, $value, $fail); 164 | return; 165 | } 166 | } 167 | 168 | private function fail(string $attribute, mixed $value, Closure $fail): void 169 | { 170 | if ($this->customMessage !== null) { 171 | $fail($this->customMessage); 172 | } else { 173 | $fail($this->customMessageTranslationKey ?? 'modelValidationRules::validation.exists_model')->translate([ 174 | 'attribute' => $attribute, 175 | 'model' => strtolower(class_basename($this->model)), 176 | 'value' => $value, 177 | ]); 178 | } 179 | } 180 | 181 | /** 182 | * @param Closure|null $builderClosure 183 | */ 184 | public function setBuilderClosure(?Closure $builderClosure): void 185 | { 186 | $this->builderClosure = $builderClosure; 187 | } 188 | 189 | /** 190 | * @param Closure $builderClosure 191 | * @return $this 192 | */ 193 | public function query(Closure $builderClosure): self 194 | { 195 | $this->setBuilderClosure($builderClosure); 196 | 197 | return $this; 198 | } 199 | 200 | /** 201 | * Activate or deactivate including soft deleted models in the query. 202 | * 203 | * @param bool $includeSoftDeleted 204 | * @return void 205 | */ 206 | public function setIncludeSoftDeleted(bool $includeSoftDeleted): void 207 | { 208 | $this->includeSoftDeleted = $includeSoftDeleted; 209 | } 210 | 211 | /** 212 | * Activate including soft deleted models in the query. 213 | * @return $this 214 | */ 215 | public function includeSoftDeleted(): self 216 | { 217 | $this->setIncludeSoftDeleted(true); 218 | 219 | return $this; 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/Rules/UniqueEloquent.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | private string $model; 21 | 22 | /** 23 | * Relevant key in the model. 24 | * 25 | * @var string|null 26 | */ 27 | private ?string $key; 28 | 29 | /** 30 | * Closure that can extend the eloquent builder 31 | * 32 | * @var Closure|null 33 | */ 34 | private ?Closure $builderClosure; 35 | 36 | /** 37 | * @var mixed 38 | */ 39 | private mixed $ignoreId = null; 40 | 41 | /** 42 | * @var string|null 43 | */ 44 | private ?string $ignoreColumn = null; 45 | 46 | /** 47 | * Custom validation message. 48 | * 49 | * @var string|null 50 | */ 51 | private ?string $customMessage = null; 52 | 53 | /** 54 | * Translation key for custom validation message. 55 | * 56 | * @var string|null 57 | */ 58 | private ?string $customMessageTranslationKey = null; 59 | 60 | /** 61 | * Include soft deleted models in the query. 62 | * 63 | * @var bool 64 | */ 65 | private bool $includeSoftDeleted = false; 66 | 67 | /** 68 | * @var bool Whether the ID is a UUID 69 | */ 70 | private bool $isFieldUuid = false; 71 | 72 | /** 73 | * UniqueEloquent constructor. 74 | * 75 | * @param class-string $model Class name of model. 76 | * @param string|null $key Relevant key in the model. 77 | * @param Closure|null $builderClosure Closure that can extend the eloquent builder 78 | */ 79 | public function __construct(string $model, ?string $key = null, ?Closure $builderClosure = null) 80 | { 81 | $this->model = $model; 82 | $this->key = $key; 83 | $this->setBuilderClosure($builderClosure); 84 | } 85 | 86 | /** 87 | * @param class-string $model Class name of model. 88 | * @param string|null $key Relevant key in the model. 89 | * @param Closure|null $builderClosure Closure that can extend the eloquent builder 90 | */ 91 | public static function make(string $model, ?string $key = null, ?Closure $builderClosure = null): self 92 | { 93 | return new self($model, $key, $builderClosure); 94 | } 95 | 96 | /** 97 | * Determine if the validation rule passes. 98 | * 99 | * @param string $attribute 100 | * @param mixed $value 101 | * @param Closure $fail 102 | * 103 | * @return void 104 | */ 105 | public function validate(string $attribute, mixed $value, Closure $fail): void 106 | { 107 | if ($this->isFieldUuid) { 108 | if (!is_string($value) || !Str::isUuid($value)) { 109 | return; 110 | } 111 | } 112 | /** @var Model|Builder $builder */ 113 | $builder = new $this->model(); 114 | $modelKeyName = $builder->getKeyName(); 115 | $builder = $builder->where(null === $this->key ? $modelKeyName : $this->key, $value); 116 | if (null !== $this->builderClosure) { 117 | $builderClosure = $this->builderClosure; 118 | $builder = $builderClosure($builder); 119 | } 120 | if (null !== $this->ignoreId) { 121 | $builder = $builder->where( 122 | null === $this->ignoreColumn ? $modelKeyName : $this->ignoreColumn, 123 | '!=', 124 | $this->ignoreId 125 | ); 126 | } 127 | 128 | if ($this->includeSoftDeleted) { 129 | $builder = $builder->withTrashed(); 130 | } 131 | 132 | if ($builder->exists()) { 133 | if ($this->customMessage !== null) { 134 | $fail($this->customMessage); 135 | } else { 136 | $fail($this->customMessageTranslationKey ?? 'modelValidationRules::validation.unique_model') 137 | ->translate([ 138 | 'attribute' => $attribute, 139 | 'model' => strtolower(class_basename($this->model)), 140 | 'value' => $value, 141 | ]); 142 | } 143 | } 144 | } 145 | 146 | /** 147 | * Set a custom validation message. 148 | * 149 | * @param string $message 150 | * @return $this 151 | */ 152 | public function withMessage(string $message): self 153 | { 154 | $this->customMessage = $message; 155 | 156 | return $this; 157 | } 158 | 159 | /** 160 | * Set a translated custom validation message. 161 | * 162 | * @param string $translationKey 163 | * @return $this 164 | */ 165 | public function withCustomTranslation(string $translationKey): self 166 | { 167 | $this->customMessageTranslationKey = $translationKey; 168 | 169 | return $this; 170 | } 171 | 172 | /** 173 | * Set a closure that can extend the eloquent builder. 174 | * 175 | * @param Closure|null $builderClosure 176 | */ 177 | public function setBuilderClosure(?Closure $builderClosure): void 178 | { 179 | $this->builderClosure = $builderClosure; 180 | } 181 | 182 | /** 183 | * @param Closure $builderClosure 184 | * @return $this 185 | */ 186 | public function query(Closure $builderClosure): self 187 | { 188 | $this->setBuilderClosure($builderClosure); 189 | 190 | return $this; 191 | } 192 | 193 | /** 194 | * @param mixed $id 195 | * @param string|null $column 196 | */ 197 | public function setIgnore(mixed $id, ?string $column = null): void 198 | { 199 | $this->ignoreId = $id; 200 | $this->ignoreColumn = $column; 201 | } 202 | 203 | /** 204 | * @param mixed $id 205 | * @param string|null $column 206 | * @return UniqueEloquent 207 | */ 208 | public function ignore(mixed $id, ?string $column = null): self 209 | { 210 | $this->setIgnore($id, $column); 211 | 212 | return $this; 213 | } 214 | 215 | /** 216 | * Activate or deactivate including soft deleted models in the query. 217 | * 218 | * @param bool $includeSoftDeleted 219 | * @return void 220 | */ 221 | public function setIncludeSoftDeleted(bool $includeSoftDeleted): void 222 | { 223 | $this->includeSoftDeleted = $includeSoftDeleted; 224 | } 225 | 226 | /** 227 | * The field has the data type UUID. 228 | * If a value is not a UUID, the validation will be skipped. 229 | * This is useful for example for Postgres databases where queries fail if a field with UUID data type is queried with a non-UUID value. 230 | * 231 | * @return $this 232 | */ 233 | public function uuid(): self 234 | { 235 | $this->isFieldUuid = true; 236 | 237 | return $this; 238 | } 239 | 240 | /** 241 | * Activate including soft deleted models in the query. 242 | * 243 | * @return $this 244 | */ 245 | public function includeSoftDeleted(): self 246 | { 247 | $this->setIncludeSoftDeleted(true); 248 | 249 | return $this; 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Laravel model validation rules 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/korridor/laravel-model-validation-rules?style=flat-square)](https://packagist.org/packages/korridor/laravel-model-validation-rules) 4 | [![License](https://img.shields.io/packagist/l/korridor/laravel-model-validation-rules?style=flat-square)](license.md) 5 | [![Supported PHP versions](https://img.shields.io/packagist/php-v/korridor/laravel-model-validation-rules?style=flat-square)](https://packagist.org/packages/korridor/laravel-model-validation-rules) 6 | [![GitHub Workflow Lint](https://img.shields.io/github/actions/workflow/status/korridor/laravel-model-validation-rules/lint.yml?label=lint&style=flat-square)](https://github.com/korridor/laravel-model-validation-rules/actions/workflows/lint.yml) 7 | [![GitHub Workflow Tests](https://img.shields.io/github/actions/workflow/status/korridor/laravel-model-validation-rules/unittests.yml?label=tests&style=flat-square)](https://github.com/korridor/laravel-model-validation-rules/actions/workflows/unittests.yml) 8 | [![Codecov](https://img.shields.io/codecov/c/github/korridor/laravel-model-validation-rules?style=flat-square)](https://codecov.io/gh/korridor/laravel-model-validation-rules) 9 | 10 | This package is an alternative to the Laravel built-in validation rules `exists` and `unique`. 11 | It uses Eloquent models instead of directly querying the database. 12 | 13 | **Advantages** 14 | - The rule can be easily extended with the Eloquent builder. (scopes etc.) 15 | - Soft deletes are working out of the box. 16 | - Logic implemented into the models work in the validation as well. (multi tenancy system, etc.) 17 | 18 | > [!NOTE] 19 | > Check out **solidtime - The modern Open Source Time-Tracker** at [solidtime.io](https://www.solidtime.io) 20 | 21 | ## Installation 22 | 23 | You can install the package via composer with following command: 24 | 25 | ```bash 26 | composer require korridor/laravel-model-validation-rules 27 | ``` 28 | 29 | If you want to use this package with older Laravel/PHP version please install the 2.1.* version. 30 | 31 | ```bash 32 | composer require korridor/laravel-model-validation-rules "^2.1" 33 | ``` 34 | 35 | ### Requirements 36 | 37 | This package is tested for the following Laravel and PHP versions: 38 | 39 | - 12.* (PHP 8.2, 8.3, 8.4) 40 | - 11.* (PHP 8.2, 8.3, 8.4) 41 | - 10.* (PHP 8.1, 8.2, 8.3) 42 | 43 | ## Usage examples 44 | 45 | **PostStoreRequest** 46 | 47 | ```php 48 | use Korridor\LaravelModelValidationRules\Rules\UniqueEloquent; 49 | use Korridor\LaravelModelValidationRules\Rules\ExistsEloquent; 50 | // ... 51 | public function rules(): array 52 | { 53 | $postId = $this->post->id; 54 | 55 | return [ 56 | 'username' => [new UniqueEloquent(User::class, 'username')], 57 | 'title' => ['string'], 58 | 'content' => ['string'], 59 | 'comments.*.id' => [ 60 | 'nullable', 61 | new ExistsEloquent(Comment::class, null, function (Builder $builder) use ($postId) { 62 | return $builder->where('post_id', $postId); 63 | }), 64 | ], 65 | 'comments.*.content' => ['string'] 66 | ]; 67 | } 68 | ``` 69 | 70 | **PostUpdateRequest** 71 | 72 | ```php 73 | use Korridor\LaravelModelValidationRules\Rules\UniqueEloquent; 74 | use Korridor\LaravelModelValidationRules\Rules\ExistsEloquent; 75 | // ... 76 | public function rules(): array 77 | { 78 | $postId = $this->post->id; 79 | 80 | return [ 81 | 'id' => [new ExistsEloquent(Post::class)], 82 | 'username' => [(new UniqueEloquent(User::class, 'username'))->ignore($postId)], 83 | 'title' => ['string'], 84 | 'content' => ['string'], 85 | 'comments.*.id' => [ 86 | 'nullable', 87 | new ExistsEloquent(Comment::class, null, function (Builder $builder) use ($postId) { 88 | return $builder->where('post_id', $postId); 89 | }), 90 | ], 91 | 'comments.*.content' => ['string'] 92 | ]; 93 | } 94 | ``` 95 | 96 | ### Custom validation message 97 | 98 | If you want to change the validation message for one specific case, you can use the `withMessage(...)` function to add a custom validation message. 99 | With `withCustomTranslation(...)` you can set a custom translation key for the validation message. 100 | As described in detail in the next example ([Customize default validation message](#customize-default-validation-message)), it is possible to use `:attribute`, `:model` and `:value` in the translation. 101 | 102 | ```php 103 | use Korridor\LaravelModelValidationRules\Rules\UniqueEloquent; 104 | use Korridor\LaravelModelValidationRules\Rules\ExistsEloquent; 105 | // ... 106 | public function rules(): array 107 | { 108 | $postId = $this->post->id; 109 | 110 | return [ 111 | 'id' => [(new ExistsEloquent(Post::class))->withMessage('The ID already exists.')], 112 | 'username' => [ 113 | (new UniqueEloquent(User::class, 'username')) 114 | ->ignore($postId) 115 | ->withCustomTranslation('validation.custom.username.unique_eloquent') 116 | ], 117 | 'title' => ['string'], 118 | 'content' => ['string'], 119 | 'comments.*.id' => [ 120 | 'nullable', 121 | new ExistsEloquent(Comment::class, null, function (Builder $builder) use ($postId) { 122 | return $builder->where('post_id', $postId); 123 | }), 124 | ], 125 | 'comments.*.content' => ['string'] 126 | ]; 127 | } 128 | ``` 129 | 130 | ### Customize default validation message 131 | 132 | If you want to customize the translations of the default validation errors you can publish the translations 133 | of the package to the `resources/lang/vendor/modelValidationRules` folder. 134 | 135 | ```bash 136 | php artisan vendor:publish --provider="Korridor\LaravelModelValidationRules\ModelValidationServiceProvider" 137 | ``` 138 | 139 | You can use the following attributes in the validation message: 140 | 141 | - `attribute` 142 | - `model` 143 | - `value` 144 | 145 | ```php 146 | return [ 147 | 'exists_model' => 'A :model with the :attribute ":value" does not exist.', 148 | 'unique_model' => 'A :model with the :attribute ":value" already exists.', 149 | ]; 150 | ``` 151 | 152 | Example outputs would be: 153 | 154 | - `A user with the id "2" does not exist.` 155 | - `A user with the id "2" already exists.` 156 | 157 | ## Contributing 158 | 159 | I am open for suggestions and contributions. Just create an issue or a pull request. 160 | 161 | ### Local docker environment 162 | 163 | The `docker` folder contains a local docker environment for development. 164 | The docker workspace has composer and xdebug installed. 165 | 166 | ```bash 167 | docker-compose run workspace bash 168 | ``` 169 | 170 | ### Testing 171 | 172 | The `composer test` command runs all tests with [phpunit](https://phpunit.de/). 173 | The `composer test-coverage` command runs all tests with phpunit and creates a coverage report into the `coverage` folder. 174 | 175 | ### Codeformatting/Linting 176 | 177 | The `composer fix` command formats the code with [php-cs-fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer). 178 | The `composer lint` command checks the code with [phpcs](https://github.com/squizlabs/PHP_CodeSniffer). 179 | 180 | ## Credits 181 | 182 | The structure of the repository and the TestClass is inspired by the 183 | project [laravel-validation-rules](https://github.com/spatie/laravel-validation-rules) by [spatie](https://github.com/spatie). 184 | 185 | ## License 186 | 187 | This package is licensed under the MIT License (MIT). Please see [license file](license.md) for more information. 188 | --------------------------------------------------------------------------------