@@ -26,11 +26,6 @@ class Grammar
2626 'not_contains ' => 'notContains ' ,
2727 ];
2828
29- /**
30- * The query wrapper.
31- */
32- protected ?string $ wrapper = null ;
33-
3429 /**
3530 * Get all the available operators.
3631 */
@@ -54,131 +49,199 @@ public function wrap(string $query, ?string $prefix = '(', ?string $suffix = ')'
5449 */
5550 public function compile (Builder $ query ): string
5651 {
57- if ($ this ->queryMustBeWrapped ($ query )) {
58- $ this ->wrapper = 'and ' ;
59- }
52+ $ filter = $ this ->compileFilters ($ query );
6053
61- $ filter = $ this ->compileRaws ($ query )
54+ return $ this ->wrapFilterIfNeeded ($ query , $ filter );
55+ }
56+
57+ /**
58+ * Compile all filters for the query.
59+ */
60+ protected function compileFilters (Builder $ query ): string
61+ {
62+ return $ this ->compileRaws ($ query )
6263 .$ this ->compileWheres ($ query )
6364 .$ this ->compileOrWheres ($ query );
65+ }
66+
67+ /**
68+ * Wrap the filter in logical operators if needed.
69+ */
70+ protected function wrapFilterIfNeeded (Builder $ query , string $ filter ): string
71+ {
72+ if ($ query ->isNested ()) {
73+ return $ filter ;
74+ }
75+
76+ // Special case: if we have exactly one AND and one OR, wrap in OR
77+ if ($ this ->shouldWrapEntireQueryInOr ($ query )) {
78+ return $ this ->compileOr ($ filter );
79+ }
80+
81+ // If we have multiple filter types, multiple AND conditions, or multiple raw filters, wrap in AND
82+ if ($ this ->hasMultipleFilterTypes ($ query ) || $ this ->hasMultipleAndConditions ($ query ) || $ this ->hasMultipleRawFilters ($ query )) {
83+ return $ this ->compileAnd ($ filter );
84+ }
85+
86+ // If we only have OR conditions and more than one, wrap in OR
87+ if ($ this ->shouldWrapInOr ($ query )) {
88+ return $ this ->compileOr ($ filter );
89+ }
6490
65- return match ($ this ->wrapper ) {
66- 'and ' => $ this ->compileAnd ($ filter ),
67- 'or ' => $ this ->compileOr ($ filter ),
68- default => $ filter ,
69- };
91+ return $ filter ;
7092 }
7193
7294 /**
73- * Determine if the query must be wrapped in an encapsulating statement .
95+ * Determine if the query has multiple filter types .
7496 */
75- protected function queryMustBeWrapped (Builder $ query ): bool
97+ protected function hasMultipleFilterTypes (Builder $ query ): bool
7698 {
77- return ! $ query ->isNested () && $ this ->hasMultipleFilters ($ query );
99+ $ filterCount = 0 ;
100+
101+ foreach (['and ' , 'or ' , 'raw ' ] as $ type ) {
102+ if (! empty ($ query ->filters [$ type ])) {
103+ $ filterCount ++;
104+ }
105+ }
106+
107+ return $ filterCount > 1 ;
78108 }
79109
80110 /**
81- * Assembles all the "raw" filters on the query .
111+ * Determine if the query has multiple AND conditions .
82112 */
83- protected function compileRaws (Builder $ builder ): string
113+ protected function hasMultipleAndConditions (Builder $ query ): bool
84114 {
85- return $ this -> concatenate ( $ builder ->filters ['raw ' ]) ;
115+ return count ( $ query ->filters ['and ' ] ?? []) > 1 ;
86116 }
87117
88118 /**
89- * Assembles all where clauses in the current wheres property .
119+ * Determine if the query has multiple raw filters .
90120 */
91- protected function compileWheres (Builder $ builder , string $ type = ' and ' ): string
121+ protected function hasMultipleRawFilters (Builder $ query ): bool
92122 {
93- $ filter = '' ;
123+ return count ($ query ->filters ['raw ' ] ?? []) > 1 ;
124+ }
94125
95- foreach ($ builder ->filters [$ type ] ?? [] as $ where ) {
96- $ filter .= $ this ->compileWhere ($ where );
97- }
126+ /**
127+ * Determine if the entire query should be wrapped in an OR statement.
128+ */
129+ protected function shouldWrapEntireQueryInOr (Builder $ query ): bool
130+ {
131+ return count ($ query ->filters ['and ' ] ?? []) === 1
132+ && count ($ query ->filters ['or ' ] ?? []) === 1
133+ && empty ($ query ->filters ['raw ' ]);
134+ }
98135
99- return $ filter ;
136+ /**
137+ * Determine if the query should be wrapped in an OR statement.
138+ */
139+ protected function shouldWrapInOr (Builder $ query ): bool
140+ {
141+ return ! empty ($ query ->filters ['or ' ])
142+ && count ($ query ->filters ['or ' ]) > 1
143+ && empty ($ query ->filters ['and ' ])
144+ && empty ($ query ->filters ['raw ' ]);
145+ }
146+
147+ /**
148+ * Assembles all the "raw" filters on the query.
149+ */
150+ protected function compileRaws (Builder $ query ): string
151+ {
152+ return $ this ->concatenate ($ query ->filters ['raw ' ] ?? []);
153+ }
154+
155+ /**
156+ * Assembles all where clauses in the current wheres property.
157+ */
158+ protected function compileWheres (Builder $ query ): string
159+ {
160+ return $ this ->compileFilterType ($ query , 'and ' );
100161 }
101162
102163 /**
103164 * Assembles all or where clauses in the current orWheres property.
104165 */
105166 protected function compileOrWheres (Builder $ query ): string
106167 {
107- $ filter = $ this ->compileWheres ($ query , 'or ' );
168+ $ filter = $ this ->compileFilterType ($ query , 'or ' );
108169
109- if (! $ this ->hasMultipleFilters ($ query )) {
170+ // If we're going to wrap the entire query in OR, don't wrap OR clauses separately
171+ if ($ this ->shouldWrapEntireQueryInOr ($ query )) {
110172 return $ filter ;
111173 }
112174
113- // Here we will detect whether the entire query can be
114- // wrapped inside of an "or" statement by checking
115- // how many filter statements exist for each type.
116- if ($ this ->queryCanBeWrappedInSingleOrStatement ($ query )) {
117- $ this ->wrapper = 'or ' ;
118- } else {
119- $ filter = $ this ->compileOr ($ filter );
175+ // If we have OR clauses and other filter types (mixed query),
176+ // wrap the OR clauses in their own OR statement
177+ if (! empty ($ filter ) && $ this ->hasMultipleFilterTypes ($ query )) {
178+ return $ this ->compileOr ($ filter );
120179 }
121180
122181 return $ filter ;
123182 }
124183
125184 /**
126- * Determine if the query can be wrapped in a single or statement .
185+ * Compile filters of a specific type .
127186 */
128- protected function queryCanBeWrappedInSingleOrStatement (Builder $ query ): bool
187+ protected function compileFilterType (Builder $ query, string $ type ): string
129188 {
130- return $ this ->has ($ query , 'or ' , '>= ' , 1 )
131- && $ this ->has ($ query , 'and ' , '<= ' , 1 )
132- && $ this ->has ($ query , 'raw ' , '= ' , 0 );
189+ $ filter = '' ;
190+
191+ foreach ($ query ->filters [$ type ] ?? [] as $ where ) {
192+ $ filter .= $ this ->compileWhere ($ where );
193+ }
194+
195+ return $ filter ;
133196 }
134197
135198 /**
136199 * Concatenates filters into a single string.
137200 */
138201 public function concatenate (array $ bindings = []): string
139202 {
140- // Filter out empty query segments.
141203 return implode (
142- array_filter ($ bindings , [ $ this , ' bindingValueIsNotEmpty ' ] )
204+ array_filter ($ bindings , fn ( mixed $ value ) => ! empty ( $ value ) )
143205 );
144206 }
145207
146208 /**
147- * Determine if the binding value is not empty.
209+ * Assembles a single where query.
210+ *
211+ * @throws UnexpectedValueException
148212 */
149- protected function bindingValueIsNotEmpty ( string $ value ): bool
213+ protected function compileWhere ( array $ where ): string
150214 {
151- return ! empty ($ value );
215+ $ method = $ this ->makeCompileMethod ($ where ['operator ' ]);
216+
217+ // Some operators like 'has' and 'notHas' don't require a value
218+ if (in_array ($ where ['operator ' ], ['* ' , '!* ' ])) {
219+ return $ this ->{$ method }($ where ['field ' ]);
220+ }
221+
222+ return $ this ->{$ method }($ where ['field ' ], $ where ['value ' ]);
152223 }
153224
154225 /**
155- * Determine if the query is using multiple filters.
226+ * Make the compile method name for the operator.
227+ *
228+ * @throws UnexpectedValueException
156229 */
157- protected function hasMultipleFilters ( Builder $ query ): bool
230+ protected function makeCompileMethod ( string $ operator ): string
158231 {
159- return $ this ->has ($ query , ['and ' , 'or ' , 'raw ' ], '> ' , 1 );
232+ if (! $ this ->operatorExists ($ operator )) {
233+ throw new UnexpectedValueException ("Invalid LDAP filter operator [' $ operator'] " );
234+ }
235+
236+ return 'compile ' .ucfirst ($ this ->operators [$ operator ]);
160237 }
161238
162239 /**
163- * Determine if the query contains the given filter statement type .
240+ * Determine if the operator exists .
164241 */
165- protected function has ( Builder $ query , array | string $ type , string $ operator = ' >= ' , int $ count = 1 ): bool
242+ protected function operatorExists ( string $ operator ): bool
166243 {
167- $ types = (array ) $ type ;
168-
169- $ filters = 0 ;
170-
171- foreach ($ types as $ type ) {
172- $ filters += count ($ query ->filters [$ type ] ?? []);
173- }
174-
175- return match ($ operator ) {
176- '> ' => $ filters > $ count ,
177- '>= ' => $ filters >= $ count ,
178- '< ' => $ filters < $ count ,
179- '<= ' => $ filters <= $ count ,
180- default => $ filters == $ count ,
181- };
244+ return array_key_exists ($ operator , $ this ->operators );
182245 }
183246
184247 /**
@@ -356,38 +419,4 @@ public function compileNot(string $query): string
356419 {
357420 return $ query ? $ this ->wrap ($ query , '(! ' ) : '' ;
358421 }
359-
360- /**
361- * Assembles a single where query.
362- *
363- * @throws UnexpectedValueException
364- */
365- protected function compileWhere (array $ where ): string
366- {
367- $ method = $ this ->makeCompileMethod ($ where ['operator ' ]);
368-
369- return $ this ->{$ method }($ where ['field ' ], $ where ['value ' ]);
370- }
371-
372- /**
373- * Make the compile method name for the operator.
374- *
375- * @throws UnexpectedValueException
376- */
377- protected function makeCompileMethod (string $ operator ): string
378- {
379- if (! $ this ->operatorExists ($ operator )) {
380- throw new UnexpectedValueException ("Invalid LDAP filter operator [' $ operator'] " );
381- }
382-
383- return 'compile ' .ucfirst ($ this ->operators [$ operator ]);
384- }
385-
386- /**
387- * Determine if the operator exists.
388- */
389- protected function operatorExists (string $ operator ): bool
390- {
391- return array_key_exists ($ operator , $ this ->operators );
392- }
393422}
0 commit comments