@@ -1857,6 +1857,170 @@ public function testRuleWithAsteriskToMultiDimensionalArray(): void
18571857 );
18581858 }
18591859
1860+ public function testRequiredWildcardFailsWhenSomeElementsMissingKey (): void
1861+ {
1862+ $ data = [
1863+ 'contacts ' => [
1864+ 'friends ' => [
1865+ ['name ' => 'Fred ' , 'age ' => 20 ],
1866+ ['age ' => 21 ],
1867+ ],
1868+ ],
1869+ ];
1870+
1871+ $ this ->validation ->setRules (['contacts.friends.*.name ' => 'required ' ]);
1872+ $ this ->assertFalse ($ this ->validation ->run ($ data ));
1873+ $ this ->assertSame (
1874+ ['contacts.friends.1.name ' => 'The contacts.friends.*.name field is required. ' ],
1875+ $ this ->validation ->getErrors (),
1876+ );
1877+ }
1878+
1879+ public function testRequiredWildcardFailsForEachMissingElement (): void
1880+ {
1881+ // One element has the key (creating a non-empty initial match set),
1882+ // the other two are missing it - each missing element gets its own error.
1883+ $ data = [
1884+ 'contacts ' => [
1885+ 'friends ' => [
1886+ ['name ' => 'Fred ' , 'age ' => 20 ],
1887+ ['age ' => 21 ],
1888+ ['age ' => 22 ],
1889+ ],
1890+ ],
1891+ ];
1892+
1893+ $ this ->validation ->setRules (['contacts.friends.*.name ' => 'required ' ]);
1894+ $ this ->assertFalse ($ this ->validation ->run ($ data ));
1895+ $ this ->assertSame (
1896+ [
1897+ 'contacts.friends.1.name ' => 'The contacts.friends.*.name field is required. ' ,
1898+ 'contacts.friends.2.name ' => 'The contacts.friends.*.name field is required. ' ,
1899+ ],
1900+ $ this ->validation ->getErrors (),
1901+ );
1902+ }
1903+
1904+ public function testWildcardNonRequiredRuleSkipsMissingElements (): void
1905+ {
1906+ // Without a required* rule, elements whose key does not exist must
1907+ // never be queued for validation - no false positives.
1908+ $ data = [
1909+ 'contacts ' => [
1910+ 'friends ' => [
1911+ ['name ' => 'Fred ' ], // passes in_list
1912+ ['age ' => 21 ], // key absent, must be skipped entirely
1913+ ],
1914+ ],
1915+ ];
1916+
1917+ $ this ->validation ->setRules (['contacts.friends.*.name ' => 'in_list[Fred,Wilma] ' ]);
1918+ $ this ->assertTrue ($ this ->validation ->run ($ data ));
1919+ $ this ->assertSame ([], $ this ->validation ->getErrors ());
1920+ }
1921+
1922+ public function testWildcardIfExistRequiredSkipsMissingElements (): void
1923+ {
1924+ // `if_exist` must short-circuit before `required` fires for elements
1925+ // whose key is absent from the data structure.
1926+ $ data = [
1927+ 'contacts ' => [
1928+ 'friends ' => [
1929+ ['name ' => 'Fred ' ], // exists and non-empty - passes
1930+ ['age ' => 21 ], // key absent - if_exist skips it
1931+ ],
1932+ ],
1933+ ];
1934+
1935+ $ this ->validation ->setRules (['contacts.friends.*.name ' => 'if_exist|required ' ]);
1936+ $ this ->assertTrue ($ this ->validation ->run ($ data ));
1937+ $ this ->assertSame ([], $ this ->validation ->getErrors ());
1938+ }
1939+
1940+ public function testWildcardPermitEmptySkipsMissingElements (): void
1941+ {
1942+ // `permit_empty` without any required* rule: an empty existing value
1943+ // passes and a missing element is never queued.
1944+ $ data = [
1945+ 'contacts ' => [
1946+ 'friends ' => [
1947+ ['name ' => '' ], // exists but empty - permit_empty lets it through
1948+ ['age ' => 21 ], // key absent - not queued (no required* rule)
1949+ ],
1950+ ],
1951+ ];
1952+
1953+ $ this ->validation ->setRules (['contacts.friends.*.name ' => 'permit_empty|min_length[2] ' ]);
1954+ $ this ->assertTrue ($ this ->validation ->run ($ data ));
1955+ $ this ->assertSame ([], $ this ->validation ->getErrors ());
1956+ }
1957+
1958+ public function testWildcardRequiredWithFailsForMissingElementWhenConditionMet (): void
1959+ {
1960+ // `required_with` is a required* variant, so missing elements ARE queued.
1961+ // When the condition field is present the rule fires and the missing
1962+ // element generates an error.
1963+ $ data = [
1964+ 'has_friends ' => '1 ' ,
1965+ 'contacts ' => [
1966+ 'friends ' => [
1967+ ['name ' => 'Fred ' , 'age ' => 20 ], // passes
1968+ ['age ' => 21 ], // missing name, condition met - error
1969+ ],
1970+ ],
1971+ ];
1972+
1973+ $ this ->validation ->setRules (['contacts.friends.*.name ' => 'required_with[has_friends] ' ]);
1974+ $ this ->assertFalse ($ this ->validation ->run ($ data ));
1975+ $ this ->assertSame (
1976+ ['contacts.friends.1.name ' => 'The contacts.friends.*.name field is required when has_friends is present. ' ],
1977+ $ this ->validation ->getErrors (),
1978+ );
1979+ }
1980+
1981+ public function testWildcardRequiredWithPassesForMissingElementWhenConditionNotMet (): void
1982+ {
1983+ // Same structure but the condition field is absent, so required_with
1984+ // does not apply and the missing element generates no error.
1985+ $ data = [
1986+ 'contacts ' => [
1987+ 'friends ' => [
1988+ ['name ' => 'Fred ' , 'age ' => 20 ], // passes
1989+ ['age ' => 21 ], // missing name, but condition absent - ok
1990+ ],
1991+ ],
1992+ ];
1993+
1994+ $ this ->validation ->setRules (['contacts.friends.*.name ' => 'required_with[has_friends] ' ]);
1995+ $ this ->assertTrue ($ this ->validation ->run ($ data ));
1996+ $ this ->assertSame ([], $ this ->validation ->getErrors ());
1997+ }
1998+
1999+ public function testWildcardRequiredNoFalsePositiveForMissingIntermediateSegment (): void
2000+ {
2001+ // users.1 has no `contacts` key at all - an intermediate segment is
2002+ // absent, not the leaf. Only the leaf-absent branch (users.0.contacts.1)
2003+ // should produce an error; the entirely-missing branch must be silent.
2004+ $ data = [
2005+ 'users ' => [
2006+ [
2007+ 'contacts ' => [
2008+ ['name ' => 'Alice ' ], // leaf present
2009+ ['age ' => 20 ], // leaf absent - error
2010+ ],
2011+ ],
2012+ ['age ' => 30 ], // intermediate segment `contacts` missing - no error
2013+ ],
2014+ ];
2015+
2016+ $ this ->validation ->setRules (['users.*.contacts.*.name ' => 'required ' ]);
2017+ $ this ->assertFalse ($ this ->validation ->run ($ data ));
2018+ $ this ->assertSame (
2019+ ['users.0.contacts.1.name ' => 'The users.*.contacts.*.name field is required. ' ],
2020+ $ this ->validation ->getErrors (),
2021+ );
2022+ }
2023+
18602024 /**
18612025 * @param array<string, mixed> $data
18622026 * @param array<string, string> $rules
0 commit comments