├── .github ├── FUNDING.yml └── workflows │ ├── ci-phpstan.yml │ └── ci-tests.yml ├── .gitignore ├── .styleci.yml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── UPGRADE.md ├── composer.json ├── phpstan.neon ├── phpunit.xml ├── resources └── views │ └── validate-config.blade.php ├── src ├── Console │ └── Commands │ │ ├── ValidateConfigCommand.php │ │ ├── ValidationMakeCommand.php │ │ └── stubs │ │ └── config-validation.stub ├── Exceptions │ ├── ConfigValidatorException.php │ ├── DirectoryNotFoundException.php │ ├── InvalidConfigValueException.php │ └── NoValidationFilesFoundException.php ├── Facades │ └── ConfigValidator.php ├── Providers │ └── ConfigValidatorProvider.php ├── Services │ ├── ConfigValidator.php │ ├── Rule.php │ └── ValidationRepository.php └── Traits │ └── LoadsConfigValidationFiles.php ├── stubs └── config-validation │ ├── app.php │ ├── auth.php │ ├── broadcasting.php │ ├── cache.php │ ├── cors.php │ ├── database.php │ ├── filesystems.php │ ├── hashing.php │ ├── logging.php │ ├── mail.php │ ├── queue.php │ ├── services.php │ ├── session.php │ └── view.php └── tests └── Unit ├── Console └── Commands │ ├── ValidateConfigCommand │ └── HandleTest.php │ └── ValidationMakeCommand │ └── ValidationMakeCommandTest.php ├── Services └── ConfigValidator │ ├── RunInlineTest.php │ └── RunTest.php ├── Stubs ├── IsFooBar.php ├── cache.php └── mail.php └── TestCase.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ash-jc-allen 2 | -------------------------------------------------------------------------------- /.github/workflows/ci-phpstan.yml: -------------------------------------------------------------------------------- 1 | name: run-phpstan 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | run-tests: 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | php: [8.2, 8.3, 8.4] 14 | laravel: ['10.*', '11.*', '12.*'] 15 | include: 16 | - laravel: 10.* 17 | testbench: 8.* 18 | - laravel: 11.* 19 | testbench: 9.* 20 | - laravel: 12.* 21 | testbench: 10.* 22 | 23 | name: PHP${{ matrix.php }} - Laravel ${{ matrix.laravel }} 24 | 25 | steps: 26 | - name: Update apt 27 | run: sudo apt-get update --fix-missing 28 | 29 | - name: Checkout code 30 | uses: actions/checkout@v2 31 | 32 | - name: Setup PHP 33 | uses: shivammathur/setup-php@v2 34 | with: 35 | php-version: ${{ matrix.php }} 36 | coverage: none 37 | 38 | - name: Setup Problem Matches 39 | run: | 40 | echo "::add-matcher::${{ runner.tool_cache }}/php.json" 41 | echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" 42 | 43 | - name: Install dependencies 44 | run: | 45 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update 46 | composer update --prefer-dist --no-interaction --no-suggest 47 | 48 | - name: Run Larastan 49 | run: vendor/bin/phpstan analyse 50 | -------------------------------------------------------------------------------- /.github/workflows/ci-tests.yml: -------------------------------------------------------------------------------- 1 | name: run-tests 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | run-tests: 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | php: [8.2, 8.3, 8.4] 14 | laravel: ['10.*', '11.*', '12.*'] 15 | include: 16 | - laravel: 10.* 17 | testbench: 8.* 18 | - laravel: 11.* 19 | testbench: 9.* 20 | - laravel: 12.* 21 | testbench: 10.* 22 | 23 | name: PHP${{ matrix.php }} - Laravel ${{ matrix.laravel }} 24 | 25 | steps: 26 | - name: Update apt 27 | run: sudo apt-get update --fix-missing 28 | 29 | - name: Checkout code 30 | uses: actions/checkout@v2 31 | 32 | - name: Setup PHP 33 | uses: shivammathur/setup-php@v2 34 | with: 35 | php-version: ${{ matrix.php }} 36 | coverage: none 37 | 38 | - name: Setup Problem Matches 39 | run: | 40 | echo "::add-matcher::${{ runner.tool_cache }}/php.json" 41 | echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" 42 | 43 | - name: Install dependencies 44 | run: | 45 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update 46 | composer update --prefer-dist --no-interaction --no-suggest 47 | 48 | - name: Execute tests 49 | run: composer test 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | vendor/ 3 | composer.lock 4 | .phpunit.result.cache -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: laravel -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | **v2.8.0 (released 2025-02-24):** 4 | 5 | - Added support for Laravel 12. [#69](https://github.com/ash-jc-allen/laravel-config-validator/pull/69) 6 | - Added support for PHPUnit 11. [#72](https://github.com/ash-jc-allen/laravel-config-validator/pull/72) 7 | - Dropped support for Laravel 8 and 9. [#72](https://github.com/ash-jc-allen/laravel-config-validator/pull/72) 8 | - Dropped support for PHP 8.0 and 8.1. [#71](https://github.com/ash-jc-allen/laravel-config-validator/pull/71) 9 | - Migrated from `nunomaduro/larastan` to `larastan/larastan` and added support for Larastan 3. [#70](https://github.com/ash-jc-allen/laravel-config-validator/pull/70) 10 | 11 | **v2.7.0 (released 2024-11-30):** 12 | 13 | - Added explicit nullable types to support PHP 8.4. [#68](https://github.com/ash-jc-allen/laravel-config-validator/pull/68) 14 | 15 | **v2.6.1 (released 2024-04-26):** 16 | 17 | - Fixed a bug that prevented deep-nested config items from being validated. [#66](https://github.com/ash-jc-allen/laravel-config-validator/pull/66) 18 | 19 | **v2.6.0 (released 2024-04-26):** 20 | 21 | - Added a `runInline` method to the validator. [#64](https://github.com/ash-jc-allen/laravel-config-validator/pull/64) 22 | 23 | **v2.5.0 (released 2024-02-27):** 24 | 25 | - Added support for Laravel 11. [#62](https://github.com/ash-jc-allen/laravel-config-validator/pull/62) 26 | 27 | **v2.4.0 (released 2023-01-12):** 28 | - Added support for Laravel 10. [#59](https://github.com/ash-jc-allen/laravel-config-validator/pull/59) 29 | 30 | **v2.3.0 (released 2022-10-17):** 31 | - Added support for PHP 8.2. [#58](https://github.com/ash-jc-allen/laravel-config-validator/pull/58) 32 | 33 | **v2.2.0 (released 2022-05-25):** 34 | - Fixed a bug that broke the command's failure output if a config value was an array. [#48](https://github.com/ash-jc-allen/laravel-config-validator/pull/48) 35 | - Added `illuminate/validation` and `illuminate/view` as Composer dependencies. [#49](https://github.com/ash-jc-allen/laravel-config-validator/pull/49) 36 | 37 | **v2.1.0 (released 2022-04-02):** 38 | - Improved command output using Termwind. Huge thanks to [Francisco Madeira](https://github.com/xiCO2k) for this! [#38](https://github.com/ash-jc-allen/laravel-config-validator/pull/38) 39 | - Removed unneeded `illuminate/cache` dependency requirement. Added dependency requirement for `illuminate/command`. [#40](https://github.com/ash-jc-allen/laravel-config-validator/pull/40) 40 | - Removed old and unneeded exceptions. [#39](https://github.com/ash-jc-allen/laravel-config-validator/pull/39) 41 | - Fixed bug that was removing underscores from validation error messages. [#43](https://github.com/ash-jc-allen/laravel-config-validator/pull/43) 42 | - Updated the PHPUnit config file to the newest format. [#45](https://github.com/ash-jc-allen/laravel-config-validator/pull/45) 43 | 44 | **v2.0.0 (released 2022-03-22):** 45 | - Added support for Laravel 9 and PHP 8.1. [#32](https://github.com/ash-jc-allen/laravel-config-validator/pull/32) 46 | - Dropped support for Laravel 6 and 7. [#32](https://github.com/ash-jc-allen/laravel-config-validator/pull/32) 47 | - Dropped support for PHP 7.3 and 7.4. [#32](https://github.com/ash-jc-allen/laravel-config-validator/pull/32) 48 | - Migrated from TravisCI to GitHub Actions for running tests. [#35](https://github.com/ash-jc-allen/laravel-config-validator/pull/35) 49 | - Added Larastan workflow to run on GitHub Actions. [#34](https://github.com/ash-jc-allen/laravel-config-validator/pull/34) 50 | - Added type hints and fields types. [#35](https://github.com/ash-jc-allen/laravel-config-validator/pull/35) 51 | - Changed default directory from `config/validation` to `config-validation`. [#31](https://github.com/ash-jc-allen/laravel-config-validator/pull/31) 52 | - Moved default config rulesets to `stubs/config-validation`. [#36](https://github.com/ash-jc-allen/laravel-config-validator/pull/36) 53 | - Fixed bug that prevented Rule objects from being used. [#33](https://github.com/ash-jc-allen/laravel-config-validator/pull/33) 54 | 55 | **v1.3.0 (released 2020-12-06):** 56 | - Added support for PHP 8. 57 | 58 | **v1.2.0 (released 2020-11-06):** 59 | - Added default config rulesets that can be published. [#26](https://github.com/ash-jc-allen/laravel-config-validator/pull/26) 60 | 61 | **v1.1.0 (released 2020-10-28):** 62 | - Added a new ` errors() ` method to the ` ConfigValidator ` class. [#22](https://github.com/ash-jc-allen/laravel-config-validator/pull/22) 63 | - Added a new ` throwExceptionOnFailure() ` method to the ` ConfigValidator ` class. [#23](https://github.com/ash-jc-allen/laravel-config-validator/pull/23) 64 | - Updated the validation console command output to be more readable and contain all the validation error messages. [#24](https://github.com/ash-jc-allen/laravel-config-validator/pull/23) 65 | 66 | **v1.0.0:** 67 | - Initial production release. 68 | - Added a generator command that can be used for making new config validation files. 69 | 70 | **v0.4.0:** 71 | - Increased minimum supported PHP version to PHP 7.3. 72 | - Added PHPUnit tests. 73 | - Fixed a bug that prevented more than one nested config item from being validated. 74 | - Updated the validator to return ``` true ``` after successfully running. 75 | 76 | **v0.3.0:** 77 | - Added checks that validate whether if the config folder exists and if the folder contains any validation files. 78 | 79 | **v0.2.1:** 80 | - Fixed a bug that prevented nested config items from being validated. 81 | 82 | **v0.2.0:** 83 | - Added the functionality to set environment-specific rules. 84 | 85 | **v0.1.1:** 86 | - Documentation updates. 87 | 88 | **v0.1.0:** 89 | - Pre-release development. 90 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ashley Allen 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | Latest Version on Packagist 7 | Build Status 8 | Total Downloads 9 | PHP from Packagist 10 | GitHub license 11 |

12 | 13 | ## Table of Contents 14 | 15 | - [Overview](#overview) 16 | - [Installation](#installation) 17 | - [Requirements](#requirements) 18 | - [Install the Package](#install-the-package) 19 | - [Publishing the Default Rulesets](#publishing-the-default-rulesets) 20 | - [Usage](#usage) 21 | - [Creating a Validation Ruleset](#creating-a-validation-ruleset) 22 | - [Using the Generator Command](#using-the-generator-command) 23 | - [Ruleset Location](#ruleset-location) 24 | - [Adding Rules to a RuleSet](#adding-rules-to-a-ruleset) 25 | - [Custom Validation Error Messages](#custom-validation-error-messages) 26 | - [Only Running in Specific App Environments](#only-running-in-specific-app-environments) 27 | - [Running the Validation](#running-the-validation) 28 | - [Running the Validation Manually](#running-the-validation-manually) 29 | - [Only Running on Selected Config Files](#only-running-on-selected-config-files) 30 | - [Custom Folder Path](#custom-folder-path) 31 | - [Using the Command](#using-the-command) 32 | - [Only Running on Selected Config Files (Command)](#only-running-on-selected-config-files-command) 33 | - [Custom Folder Path (Command)](#custom-folder-path-command) 34 | - [Using a Service Provider](#using-a-service-provider) 35 | - [Throwing and Preventing Exceptions](#throwing-and-preventing-exceptions) 36 | - [Facade](#facade) 37 | - [Security](#security) 38 | - [Contribution](#contribution) 39 | - [Changelog](#changelog) 40 | - [Upgrading](#upgrading) 41 | - [License](#license) 42 | 43 | ## Overview 44 | 45 | A Laravel package that allows you to validate your config values and environment. 46 | 47 | ## Installation 48 | 49 | ### Requirements 50 | The package has been developed and tested to work with the following minimum requirements: 51 | 52 | - PHP 8.0 53 | - Laravel 8 54 | 55 | ### Install the Package 56 | You can install the package via Composer: 57 | 58 | ```bash 59 | composer require ashallendesign/laravel-config-validator 60 | ``` 61 | 62 | ### Publishing the Default Rulesets 63 | 64 | To get you started with validating your app's config, Laravel Config Validator comes with some default rulesets. To start 65 | using these rulesets, you can publish them using the following command: 66 | 67 | ```bash 68 | php artisan vendor:publish --tag=config-validator-defaults 69 | ``` 70 | 71 | The above command will copy the validation files and place in a ` config-validation ` folder in your project's root. These rules 72 | are just to get you started, so there are likely going to be rule in the files that don't apply to your app. So, once you've 73 | published them, feel free to delete them or edit them as much as you'd like. 74 | 75 | ## Usage 76 | 77 | ### Creating a Validation Ruleset 78 | 79 | #### Using the Generator Command 80 | 81 | This package comes with a command that you can use to quickly create a validation file to get you started right away. 82 | Lets say that you wanted to create a validation file for validating the config in the ``` config/app.php ``` file. To do 83 | this, you could use the following command: 84 | 85 | ```bash 86 | php artisan make:config-validation app 87 | ``` 88 | 89 | Running the above command would create a file in ``` config-validation/app.php ``` ready for you to start adding your config 90 | validation. 91 | 92 | #### Ruleset Location 93 | 94 | To validate your application's config, you need to define the validation rules first. You can do this by placing them inside 95 | files in a ``` config-validation ``` folder with names that match the config file you're validating. As an example, to 96 | validate the ``` config/app.php ``` config file, you would create a new file at ``` config-validation/app.php ``` that 97 | would hold the rules. 98 | 99 | #### Adding Rules to a Ruleset 100 | 101 | Once you have your ruleset file created in the ``` config-validation ``` folder, you can start adding your validation 102 | rules. 103 | 104 | Under the hood, Laravel Config Validator uses the built-in ``` Validator ``` class, so it should seem pretty familiar 105 | to work with. To check out the available Laravel validation rules that can be used, [click here](https://laravel.com/docs/8.x/validation#available-validation-rules). 106 | 107 | As an example, we might want to add a config validation rule to ensure that the ``` driver ``` field in the ``` app/mail.php ``` 108 | file is a supported field. To do this, we could create a file at ``` config-validation/mail.php ``` with the following: 109 | 110 | ```php 111 | rules(['in:smtp,sendmail,mailgun,ses,postmark,log,array']), 117 | // ... 118 | ]; 119 | ``` 120 | 121 | #### Custom Validation Error Messages 122 | 123 | There may be times when you want to override the error message for a specific validation rule. This can be done by passing 124 | in an array containing the messages to the ``` ->messages() ``` method for a ``` Rule ```. This array should follow the same 125 | pattern that would be used in a standard Laravel Validator object. 126 | 127 | As an example, we might want to add a config validation rule to ensure that the ``` driver ``` field in the ``` app/mail.php ``` 128 | file is a supported field and also use a custom error message. To do this, we could update our validation file to the following: 129 | 130 | ```php 131 | rules(['in:smtp,sendmail,mailgun,ses,postmark,log,array']) 138 | ->messages(['in' => 'The mail driver is invalid']), 139 | // ... 140 | ]; 141 | ``` 142 | 143 | #### Only Running in Specific App Environments 144 | 145 | You might not always want the same rule to be run in different environments. For example, you might want to have a relaxed 146 | set of validation rules for your local development environment and have a stricter set of rules for production. 147 | 148 | To explicitly specify the environment that a rule can be run in, you can use the ``` ->environments() ``` method. If no 149 | environment is defined, the rule will be run in all environments. 150 | 151 | The following example shows how you could set 2 different rules, one for production and one for local: 152 | 153 | ```php 154 | rules(['in:smtp,sendmail,mailgun,ses,postmark,log,array']) 161 | ->environments([Rule::ENV_LOCAL]), 162 | 163 | Rule::make('driver') 164 | ->rules(['in:mailgun']) 165 | ->environments([Rule::ENV_PRODUCTION]) 166 | ]; 167 | ``` 168 | 169 | ### Running the Validation 170 | 171 | #### Running the Validation Manually 172 | 173 | To run the config validation you can call the ``` ->run() ``` method on a ``` ConfigValidator ``` object. The example below 174 | shows how you could do this in a controller: 175 | 176 | ```php 177 | run(); 190 | 191 | return response()->json(['success' => true]); 192 | } 193 | } 194 | ``` 195 | 196 | ##### Only Running on Selected Config Files 197 | 198 | You might not always want to validate all of the config values in your application. So, you can specify the config files 199 | that you want to validate by passing the config names to the ``` ->run() ``` method as the first parameter. As an example, if you only wanted to validate 200 | the ``` auth.php ``` config file, you could use the following: 201 | 202 | ```php 203 | $configValidator = new ConfigValidator(); 204 | 205 | $configValidator->run(['auth']); 206 | ``` 207 | 208 | ##### Custom Folder Path 209 | 210 | If you aren't storing your validation files in the default ``` config/validation ``` folder, you can pass a custom folder path 211 | into the ``` ->run() ``` method as the second parameter. As an example, if you had the files stored in a ``` app/Custom/Validation ``` folder, you 212 | could use the following: 213 | 214 | ```php 215 | $configValidator = new ConfigValidator(); 216 | 217 | $configValidator->run([], 'app/Custom/Validation'); 218 | ``` 219 | 220 | ##### Running the Validator with Inline Rules 221 | 222 | There may be times when you want to run the validator with inline rules instead of using the rules defined in your config validation files. This can be useful if you want to run a one-off validation check, or validate the config values inside a package you maintain. 223 | 224 | To do this, you can use the `runInline` method like so: 225 | 226 | ```php 227 | use AshAllenDesign\ConfigValidator\Services\ConfigValidator; 228 | use AshAllenDesign\ConfigValidator\Services\Rule; 229 | 230 | $configValidator = new ConfigValidator(); 231 | 232 | $configValidator->runInline([ 233 | 'app' => [ 234 | Rule::make('env')->rules(['in:local,production']), 235 | Rule::make('debug')->rules(['boolean']), 236 | ], 237 | 'mail' => [ 238 | Rule::make('driver')->rules(['in:smtp,sendmail,mailgun,ses,postmark,log,array']), 239 | ], 240 | ]); 241 | ``` 242 | 243 | In the example above, we're running the validator with inline rules for the `app` and `mail` config files. The rules are the same as the ones we would define in the config validation files. 244 | 245 | The behaviour of the `runInline` method is the same as the `run` method. It will throw an exception if the validation fails, or return a boolean value if the `throwExceptionOnFailure` method has been set to `false`. 246 | 247 | #### Using the Command 248 | 249 | The library comes with a useful command that you can use to validate your config. To use it, you can run the following in 250 | the command line: 251 | 252 | ```bash 253 | php artisan config:validate 254 | ``` 255 | 256 | ##### Only Running on Selected Config Files (Command) 257 | 258 | You might not always want to validate all of the config values in your application. So, you can specify the config files 259 | that you want to validate in the command using the ``` --files ``` option. As an example, if you only wanted to validate 260 | the ``` auth.php ``` config file, you could use the following: 261 | 262 | ```bash 263 | php artisan config:validate --files=auth 264 | ``` 265 | 266 | As a further example, if you wanted to validate the ``` auth.php ``` and ``` app.php ``` files, you could use the following: 267 | 268 | ```bash 269 | php artisan config:validate --files=auth,app 270 | ``` 271 | 272 | ##### Custom Folder Path (Command) 273 | 274 | If you aren't storing your validation files in the default ``` config/validation ``` folder, you can pass a custom folder path 275 | into the ``` --path ``` option. As an example, if you had the files stored in a ``` app/Custom/Validation ``` folder, you 276 | could use the following: 277 | 278 | ```bash 279 | php artisan config:validate --path=app/Custom/Validation 280 | ``` 281 | 282 | #### Using a Service Provider 283 | 284 | You might want to run the config validator automatically on each request to ensure that you have the correct config. This 285 | can be particularly useful if you are in a local environment and switching between Git branches often. However, you might 286 | not want it to always run automatically in production for performance reasons. To run the validation automatically on each 287 | request, you can add it to the ``` boot ``` method of a service provider. 288 | 289 | The example below shows how to only run the validation in the local environment using the ``` AppServiceProvider ```: 290 | 291 | ```php 292 | run(); 306 | } 307 | } 308 | } 309 | 310 | ``` 311 | 312 | #### Throwing and Preventing Exceptions 313 | 314 | By default, the ` ConfigValidator ` will throw an ` InvalidConfigValueException ` exception if the validation fails. The exception will contain 315 | the error message of the first config value that failed the validation. You can prevent the exception from being thrown and instead 316 | rely on the boolean return value of the ` ->run() ` method by using the ` ->throwExceptionOnFailure() ` method. 317 | 318 | By preventing any exceptions from being thrown, it makes it easier for you to get all the failed validation errors using the 319 | ` ->errors() ` method. This will return the errors as an array. 320 | 321 | The example belows shows how you could prevent any exceptions from being thrown so that you can grab the errors: 322 | 323 | ```php 324 | $configValidator = new ConfigValidator(); 325 | 326 | $configValidator->throwExceptionOnFailure(false) 327 | ->run(); 328 | 329 | $errors = $configValidator->errors(); 330 | ``` 331 | 332 | ### Facade 333 | 334 | If you prefer to use facades in Laravel, you can choose to use the provided ``` ConfigValidator ``` facade instead of instantiating the ``` AshAllenDesign\ConfigValidator\Classes\ConfigValidator ``` 335 | class manually. 336 | 337 | The example below shows an example of how you could use the facade to run the config validation: 338 | 339 | ```php 340 | json(['success' => true]); 353 | } 354 | } 355 | ``` 356 | 357 | ## Security 358 | 359 | If you find any security related issues, please contact me directly at [mail@ashallendesign.co.uk](mailto:mail@ashallendesign.co.uk) to report it. 360 | 361 | ## Contribution 362 | 363 | If you wish to make any changes or improvements to the package, feel free to make a pull request. 364 | 365 | Note: A contribution guide will be added soon. 366 | 367 | ## Changelog 368 | 369 | Check the [CHANGELOG](CHANGELOG.md) to get more information about the latest changes. 370 | 371 | ## Upgrading 372 | 373 | Check the [UPGRADE](UPGRADE.md) guide to get more information on how to update this library to newer versions. 374 | 375 | ## License 376 | 377 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 378 | 379 | ## Support Me 380 | 381 | If you've found this package useful, please consider buying a copy of [Battle Ready Laravel](https://battle-ready-laravel.com) to support me and my work. 382 | 383 | Every sale makes a huge difference to me and allows me to spend more time working on open-source projects and tutorials. 384 | 385 | To say a huge thanks, you can use the code **BATTLE20** to get a 20% discount on the book. 386 | 387 | [👉 Get Your Copy!](https://battle-ready-laravel.com) 388 | 389 | [![Battle Ready Laravel](https://ashallendesign.co.uk/images/custom/sponsors/battle-ready-laravel-horizontal-banner.png)](https://battle-ready-laravel.com) 390 | -------------------------------------------------------------------------------- /UPGRADE.md: -------------------------------------------------------------------------------- 1 | # Upgrade Guide 2 | 3 | ## Contents 4 | 5 | - [Upgrading from 1.* to 2.0.0](#upgrading-from-1-to-200) 6 | 7 | ## Upgrading from 1.* to 2.0.0 8 | 9 | ### Minimum PHP Version 10 | 11 | As of v2.0.0, support for PHP 7.3 and 7.4 is removed, so you'll need to use at least PHP 8.0. 12 | 13 | ### Minimum Laravel Version 14 | 15 | As of v2.0.0, support for Laravel 6 and 7 is removed, so you'll need to use at least Laravel 8. 16 | 17 | ### Updated Default Directory 18 | 19 | In previous versions of Laravel Config Validator, the config rules were stored by default in your Laravel application in a `config/validation` folder. This meant that if you ran the `artisan config:cache` command, an exception would be thrown because Laravel would attempt to cache your config validation rules. 20 | 21 | So, as of v2.0.0, the default location has been moved from `config/validation` to `config-validation` in your project's root. 22 | 23 | If you have any existing rules in your project, please make sure to move them to this new folder. 24 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ashallendesign/laravel-config-validator", 3 | "description": "A package for validating your Laravel app's config.", 4 | "type": "library", 5 | "homepage": "https://github.com/ash-jc-allen/laravel-config-validator", 6 | "keywords": [ 7 | "ashallendesign", 8 | "config", 9 | "validation", 10 | "laravel" 11 | ], 12 | "license": "MIT", 13 | "authors": [ 14 | { 15 | "name": "Ash Allen", 16 | "email": "mail@ashallendesign.co.uk" 17 | } 18 | ], 19 | "require": { 20 | "php": "^8.2", 21 | "illuminate/console": "^10.0|^11.0|^12.0", 22 | "illuminate/container": "^10.0|^11.0|^12.0", 23 | "illuminate/validation": "^10.0|^11.0|^12.0", 24 | "illuminate/view": "^10.0|^11.0|^12.0", 25 | "nunomaduro/termwind": "^1.6|^2.0", 26 | "ext-json": "*" 27 | }, 28 | "require-dev": { 29 | "mockery/mockery": "^1.0", 30 | "larastan/larastan": "^1.0|^2.0|^3.0", 31 | "orchestra/testbench": "^8.0|^9.0|^10.0", 32 | "phpunit/phpunit": "^9.0|^10.5|^11.5.3" 33 | }, 34 | "autoload": { 35 | "psr-4": { 36 | "AshAllenDesign\\ConfigValidator\\": "src/" 37 | } 38 | }, 39 | "autoload-dev": { 40 | "psr-4": { 41 | "AshAllenDesign\\ConfigValidator\\Tests\\": "tests/" 42 | } 43 | }, 44 | "extra": { 45 | "laravel": { 46 | "providers": [ 47 | "AshAllenDesign\\ConfigValidator\\Providers\\ConfigValidatorProvider" 48 | ], 49 | "aliases": { 50 | "ConfigValidator": "AshAllenDesign\\ConfigValidator\\Facades\\ConfigValidator" 51 | } 52 | } 53 | }, 54 | "scripts": { 55 | "test": "vendor/bin/phpunit", 56 | "larastan": "vendor/bin/phpstan analyse" 57 | }, 58 | "minimum-stability": "dev", 59 | "prefer-stable": true 60 | } 61 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | includes: 2 | - ./vendor/larastan/larastan/extension.neon 3 | 4 | parameters: 5 | 6 | paths: 7 | - src 8 | 9 | level: 6 10 | 11 | reportUnmatchedIgnoredErrors: false 12 | 13 | ignoreErrors: 14 | - '#Parameter \#1 \$view of function view expects view-string\|null, string given.#' 15 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | src/ 6 | 7 | 8 | 9 | 10 | tests 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /resources/views/validate-config.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
Config validation failed!
3 | 4 |
{{ count($allErrors) }} errors found in your application:
5 |
6 | @foreach ($allErrors as $configField => $errors) 7 |
8 |
9 | {{ $configField }} 10 | 11 | Value: 12 | @if (config($configField)) 13 | @if(is_array(config($configField))) 14 | {{ json_encode(config($configField, JSON_PRETTY_PRINT)) }} 15 | @else 16 | {{ config($configField) }} 17 | @endif 18 | @else 19 | [empty field] 20 | @endif 21 | 22 |
23 | @foreach ($errors as $error) 24 |
- {{ $error }}
25 | @endforeach 26 |
27 | @endforeach 28 |
29 |
30 | -------------------------------------------------------------------------------- /src/Console/Commands/ValidateConfigCommand.php: -------------------------------------------------------------------------------- 1 | configValidator = $configValidator; 49 | } 50 | 51 | /** 52 | * Execute the console command. 53 | * 54 | * @return int 55 | * 56 | * @throws DirectoryNotFoundException 57 | * @throws InvalidConfigValueException 58 | * @throws NoValidationFilesFoundException 59 | */ 60 | public function handle(): int 61 | { 62 | try { 63 | $this->configValidator 64 | ->throwExceptionOnFailure(false) 65 | ->run($this->determineFilesToValidate(), $this->option('path')); 66 | } catch (DirectoryNotFoundException|NoValidationFilesFoundException $exception) { 67 | $this->displayErrorMessage($exception->getMessage()); 68 | 69 | return self::FAILURE; 70 | } 71 | 72 | if (! empty($this->configValidator->errors())) { 73 | render(view('config-validator::validate-config', [ 74 | 'allErrors' => $this->configValidator->errors(), 75 | ])); 76 | 77 | return self::FAILURE; 78 | } 79 | 80 | $this->displaySuccessfulValidationMessage(); 81 | 82 | return self::SUCCESS; 83 | } 84 | 85 | /** 86 | * Determine the config files that should be validated. 87 | * We do this by mapping any comma-separated 'files' 88 | * inputs to an array of strings that can be 89 | * passed to the ConfigValidator object. 90 | * 91 | * @return string[] 92 | */ 93 | private function determineFilesToValidate(): array 94 | { 95 | $filesToValidate = []; 96 | 97 | foreach ($this->option('files') as $fileOption) { 98 | if (Str::contains($fileOption, ',')) { 99 | $exploded = explode(',', $fileOption); 100 | 101 | $filesToValidate = array_merge($filesToValidate, $exploded); 102 | 103 | continue; 104 | } 105 | 106 | $filesToValidate = array_merge($filesToValidate, [$fileOption]); 107 | } 108 | 109 | return $filesToValidate; 110 | } 111 | 112 | private function displaySuccessfulValidationMessage(): void 113 | { 114 | render(<<<'HTML' 115 |
116 | Config validation passed! 117 |
118 | HTML); 119 | } 120 | 121 | private function displayErrorMessage(string $message): void 122 | { 123 | render(<<$message 125 | HTML); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/Console/Commands/ValidationMakeCommand.php: -------------------------------------------------------------------------------- 1 | resolveStubPath('/stubs/config-validation.stub'); 38 | } 39 | 40 | /** 41 | * Resolve the fully-qualified path to the stub. 42 | * 43 | * @param string $stub 44 | * @return string 45 | */ 46 | protected function resolveStubPath(string $stub): string 47 | { 48 | return file_exists($customPath = $this->laravel->basePath(trim($stub, '/'))) 49 | ? $customPath 50 | : __DIR__.$stub; 51 | } 52 | 53 | /** 54 | * Get the destination class path. 55 | * 56 | * @param string $name 57 | * @return string 58 | */ 59 | protected function getPath($name): string 60 | { 61 | return $this->laravel->basePath() 62 | .'/config-validation/' 63 | .str_replace('\\', '/', $this->argument('name')).'.php'; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Console/Commands/stubs/config-validation.stub: -------------------------------------------------------------------------------- 1 | rules([]), 7 | ]; 8 | -------------------------------------------------------------------------------- /src/Exceptions/ConfigValidatorException.php: -------------------------------------------------------------------------------- 1 | errors() 11 | * 12 | * @see \AshAllenDesign\ConfigValidator\Services\ConfigValidator 13 | */ 14 | class ConfigValidator extends Facade 15 | { 16 | /** 17 | * Get the registered name of the component. 18 | * 19 | * @return string 20 | */ 21 | protected static function getFacadeAccessor(): string 22 | { 23 | return 'config-validator'; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Providers/ConfigValidatorProvider.php: -------------------------------------------------------------------------------- 1 | app->alias(ConfigValidator::class, 'config-validator'); 20 | } 21 | 22 | /** 23 | * Bootstrap any application services. 24 | * 25 | * @return void 26 | */ 27 | public function boot(): void 28 | { 29 | // Publish the default config validation rules. 30 | $this->publishes([ 31 | __DIR__.'/../../stubs/config-validation' => base_path('config-validation'), 32 | ], 'config-validator-defaults'); 33 | 34 | if ($this->app->runningInConsole()) { 35 | $this->loadViewsFrom(__DIR__.'/../../resources/views', 'config-validator'); 36 | 37 | $this->commands([ 38 | ValidateConfigCommand::class, 39 | ValidationMakeCommand::class, 40 | ]); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Services/ConfigValidator.php: -------------------------------------------------------------------------------- 1 | 28 | */ 29 | private array $errors = []; 30 | 31 | /** 32 | * Specifies whether if an exception should be thrown 33 | * if the config validation fails. 34 | * 35 | * @var bool 36 | */ 37 | private bool $throwExceptionOnFailure = true; 38 | 39 | /** 40 | * ConfigValidator constructor. 41 | * 42 | * @param ValidationRepository|null $validationRepository 43 | */ 44 | public function __construct(?ValidationRepository $validationRepository = null) 45 | { 46 | $this->validationRepository = $validationRepository ?? new ValidationRepository(); 47 | } 48 | 49 | /** 50 | * Return the validation error messages. 51 | * 52 | * @return array 53 | */ 54 | public function errors(): array 55 | { 56 | return $this->errors; 57 | } 58 | 59 | /** 60 | * Determine whether an exception should be thrown if 61 | * the validation fails. 62 | * 63 | * @param bool $throwException 64 | * @return ConfigValidator 65 | */ 66 | public function throwExceptionOnFailure(bool $throwException = true): self 67 | { 68 | $this->throwExceptionOnFailure = $throwException; 69 | 70 | return $this; 71 | } 72 | 73 | /** 74 | * Handle the loading of the config validation files 75 | * and then validate the config. 76 | * 77 | * @param string[] $configFiles 78 | * @param string|null $validationFolderPath 79 | * @return bool 80 | * 81 | * @throws InvalidConfigValueException 82 | * @throws DirectoryNotFoundException 83 | * @throws NoValidationFilesFoundException 84 | */ 85 | public function run(array $configFiles = [], ?string $validationFolderPath = null): bool 86 | { 87 | $validationFiles = $this->getValidationFiles($configFiles, $validationFolderPath); 88 | 89 | foreach ($validationFiles as $key => $path) { 90 | $ruleSet = require $path; 91 | 92 | $this->validationRepository->push($key, $ruleSet); 93 | } 94 | 95 | return $this->runValidator(); 96 | } 97 | 98 | /** 99 | * Validate config values with rules that are passed in inline rather 100 | * than being read from a file in the filesystem. 101 | * 102 | * @param array> $ruleGroups 103 | * 104 | * @throws InvalidConfigValueException 105 | */ 106 | public function runInline(array $ruleGroups): bool 107 | { 108 | foreach ($ruleGroups as $configKey => $rules) { 109 | $this->validationRepository->push($configKey, $rules); 110 | } 111 | 112 | return $this->runValidator(); 113 | } 114 | 115 | /** 116 | * Validate the config values against the config rules 117 | * that have been set. If throwExceptionOnFailure is 118 | * set to true, the validator's first error message 119 | * will be used as the message in the thrown 120 | * exception. 121 | * 122 | * @return bool 123 | * 124 | * @throws InvalidConfigValueException 125 | */ 126 | private function runValidator(): bool 127 | { 128 | $ruleSet = $this->validationRepository->asArray(); 129 | 130 | // Build an associative array of the keys that we are validating. We 131 | // can pass this to the validator so that the config key names 132 | // are preserved in the error messages and not changed. 133 | $attributes = array_combine( 134 | array_keys($ruleSet['rules']), 135 | array_keys($ruleSet['rules']), 136 | ); 137 | 138 | $validator = Validator::make( 139 | $ruleSet['config_values'], 140 | $ruleSet['rules'], 141 | $ruleSet['messages'], 142 | $attributes, 143 | ); 144 | 145 | if ($validator->fails()) { 146 | $this->errors = $validator->errors()->messages(); 147 | 148 | if ($this->throwExceptionOnFailure) { 149 | throw new InvalidConfigValueException($validator->errors()->first()); 150 | } 151 | 152 | return false; 153 | } 154 | 155 | return true; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/Services/Rule.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | private array $rules = []; 28 | 29 | /** 30 | * The custom messages being used when validating the 31 | * config field. 32 | * 33 | * @var array 34 | */ 35 | private array $messages = []; 36 | 37 | /** 38 | * @var string[] 39 | */ 40 | private array $environments = []; 41 | 42 | /** 43 | * Rule constructor. 44 | * 45 | * @param string $fieldName 46 | */ 47 | public function __construct(string $fieldName) 48 | { 49 | $this->fieldName = $fieldName; 50 | } 51 | 52 | /** 53 | * A helper method used for creating a new rule. 54 | * 55 | * @param string $fieldName 56 | * @return Rule 57 | */ 58 | public static function make(string $fieldName): Rule 59 | { 60 | return new self($fieldName); 61 | } 62 | 63 | /** 64 | * Set the rules used for validating the config. 65 | * 66 | * @param array $rules 67 | * @return $this 68 | */ 69 | public function rules(array $rules): self 70 | { 71 | $this->rules = array_merge($this->rules, $rules); 72 | 73 | return $this; 74 | } 75 | 76 | /** 77 | * Set the custom messages used when validating the 78 | * config. 79 | * 80 | * @param array $messages 81 | * @return $this 82 | */ 83 | public function messages(array $messages): self 84 | { 85 | $this->messages = array_merge($this->messages, $messages); 86 | 87 | return $this; 88 | } 89 | 90 | /** 91 | * @param string[] $environments 92 | */ 93 | public function environments(array $environments): self 94 | { 95 | $this->environments = array_merge($this->environments, $environments); 96 | 97 | return $this; 98 | } 99 | 100 | /** 101 | * Get the config field name that this rule relates to. 102 | * 103 | * @return string 104 | */ 105 | public function getFieldName(): string 106 | { 107 | return $this->fieldName; 108 | } 109 | 110 | /** 111 | * Get the validation rules set within this rule. 112 | * 113 | * @return array 114 | */ 115 | public function getRules(): array 116 | { 117 | return $this->rules; 118 | } 119 | 120 | /** 121 | * Get the validation messages set within this rule. 122 | * 123 | * @return array 124 | */ 125 | public function getMessages(): array 126 | { 127 | return $this->messages; 128 | } 129 | 130 | /** 131 | * Get the app environments set within this rule. 132 | * 133 | * @return string[] 134 | */ 135 | public function getEnvironments(): array 136 | { 137 | return $this->environments; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/Services/ValidationRepository.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | private array $configValues = []; 16 | 17 | /** 18 | * An array of rules that are to be used for validating 19 | * the config values. 20 | * 21 | * @var array> 22 | */ 23 | private array $rules = []; 24 | 25 | /** 26 | * An array of custom messages that are to be used when 27 | * validating the config values. 28 | * 29 | * @var array 30 | */ 31 | private array $messages = []; 32 | 33 | /** 34 | * Add the new Rules' data to the repository so that we 35 | * can read the data later when validating. When doing 36 | * this, we check that the rule is supposed to run in 37 | * this environment. Any rules set to run in other 38 | * environments won't be stored and will just be 39 | * skipped. 40 | * 41 | * @param string $key 42 | * @param Rule[] $rules 43 | */ 44 | public function push(string $key, array $rules): void 45 | { 46 | foreach ($rules as $field => $rule) { 47 | if (! $this->shouldValidateUsingThisRule($rule)) { 48 | continue; 49 | } 50 | 51 | $configKey = $key.'.'.$rule->getFieldName(); 52 | 53 | // Add the rules for the field to the repository. 54 | $this->rules[$configKey] = $rule->getRules(); 55 | 56 | // Add the current config values for the field to the repository. 57 | $this->fetchCurrentConfigValues($key, $rule); 58 | 59 | // Add any custom messages for the field to the repository. 60 | foreach ($rule->getMessages() as $messageField => $message) { 61 | $this->messages[$configKey.'.'.$messageField] = $message; 62 | } 63 | } 64 | } 65 | 66 | /** 67 | * Return the class' rules, config values and messages 68 | * as an array. 69 | * 70 | * @return array 71 | */ 72 | public function asArray(): array 73 | { 74 | return [ 75 | 'rules' => $this->rules, 76 | 'messages' => $this->messages, 77 | 'config_values' => $this->configValues, 78 | ]; 79 | } 80 | 81 | /** 82 | * Determine whether if we should be storing the rule 83 | * to use for validation. We do this by checking if 84 | * an environment has been explicitly defined on 85 | * the rule. If it hasn't, we can add the rule. 86 | * If it has, we can only add the rule if the 87 | * environment matches. 88 | * 89 | * @param Rule $rule 90 | * @return bool 91 | */ 92 | private function shouldValidateUsingThisRule(Rule $rule): bool 93 | { 94 | $environments = $rule->getEnvironments(); 95 | 96 | if (empty($environments)) { 97 | return true; 98 | } 99 | 100 | return in_array(app()->environment(), $environments, true); 101 | } 102 | 103 | /** 104 | * This takes a config field and hydrates it into a 105 | * nested array that we can validate against. For 106 | * example, we can use it convert the config 107 | * item 'mail.from.address' that is equal 108 | * to 'example@domain.com' to this... 109 | * 110 | * [ 111 | * 'mail' => [ 112 | * 'from' => [ 113 | * 'address' => 'example@domain.com' 114 | * ] 115 | * ] 116 | * ] 117 | * 118 | * @param string $configKey 119 | * @param Rule $rule 120 | * @return array 121 | */ 122 | private function hydrateConfigValueArray(string $configKey, Rule $rule): array 123 | { 124 | $keys = explode('.', $rule->getFieldName()); 125 | $value = config($configKey.'.'.$rule->getFieldName()); 126 | 127 | while ($key = array_pop($keys)) { 128 | $value = [$key => $value]; 129 | } 130 | 131 | return $value; 132 | } 133 | 134 | /** 135 | * Fetch the current config values that are set in the 136 | * system and then add them to the repository for 137 | * validating. 138 | * 139 | * @param string $key 140 | * @param Rule $rule 141 | */ 142 | private function fetchCurrentConfigValues(string $key, Rule $rule): void 143 | { 144 | $hydrated = $this->hydrateConfigValueArray($key, $rule); 145 | $field = array_keys($hydrated)[0]; 146 | 147 | if (empty($this->configValues[$key][$field])) { 148 | $this->configValues[$key][$field] = []; 149 | } 150 | 151 | // If the config value is nested (e.g. mail.from.address) then we add 152 | // it differently to standard non-nested fields. 153 | if (is_array($hydrated[$field])) { 154 | $this->configValues[$key][$field] = array_merge_recursive( 155 | $this->configValues[$key][$field], 156 | $hydrated[$field], 157 | ); 158 | } else { 159 | $this->configValues[$key][array_key_first($hydrated)] = $hydrated[array_key_first($hydrated)]; 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/Traits/LoadsConfigValidationFiles.php: -------------------------------------------------------------------------------- 1 | 19 | * 20 | * @throws DirectoryNotFoundException 21 | * @throws NoValidationFilesFoundException 22 | */ 23 | private function getValidationFiles(array $configFiles = [], ?string $validationFolderPath = null): array 24 | { 25 | $files = []; 26 | 27 | $folderPath = $this->determineFolderPath($validationFolderPath); 28 | $configFileNames = $this->determineFilesToRead($configFiles); 29 | 30 | $configValidationFiles = Finder::create()->files()->name($configFileNames)->in($folderPath); 31 | 32 | if (! $configValidationFiles->count()) { 33 | throw new NoValidationFilesFoundException('No config validation files were found inside the directory.'); 34 | } 35 | 36 | foreach ($configValidationFiles as $file) { 37 | $directory = $this->getNestedDirectory($file, $folderPath); 38 | 39 | $files[$directory.basename($file->getRealPath(), '.php')] = $file->getRealPath(); 40 | } 41 | 42 | return $files; 43 | } 44 | 45 | /** 46 | * If a custom validation folder path has been set then 47 | * get the full path. Otherwise, return the default 48 | * path in the config/validation folder. If the 49 | * folder does not exist, an exception will 50 | * be thrown. 51 | * 52 | * @param string|null $validationFolderPath 53 | * @return string 54 | * 55 | * @throws DirectoryNotFoundException 56 | */ 57 | protected function determineFolderPath(?string $validationFolderPath = null): string 58 | { 59 | $path = base_path($validationFolderPath ?? 'config-validation'); 60 | 61 | if ($folderPath = realpath($path)) { 62 | return $folderPath; 63 | } 64 | 65 | throw new DirectoryNotFoundException('The directory '.$path.' does not exist.'); 66 | } 67 | 68 | /** 69 | * Get the configuration file nesting path. 70 | * 71 | * @param SplFileInfo $file 72 | * @param string $configPath 73 | * @return string 74 | */ 75 | protected function getNestedDirectory(SplFileInfo $file, string $configPath): string 76 | { 77 | $directory = $file->getPath(); 78 | 79 | if ($nested = trim(str_replace($configPath, '', $directory), DIRECTORY_SEPARATOR)) { 80 | $nested = str_replace(DIRECTORY_SEPARATOR, '.', $nested).'.'; 81 | } 82 | 83 | return $nested; 84 | } 85 | 86 | /** 87 | * If the no specific files were defined, we will read 88 | * all of the files in the directory. Otherwise, we 89 | * only read the files specified. For example, if 90 | * ['cache', 'auth'] were passed in, we would 91 | * return ['cache.php', 'auth.php']. 92 | * 93 | * @param string[] $configFiles 94 | * @return string[] 95 | */ 96 | protected function determineFilesToRead(array $configFiles = []): array 97 | { 98 | if (empty($configFiles)) { 99 | return ['*.php']; 100 | } 101 | 102 | return array_map(static function (string $configValue): string { 103 | return $configValue.'.php'; 104 | }, $configFiles); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /stubs/config-validation/app.php: -------------------------------------------------------------------------------- 1 | rules(['string']), 7 | 8 | Rule::make('env')->rules(['string']), 9 | 10 | Rule::make('debug')->rules(['bool']), 11 | 12 | Rule::make('url')->rules(['url']), 13 | 14 | Rule::make('asset_url')->rules(['url', 'nullable']), 15 | 16 | Rule::make('timezone')->rules(['string']), 17 | 18 | Rule::make('locale')->rules(['string']), 19 | 20 | Rule::make('faker_locale')->rules(['string']), 21 | 22 | Rule::make('key')->rules(['string']), 23 | 24 | Rule::make('cipher')->rules(['string']), 25 | 26 | Rule::make('providers')->rules(['array']), 27 | 28 | Rule::make('aliases')->rules(['array']), 29 | ]; 30 | -------------------------------------------------------------------------------- /stubs/config-validation/auth.php: -------------------------------------------------------------------------------- 1 | rules(['array']), 7 | 8 | Rule::make('defaults.guard')->rules(['string']), 9 | 10 | Rule::make('defaults.passwords')->rules(['string']), 11 | 12 | Rule::make('guards')->rules(['array']), 13 | 14 | Rule::make('guards.web')->rules(['array']), 15 | 16 | Rule::make('guards.web.driver')->rules(['string']), 17 | 18 | Rule::make('guards.web.provider')->rules(['string']), 19 | 20 | Rule::make('guards.api')->rules(['array']), 21 | 22 | Rule::make('guards.api.driver')->rules(['string']), 23 | 24 | Rule::make('guards.api.provider')->rules(['string']), 25 | 26 | Rule::make('guards.api.hash')->rules(['bool']), 27 | 28 | Rule::make('providers')->rules(['array']), 29 | 30 | Rule::make('providers.users')->rules(['array']), 31 | 32 | Rule::make('providers.users.driver')->rules(['string', 'in:eloquent,database']), 33 | 34 | Rule::make('providers.users.model')->rules(['string']), 35 | 36 | Rule::make('passwords')->rules(['array']), 37 | 38 | Rule::make('passwords.users')->rules(['array']), 39 | 40 | Rule::make('passwords.users.provider')->rules(['string']), 41 | 42 | Rule::make('passwords.users.table')->rules(['string']), 43 | 44 | Rule::make('passwords.users.expire')->rules(['integer']), 45 | 46 | Rule::make('passwords.users.throttle')->rules(['integer']), 47 | 48 | Rule::make('password_timeout')->rules(['integer']), 49 | ]; 50 | -------------------------------------------------------------------------------- /stubs/config-validation/broadcasting.php: -------------------------------------------------------------------------------- 1 | rules(['string', 'in:pusher,redis,log,null']), 7 | 8 | Rule::make('connections')->rules(['array']), 9 | 10 | Rule::make('connections.pusher')->rules(['array']), 11 | 12 | Rule::make('connections.pusher.driver')->rules(['string']), 13 | 14 | Rule::make('connections.pusher.key')->rules(['string']), 15 | 16 | Rule::make('connections.pusher.secret')->rules(['string']), 17 | 18 | Rule::make('connections.pusher.app_id')->rules(['string']), 19 | 20 | Rule::make('connections.pusher.options')->rules(['array']), 21 | 22 | Rule::make('connections.pusher.options.cluster')->rules(['string']), 23 | 24 | Rule::make('connections.pusher.options.useTLS')->rules(['bool']), 25 | 26 | Rule::make('connections.redis')->rules(['array']), 27 | 28 | Rule::make('connections.redis.driver')->rules(['string']), 29 | 30 | Rule::make('connections.redis.connection')->rules(['string']), 31 | 32 | Rule::make('connections.log')->rules(['array']), 33 | 34 | Rule::make('connections.log.driver')->rules(['string']), 35 | 36 | Rule::make('connections.null')->rules(['array']), 37 | 38 | Rule::make('connections.null.driver')->rules(['string']), 39 | ]; 40 | -------------------------------------------------------------------------------- /stubs/config-validation/cache.php: -------------------------------------------------------------------------------- 1 | rules(['string', 'in:apc,array,database,file,memcached,redis,dynamodb']), 7 | 8 | Rule::make('stores')->rules(['array']), 9 | 10 | Rule::make('stores.apc')->rules(['array']), 11 | Rule::make('stores.apc.driver')->rules(['string']), 12 | 13 | Rule::make('stores.array')->rules(['array']), 14 | Rule::make('stores.array.driver')->rules(['string']), 15 | Rule::make('stores.array.serialize')->rules(['bool']), 16 | 17 | Rule::make('stores.database')->rules(['array']), 18 | Rule::make('stores.database.driver')->rules(['string']), 19 | Rule::make('stores.database.table')->rules(['string']), 20 | Rule::make('stores.database.connection')->rules(['string', 'nullable']), 21 | 22 | Rule::make('stores.file')->rules(['array']), 23 | Rule::make('stores.file.driver')->rules(['string']), 24 | Rule::make('stores.file.path')->rules(['string']), 25 | 26 | Rule::make('stores.memcached')->rules(['array']), 27 | Rule::make('stores.memcached.driver')->rules(['string']), 28 | Rule::make('stores.memcached.persistent_id')->rules(['string', 'nullable']), 29 | Rule::make('stores.memcached.sasl')->rules(['array']), 30 | Rule::make('stores.memcached.options')->rules(['array']), 31 | Rule::make('stores.memcached.servers')->rules(['array']), 32 | 33 | Rule::make('stores.redis')->rules(['array']), 34 | Rule::make('stores.redis.driver')->rules(['string']), 35 | Rule::make('stores.redis.connection')->rules(['string']), 36 | 37 | Rule::make('stores.dynamodb')->rules(['array']), 38 | Rule::make('stores.dynamodb.driver')->rules(['string']), 39 | Rule::make('stores.dynamodb.key')->rules(['string', 'nullable']), 40 | Rule::make('stores.dynamodb.secret')->rules(['string', 'nullable']), 41 | Rule::make('stores.dynamodb.region')->rules(['string']), 42 | Rule::make('stores.dynamodb.table')->rules(['string']), 43 | Rule::make('stores.dynamodb.endpoint')->rules(['string', 'nullable']), 44 | 45 | Rule::make('prefix')->rules(['string']), 46 | ]; 47 | -------------------------------------------------------------------------------- /stubs/config-validation/cors.php: -------------------------------------------------------------------------------- 1 | rules(['array']), 7 | 8 | Rule::make('allowed_methods')->rules(['array']), 9 | 10 | Rule::make('allowed_origins')->rules(['array']), 11 | 12 | Rule::make('allowed_origins_patterns')->rules(['array']), 13 | 14 | Rule::make('allowed_headers')->rules(['array']), 15 | 16 | Rule::make('max_age')->rules(['integer']), 17 | 18 | Rule::make('supports_credentials')->rules(['bool']), 19 | 20 | ]; 21 | -------------------------------------------------------------------------------- /stubs/config-validation/database.php: -------------------------------------------------------------------------------- 1 | rules(['string']), 7 | 8 | Rule::make('connections')->rules(['array']), 9 | 10 | Rule::make('connections.sqlite')->rules(['array']), 11 | Rule::make('connections.sqlite.driver')->rules(['string']), 12 | Rule::make('connections.sqlite.url')->rules(['string', 'nullable']), 13 | Rule::make('connections.sqlite.database')->rules(['string']), 14 | Rule::make('connections.sqlite.prefix')->rules(['string']), 15 | Rule::make('connections.sqlite.foreign_key_constraints')->rules(['bool']), 16 | 17 | Rule::make('connections.mysql')->rules(['array']), 18 | Rule::make('connections.mysql.driver')->rules(['string']), 19 | Rule::make('connections.mysql.url')->rules(['string', 'nullable']), 20 | Rule::make('connections.mysql.host')->rules(['string']), 21 | Rule::make('connections.mysql.port')->rules(['string']), 22 | Rule::make('connections.mysql.database')->rules(['string']), 23 | Rule::make('connections.mysql.username')->rules(['string']), 24 | Rule::make('connections.mysql.password')->rules(['string']), 25 | Rule::make('connections.mysql.unix_socket')->rules(['string']), 26 | Rule::make('connections.mysql.charset')->rules(['string']), 27 | Rule::make('connections.mysql.collation')->rules(['string']), 28 | Rule::make('connections.mysql.prefix')->rules(['string']), 29 | Rule::make('connections.mysql.prefix_indexes')->rules(['bool']), 30 | Rule::make('connections.mysql.strict')->rules(['bool']), 31 | Rule::make('connections.mysql.engine')->rules(['string', 'nullable']), 32 | Rule::make('connections.mysql.options')->rules(['array']), 33 | 34 | Rule::make('connections.pgsql')->rules(['array']), 35 | Rule::make('connections.pgsql.driver')->rules(['string']), 36 | Rule::make('connections.pgsql.url')->rules(['string', 'nullable']), 37 | Rule::make('connections.pgsql.host')->rules(['string']), 38 | Rule::make('connections.pgsql.port')->rules(['string']), 39 | Rule::make('connections.pgsql.database')->rules(['string']), 40 | Rule::make('connections.pgsql.username')->rules(['string']), 41 | Rule::make('connections.pgsql.password')->rules(['string']), 42 | Rule::make('connections.pgsql.charset')->rules(['string']), 43 | Rule::make('connections.pgsql.prefix')->rules(['string']), 44 | Rule::make('connections.pgsql.prefix_indexes')->rules(['bool']), 45 | Rule::make('connections.pgsql.schema')->rules(['string']), 46 | Rule::make('connections.pgsql.sslmode')->rules(['string']), 47 | 48 | Rule::make('connections.sqlsrv')->rules(['array']), 49 | Rule::make('connections.sqlsrv.driver')->rules(['string']), 50 | Rule::make('connections.sqlsrv.url')->rules(['string', 'nullable']), 51 | Rule::make('connections.sqlsrv.host')->rules(['string']), 52 | Rule::make('connections.sqlsrv.port')->rules(['string']), 53 | Rule::make('connections.sqlsrv.database')->rules(['string']), 54 | Rule::make('connections.sqlsrv.username')->rules(['string']), 55 | Rule::make('connections.sqlsrv.password')->rules(['string']), 56 | Rule::make('connections.sqlsrv.charset')->rules(['string']), 57 | Rule::make('connections.sqlsrv.prefix')->rules(['string']), 58 | Rule::make('connections.sqlsrv.prefix_indexes')->rules(['bool']), 59 | 60 | Rule::make('migrations')->rules(['string']), 61 | 62 | Rule::make('redis')->rules(['array']), 63 | 64 | Rule::make('redis.client')->rules(['string']), 65 | 66 | Rule::make('redis.options')->rules(['array']), 67 | Rule::make('redis.options.cluster')->rules(['string']), 68 | Rule::make('redis.options.prefix')->rules(['string']), 69 | 70 | Rule::make('redis.default')->rules(['array']), 71 | Rule::make('redis.default.url')->rules(['string', 'nullable']), 72 | Rule::make('redis.default.host')->rules(['string']), 73 | Rule::make('redis.default.password')->rules(['string', 'nullable']), 74 | Rule::make('redis.default.port')->rules(['string']), 75 | Rule::make('redis.default.database')->rules(['alpha_dash']), 76 | 77 | Rule::make('redis.cache')->rules(['array']), 78 | Rule::make('redis.cache.url')->rules(['string', 'nullable']), 79 | Rule::make('redis.cache.host')->rules(['string']), 80 | Rule::make('redis.cache.password')->rules(['string', 'nullable']), 81 | Rule::make('redis.cache.port')->rules(['string']), 82 | Rule::make('redis.cache.database')->rules(['alpha_dash']), 83 | ]; 84 | -------------------------------------------------------------------------------- /stubs/config-validation/filesystems.php: -------------------------------------------------------------------------------- 1 | rules(['string']), 7 | 8 | Rule::make('cloud')->rules(['string']), 9 | 10 | Rule::make('disks')->rules(['array']), 11 | 12 | Rule::make('disks.local')->rules(['array']), 13 | Rule::make('disks.local.driver')->rules(['string']), 14 | Rule::make('disks.local.root')->rules(['string']), 15 | 16 | Rule::make('disks.public')->rules(['array']), 17 | Rule::make('disks.public.driver')->rules(['string']), 18 | Rule::make('disks.public.root')->rules(['string']), 19 | Rule::make('disks.public.url')->rules(['string']), 20 | Rule::make('disks.public.visibility')->rules(['string']), 21 | 22 | Rule::make('disks.s3')->rules(['array']), 23 | Rule::make('disks.s3.driver')->rules(['string']), 24 | Rule::make('disks.s3.key')->rules(['string', 'nullable']), 25 | Rule::make('disks.s3.secret')->rules(['string', 'nullable']), 26 | Rule::make('disks.s3.region')->rules(['string', 'nullable']), 27 | Rule::make('disks.s3.bucket')->rules(['string', 'nullable']), 28 | Rule::make('disks.s3.url')->rules(['string', 'nullable']), 29 | Rule::make('disks.s3.endpoint')->rules(['string', 'nullable']), 30 | 31 | Rule::make('links')->rules(['array']), 32 | ]; 33 | -------------------------------------------------------------------------------- /stubs/config-validation/hashing.php: -------------------------------------------------------------------------------- 1 | rules(['string', 'in:bcrypt,argon,argon2id']), 7 | 8 | Rule::make('bcrypt')->rules(['array']), 9 | Rule::make('bcrypt.rounds')->rules(['integer']), 10 | 11 | Rule::make('argon')->rules(['array']), 12 | Rule::make('argon.memory')->rules(['integer']), 13 | Rule::make('argon.threads')->rules(['integer']), 14 | Rule::make('argon.time')->rules(['integer']), 15 | ]; 16 | -------------------------------------------------------------------------------- /stubs/config-validation/logging.php: -------------------------------------------------------------------------------- 1 | rules(['string']), 7 | 8 | Rule::make('channels')->rules(['array']), 9 | 10 | Rule::make('channels.stack')->rules(['array']), 11 | Rule::make('channels.stack.driver')->rules(['string']), 12 | Rule::make('channels.stack.channels')->rules(['array']), 13 | Rule::make('channels.stack.ignore_exceptions')->rules(['bool']), 14 | 15 | Rule::make('channels.single')->rules(['array']), 16 | Rule::make('channels.single.driver')->rules(['string']), 17 | Rule::make('channels.single.path')->rules(['string']), 18 | Rule::make('channels.single.level')->rules(['string']), 19 | 20 | Rule::make('channels.daily')->rules(['array']), 21 | Rule::make('channels.daily.driver')->rules(['string']), 22 | Rule::make('channels.daily.path')->rules(['string']), 23 | Rule::make('channels.daily.level')->rules(['string']), 24 | Rule::make('channels.daily.days')->rules(['integer']), 25 | 26 | Rule::make('channels.slack')->rules(['array']), 27 | Rule::make('channels.slack.driver')->rules(['string']), 28 | Rule::make('channels.slack.url')->rules(['string', 'nullable']), 29 | Rule::make('channels.slack.username')->rules(['string']), 30 | Rule::make('channels.slack.emoji')->rules(['string']), 31 | Rule::make('channels.slack.level')->rules(['string']), 32 | 33 | Rule::make('channels.papertrail')->rules(['array']), 34 | Rule::make('channels.papertrail.driver')->rules(['string']), 35 | Rule::make('channels.papertrail.level')->rules(['string']), 36 | Rule::make('channels.papertrail.handler')->rules(['string']), 37 | Rule::make('channels.papertrail.handler_with')->rules(['array']), 38 | 39 | Rule::make('channels.stderr')->rules(['array']), 40 | Rule::make('channels.stderr.driver')->rules(['string']), 41 | Rule::make('channels.stderr.handler')->rules(['string']), 42 | Rule::make('channels.stderr.formatter')->rules(['string', 'nullable']), 43 | Rule::make('channels.stderr.with')->rules(['array']), 44 | 45 | Rule::make('channels.syslog')->rules(['array']), 46 | Rule::make('channels.syslog.driver')->rules(['string']), 47 | Rule::make('channels.syslog.level')->rules(['string']), 48 | 49 | Rule::make('channels.errorlog')->rules(['array']), 50 | Rule::make('channels.errorlog.driver')->rules(['string']), 51 | Rule::make('channels.errorlog.level')->rules(['string']), 52 | 53 | Rule::make('channels.null')->rules(['array']), 54 | Rule::make('channels.null.driver')->rules(['string']), 55 | Rule::make('channels.null.handler')->rules(['string']), 56 | 57 | Rule::make('channels.emergency')->rules(['array']), 58 | Rule::make('channels.emergency.path')->rules(['string']), 59 | ]; 60 | -------------------------------------------------------------------------------- /stubs/config-validation/mail.php: -------------------------------------------------------------------------------- 1 | rules(['string', 'in:smtp,sendmail,mailgun,ses,postmark,log,array']), 7 | 8 | Rule::make('mailers')->rules(['array']), 9 | 10 | Rule::make('mailers.smtp')->rules(['array']), 11 | Rule::make('mailers.smtp.transport')->rules(['string']), 12 | Rule::make('mailers.smtp.host')->rules(['string']), 13 | Rule::make('mailers.smtp.port')->rules(['integer']), 14 | Rule::make('mailers.smtp.encryption')->rules(['string', 'nullable']), 15 | Rule::make('mailers.smtp.username')->rules(['string', 'nullable']), 16 | Rule::make('mailers.smtp.password')->rules(['string', 'nullable']), 17 | Rule::make('mailers.smtp.timeout')->rules(['integer', 'nullable']), 18 | Rule::make('mailers.smtp.auth_mode')->rules(['string', 'nullable']), 19 | 20 | Rule::make('mailers.ses')->rules(['array']), 21 | Rule::make('mailers.ses.transport')->rules(['string']), 22 | 23 | Rule::make('mailers.mailgun')->rules(['array']), 24 | Rule::make('mailers.mailgun.transport')->rules(['string']), 25 | 26 | Rule::make('mailers.postmark')->rules(['array']), 27 | Rule::make('mailers.postmark.transport')->rules(['string']), 28 | 29 | Rule::make('mailers.sendmail')->rules(['array']), 30 | Rule::make('mailers.sendmail.transport')->rules(['string']), 31 | Rule::make('mailers.sendmail.path')->rules(['string']), 32 | 33 | Rule::make('mailers.log')->rules(['array']), 34 | Rule::make('mailers.log.transport')->rules(['string']), 35 | Rule::make('mailers.log.channel')->rules(['string', 'nullable']), 36 | 37 | Rule::make('mailers.array')->rules(['array']), 38 | Rule::make('mailers.array.transport')->rules(['string']), 39 | 40 | Rule::make('from')->rules(['array']), 41 | Rule::make('from.address')->rules(['email']), 42 | Rule::make('from.name')->rules(['string']), 43 | 44 | Rule::make('markdown')->rules(['array']), 45 | Rule::make('markdown.theme')->rules(['string']), 46 | Rule::make('markdown.paths')->rules(['array']), 47 | ]; 48 | -------------------------------------------------------------------------------- /stubs/config-validation/queue.php: -------------------------------------------------------------------------------- 1 | rules(['string', 'in:sync,database,beanstalkd,sqs,redis,null']), 7 | 8 | Rule::make('connections')->rules(['array']), 9 | 10 | Rule::make('connections.sync')->rules(['array']), 11 | Rule::make('connections.sync.driver')->rules(['string']), 12 | 13 | Rule::make('connections.database')->rules(['array']), 14 | Rule::make('connections.database.driver')->rules(['string']), 15 | Rule::make('connections.database.table')->rules(['string']), 16 | Rule::make('connections.database.queue')->rules(['string']), 17 | Rule::make('connections.database.retry_after')->rules(['integer']), 18 | 19 | Rule::make('connections.beanstalkd')->rules(['array']), 20 | Rule::make('connections.beanstalkd.driver')->rules(['string']), 21 | Rule::make('connections.beanstalkd.host')->rules(['string']), 22 | Rule::make('connections.beanstalkd.queue')->rules(['string']), 23 | Rule::make('connections.beanstalkd.retry_after')->rules(['integer']), 24 | Rule::make('connections.beanstalkd.block_for')->rules(['integer']), 25 | 26 | Rule::make('connections.sqs')->rules(['array']), 27 | Rule::make('connections.sqs.driver')->rules(['string']), 28 | Rule::make('connections.sqs.key')->rules(['string', 'nullable']), 29 | Rule::make('connections.sqs.secret')->rules(['string', 'nullable']), 30 | Rule::make('connections.sqs.prefix')->rules(['string']), 31 | Rule::make('connections.sqs.queue')->rules(['string']), 32 | Rule::make('connections.sqs.suffix')->rules(['string', 'nullable']), 33 | Rule::make('connections.sqs.region')->rules(['string']), 34 | 35 | Rule::make('connections.redis')->rules(['array']), 36 | Rule::make('connections.redis.driver')->rules(['string']), 37 | Rule::make('connections.redis.connection')->rules(['string']), 38 | Rule::make('connections.redis.queue')->rules(['string']), 39 | Rule::make('connections.redis.retry_after')->rules(['integer', 'nullable']), 40 | Rule::make('connections.redis.block_for')->rules(['integer', 'nullable']), 41 | 42 | Rule::make('failed')->rules(['array']), 43 | Rule::make('failed.driver')->rules(['string']), 44 | Rule::make('failed.database')->rules(['string']), 45 | Rule::make('failed.table')->rules(['string']), 46 | ]; 47 | -------------------------------------------------------------------------------- /stubs/config-validation/services.php: -------------------------------------------------------------------------------- 1 | rules(['array']), 7 | Rule::make('mailgun.domain')->rules(['string', 'nullable']), 8 | Rule::make('mailgun.secret')->rules(['string', 'nullable']), 9 | Rule::make('mailgun.endpoint')->rules(['string']), 10 | 11 | Rule::make('postmark')->rules(['array']), 12 | Rule::make('postmark.token')->rules(['string', 'nullable']), 13 | 14 | Rule::make('ses')->rules(['array']), 15 | Rule::make('ses.key')->rules(['string', 'nullable']), 16 | Rule::make('ses.secret')->rules(['string', 'nullable']), 17 | Rule::make('ses.region')->rules(['string']), 18 | ]; 19 | -------------------------------------------------------------------------------- /stubs/config-validation/session.php: -------------------------------------------------------------------------------- 1 | rules(['string', 'in:file,cookie,database,apc,memcached,redis,dynamodb,array']), 7 | 8 | Rule::make('lifetime')->rules(['integer']), 9 | 10 | Rule::make('expire_on_close')->rules(['bool']), 11 | 12 | Rule::make('encrypt')->rules(['bool']), 13 | 14 | Rule::make('files')->rules(['string']), 15 | 16 | Rule::make('connection')->rules(['string', 'nullable']), 17 | 18 | Rule::make('table')->rules(['string']), 19 | 20 | Rule::make('store')->rules(['string', 'nullable']), 21 | 22 | Rule::make('lottery')->rules(['array']), 23 | 24 | Rule::make('cookie')->rules(['string']), 25 | 26 | Rule::make('path')->rules(['string']), 27 | 28 | Rule::make('domain')->rules(['string', 'nullable']), 29 | 30 | Rule::make('secure')->rules(['bool', 'nullable']), 31 | 32 | Rule::make('http_only')->rules(['bool']), 33 | 34 | Rule::make('same_site')->rules(['string', 'nullable', 'in:lax,strict,none']), 35 | ]; 36 | -------------------------------------------------------------------------------- /stubs/config-validation/view.php: -------------------------------------------------------------------------------- 1 | rules(['array']), 7 | 8 | Rule::make('compiled')->rules(['string']), 9 | ]; 10 | -------------------------------------------------------------------------------- /tests/Unit/Console/Commands/ValidateConfigCommand/HandleTest.php: -------------------------------------------------------------------------------- 1 | createMockValidationFile(); 35 | 36 | $output = $this->runCommand('config:validate'); 37 | 38 | $this->assertStringContainsString('Config validation passed!', $output); 39 | } 40 | 41 | /** @test */ 42 | public function command_can_be_run_with_the_path_option(): void 43 | { 44 | $this->mock(ConfigValidator::class, function ($mock) { 45 | $mock->shouldReceive('throwExceptionOnFailure')->withArgs([false])->andReturn($mock); 46 | $mock->shouldReceive('run')->withArgs([[], 'hello']); 47 | $mock->shouldReceive('errors')->withNoArgs()->andReturn([]); 48 | }); 49 | 50 | $output = $this->runCommand('config:validate --path=hello'); 51 | $this->assertStringContainsString('Config validation passed!', $output); 52 | } 53 | 54 | /** @test */ 55 | public function command_can_be_run_with_the_files_option(): void 56 | { 57 | $this->mock(ConfigValidator::class, function ($mock) { 58 | $mock->shouldReceive('throwExceptionOnFailure')->withArgs([false])->andReturn($mock); 59 | $mock->shouldReceive('run')->withArgs([['auth', 'mail', 'telescope'], null]); 60 | $mock->shouldReceive('errors')->withNoArgs()->andReturn([]); 61 | }); 62 | 63 | $output = $this->runCommand('config:validate --files=auth,mail,telescope'); 64 | 65 | $this->assertStringContainsString('Config validation passed!', $output); 66 | } 67 | 68 | /** @test */ 69 | public function error_is_displayed_if_a_config_validation_fails(): void 70 | { 71 | Config::set('cache.field', ['invalid', 'items', 'in', 'an', 'array']); 72 | 73 | Config::set('mail.port', [ 74 | 'assoc' => 'array', 75 | 'invalid' => 'item', 76 | ]); 77 | 78 | $this->mock(ConfigValidator::class, function ($mock) { 79 | $mock->shouldReceive('throwExceptionOnFailure')->withArgs([false])->andReturn($mock); 80 | $mock->shouldReceive('run')->withArgs([['auth', 'mail', 'telescope'], null]); 81 | $mock->shouldReceive('errors')->withNoArgs()->andReturn([ 82 | 'cache.field' => [ 83 | 'The cache.field must be a string.', 84 | 'The cache.field field is required.', 85 | ], 86 | 'cache.prefix' => [ 87 | 'The cache.prefix must be equal to foobar.', 88 | ], 89 | 'mail.port' => [ 90 | 'The mail.port must be an integer.', 91 | ], 92 | 'mail.empty' => [ 93 | 'The mail.empty must be a string.', 94 | ], 95 | ]); 96 | }); 97 | 98 | $output = $this->runCommand('config:validate --files=auth,mail,telescope'); 99 | 100 | $this->assertStringContainsString('Config validation failed!', $output); 101 | $this->assertStringContainsString('4 errors found in your application:', $output); 102 | 103 | $this->assertStringContainsString('cache.field', $output); 104 | $this->assertStringContainsString('["invalid","items","in","an","array"]', $output); 105 | $this->assertStringContainsString('The cache.field must be a string.', $output); 106 | $this->assertStringContainsString('The cache.field field is required.', $output); 107 | 108 | $this->assertStringContainsString('cache.prefix Value: laravel_cache', $output); 109 | $this->assertStringContainsString('The cache.prefix must be equal to foobar.', $output); 110 | 111 | $this->assertStringContainsString('mail.port', $output); 112 | $this->assertStringContainsString('{"assoc":"array","invalid":"item"}', $output); 113 | $this->assertStringContainsString('The mail.port must be an integer.', $output); 114 | 115 | $this->assertStringContainsString('mail.empty', $output); 116 | $this->assertStringContainsString('The mail.empty must be a string.', $output); 117 | } 118 | 119 | /** @test */ 120 | public function error_is_displayed_if_a_directory_does_not_exist(): void 121 | { 122 | $this->mock(ConfigValidator::class, function (MockInterface $mock): void { 123 | $mock->shouldReceive('throwExceptionOnFailure') 124 | ->withArgs([false]) 125 | ->andReturn($mock); 126 | 127 | $mock->shouldReceive('run') 128 | ->withArgs([[], null]) 129 | ->andThrow(new DirectoryNotFoundException('The directory does not exist.')); 130 | }); 131 | 132 | $output = $this->runCommand('config:validate'); 133 | 134 | $this->assertStringContainsString('The directory does not exist.', $output); 135 | } 136 | 137 | /** @test */ 138 | public function error_is_displayed_if_a_directory_is_empty(): void 139 | { 140 | $this->mock(ConfigValidator::class, function (MockInterface $mock): void { 141 | $mock->shouldReceive('throwExceptionOnFailure') 142 | ->withArgs([false]) 143 | ->andReturn($mock); 144 | 145 | $mock->shouldReceive('run') 146 | ->withArgs([[], null]) 147 | ->andThrow(new NoValidationFilesFoundException('The directory does not contain any validation files.')); 148 | }); 149 | 150 | $output = $this->runCommand('config:validate'); 151 | 152 | $this->assertStringContainsString('The directory does not contain any validation files.', $output); 153 | } 154 | 155 | private function createMockValidationFile(): void 156 | { 157 | $stubFilePath = __DIR__.'/../../../Stubs/cache.php'; 158 | 159 | File::makeDirectory(base_path('config-validation')); 160 | File::put(base_path('config-validation/cache.php'), file_get_contents($stubFilePath)); 161 | } 162 | 163 | protected function tearDown(): void 164 | { 165 | File::deleteDirectory(base_path('config-validation')); 166 | 167 | parent::tearDown(); 168 | } 169 | 170 | private function runCommand(string $command): string 171 | { 172 | $output = new BufferedOutput(); 173 | renderUsing($output); 174 | 175 | Artisan::call($command); 176 | 177 | return $output->fetch(); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /tests/Unit/Console/Commands/ValidationMakeCommand/ValidationMakeCommandTest.php: -------------------------------------------------------------------------------- 1 | assertFalse(File::exists($validationFile)); 28 | 29 | Artisan::call('make:config-validation app'); 30 | 31 | $this->assertTrue(File::exists($validationFile)); 32 | 33 | $this->assertEquals($this->expectedFileContents(), file_get_contents($validationFile)); 34 | } 35 | 36 | private function expectedFileContents(): string 37 | { 38 | return "rules([]), 44 | ]; 45 | "; 46 | } 47 | 48 | protected function tearDown(): void 49 | { 50 | File::deleteDirectory(base_path('config-validation')); 51 | 52 | parent::tearDown(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/Unit/Services/ConfigValidator/RunInlineTest.php: -------------------------------------------------------------------------------- 1 | assertTrue( 32 | $configValidator->runInline([ 33 | 'mail' => $this->mailRules(), 34 | 'cache' => $this->cacheRules(), 35 | 'short-url' => [ 36 | Rule::make('tracking.fields.ip_address')->rules(['required', 'boolean']), 37 | Rule::make('tracking.fields.user_agent')->rules(['required', 'boolean']), 38 | ], 39 | ]), 40 | ); 41 | } 42 | 43 | /** @test */ 44 | public function exception_is_thrown_if_the_validation_fails_with_a_custom_rule_message(): void 45 | { 46 | Config::set('mail.host', null); 47 | 48 | $this->expectException(InvalidConfigValueException::class); 49 | $this->expectExceptionMessage('This is a custom message.'); 50 | 51 | $configValidator = new ConfigValidator(); 52 | $configValidator->throwExceptionOnFailure(true)->runInline([ 53 | 'mail' => $this->mailRules(), 54 | ]); 55 | } 56 | 57 | /** @test */ 58 | public function exception_is_thrown_if_the_validation_fails(): void 59 | { 60 | Config::set('cache.default', null); 61 | 62 | $this->expectException(InvalidConfigValueException::class); 63 | 64 | // The validation failed message structure changed in Laravel 10. 65 | // So we need to check for both messages depending on the 66 | // Laravel framework version. 67 | if (version_compare(app()->version(), '10.0.0', '>=')) { 68 | $this->expectExceptionMessage('The cache.default field must be a string.'); 69 | } else { 70 | $this->expectExceptionMessage('The cache.default must be a string.'); 71 | } 72 | 73 | $configValidator = new ConfigValidator(); 74 | $configValidator->runInline([ 75 | 'cache' => $this->cacheRules(), 76 | ]); 77 | } 78 | 79 | /** @test */ 80 | public function validation_error_messages_can_be_returned(): void 81 | { 82 | // Set valid config values that will pass all of the validation rules. 83 | Config::set('mail.from.to', 'Ashley Allen'); 84 | Config::set('mail.host', 'a random string'); 85 | 86 | // Set invalid config values that will have their error messages stored. 87 | Config::set('mail.from.address', 'INVALID'); 88 | Config::set('cache.default', null); 89 | Config::set('cache.prefix', null); 90 | Config::set('mail.port', 'INVALID'); 91 | Config::set('mail.field_with_underscores', 'INVALID'); 92 | 93 | $configValidator = new ConfigValidator(); 94 | 95 | $this->assertEquals([], $configValidator->errors()); 96 | 97 | try { 98 | $configValidator->runInline([ 99 | 'mail' => $this->mailRules(), 100 | 'cache' => $this->cacheRules(), 101 | ]); 102 | } catch (InvalidConfigValueException $e) { 103 | // Suppress the exception so that we can continue 104 | // testing the error output. 105 | } 106 | 107 | // The validation failed message structure changed in Laravel 10. 108 | // So we need to check for both messages depending on the 109 | // Laravel framework version. 110 | if (version_compare(app()->version(), '10.0.0', '>=')) { 111 | $this->assertEquals([ 112 | 'cache.default' => [ 113 | 'The cache.default field must be a string.', 114 | 'The cache.default field is required.', 115 | ], 116 | 'cache.prefix' => [ 117 | 'The cache.prefix must be equal to foobar.', 118 | ], 119 | 'mail.from.address' => [ 120 | 'The mail.from.address field must be a valid email address.', 121 | ], 122 | 'mail.port' => [ 123 | 'The mail.port field must be an integer.', 124 | ], 125 | 'mail.field_with_underscores' => [ 126 | 'The mail.field_with_underscores field must be an integer.', 127 | ], 128 | ], $configValidator->errors()); 129 | } else { 130 | $this->assertEquals([ 131 | 'cache.default' => [ 132 | 'The cache.default must be a string.', 133 | 'The cache.default field is required.', 134 | ], 135 | 'cache.prefix' => [ 136 | 'The cache.prefix must be equal to foobar.', 137 | ], 138 | 'mail.from.address' => [ 139 | 'The mail.from.address must be a valid email address.', 140 | ], 141 | 'mail.port' => [ 142 | 'The mail.port must be an integer.', 143 | ], 144 | 'mail.field_with_underscores' => [ 145 | 'The mail.field_with_underscores must be an integer.', 146 | ], 147 | ], $configValidator->errors()); 148 | } 149 | } 150 | 151 | /** @test */ 152 | public function exception_is_not_thrown_if_it_is_disabled_before_running_the_validator(): void 153 | { 154 | // Set invalid config values that will have their error messages stored. 155 | Config::set('cache.default', null); 156 | 157 | $configValidator = new ConfigValidator(); 158 | $this->assertFalse( 159 | $configValidator->throwExceptionOnFailure(false)->runInline([ 160 | 'cache' => $this->cacheRules(), 161 | ]), 162 | ); 163 | } 164 | 165 | private function mailRules(): array 166 | { 167 | return [ 168 | Rule::make('host')->rules(['string'])->messages(['string' => 'This is a custom message.']), 169 | Rule::make('port')->rules(['integer']), 170 | Rule::make('from.address')->rules(['email', 'required']), 171 | Rule::make('from.to')->rules(['string', 'required']), 172 | Rule::make('field_with_underscores')->rules(['integer', 'nullable']), 173 | ]; 174 | } 175 | 176 | private function cacheRules(): array 177 | { 178 | return [ 179 | Rule::make('default')->rules(['string', 'required', 'in:apc,array,database,file,memcached,redis,dynamodb']), 180 | Rule::make('prefix')->rules([new IsFooBar()]), 181 | ]; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /tests/Unit/Services/ConfigValidator/RunTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($configValidator->run()); 34 | } 35 | 36 | /** @test */ 37 | public function validator_can_be_run_in_a_custom_location() 38 | { 39 | // Set valid config values that will pass all of the validation rules. 40 | Config::set('mail.from.address', 'mail@ashallendesign.co.uk'); 41 | Config::set('mail.from.to', 'Ashley Allen'); 42 | Config::set('mail.host', 'a random string'); 43 | Config::set('mail.port', 1234); 44 | Config::set('cache.prefix', 'foobar'); 45 | 46 | $mailStubFilePath = __DIR__.'/../../Stubs/mail.php'; 47 | $cacheStubFilePath = __DIR__.'/../../Stubs/cache.php'; 48 | 49 | File::deleteDirectory(base_path('custom-validation')); 50 | 51 | File::makeDirectory(base_path('custom-validation')); 52 | File::put(base_path('custom-validation/mail.php'), file_get_contents($mailStubFilePath)); 53 | File::put(base_path('custom-validation/cache.php'), file_get_contents($cacheStubFilePath)); 54 | 55 | $configValidator = new ConfigValidator(); 56 | $this->assertTrue($configValidator->run([], 'custom-validation')); 57 | } 58 | 59 | /** @test */ 60 | public function validator_can_be_run_with_custom_files() 61 | { 62 | // Set valid config values that will pass all of the validation rules. 63 | Config::set('mail.from.address', 'mail@ashallendesign.co.uk'); 64 | Config::set('mail.from.to', 'Ashley Allen'); 65 | Config::set('mail.host', 'a random string'); 66 | Config::set('mail.port', 1234); 67 | Config::set('cache.prefix', 'foobar'); 68 | 69 | // Set an invalid config that would purposely cause the validation to fail. 70 | // The validator will still pass though because we won't be validating 71 | // the cache. 72 | Config::set('cache.default', null); 73 | 74 | $mailStubFilePath = __DIR__.'/../../Stubs/mail.php'; 75 | $cacheStubFilePath = __DIR__.'/../../Stubs/cache.php'; 76 | 77 | File::makeDirectory(base_path('config-validation')); 78 | File::put(base_path('config-validation/mail.php'), file_get_contents($mailStubFilePath)); 79 | File::put(base_path('config-validation/cache.php'), file_get_contents($cacheStubFilePath)); 80 | 81 | $configValidator = new ConfigValidator(); 82 | $this->assertTrue($configValidator->run(['mail'])); 83 | } 84 | 85 | /** @test */ 86 | public function exception_is_thrown_if_the_validation_fails_with_a_custom_rule_message() 87 | { 88 | Config::set('mail.host', null); 89 | 90 | $this->expectException(InvalidConfigValueException::class); 91 | $this->expectExceptionMessage('This is a custom message.'); 92 | 93 | $stubFilePath = __DIR__.'/../../Stubs/mail.php'; 94 | 95 | File::makeDirectory(base_path('config-validation')); 96 | File::put(base_path('config-validation/mail.php'), file_get_contents($stubFilePath)); 97 | 98 | $configValidator = new ConfigValidator(); 99 | $configValidator->throwExceptionOnFailure(true)->run(); 100 | } 101 | 102 | /** @test */ 103 | public function exception_is_thrown_if_the_validation_fails() 104 | { 105 | Config::set('cache.default', null); 106 | 107 | $this->expectException(InvalidConfigValueException::class); 108 | 109 | // The validation failed message structure changed in Laravel 10. 110 | // So we need to check for both messages depending on the 111 | // Laravel framework version. 112 | if (version_compare(app()->version(), '10.0.0', '>=')) { 113 | $this->expectExceptionMessage('The cache.default field must be a string.'); 114 | } else { 115 | $this->expectExceptionMessage('The cache.default must be a string.'); 116 | } 117 | 118 | $stubFilePath = __DIR__.'/../../Stubs/cache.php'; 119 | 120 | File::makeDirectory(base_path('config-validation')); 121 | File::put(base_path('config-validation/cache.php'), file_get_contents($stubFilePath)); 122 | 123 | $configValidator = new ConfigValidator(); 124 | $configValidator->run(); 125 | } 126 | 127 | /** @test */ 128 | public function exception_is_thrown_if_the_directory_does_not_exist() 129 | { 130 | $this->expectException(DirectoryNotFoundException::class); 131 | $this->expectExceptionMessage('The directory '.base_path('invalid_path').' does not exist.'); 132 | 133 | $configValidator = new ConfigValidator(); 134 | $configValidator->run([], 'invalid_path'); 135 | } 136 | 137 | /** @test */ 138 | public function exception_is_thrown_if_the_directory_contains_no_files() 139 | { 140 | $this->expectException(NoValidationFilesFoundException::class); 141 | $this->expectExceptionMessage('No config validation files were found inside the directory.'); 142 | 143 | // Create a file that isn't a PHP file. 144 | File::makeDirectory(base_path('config-validation')); 145 | File::put(base_path('config-validation/cache.js'), 'https://ashallendesign.co.uk'); 146 | 147 | $configValidator = new ConfigValidator(); 148 | $configValidator->run(); 149 | } 150 | 151 | /** @test */ 152 | public function validation_error_messages_can_be_returned() 153 | { 154 | // Set valid config values that will pass all of the validation rules. 155 | Config::set('mail.from.to', 'Ashley Allen'); 156 | Config::set('mail.host', 'a random string'); 157 | 158 | // Set invalid config values that will have their error messages stored. 159 | Config::set('mail.from.address', 'INVALID'); 160 | Config::set('cache.default', null); 161 | Config::set('cache.prefix', null); 162 | Config::set('mail.port', 'INVALID'); 163 | Config::set('mail.field_with_underscores', 'INVALID'); 164 | 165 | $mailStubFilePath = __DIR__.'/../../Stubs/mail.php'; 166 | $cacheStubFilePath = __DIR__.'/../../Stubs/cache.php'; 167 | 168 | File::makeDirectory(base_path('config-validation')); 169 | File::put(base_path('config-validation/mail.php'), file_get_contents($mailStubFilePath)); 170 | File::put(base_path('config-validation/cache.php'), file_get_contents($cacheStubFilePath)); 171 | 172 | $configValidator = new ConfigValidator(); 173 | 174 | $this->assertEquals([], $configValidator->errors()); 175 | 176 | try { 177 | $configValidator->run(); 178 | } catch (InvalidConfigValueException $e) { 179 | // Suppress the exception so that we can continue 180 | // testing the error output. 181 | } 182 | 183 | // The validation failed message structure changed in Laravel 10. 184 | // So we need to check for both messages depending on the 185 | // Laravel framework version. 186 | if (version_compare(app()->version(), '10.0.0', '>=')) { 187 | $this->assertEquals([ 188 | 'cache.default' => [ 189 | 'The cache.default field must be a string.', 190 | 'The cache.default field is required.', 191 | ], 192 | 'cache.prefix' => [ 193 | 'The cache.prefix must be equal to foobar.', 194 | ], 195 | 'mail.from.address' => [ 196 | 'The mail.from.address field must be a valid email address.', 197 | ], 198 | 'mail.port' => [ 199 | 'The mail.port field must be an integer.', 200 | ], 201 | 'mail.field_with_underscores' => [ 202 | 'The mail.field_with_underscores field must be an integer.', 203 | ], 204 | ], $configValidator->errors()); 205 | } else { 206 | $this->assertEquals([ 207 | 'cache.default' => [ 208 | 'The cache.default must be a string.', 209 | 'The cache.default field is required.', 210 | ], 211 | 'cache.prefix' => [ 212 | 'The cache.prefix must be equal to foobar.', 213 | ], 214 | 'mail.from.address' => [ 215 | 'The mail.from.address must be a valid email address.', 216 | ], 217 | 'mail.port' => [ 218 | 'The mail.port must be an integer.', 219 | ], 220 | 'mail.field_with_underscores' => [ 221 | 'The mail.field_with_underscores must be an integer.', 222 | ], 223 | ], $configValidator->errors()); 224 | } 225 | } 226 | 227 | /** @test */ 228 | public function exception_is_not_thrown_if_it_is_disabled_before_running_the_validator() 229 | { 230 | // Set invalid config values that will have their error messages stored. 231 | Config::set('cache.default', null); 232 | 233 | $cacheStubFilePath = __DIR__.'/../../Stubs/cache.php'; 234 | 235 | File::makeDirectory(base_path('config-validation')); 236 | File::put(base_path('config-validation/cache.php'), file_get_contents($cacheStubFilePath)); 237 | 238 | $configValidator = new ConfigValidator(); 239 | $this->assertFalse($configValidator->throwExceptionOnFailure(false)->run()); 240 | } 241 | 242 | protected function tearDown(): void 243 | { 244 | File::deleteDirectory(base_path('config-validation')); 245 | 246 | parent::tearDown(); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /tests/Unit/Stubs/IsFooBar.php: -------------------------------------------------------------------------------- 1 | rules(['string', 'required', 'in:apc,array,database,file,memcached,redis,dynamodb']), 9 | 10 | Rule::make('prefix') 11 | ->rules([new IsFooBar()]), 12 | ]; 13 | -------------------------------------------------------------------------------- /tests/Unit/Stubs/mail.php: -------------------------------------------------------------------------------- 1 | rules(['string']) 8 | ->messages(['string' => 'This is a custom message.']), 9 | 10 | Rule::make('port') 11 | ->rules(['integer']), 12 | 13 | Rule::make('from.address') 14 | ->rules(['email', 'required']), 15 | 16 | Rule::make('from.to') 17 | ->rules(['string', 'required']), 18 | 19 | Rule::make('field_with_underscores') 20 | ->rules(['integer', 'nullable']), 21 | ]; 22 | -------------------------------------------------------------------------------- /tests/Unit/TestCase.php: -------------------------------------------------------------------------------- 1 | ConfigValidator::class, 33 | ]; 34 | } 35 | } 36 | --------------------------------------------------------------------------------