├── .github ├── FUNDING.yml └── workflows │ ├── pint.yml │ ├── run-tests.yml │ └── update-changelog.yml ├── .phpunit.cache └── test-results ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── UPGRADING.md ├── composer.json ├── database └── factories │ └── UserFactory.php ├── resources └── lang │ └── en │ └── messages.php └── src ├── Rules ├── Authorized.php ├── CountryCode.php ├── Currency.php ├── Delimited.php ├── Enum.php └── ModelsExist.php └── ValidationRulesServiceProvider.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: spatie 2 | -------------------------------------------------------------------------------- /.github/workflows/pint.yml: -------------------------------------------------------------------------------- 1 | name: Check & fix styling 2 | 3 | on: [push] 4 | 5 | jobs: 6 | php-cs-fixer: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Checkout code 11 | uses: actions/checkout@v3 12 | with: 13 | ref: ${{ github.head_ref }} 14 | 15 | - name: Fix styling issues 16 | uses: aglipanci/laravel-pint-action@0.1.0 17 | 18 | - name: Commit changes 19 | uses: stefanzweifel/git-auto-commit-action@v4 20 | with: 21 | commit_message: Fix styling 22 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: run-tests 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | test: 9 | runs-on: ${{ matrix.os }} 10 | 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | os: [ubuntu-latest] 15 | php: [8.4, 8.3, 8.2] 16 | laravel: ['10.*', '11.*', '12.*'] 17 | dependency-version: [prefer-lowest, prefer-stable] 18 | include: 19 | - laravel: 10.* 20 | testbench: 8.* 21 | - laravel: 11.* 22 | testbench: 9.* 23 | - laravel: 12.* 24 | testbench: 10.* 25 | 26 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} - ${{ matrix.os }} 27 | 28 | steps: 29 | - name: Checkout code 30 | uses: actions/checkout@v3 31 | 32 | - name: Setup PHP 33 | uses: shivammathur/setup-php@v2 34 | with: 35 | php-version: ${{ matrix.php }} 36 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick 37 | coverage: none 38 | 39 | - name: Setup problem matchers 40 | run: | 41 | echo "::add-matcher::${{ runner.tool_cache }}/php.json" 42 | echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" 43 | 44 | - name: Install dependencies 45 | run: | 46 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update 47 | composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction 48 | 49 | - name: Execute tests 50 | run: vendor/bin/pest --ci 51 | -------------------------------------------------------------------------------- /.github/workflows/update-changelog.yml: -------------------------------------------------------------------------------- 1 | name: "Update Changelog" 2 | 3 | on: 4 | release: 5 | types: [released] 6 | 7 | jobs: 8 | update: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v2 14 | with: 15 | ref: main 16 | 17 | - name: Update Changelog 18 | uses: stefanzweifel/changelog-updater-action@v1 19 | with: 20 | latest-version: ${{ github.event.release.name }} 21 | release-notes: ${{ github.event.release.body }} 22 | 23 | - name: Commit updated CHANGELOG 24 | uses: stefanzweifel/git-auto-commit-action@v4 25 | with: 26 | branch: main 27 | commit_message: Update CHANGELOG 28 | file_pattern: CHANGELOG.md 29 | -------------------------------------------------------------------------------- /.phpunit.cache/test-results: -------------------------------------------------------------------------------- 1 | {"version":"pest_3.7.4","defects":[],"times":{"P\\Tests\\Rules\\AuthorizedTest::__pest_evaluable_it_will_return_true_if_the_gate_returns_true_for_the_given_ability_name":0.099,"P\\Tests\\Rules\\AuthorizedTest::__pest_evaluable_it_will_return_false_if_none_is_logged_in":0.001,"P\\Tests\\Rules\\AuthorizedTest::__pest_evaluable_it_will_return_false_if_the_model_is_not_found":0.013,"P\\Tests\\Rules\\AuthorizedTest::__pest_evaluable_it_will_return_false_if_the_gate_returns_false":0.001,"P\\Tests\\Rules\\AuthorizedTest::__pest_evaluable_it_passes_attribute_ability_and_class_name_to_the_validation_message":0.001,"P\\Tests\\Rules\\AuthorizedTest::__pest_evaluable_it_will_pass_when_using_alternate_route_key_name":0.001,"P\\Tests\\Rules\\AuthorizedTest::__pest_evaluable_it_will_pass_when_using_alternate_column_name":0.001,"P\\Tests\\Rules\\AuthorizedTest::__pest_evaluable_it_will_pass_if_alternate_auth_guard_is_specified":0.001,"P\\Tests\\Rules\\AuthorizedTest::__pest_evaluable_it_will_return_false_if_auth_guard_is_incorrect":0.001,"P\\Tests\\Rules\\CurrencyTest::__pest_evaluable_valid_currency_passes":0.001,"P\\Tests\\Rules\\CurrencyTest::__pest_evaluable_invalid_currency_fails":0,"P\\Tests\\Rules\\CurrencyTest::__pest_evaluable_null_currency_fails":0,"P\\Tests\\Rules\\CurrencyTest::__pest_evaluable_empty_currency_fails":0,"P\\Tests\\Rules\\CurrencyTest::__pest_evaluable_nullable_field":0.003,"P\\Tests\\Rules\\CurrencyTest::__pest_evaluable_empty_field":0,"P\\Tests\\Rules\\CurrencyTest::__pest_evaluable_required_field":0.001,"P\\Tests\\Rules\\DelimitedTest::__pest_evaluable_it_can_use_custom_errors_messages":0,"P\\Tests\\Rules\\DelimitedTest::__pest_evaluable_it_can_validate_comma_separated_email_addresses":0.006,"P\\Tests\\Rules\\DelimitedTest::__pest_evaluable_it_can_validate_a_minimum_of_valid_addresses":0.001,"P\\Tests\\Rules\\DelimitedTest::__pest_evaluable_it_can_validate_a_maximum_amount_of_emailaddress":0.001,"P\\Tests\\Rules\\DelimitedTest::__pest_evaluable_it_will_fail_if_not_all_email_addresses_are_unique":0,"P\\Tests\\Rules\\DelimitedTest::__pest_evaluable_it_can_allow_duplicates":0,"P\\Tests\\Rules\\DelimitedTest::__pest_evaluable_it_can_use_a_custom_separator":0.001,"P\\Tests\\Rules\\DelimitedTest::__pest_evaluable_it_can_skip_trimming_items":0.001,"P\\Tests\\Rules\\DelimitedTest::__pest_evaluable_it_can_treat_input_as_csv":0.003,"P\\Tests\\Rules\\DelimitedTest::__pest_evaluable_it_can_accept_a_rule_as_an_array":0,"P\\Tests\\Rules\\DelimitedTest::__pest_evaluable_it_can_use_composite_rules":0.001,"P\\Tests\\Rules\\DelimitedTest::__pest_evaluable_it_can_accept_chained_properties":0.001,"P\\Tests\\Rules\\DelimitedTest::__pest_evaluable_it_can_accept_another_rule":0.001,"P\\Tests\\Rules\\DelimitedTest::__pest_evaluable_it_can_handle_numeric_values_properly":0,"P\\Tests\\Rules\\DelimitedTest::__pest_evaluable_it_can_validate_nested_attributes_properly":0,"P\\Tests\\Rules\\DelimitedTest::__pest_evaluable_it_can_handle_custom_error_messages":0.001,"P\\Tests\\Rules\\DelimitedTest::__pest_evaluable_it_can_only_validate_string_values":0,"P\\Tests\\Rules\\EnumTest::__pest_evaluable_myclabs_it_will_return_true_for_a_value_that_is_part_of_the_enum":0,"P\\Tests\\Rules\\EnumTest::__pest_evaluable_myclabs_it_passes_attribute_and_valid_values_to_the_validation_message":0,"P\\Tests\\Rules\\EnumTest::__pest_evaluable_spatie_it_will_return_true_for_a_value_that_is_part_of_the_enum":0,"P\\Tests\\Rules\\EnumTest::__pest_evaluable_spatie_it_passes_attribute_and_valid_values_to_the_validation_message":0,"P\\Tests\\Rules\\ModelsExistTest::__pest_evaluable_it_will_return_true_if_all_model_ids_exist":0.002,"P\\Tests\\Rules\\ModelsExistTest::__pest_evaluable_it_can_validate_existence_of_models_by_column":0.001,"P\\Tests\\Rules\\ModelsExistTest::__pest_evaluable_it_passes_relevant_data_to_the_validation_message":0}} -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `laravel-validation-rules` will be documented in this file 4 | 5 | ## 3.4.3 - 2025-02-25 6 | 7 | ### What's Changed 8 | 9 | * Add delimiter string erorr message by @carestad in https://github.com/spatie/laravel-validation-rules/pull/76 10 | 11 | **Full Changelog**: https://github.com/spatie/laravel-validation-rules/compare/3.4.2...3.4.3 12 | 13 | ## 3.4.2 - 2025-02-24 14 | 15 | ### What's Changed 16 | 17 | * Only allow string values to be processed by @carestad in https://github.com/spatie/laravel-validation-rules/pull/74 18 | 19 | ### New Contributors 20 | 21 | * @carestad made their first contribution in https://github.com/spatie/laravel-validation-rules/pull/74 22 | 23 | **Full Changelog**: https://github.com/spatie/laravel-validation-rules/compare/3.4.1...3.4.2 24 | 25 | ## 3.4.1 - 2025-02-17 26 | 27 | ### What's Changed 28 | 29 | * Fixed typo in README.md by @snipe in https://github.com/spatie/laravel-validation-rules/pull/71 30 | * Laravel 12.x Compatibility by @laravel-shift in https://github.com/spatie/laravel-validation-rules/pull/72 31 | 32 | ### New Contributors 33 | 34 | * @snipe made their first contribution in https://github.com/spatie/laravel-validation-rules/pull/71 35 | 36 | **Full Changelog**: https://github.com/spatie/laravel-validation-rules/compare/3.4.0...3.4.1 37 | 38 | ## 3.4.0 - 2024-03-02 39 | 40 | ### What's Changed 41 | 42 | * Fix test badge by @erikn69 in https://github.com/spatie/laravel-validation-rules/pull/67 43 | * Laravel 11.x Compatibility by @laravel-shift in https://github.com/spatie/laravel-validation-rules/pull/70 44 | 45 | ### New Contributors 46 | 47 | * @erikn69 made their first contribution in https://github.com/spatie/laravel-validation-rules/pull/67 48 | 49 | **Full Changelog**: https://github.com/spatie/laravel-validation-rules/compare/3.3.1...3.4.0 50 | 51 | ## 3.3.1 - 2023-05-29 52 | 53 | ### What's Changed 54 | 55 | - Convert test to pest by @lloricode in https://github.com/spatie/laravel-validation-rules/pull/65 56 | - Add parameter column on `Authorized`, use php8 syntax by @lloricode in https://github.com/spatie/laravel-validation-rules/pull/64 57 | - Use pest on composer test script by @lloricode in https://github.com/spatie/laravel-validation-rules/pull/66 58 | 59 | ### New Contributors 60 | 61 | - @lloricode made their first contribution in https://github.com/spatie/laravel-validation-rules/pull/65 62 | 63 | **Full Changelog**: https://github.com/spatie/laravel-validation-rules/compare/3.3.0...3.3.1 64 | 65 | ## 3.3.0 - 2023-05-09 66 | 67 | ### What's Changed 68 | 69 | - use str_getcsv instead of explode in Delimited rule by @ragulka in https://github.com/spatie/laravel-validation-rules/pull/62 70 | 71 | ### New Contributors 72 | 73 | - @ragulka made their first contribution in https://github.com/spatie/laravel-validation-rules/pull/62 74 | 75 | **Full Changelog**: https://github.com/spatie/laravel-validation-rules/compare/3.2.2...3.3.0 76 | 77 | ## 3.2.2 - 2023-01-25 78 | 79 | ### What's Changed 80 | 81 | - Add PHP 8.2 Support by @patinthehat in https://github.com/spatie/laravel-validation-rules/pull/57 82 | - Install Laravel Pint by @imdhemy in https://github.com/spatie/laravel-validation-rules/pull/59 83 | 84 | ### New Contributors 85 | 86 | - @patinthehat made their first contribution in https://github.com/spatie/laravel-validation-rules/pull/57 87 | - @imdhemy made their first contribution in https://github.com/spatie/laravel-validation-rules/pull/59 88 | 89 | **Full Changelog**: https://github.com/spatie/laravel-validation-rules/compare/3.2.1...3.2.2 90 | 91 | ## 3.2.1 - 2022-08-01 92 | 93 | - fix chaining `doNotTrimItems` 94 | 95 | ## 3.2.0 - 2022-01-12 96 | 97 | ## What's Changed 98 | 99 | - fix psr version by @iamfarhad in https://github.com/spatie/laravel-validation-rules/pull/50 100 | - Allow Laravel 9 101 | 102 | ## New Contributors 103 | 104 | - @iamfarhad made their first contribution in https://github.com/spatie/laravel-validation-rules/pull/50 105 | 106 | **Full Changelog**: https://github.com/spatie/laravel-validation-rules/compare/3.1.1...3.2.0 107 | 108 | ## 3.1.1 - 2021-08-15 109 | 110 | - fix custom validation messages on delimited rule 111 | 112 | ## 3.1.0 - 2021-08-06 113 | 114 | - add ISO4217 currency validation rule (#48) 115 | 116 | ## 3.0.0 - 2020-11-30 117 | 118 | - move iso3166 package to `suggest` 119 | 120 | ## 2.7.1 - 2020-11-30 121 | 122 | - add support for PHP 8 123 | 124 | ## 2.7.0 - 2020-10-28 125 | 126 | - support custom error messages (#44) 127 | 128 | ## 2.6.1 - 2020-09-08 129 | 130 | - add support for Laravel 8 131 | 132 | ## 2.6.0 - 2020-09-01 133 | 134 | - adds the ability to specify an auth guard, and uses route name resolution for models (#41) 135 | 136 | ## 2.5.2 - 2020-07-01 137 | 138 | - fix translation (#40) 139 | 140 | ## 2.5.1 - 2020-04-01 141 | 142 | - fix nested attributes validation (#38) 143 | 144 | ## 2.5.0 - 2020-03-02 145 | 146 | - add support for Laravel 7 147 | 148 | ## 2.4.0 - 2019-09-04 149 | 150 | - add support for Laravel 6 151 | 152 | ## 2.3.4 - 2019-06-14 153 | 154 | - fix validation messages 155 | 156 | ## 2.3.3 - 2019-06-12 157 | 158 | - fix for determining the amount of value in the `Delimited` rule 159 | 160 | ## 2.3.2 - 2019-05-15 161 | 162 | - fix for delimiting rule with a minimum of 1 163 | 164 | ## 2.3.1 - 2019-05-16 165 | 166 | - fix for validating arrays 167 | 168 | ## 2.3.0 - 2019-05-16 169 | 170 | - added `Delimited` 171 | 172 | ## 2.1.1 - 2019-02-27 173 | 174 | - use PHPUnit 8 to run tests 175 | 176 | ## 2.1.0 - 2019-01-02 177 | 178 | - add `CountryCode` rule for ISO3266 country codes 179 | 180 | ## 2.0.0 - 2018-12-19 181 | 182 | - move all validation message translations to the `validation.*` group to be more consistent with Laravel 183 | - add relevant data for each rule to validation message translations (see `message` method for each rule) 184 | 185 | ## 1.0.3 - 2018-10-18 186 | 187 | - fix `Enum` rule 188 | 189 | ## 1.0.2 - 2018-10-14 190 | 191 | - fix typo in validation message 192 | 193 | ## 1.0.1 - 2018-10-12 194 | 195 | - fix `Authorized` rule 196 | 197 | ## 1.0.0 - 2018-10-12 198 | 199 | - initial release 200 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Spatie bvba 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A set of useful Laravel validation rules 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/laravel-validation-rules.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-validation-rules) 4 | [![Tests](https://img.shields.io/github/actions/workflow/status/spatie/laravel-validation-rules/run-tests.yml?branch=main&label=Tests)](https://github.com/spatie/laravel-validation-rules/actions/workflows/run-tests.yml) 5 | [![Total Downloads](https://img.shields.io/packagist/dt/spatie/laravel-validation-rules.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-validation-rules) 6 | 7 | This repository contains some useful Laravel validation rules. 8 | 9 | ## Support us 10 | 11 | [](https://spatie.be/github-ad-click/laravel-validation-rules) 12 | 13 | We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). 14 | 15 | We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). 16 | 17 | ## Installation 18 | 19 | You can install the package via composer: 20 | 21 | ```bash 22 | composer require spatie/laravel-validation-rules 23 | ``` 24 | 25 | The package will automatically register itself. 26 | 27 | ### Translations 28 | 29 | If you wish to edit the package translations, you can run the following command to publish them into your `resources/lang` folder 30 | 31 | ```bash 32 | php artisan vendor:publish --provider="Spatie\ValidationRules\ValidationRulesServiceProvider" 33 | ``` 34 | 35 | ## Available rules 36 | 37 | - [`Authorized`](#authorized) 38 | - [`CountryCode`](#countrycode) 39 | - [`Currency`](#currency) 40 | - [`Enum`](#enum) 41 | - [`ModelsExist`](#modelsexist) 42 | - [`Delimited`](#delimited) 43 | 44 | ### `Authorized` 45 | 46 | Determine if the user is authorized to perform an ability on an instance of the given model. The id of the model is the field under validation 47 | 48 | Consider the following policy: 49 | 50 | ```php 51 | class ModelPolicy 52 | { 53 | use HandlesAuthorization; 54 | 55 | public function edit(User $user, Model $model): bool 56 | { 57 | return $model->user->id === $user->id; 58 | } 59 | } 60 | ``` 61 | 62 | This validation rule will pass if the id of the logged in user matches the `user_id` on `TestModel` who's it is in the `model_id` key of the request. 63 | 64 | ```php 65 | // in a `FormRequest` 66 | 67 | use Spatie\ValidationRules\Rules\Authorized; 68 | 69 | public function rules() 70 | { 71 | return [ 72 | 'model_id' => [new Authorized('edit', TestModel::class)], 73 | ]; 74 | } 75 | ``` 76 | 77 | Optionally, you can provide an authentication guard as the third parameter. 78 | 79 | ```php 80 | new Authorized('edit', TestModel::class, 'guard-name') 81 | ``` 82 | 83 | #### Model resolution 84 | If you have implemented the `getRouteKeyName` method in your model, it will be used to resolve the model instance. For further information see [Customizing The Default Key Name](https://laravel.com/docs/7.x/routing) 85 | 86 | ### `CountryCode` 87 | 88 | Determine if the field under validation is a valid [2 letter ISO3166 country code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Current_codes) (example of valid country codes: `GB`, `DK`, `NL`). 89 | 90 | **Note** that this rule requires the package [`league/iso3166`](https://github.com/thephpleague/iso3166) to be installed: `composer require league/iso3166` 91 | 92 | ```php 93 | // in a `FormRequest` 94 | 95 | use Spatie\ValidationRules\Rules\CountryCode; 96 | 97 | public function rules() 98 | { 99 | return [ 100 | 'country_code' => ['required', new CountryCode()], 101 | ]; 102 | } 103 | ``` 104 | 105 | If you want to validate a nullable country code field, you can call the `nullable()` method on the `CountryCode` rule. This way `null` and `0` are also passing values: 106 | 107 | ```php 108 | // in a `FormRequest` 109 | 110 | use Spatie\ValidationRules\Rules\CountryCode; 111 | 112 | public function rules() 113 | { 114 | return [ 115 | 'country_code' => [(new CountryCode())->nullable()], 116 | ]; 117 | } 118 | ``` 119 | 120 | ### `Currency` 121 | 122 | Determine if the field under validation is a valid [3 letter ISO4217 currency code](https://en.wikipedia.org/wiki/ISO_4217#Active_codes) (example of valid currencies: `EUR`, `USD`, `CAD`). 123 | 124 | **Note** that this rule require the package [`league/iso3166`](https://github.com/thephpleague/iso3166) to be installed: `composer require league/iso3166` 125 | 126 | ```php 127 | // in a `FormRequest` 128 | 129 | use Spatie\ValidationRules\Rules\Currency; 130 | 131 | public function rules() 132 | { 133 | return [ 134 | 'currency' => ['required', new Currency()], // Must be present and a valid currency 135 | ]; 136 | } 137 | ``` 138 | 139 | If you want to validate a nullable currency field, simple do not let it be required as described in the [Laravel Docs for implicit validation rules](https://laravel.com/docs/master/validation#implicit-rules): 140 | > ... when an attribute being validated is not present or contains an empty string, normal validation rules, including custom rules, are not run 141 | 142 | ```php 143 | // in a `FormRequest` 144 | 145 | use Spatie\ValidationRules\Rules\Currency; 146 | 147 | public function rules() 148 | { 149 | return [ 150 | 'currency' => [new Currency()], // This will pass for any valid currency, an empty value or null 151 | ]; 152 | } 153 | ``` 154 | 155 | ### `Enum` 156 | 157 | This rule will validate if the value under validation is part of the given enum class. We assume that the enum class has a static `toArray` method that returns all valid values. If you're looking for a good enum class, take a look at [spatie/enum](https://github.com/spatie/enum) or [myclabs/php-enum](https://github.com/myclabs/php-enum). 158 | 159 | Consider the following enum class: 160 | 161 | ```php 162 | class UserRole extends MyCLabs\Enum\Enum 163 | { 164 | const ADMIN = 'admin'; 165 | const REVIEWER = 'reviewer'; 166 | } 167 | ``` 168 | 169 | The `Enum` rule can be used like this: 170 | 171 | ```php 172 | // in a `FormRequest` 173 | 174 | use Spatie\ValidationRules\Rules\Enum; 175 | 176 | public function rules() 177 | { 178 | return [ 179 | 'role' => [new Enum(UserRole::class)], 180 | ]; 181 | } 182 | ``` 183 | 184 | The request will only be valid if `role` contains `ADMIN` or `REVIEWER`. 185 | 186 | ### `ModelsExist` 187 | 188 | Determine if all of the values in the input array exist as attributes for the given model class. 189 | 190 | By default the rule assumes that you want to validate using `id` attribute. In the example below the validation will pass if all `model_ids` exist for the `Model`. 191 | 192 | 193 | ```php 194 | // in a `FormRequest` 195 | 196 | use Spatie\ValidationRules\Rules\ModelsExist; 197 | 198 | public function rules() 199 | { 200 | return [ 201 | 'model_ids' => ['array', new ModelsExist(Model::class)], 202 | ]; 203 | } 204 | ``` 205 | 206 | 207 | You can also pass an attribute name as the second argument. In the example below the validation will pass if there are users for each email given in the `user_emails` of the request. 208 | 209 | ```php 210 | // in a `FormRequest` 211 | 212 | use Spatie\ValidationRules\Rules\ModelsExist; 213 | 214 | public function rules() 215 | { 216 | return [ 217 | 'user_emails' => ['array', new ModelsExist(User::class, 'emails')], 218 | ]; 219 | } 220 | ``` 221 | 222 | ### `Delimited` 223 | 224 | This rule can validate a string containing delimited values. The constructor accepts a rule that is used to validate all separate values. 225 | 226 | Here's an example where we are going to validate a string containing comma separated email addresses. 227 | 228 | ```php 229 | // in a `FormRequest` 230 | 231 | use Spatie\ValidationRules\Rules\Delimited; 232 | 233 | public function rules() 234 | { 235 | return [ 236 | 'emails' => [new Delimited('email')], 237 | ]; 238 | } 239 | ``` 240 | 241 | Here's some example input that passes this rule: 242 | 243 | - `'sebastian@example.com, alex@example.com'` 244 | - `''` 245 | - `'sebastian@example.com'` 246 | - `'sebastian@example.com, alex@example.com, brent@example.com'` 247 | - `' sebastian@example.com , alex@example.com , brent@example.com '` 248 | 249 | This input will not pass: 250 | - `'@example.com'` 251 | - `'nocomma@example.com nocommatoo@example.com'` 252 | - `'valid@example.com, invalid@'` 253 | 254 | #### Setting a minimum 255 | You can set minimum amout of items that should be present: 256 | 257 | ```php 258 | (new Delimited('email'))->min(2) 259 | ``` 260 | 261 | - `'sebastian@example.com, alex@example.com'` // passes 262 | - `'sebastian@example.com'` // fails 263 | 264 | #### Setting a maximum 265 | 266 | ```php 267 | (new Delimited('email'))->max(2) 268 | ``` 269 | 270 | - `'sebastian@example.com'` // passes 271 | - `'sebastian@example.com, alex@example.com, brent@example.com'` // fails 272 | 273 | #### Allowing duplicate items 274 | 275 | By default the rule will fail if there are duplicate items found. 276 | 277 | - `'sebastian@example.com, sebastian@example.com'` // fails 278 | 279 | You can allowing duplicate items like this: 280 | 281 | ```php 282 | (new Delimited('numeric'))->allowDuplicates() 283 | ``` 284 | 285 | Now this will pass: `1,1,2,2,3,3` 286 | 287 | #### Customizing the separator 288 | 289 | ```php 290 | (new Delimited('email'))->separatedBy(';') 291 | ``` 292 | 293 | - `'sebastian@example.com; alex@example.com; brent@example.com'` // passes 294 | - `'sebastian@example.com, alex@example.com, brent@example.com'` // fails 295 | 296 | #### Skip trimming of items 297 | 298 | ```php 299 | (new Delimited('email'))->doNotTrimItems() 300 | ``` 301 | 302 | - `'sebastian@example.com,freek@example.com'` // passes 303 | - `'sebastian@example.com, freek@example.com'` // fails 304 | - `'sebastian@example.com , freek@example.com'` // fails 305 | 306 | #### Composite rules 307 | 308 | The constructor of the validator accepts a validation rule string, a validate instance, or an array. 309 | 310 | ```php 311 | new Delimited('email|max:20') 312 | ``` 313 | - `'short@example.com'` // passes 314 | - `'invalid'` // fails 315 | - `'loooooooonnnggg@example.com'` // fails 316 | 317 | #### Passing custom error messages 318 | 319 | The constructor of the validator accepts a custom error messages array as second parameter. 320 | 321 | ```php 322 | // in a `FormRequest` 323 | 324 | use Spatie\ValidationRules\Rules\Delimited; 325 | 326 | public function rules() 327 | { 328 | return [ 329 | 'emails' => [new Delimited('email', $this->messages())], 330 | ]; 331 | } 332 | 333 | public function messages() 334 | { 335 | return [ 336 | 'emails.email' => 'Not all the given e-mails are valid.', 337 | ]; 338 | } 339 | ``` 340 | 341 | ### Testing 342 | 343 | ``` bash 344 | composer test 345 | ``` 346 | 347 | ### Changelog 348 | 349 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 350 | 351 | ## Contributing 352 | 353 | Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. 354 | 355 | ### Security 356 | 357 | If you've found a bug regarding security please mail [security@spatie.be](mailto:security@spatie.be) instead of using the issue tracker. 358 | 359 | ## Credits 360 | 361 | - [Freek Van der Herten](https://github.com/freekmurze) 362 | - [All Contributors](../../contributors) 363 | 364 | ## Support us 365 | 366 | Spatie is a webdesign agency based in Antwerp, Belgium. You'll find an overview of all our open source projects [on our website](https://spatie.be/opensource). 367 | 368 | Does your business depend on our contributions? Reach out and support us on [Patreon](https://www.patreon.com/spatie). 369 | All pledges will be dedicated to allocating workforce on maintenance and new awesome stuff. 370 | 371 | ## License 372 | 373 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 374 | -------------------------------------------------------------------------------- /UPGRADING.md: -------------------------------------------------------------------------------- 1 | ## From v2 to v3 2 | 3 | - the API has not changed. If you use the country code rule, you must install the suggested iso3166 package 4 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spatie/laravel-validation-rules", 3 | "description": "A set of useful Laravel validation rules", 4 | "keywords": [ 5 | "spatie", 6 | "laravel-validation-rules" 7 | ], 8 | "homepage": "https://github.com/spatie/laravel-validation-rules", 9 | "license": "MIT", 10 | "authors": [ 11 | { 12 | "name": "Freek Van der Herten", 13 | "email": "freek@spatie.be", 14 | "homepage": "https://spatie.be", 15 | "role": "Developer" 16 | } 17 | ], 18 | "require": { 19 | "php": "^8.0", 20 | "illuminate/support": "^8.0|^9.0|^10.0|^11.0|^12.0" 21 | }, 22 | "require-dev": { 23 | "laravel/pint": "^1.3", 24 | "league/iso3166": "^3.0|^4.3", 25 | "myclabs/php-enum": "^1.6", 26 | "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0|^10.0", 27 | "pestphp/pest": "^1.23|^2.6|^3.7", 28 | "spatie/enum": "^2.2|^3.0" 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "Spatie\\ValidationRules\\": "src", 33 | "Spatie\\ValidationRules\\Database\\Factories\\": "database/factories" 34 | } 35 | }, 36 | "autoload-dev": { 37 | "psr-4": { 38 | "Spatie\\ValidationRules\\Tests\\": "tests" 39 | } 40 | }, 41 | "suggest": { 42 | "league/iso3166": "Needed for the CountryCode rule and Currency rule" 43 | }, 44 | "scripts": { 45 | "test": "vendor/bin/pest", 46 | "test-coverage": "vendor/bin/pest --coverage-html coverage", 47 | "format": "./vendor/bin/pint", 48 | "inspect": "./vendor/bin/pint --test" 49 | }, 50 | "config": { 51 | "sort-packages": true, 52 | "allow-plugins": { 53 | "pestphp/pest-plugin": true 54 | } 55 | }, 56 | "extra": { 57 | "laravel": { 58 | "providers": [ 59 | "Spatie\\ValidationRules\\ValidationRulesServiceProvider" 60 | ] 61 | } 62 | }, 63 | "minimum-stability": "dev", 64 | "prefer-stable": true 65 | } 66 | -------------------------------------------------------------------------------- /database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | $this->faker->name, 19 | 'email' => $this->faker->unique()->safeEmail, 20 | 'password' => $password ?: $password = bcrypt('secret'), 21 | 'remember_token' => Str::random(10), 22 | ]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /resources/lang/en/messages.php: -------------------------------------------------------------------------------- 1 | 'You are not authorized to use this value.', 5 | 'enum' => 'This is not a valid value.', 6 | 'model_ids' => 'Some of the given ids do not exist.', 7 | 'country_code' => 'Invalid country code.', 8 | 'currency' => 'The :attribute must be a valid ISO4217 currency.', 9 | 'delimited' => [ 10 | 'unique' => 'You may not specify duplicates.', 11 | 'min' => 'You must specify at least :min :item', 12 | 'max' => 'You can only specify :max :item', 13 | 'string' => ':attribute must be a string', 14 | ], 15 | ]; 16 | -------------------------------------------------------------------------------- /src/Rules/Authorized.php: -------------------------------------------------------------------------------- 1 | attribute = $attribute; 22 | 23 | if (! $user = Auth::guard($this->guard)->user()) { 24 | return false; 25 | } 26 | 27 | if (! $model = app($this->className)->resolveRouteBinding($value, $this->column)) { 28 | return false; 29 | } 30 | 31 | return $user->can($this->ability, $model); 32 | } 33 | 34 | public function message(): string 35 | { 36 | $classBasename = class_basename($this->className); 37 | 38 | return __('validationRules::messages.authorized', [ 39 | 'attribute' => $this->attribute, 40 | 'ability' => $this->ability, 41 | 'className' => $classBasename, 42 | ]); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Rules/CountryCode.php: -------------------------------------------------------------------------------- 1 | required = $required; 20 | } 21 | 22 | public function nullable(): self 23 | { 24 | $this->required = false; 25 | 26 | return $this; 27 | } 28 | 29 | public function passes($attribute, $value): bool 30 | { 31 | $this->attribute = $attribute; 32 | 33 | if (! $this->required && ($value === '0' || $value === 0 || $value === null)) { 34 | return true; 35 | } 36 | 37 | $countries = Arr::pluck((new ISO3166)->all(), ISO3166::KEY_ALPHA2); 38 | 39 | return in_array($value, $countries, true); 40 | } 41 | 42 | public function message(): string 43 | { 44 | return __('validationRules::messages.country_code', [ 45 | 'attribute' => $this->attribute, 46 | ]); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Rules/Currency.php: -------------------------------------------------------------------------------- 1 | attribute = $attribute; 16 | 17 | if ($value === null || $value === '') { 18 | return false; 19 | } 20 | 21 | $currencies = array_unique(data_get((new ISO3166)->all(), '*.currency.*')); 22 | 23 | return in_array($value, $currencies, true); 24 | } 25 | 26 | public function message(): string 27 | { 28 | return __('validationRules::messages.currency', [ 29 | 'attribute' => $this->attribute, 30 | ]); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Rules/Delimited.php: -------------------------------------------------------------------------------- 1 | rule = $rule; 39 | $this->customErrorMessages = $customErrorMessages; 40 | } 41 | 42 | public function min(int $minimum) 43 | { 44 | $this->minimum = $minimum; 45 | 46 | return $this; 47 | } 48 | 49 | public function max(int $maximum) 50 | { 51 | $this->maximum = $maximum; 52 | 53 | return $this; 54 | } 55 | 56 | public function allowDuplicates(bool $allowed = true) 57 | { 58 | $this->allowDuplicates = $allowed; 59 | 60 | return $this; 61 | } 62 | 63 | public function separatedBy(string $separator) 64 | { 65 | $this->separatedBy = $separator; 66 | 67 | return $this; 68 | } 69 | 70 | public function doNotTrimItems() 71 | { 72 | $this->trimItems = false; 73 | 74 | return $this; 75 | } 76 | 77 | public function validationMessageWord(string $word) 78 | { 79 | $this->validationMessageWord = $word; 80 | 81 | return $this; 82 | } 83 | 84 | public function asCsv(): static 85 | { 86 | $this->asCsv = true; 87 | 88 | return $this; 89 | } 90 | 91 | public function passes($attribute, $value) 92 | { 93 | if (! is_string($value)) { 94 | $this->message = $this->getErrorMessage($attribute, 'string', [ 95 | 'attribute' => $attribute, 96 | ]); 97 | 98 | return false; 99 | } 100 | 101 | if ($this->trimItems) { 102 | $value = trim($value); 103 | } 104 | 105 | $items = collect($this->asCsv ? str_getcsv($value, $this->separatedBy) : explode($this->separatedBy, $value)) 106 | ->filter(function ($item) { 107 | return strlen((string) $item) > 0; 108 | }); 109 | 110 | if (! is_null($this->minimum)) { 111 | if ($items->count() < $this->minimum) { 112 | $this->message = $this->getErrorMessage($attribute, 'min', [ 113 | 'min' => $this->minimum, 114 | 'actual' => $items->count(), 115 | 'item' => Str::plural($this->validationMessageWord, $items->count()), 116 | ]); 117 | 118 | return false; 119 | } 120 | } 121 | 122 | if (! is_null($this->maximum)) { 123 | if ($items->count() > $this->maximum) { 124 | $this->message = $this->getErrorMessage($attribute, 'max', [ 125 | 'max' => $this->maximum, 126 | 'actual' => $items->count(), 127 | 'item' => Str::plural($this->validationMessageWord, $items->count()), 128 | ]); 129 | 130 | return false; 131 | } 132 | } 133 | 134 | if ($this->trimItems) { 135 | $items = $items->map(function (string $item) { 136 | return trim($item); 137 | }); 138 | } 139 | 140 | foreach ($items as $item) { 141 | [$isValid, $validationMessage] = $this->validate($attribute, $item); 142 | 143 | if (! $isValid) { 144 | $this->message = $validationMessage; 145 | 146 | return false; 147 | } 148 | } 149 | 150 | if (! $this->allowDuplicates) { 151 | if ($items->unique()->count() !== $items->count()) { 152 | $this->message = $this->getErrorMessage($attribute, 'unique'); 153 | 154 | return false; 155 | } 156 | } 157 | 158 | return true; 159 | } 160 | 161 | public function message() 162 | { 163 | return $this->message; 164 | } 165 | 166 | protected function validate(string $attribute, string $item): array 167 | { 168 | $attribute = Arr::last(explode('.', $attribute)); 169 | 170 | $validator = Validator::make( 171 | [$attribute => $item], 172 | [$attribute => $this->rule], 173 | $this->customErrorMessages 174 | ); 175 | 176 | return [ 177 | $validator->passes(), 178 | $validator->getMessageBag()->first($attribute), 179 | ]; 180 | } 181 | 182 | protected function getErrorMessage($attribute, $rule, $data = []) 183 | { 184 | if (array_key_exists($attribute.'.'.$rule, $this->customErrorMessages)) { 185 | return __($this->customErrorMessages[$attribute.'.'.$rule], $data); 186 | } 187 | 188 | return __('validationRules::messages.delimited.'.$rule, $data); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/Rules/Enum.php: -------------------------------------------------------------------------------- 1 | validValues = array_keys($enumClass::toArray()); 18 | } 19 | 20 | public function passes($attribute, $value): bool 21 | { 22 | $this->attribute = $attribute; 23 | 24 | return in_array($value, $this->validValues); 25 | } 26 | 27 | public function message(): string 28 | { 29 | $validValues = implode(', ', $this->validValues); 30 | 31 | return __('validationRules::messages.enum', [ 32 | 'attribute' => $this->attribute, 33 | 'validValues' => $validValues, 34 | ]); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Rules/ModelsExist.php: -------------------------------------------------------------------------------- 1 | modelClassName = $modelClassName; 24 | 25 | $this->modelAttribute = $attribute; 26 | } 27 | 28 | public function passes($attribute, $value): bool 29 | { 30 | $this->attribute = $attribute; 31 | 32 | $value = array_filter($value); 33 | 34 | $this->modelIds = array_unique($value); 35 | 36 | $modelCount = $this->modelClassName::whereIn($this->modelAttribute, $this->modelIds)->count(); 37 | 38 | return count($this->modelIds) === $modelCount; 39 | } 40 | 41 | public function message(): string 42 | { 43 | $modelIds = implode(', ', $this->modelIds); 44 | 45 | $classBasename = class_basename($this->modelClassName); 46 | 47 | return __('validationRules::messages.model_ids', [ 48 | 'attribute' => $this->attribute, 49 | 'model' => $classBasename, 50 | 'modelAttribute' => $this->modelAttribute, 51 | 'modelIds' => $modelIds, 52 | ]); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/ValidationRulesServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishes([ 12 | __DIR__.'/../resources/lang' => resource_path('lang/vendor/validationRules'), 13 | ]); 14 | 15 | $this->loadTranslationsFrom(__DIR__.'/../resources/lang/', 'validationRules'); 16 | } 17 | } 18 | --------------------------------------------------------------------------------