1818use function is_array ;
1919use function is_scalar ;
2020use function is_string ;
21+ use function next ;
22+ use function reset ;
2123use function str_contains ;
2224
2325class PredicateSet extends AbstractExpression implements PredicateInterface, Countable
@@ -55,6 +57,9 @@ class PredicateSet extends AbstractExpression implements PredicateInterface, Cou
5557 /** SQL clause prefix (e.g., 'WHERE', 'HAVING') - override in subclasses */
5658 protected string $ prefix = '' ;
5759
60+ /** Whether this predicate set needs parentheses when nested (cached for performance) */
61+ protected bool $ hasParentheses = false ;
62+
5863 /**
5964 * Constructor
6065 */
@@ -75,7 +80,8 @@ public function __construct(?array $predicates = null, string $defaultCombinatio
7580 public function addPredicate (PredicateInterface $ predicate , ?string $ combination = null ): static
7681 {
7782 $ predicate ->setCombination ($ combination ?? $ this ->defaultCombination );
78- $ this ->predicates [] = $ predicate ;
83+ $ this ->predicates [] = $ predicate ;
84+ $ this ->hasParentheses = $ this ->hasParentheses || count ($ this ->predicates ) > 1 ;
7985
8086 return $ this ;
8187 }
@@ -124,26 +130,30 @@ public function addPredicates(
124130 $ this ->predicates [] = $ predicate ;
125131 }
126132
133+ $ this ->hasParentheses = $ this ->hasParentheses || count ($ this ->predicates ) > 1 ;
127134 return $ this ;
128135 }
129136
130137 if ($ predicates instanceof PredicateInterface) {
131138 $ predicates ->setCombination ($ combination );
132- $ this ->predicates [] = $ predicates ;
139+ $ this ->predicates [] = $ predicates ;
140+ $ this ->hasParentheses = $ this ->hasParentheses || count ($ this ->predicates ) > 1 ;
133141
134142 return $ this ;
135143 }
136144
137145 if ($ predicates instanceof Closure) {
138146 $ predicates ($ this );
147+ $ this ->hasParentheses = $ this ->hasParentheses || count ($ this ->predicates ) > 1 ;
139148
140149 return $ this ;
141150 }
142151
143152 $ predicate = str_contains ($ predicates , Expression::PLACEHOLDER )
144153 ? new PredicateExpression ($ predicates ) : new Literal ($ predicates );
145154 $ predicate ->setCombination ($ combination );
146- $ this ->predicates [] = $ predicate ;
155+ $ this ->predicates [] = $ predicate ;
156+ $ this ->hasParentheses = $ this ->hasParentheses || count ($ this ->predicates ) > 1 ;
147157
148158 return $ this ;
149159 }
@@ -218,6 +228,12 @@ public function count(): int
218228 return count ($ this ->predicates );
219229 }
220230
231+ #[Override]
232+ public function hasParentheses (): bool
233+ {
234+ return $ this ->hasParentheses ;
235+ }
236+
221237 /** @inheritDoc */
222238 #[Override]
223239 public function prepareSqlString (PreparableSqlBuilder $ builder ): string
@@ -226,23 +242,13 @@ public function prepareSqlString(PreparableSqlBuilder $builder): string
226242 return '' ;
227243 }
228244
229- $ result = '' ;
230- $ first = true ;
245+ $ predicate = reset ($ this ->predicates );
246+ $ sql = $ predicate ->prepareSqlString ($ builder );
247+ $ result = $ predicate ->hasParentheses () ? "( $ sql) " : $ sql ;
231248
232- foreach ($ this ->predicates as $ predicate ) {
233- $ sql = $ predicate ->prepareSqlString ($ builder );
234-
235- // Nested predicate sets with multiple predicates need parentheses
236- if ($ predicate instanceof self && $ predicate ->count () > 1 ) {
237- $ sql = '( ' . $ sql . ') ' ;
238- }
239-
240- if ($ first ) {
241- $ result = $ sql ;
242- $ first = false ;
243- } else {
244- $ result .= ' ' . $ predicate ->combination . ' ' . $ sql ;
245- }
249+ while ($ predicate = next ($ this ->predicates )) {
250+ $ sql = $ predicate ->prepareSqlString ($ builder );
251+ $ result .= " $ predicate ->combination " . ($ predicate ->hasParentheses () ? "( $ sql) " : $ sql );
246252 }
247253
248254 return $ this ->prefix === '' ? $ result : ' ' . $ this ->prefix . ' ' . $ result ;
0 commit comments