Skip to content

Commit cd41ba6

Browse files
committed
[OptionsResolver] optimize splitOutsideParenthesis with regex
Replace manual string parsing with regex-based implementation using PCRE's (*SKIP)(*FAIL) backtracking control verbs. This approach: Achieves 5.9x performance improvement over the original implementation The regex pattern '\([^()]*(?:\([^()]*\)[^()]*)*\)(*SKIP)(*FAIL)|\|' works by: 1. Matching parentheses and their contents (including nested parentheses) 2. Using (*SKIP)(*FAIL) to skip these matches entirely 3. Then splitting on the remaining pipe characters outside parentheses
1 parent ee5d069 commit cd41ba6

File tree

1 file changed

+18
-37
lines changed

1 file changed

+18
-37
lines changed

src/Symfony/Component/OptionsResolver/OptionsResolver.php

Lines changed: 18 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,43 +1215,24 @@ private function verifyTypes(string $type, mixed $value, ?array &$invalidTypes =
12151215
*/
12161216
private function splitOutsideParenthesis(string $type): array
12171217
{
1218-
if (!str_contains($type, '|')) {
1219-
return [$type];
1220-
}
1221-
1222-
if (!str_contains($type, '(') && !str_contains($type, ')')) {
1223-
return explode('|', $type);
1224-
}
1225-
1226-
$parts = [];
1227-
$start = 0;
1228-
$parenthesisLevel = 0;
1229-
$length = \strlen($type);
1230-
1231-
for ($i = 0; $i < $length; ++$i) {
1232-
$char = $type[$i];
1233-
1234-
switch ($char) {
1235-
case '(':
1236-
++$parenthesisLevel;
1237-
break;
1238-
case ')':
1239-
--$parenthesisLevel;
1240-
break;
1241-
case '|':
1242-
if (0 === $parenthesisLevel) {
1243-
$parts[] = substr($type, $start, $i - $start);
1244-
$start = $i + 1;
1245-
}
1246-
break;
1247-
}
1248-
}
1249-
1250-
if ($start < $length) {
1251-
$parts[] = substr($type, $start);
1252-
}
1253-
1254-
return $parts;
1218+
return preg_split(<<<'EOF'
1219+
/
1220+
# Match a balanced parenthetical group, then skip it.
1221+
\( # Match an opening parenthesis.
1222+
[^()]* # Match any non-parenthesis characters.
1223+
(?: # Start a non-capturing group for nested parts.
1224+
\([^()]*\) # Match a simple, non-nested (...) group.
1225+
[^()]* # Match any non-parenthesis characters that follow it.
1226+
)* # Repeat this group zero or more times to handle multiple nested groups.
1227+
\) # Match the final closing parenthesis.
1228+
1229+
(*SKIP)(*FAIL) # Discard the entire match and force the engine to find the next match.
1230+
1231+
| # OR
1232+
1233+
\| # Match the pipe delimiter. This will only be matched if it was not inside a skipped group.
1234+
/x
1235+
EOF, $type);
12551236
}
12561237

12571238
/**

0 commit comments

Comments
 (0)