Skip to content

Commit 15f56bb

Browse files
authored
feat: require double curly braces for placeholders in regex_match rule (#9597)
* feat: require double curly braces for placeholders in regex_match rule * cs fix
1 parent 06340ce commit 15f56bb

File tree

4 files changed

+69
-4
lines changed

4 files changed

+69
-4
lines changed

system/Validation/Validation.php

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -823,8 +823,12 @@ protected function fillPlaceholders(array $rules, array $data): array
823823
continue;
824824
}
825825

826-
// Replace the placeholder in the rule
827-
$ruleSet = str_replace('{' . $field . '}', (string) $data[$field], $ruleSet);
826+
// Replace the placeholder in the current rule string
827+
if (str_starts_with($row, 'regex_match[')) {
828+
$row = str_replace('{{' . $field . '}}', (string) $data[$field], $row);
829+
} else {
830+
$row = str_replace('{' . $field . '}', (string) $data[$field], $row);
831+
}
828832
}
829833
}
830834
}
@@ -840,7 +844,13 @@ protected function fillPlaceholders(array $rules, array $data): array
840844
*/
841845
private function retrievePlaceholders(string $rule, array $data): array
842846
{
843-
preg_match_all('/{(.+?)}/', $rule, $matches);
847+
if (str_starts_with($rule, 'regex_match[')) {
848+
// For regex_match rules, only look for double-bracket placeholders
849+
preg_match_all('/\{\{((?:(?![{}]).)+?)\}\}/', $rule, $matches);
850+
} else {
851+
// For all other rules, use single-bracket placeholders
852+
preg_match_all('/{(.+?)}/', $rule, $matches);
853+
}
844854

845855
return array_intersect($matches[1], array_keys($data));
846856
}

tests/system/Validation/FormatRulesTest.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,48 @@ public function testRegexMatchFalse(): void
8686
$this->assertFalse($this->validation->run($data));
8787
}
8888

89+
/**
90+
* @see https://github.com/codeigniter4/CodeIgniter4/issues/9596
91+
*/
92+
public function testRegexMatchWithArrayData(): void
93+
{
94+
$data = [
95+
['uid' => '2025/06/000001'],
96+
['uid' => '2025/06/000002'],
97+
['uid' => '2025/06/000003'],
98+
['uid' => '2025/06/000004'],
99+
['uid' => '2025/06/000005'],
100+
];
101+
102+
$this->validation->setRules([
103+
'*.uid' => 'regex_match[/^(\d{4})\/(0[1-9]|1[0-2])\/\d{6}$/]',
104+
]);
105+
106+
$this->assertTrue($this->validation->run($data));
107+
}
108+
109+
public function testRegexMatchWithPlaceholder(): void
110+
{
111+
$data = [
112+
'code' => 'ABC1234',
113+
'phone' => '1234567890',
114+
'prefix' => 'ABC',
115+
'min_digits' => 10,
116+
'max_digits' => 15,
117+
];
118+
119+
$this->validation->setRules([
120+
'prefix' => 'required|string',
121+
'min_digits' => 'required|integer',
122+
'max_digits' => 'required|integer',
123+
'code' => 'required|regex_match[/^{{prefix}}\d{4}$/]',
124+
'phone' => 'required|regex_match[/^\d{{{min_digits}},{{max_digits}}}$/]',
125+
]);
126+
127+
$result = $this->validation->run($data);
128+
$this->assertTrue($result);
129+
}
130+
89131
#[DataProvider('provideValidUrl')]
90132
public function testValidURL(?string $url, bool $isLoose, bool $isStrict): void
91133
{

user_guide_src/source/changelogs/v4.7.0.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@ BREAKING
2323
Behavior Changes
2424
================
2525

26+
Validation Rules
27+
----------------
28+
29+
Placeholders in the ``regex_match`` validation rule must now use double curly braces.
30+
If you previously used single braces like ``regex_match[/^{placeholder}$/]``, you must
31+
update it to use double braces: ``regex_match[/^{{placeholder}}$/]``.
32+
33+
This change was introduced to avoid ambiguity with regular expression syntax,
34+
where single curly braces (e.g., ``{1,3}``) are used for quantifiers.
35+
2636
Interface Changes
2737
=================
2838

user_guide_src/source/libraries/validation.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -976,7 +976,10 @@ numeric No Fails if field contains anything other than
976976
permit_empty No Allows the field to receive an empty array,
977977
empty string, null or false.
978978
regex_match Yes Fails if field does not match the regular ``regex_match[/regex/]``
979-
expression.
979+
expression. **Note:** Since v4.7.0, if
980+
you're using a placeholder with this rule,
981+
you must use double braces ``{{...}}``
982+
instead of single ones ``{...}``.
980983
required No Fails if the field is an empty array, empty
981984
string, null or false.
982985
required_with Yes The field is required when any of the other ``required_with[field1,field2]``

0 commit comments

Comments
 (0)