1212use Traversable ;
1313use Yiisoft \Strings \StringHelper ;
1414use Yiisoft \Validator \BeforeValidationInterface ;
15+ use Yiisoft \Validator \PropagateOptionsInterface ;
1516use Yiisoft \Validator \Rule \Trait \BeforeValidationTrait ;
1617use Yiisoft \Validator \Rule \Trait \RuleNameTrait ;
1718use Yiisoft \Validator \Rule \Trait \SkipOnEmptyTrait ;
3536 * Can be used for validation of nested structures.
3637 */
3738#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE )]
38- final class Nested implements SerializableRuleInterface, BeforeValidationInterface, SkipOnEmptyInterface
39+ final class Nested implements
40+ SerializableRuleInterface,
41+ BeforeValidationInterface,
42+ SkipOnEmptyInterface,
43+ PropagateOptionsInterface
3944{
4045 use BeforeValidationTrait;
4146 use RuleNameTrait;
@@ -80,6 +85,7 @@ public function __construct(
8085 private bool $ requirePropertyPath = false ,
8186 private string $ noPropertyPathMessage = 'Property path "{path}" is not found. ' ,
8287 private bool $ normalizeRules = true ,
88+ private bool $ propagateOptions = false ,
8389
8490 /**
8591 * @var bool|callable|null
@@ -92,7 +98,7 @@ public function __construct(
9298 */
9399 private ?Closure $ when = null ,
94100 ) {
95- $ this ->rules = $ this -> prepareRules ($ rules );
101+ $ this ->prepareRules ($ rules );
96102 }
97103
98104 /**
@@ -127,10 +133,12 @@ public function getNoPropertyPathMessage(): string
127133 /**
128134 * @param class-string|iterable<Closure|Closure[]|RuleInterface|RuleInterface[]>|RulesProviderInterface|null $source
129135 */
130- private function prepareRules (iterable |object |string |null $ source ): ? iterable
136+ private function prepareRules (iterable |object |string |null $ source ): void
131137 {
132138 if ($ source === null ) {
133- return null ;
139+ $ this ->rules = null ;
140+
141+ return ;
134142 }
135143
136144 if ($ source instanceof RulesProviderInterface) {
@@ -144,7 +152,15 @@ private function prepareRules(iterable|object|string|null $source): ?iterable
144152 $ rules = $ rules instanceof Traversable ? iterator_to_array ($ rules ) : $ rules ;
145153 self ::ensureArrayHasRules ($ rules );
146154
147- return $ this ->normalizeRules ? $ this ->normalizeRules ($ rules ) : $ rules ;
155+ $ this ->rules = $ rules ;
156+
157+ if ($ this ->normalizeRules ) {
158+ $ this ->normalizeRules ();
159+ }
160+
161+ if ($ this ->propagateOptions ) {
162+ $ this ->propagateOptions ();
163+ }
148164 }
149165
150166 private static function ensureArrayHasRules (iterable &$ rules )
@@ -163,8 +179,11 @@ private static function ensureArrayHasRules(iterable &$rules)
163179 }
164180 }
165181
166- private function normalizeRules (iterable $ rules ): iterable
182+ private function normalizeRules (): void
167183 {
184+ /** @var iterable $rules */
185+ $ rules = $ this ->rules ;
186+
168187 while (true ) {
169188 $ breakWhile = true ;
170189 $ rulesMap = [];
@@ -209,7 +228,27 @@ private function normalizeRules(iterable $rules): iterable
209228 }
210229 }
211230
212- return $ rules ;
231+ $ this ->rules = $ rules ;
232+ }
233+
234+ public function propagateOptions (): void
235+ {
236+ $ rules = [];
237+ foreach ($ this ->rules as $ attributeRulesIndex => $ attributeRules ) {
238+ foreach ($ attributeRules as $ attributeRule ) {
239+ $ attributeRule = $ attributeRule ->skipOnEmpty ($ this ->skipOnEmpty );
240+ $ attributeRule = $ attributeRule ->skipOnError ($ this ->skipOnError );
241+ $ attributeRule = $ attributeRule ->when ($ this ->when );
242+
243+ $ rules [$ attributeRulesIndex ][] = $ attributeRule ;
244+
245+ if ($ attributeRule instanceof PropagateOptionsInterface) {
246+ $ attributeRule ->propagateOptions ();
247+ }
248+ }
249+ }
250+
251+ $ this ->rules = $ rules ;
213252 }
214253
215254 #[ArrayShape([
0 commit comments