├── .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 |
7 |
8 |
9 |
10 |
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 | [](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 |
--------------------------------------------------------------------------------