├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── UPGRADE.md
├── composer.json
└── src
├── CompositeRule.php
└── DynamicCompositeRule.php
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Laravel Composite Validation
2 | ============================
3 |
4 | 1.2.6, March 6, 2025
5 | --------------------
6 |
7 | - Enh #12: Added support for "illuminate/support" 12.0 (klimov-paul)
8 |
9 |
10 | 1.2.5, March 25, 2024
11 | ---------------------
12 |
13 | - Enh #11: Added support for "illuminate/support" 11.0 (klimov-paul)
14 |
15 |
16 | 1.2.4, February 24, 2023
17 | ------------------------
18 |
19 | - Enh #10: Added support for "illuminate/support" 10.0 (klimov-paul)
20 |
21 |
22 | 1.2.3, February 9, 2022
23 | -----------------------
24 |
25 | - Enh: Added support for "illuminate/support" 9.0 (klimov-paul)
26 |
27 |
28 | 1.2.2, September 9, 2020
29 | ------------------------
30 |
31 | - Enh #7: Added support for "illuminate/support" 8.0 (JeffreyDavidson, klimov-paul)
32 |
33 |
34 | 1.2.1, March 4, 2020
35 | --------------------
36 |
37 | - Enh: Added support for "illuminate/support" 7.0 (klimov-paul)
38 |
39 |
40 | 1.2.0, February 19, 2020
41 | ------------------------
42 |
43 | - Enh #6: Added `CompositeRule::messages()`, for the custom validation messages specification (klimov-paul)
44 |
45 |
46 | 1.1.0, September 6, 2019
47 | ------------------------
48 |
49 | - Enh #4: Added support for "illuminate/support" 6.0 (klimov-paul)
50 |
51 |
52 | 1.0.1, July 22, 2019
53 | --------------------
54 |
55 | - Bug #3: Fixed `CompositeRule` skipped on array attributes like 'item_ids.*' or 'items.*.id' (klimov-paul)
56 |
57 |
58 | 1.0.0, June 18, 2019
59 | --------------------
60 |
61 | - Initial release.
62 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | This is free software. It is released under the terms of the
2 | following BSD License.
3 |
4 | Copyright © 2019 by Illuminatech (https://github.com/illuminatech)
5 | All rights reserved.
6 |
7 | Redistribution and use in source and binary forms, with or without
8 | modification, are permitted provided that the following conditions
9 | are met:
10 |
11 | * Redistributions of source code must retain the above copyright
12 | notice, this list of conditions and the following disclaimer.
13 | * Redistributions in binary form must reproduce the above copyright
14 | notice, this list of conditions and the following disclaimer in
15 | the documentation and/or other materials provided with the
16 | distribution.
17 | * Neither the name of Illuminatech nor the names of its
18 | contributors may be used to endorse or promote products derived
19 | from this software without specific prior written permission.
20 |
21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 | POSSIBILITY OF SUCH DAMAGE.
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Laravel Composite Validation
6 |
7 |
8 |
9 | This extension allows uniting several Laravel validation rules into a single one for easy re-usage.
10 |
11 | For license information check the [LICENSE](LICENSE.md)-file.
12 |
13 | [](https://packagist.org/packages/illuminatech/validation-composite)
14 | [](https://packagist.org/packages/illuminatech/validation-composite)
15 | [](https://github.com/illuminatech/validation-composite/actions)
16 |
17 |
18 | Installation
19 | ------------
20 |
21 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/).
22 |
23 | Either run
24 |
25 | ```
26 | php composer.phar require --prefer-dist illuminatech/validation-composite
27 | ```
28 |
29 | or add
30 |
31 | ```json
32 | "illuminatech/validation-composite": "*"
33 | ```
34 |
35 | to the require section of your composer.json.
36 |
37 |
38 | Usage
39 | -----
40 |
41 | The same sequence of the validation rules may repeat over the application many times. For example: you may have a set of
42 | restrictions related to the user's password, like it should be at least 8 symbols long, but shorter then 200 to fit the
43 | database field reserved for its storage. Your program may also allow user to upload an image to be his avatar, but in order
44 | to make it safe, you should validate uploaded file mime type and size.
45 | Thus validation for the user profile form may looks like following:
46 |
47 | ```php
48 | validate([
59 | 'password' => ['required', 'string', 'min:8', 'max:200'],
60 | 'avatar' => ['required', 'file', 'mimes:png,jpg,jpeg', 'max:1024'],
61 | // ...
62 | ]);
63 |
64 | // ...
65 | }
66 | }
67 | ```
68 |
69 | The problem is: validation for user's password or avatar may appear in several different places. For example: password
70 | can be setup at sign-up process, during password reset and so on. You may also have a separated administration panel,
71 | which allows system administrator to adjust existing user's record or create a new one. Thus you will have to duplicate
72 | all these validation rules many times throughout your project source code. In case requirements change, for example:
73 | we decide that password length should be at least 10 symbols instead of 8, or disallow '*.png' files from avatar - you'll
74 | have to manually changes validation rules at all those places.
75 |
76 | This extension allows uniting several validation rules into a single one for easy re-usage. For the example above you
77 | should create 2 separated validation rule classes extending `Illuminatech\Validation\Composite\CompositeRule`:
78 |
79 | ```php
80 | validate([
120 | 'password' => ['required', new PasswordRule],
121 | 'avatar' => ['required', new AvatarRule],
122 | // ...
123 | ]);
124 |
125 | // ...
126 | }
127 | }
128 | ```
129 |
130 | With such approach you can change validation for the 'password' and 'avatar' at the single place.
131 |
132 | In case composite validation rule fails, validator instance will pick up an error message from the particular sub-rule.
133 | For example:
134 |
135 | ```php
136 | 'short'],
143 | [
144 | 'password' => ['required', new PasswordRule],
145 | ]
146 | );
147 |
148 | if ($validator->fails()) {
149 | echo $validator->errors()->first('password'); // outputs 'The password must be at least 8 characters.'
150 | }
151 | ```
152 |
153 | > Note: do not use rules like 'sometimes', 'required', 'required_with', 'required_without' and so on in the composite rule.
154 | These are processed at the different validation level and thus will have no effect or may behave unexpectedly.
155 |
156 | You may define composite validation rules using [validation factory extensions](https://laravel.com/docs/validation#using-extensions) feature.
157 | For such case you may use `Illuminatech\Validation\Composite\DynamicCompositeRule`. For example:
158 |
159 | ```php
160 | app->extend('validator', function (Factory $validatorFactory) {
173 | $validatorFactory->extend('password', function ($attribute, $value) {
174 | return (new DynamicCompositeRule(['string', 'min:8', 'max:200']))->passes($attribute, $value);
175 | });
176 |
177 | $validatorFactory->extend('avatar', function ($attribute, $value) {
178 | return (new DynamicCompositeRule(['file', 'mimes:png,jpg,jpeg', 'max:1024']))->passes($attribute, $value);
179 | });
180 |
181 | return $validatorFactory;
182 | });
183 |
184 | // ...
185 | }
186 | }
187 | ```
188 |
189 | Note that with such approach automatic pick up of the validation error message becomes impossible, and you will have to setup
190 | it explicitly in language files.
191 |
192 | You may specify [custom error messages](https://laravel.com/docs/validation#custom-error-messages) per each validation rule used in the composite one,
193 | overriding `messages()` method. For example:
194 |
195 | ```php
196 | 'Only string is allowed.',
213 | 'min' => ':attribute is too short.',
214 | 'max' => ':attribute is too long.',
215 | ];
216 | }
217 | }
218 | ```
219 |
--------------------------------------------------------------------------------
/UPGRADE.md:
--------------------------------------------------------------------------------
1 | Upgrading Instructions for Laravel Composite Validation
2 | =======================================================
3 |
4 | !!!IMPORTANT!!!
5 |
6 | The following upgrading instructions are cumulative. That is,
7 | if you want to upgrade from version A to version C and there is
8 | version B between A and C, you need to following the instructions
9 | for both A and B.
10 |
11 | Upgrade from 1.1.0
12 | ------------------
13 |
14 | * Signature of the `DynamicCompositeRule` constructor has been changed, adding extra `$messages` parameter.
15 | Make sure to upgrade your code in case you are using `DynamicCompositeRule`.
16 |
17 |
18 | Upgrade from 1.0.1
19 | ------------------
20 |
21 | * "illuminate/support" package requirements were raised to 6.0. Make sure to upgrade your code accordingly.
22 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "illuminatech/validation-composite",
3 | "description": "Allows uniting several validation rules into a single one for easy re-usage",
4 | "keywords": ["laravel", "validation", "validator", "rule", "composite", "combine"],
5 | "license": "BSD-3-Clause",
6 | "support": {
7 | "issues": "https://github.com/illuminatech/validation-composite/issues",
8 | "wiki": "https://github.com/illuminatech/validation-composite/wiki",
9 | "source": "https://github.com/illuminatech/validation-composite"
10 | },
11 | "authors": [
12 | {
13 | "name": "Paul Klimov",
14 | "email": "klimov.paul@gmail.com"
15 | }
16 | ],
17 | "require": {
18 | "illuminate/support": "^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0"
19 | },
20 | "require-dev": {
21 | "illuminate/validation": "*",
22 | "phpunit/phpunit": "^7.5 || ^8.0 || ^9.3 || ^10.5"
23 | },
24 | "autoload": {
25 | "psr-4": {
26 | "Illuminatech\\Validation\\Composite\\": "src"
27 | }
28 | },
29 | "autoload-dev": {
30 | "psr-4": {
31 | "Illuminatech\\Validation\\Composite\\Test\\": "tests"
32 | }
33 | },
34 | "extra": {
35 | "branch-alias": {
36 | "dev-master": "1.0.x-dev"
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/src/CompositeRule.php:
--------------------------------------------------------------------------------
1 | validate([
42 | * 'password' => ['required', new PasswordRule],
43 | * 'avatar' => ['required', new AvatarRule],
44 | * ]);
45 | * ```
46 | *
47 | * @author Paul Klimov
48 | * @since 1.0
49 | */
50 | abstract class CompositeRule implements Rule
51 | {
52 | /**
53 | * @var \Illuminate\Contracts\Validation\Factory validator factory used for slave validator creation.
54 | */
55 | private $validatorFactory;
56 |
57 | /**
58 | * @var string validation error message from particular underlying validator.
59 | */
60 | private $message;
61 |
62 | /**
63 | * Constructor.
64 | *
65 | * @param \Illuminate\Contracts\Validation\Factory|null $validatorFactory validator factory used for slave validator creation.
66 | */
67 | public function __construct(?Factory $validatorFactory = null)
68 | {
69 | if ($validatorFactory !== null) {
70 | $this->setValidatorFactory($validatorFactory);
71 | }
72 | }
73 |
74 | /**
75 | * Defines list of the validation rules, which are combined into this one.
76 | *
77 | * @return array validation rules definition.
78 | */
79 | abstract protected function rules(): array;
80 |
81 | /**
82 | * Defines custom error messages for the validation rules defined at {@see rules()}.
83 | * @since 1.2.0
84 | *
85 | * @return array error messages.
86 | */
87 | protected function messages(): array
88 | {
89 | return [];
90 | }
91 |
92 | /**
93 | * {@inheritdoc}
94 | */
95 | public function passes($attribute, $value): bool
96 | {
97 | $data = [];
98 |
99 | Arr::set($data, $attribute, $value); // ensure correct validation for array attributes like 'item_ids.*' or 'items.*.id'
100 |
101 | $validator = $this->getValidatorFactory()->make(
102 | $data,
103 | [
104 | $attribute => $this->rules(),
105 | ],
106 | $this->messages()
107 | );
108 |
109 | if ($validator->fails()) {
110 | $this->message = $validator->getMessageBag()->first();
111 |
112 | return false;
113 | }
114 |
115 | return true;
116 | }
117 |
118 | /**
119 | * {@inheritdoc}
120 | */
121 | public function message()
122 | {
123 | return $this->message;
124 | }
125 |
126 | /**
127 | * @return \Illuminate\Contracts\Validation\Factory validator factory used for slave validator creation.
128 | */
129 | public function getValidatorFactory(): Factory
130 | {
131 | if ($this->validatorFactory === null) {
132 | $this->validatorFactory = Validator::getFacadeRoot();
133 | }
134 |
135 | return $this->validatorFactory;
136 | }
137 |
138 | /**
139 | * @param \Illuminate\Contracts\Validation\Factory $validatorFactory validator factory used for slave validator creation.
140 | * @return static self reference.
141 | */
142 | public function setValidatorFactory(Factory $validatorFactory): self
143 | {
144 | $this->validatorFactory = $validatorFactory;
145 |
146 | return $this;
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/DynamicCompositeRule.php:
--------------------------------------------------------------------------------
1 | app->extend('validator', function (Factory $validatorFactory) {
37 | * $validatorFactory->extend('password', function ($attribute, $value) {
38 | * return (new DynamicCompositeRule(['string', 'min:8', 'max:200']))->passes($attribute, $value);
39 | * });
40 | *
41 | * return $validatorFactory;
42 | * });
43 | *
44 | * // ...
45 | * }
46 | * }
47 | * ```
48 | *
49 | * @author Paul Klimov
50 | * @since 1.0
51 | */
52 | class DynamicCompositeRule extends CompositeRule
53 | {
54 | /**
55 | * @var array list of the validation rules, which should be combined into this one.
56 | */
57 | private $rules;
58 |
59 | /**
60 | * @var array custom error messages for the validation rules.
61 | */
62 | private $messages;
63 |
64 | /**
65 | * Constructor.
66 | *
67 | * @param array $rules list of the validation rules, which should be combined into this one.
68 | * @param array $messages custom error messages for the validation rules.
69 | * @param \Illuminate\Contracts\Validation\Factory|null $validatorFactory validator factory used for slave validator creation.
70 | */
71 | public function __construct(array $rules, array $messages = [], ?Factory $validatorFactory = null)
72 | {
73 | $this->rules = $rules;
74 | $this->messages = $messages;
75 |
76 | parent::__construct($validatorFactory);
77 | }
78 |
79 | /**
80 | * {@inheritdoc}
81 | */
82 | protected function rules(): array
83 | {
84 | return $this->rules;
85 | }
86 |
87 | /**
88 | * {@inheritdoc}
89 | */
90 | protected function messages(): array
91 | {
92 | return $this->messages;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------