blob: dcda3d73378a0ea64dc5f1d312edf8b90064372a [file] [log] [blame]
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001<?php
2
3namespace Adldap\Query;
4
5class Grammar
6{
7 /**
8 * Wraps a query string in brackets.
9 *
10 * Produces: (query)
11 *
12 * @param string $query
13 * @param string $prefix
14 * @param string $suffix
15 *
16 * @return string
17 */
18 public function wrap($query, $prefix = '(', $suffix = ')')
19 {
20 return $prefix.$query.$suffix;
21 }
22
23 /**
24 * Compiles the Builder instance into an LDAP query string.
25 *
26 * @param Builder $builder
27 *
28 * @return string
29 */
30 public function compile(Builder $builder)
31 {
32 $ands = $builder->filters['and'];
33 $ors = $builder->filters['or'];
34 $raws = $builder->filters['raw'];
35
36 $query = $this->concatenate($raws);
37
38 $query = $this->compileWheres($ands, $query);
39
40 $query = $this->compileOrWheres($ors, $query);
41
42 // We need to check if the query is already nested, otherwise
43 // we'll nest it here and return the result.
44 if (!$builder->isNested()) {
45 $total = count($ands) + count($raws);
46
47 // Make sure we wrap the query in an 'and' if using
48 // multiple filters. We also need to check if only
49 // one where is used with multiple orWheres, that
50 // we wrap it in an `and` query.
51 if ($total > 1 || (count($ands) === 1 && count($ors) > 0)) {
52 $query = $this->compileAnd($query);
53 }
54 }
55
56 return $query;
57 }
58
59 /**
60 * Concatenates filters into a single string.
61 *
62 * @param array $bindings
63 *
64 * @return string
65 */
66 public function concatenate(array $bindings = [])
67 {
68 // Filter out empty query segments.
69 $bindings = array_filter($bindings, function ($value) {
70 return (string) $value !== '';
71 });
72
73 return implode('', $bindings);
74 }
75
76 /**
77 * Returns a query string for equals.
78 *
79 * Produces: (field=value)
80 *
81 * @param string $field
82 * @param string $value
83 *
84 * @return string
85 */
86 public function compileEquals($field, $value)
87 {
88 return $this->wrap($field.Operator::$equals.$value);
89 }
90
91 /**
92 * Returns a query string for does not equal.
93 *
94 * Produces: (!(field=value))
95 *
96 * @param string $field
97 * @param string $value
98 *
99 * @return string
100 */
101 public function compileDoesNotEqual($field, $value)
102 {
103 return $this->compileNot($this->compileEquals($field, $value));
104 }
105
106 /**
107 * Alias for does not equal operator (!=) operator.
108 *
109 * Produces: (!(field=value))
110 *
111 * @param string $field
112 * @param string $value
113 *
114 * @return string
115 */
116 public function compileDoesNotEqualAlias($field, $value)
117 {
118 return $this->compileDoesNotEqual($field, $value);
119 }
120
121 /**
122 * Returns a query string for greater than or equals.
123 *
124 * Produces: (field>=value)
125 *
126 * @param string $field
127 * @param string $value
128 *
129 * @return string
130 */
131 public function compileGreaterThanOrEquals($field, $value)
132 {
133 return $this->wrap($field.Operator::$greaterThanOrEquals.$value);
134 }
135
136 /**
137 * Returns a query string for less than or equals.
138 *
139 * Produces: (field<=value)
140 *
141 * @param string $field
142 * @param string $value
143 *
144 * @return string
145 */
146 public function compileLessThanOrEquals($field, $value)
147 {
148 return $this->wrap($field.Operator::$lessThanOrEquals.$value);
149 }
150
151 /**
152 * Returns a query string for approximately equals.
153 *
154 * Produces: (field~=value)
155 *
156 * @param string $field
157 * @param string $value
158 *
159 * @return string
160 */
161 public function compileApproximatelyEquals($field, $value)
162 {
163 return $this->wrap($field.Operator::$approximatelyEquals.$value);
164 }
165
166 /**
167 * Returns a query string for starts with.
168 *
169 * Produces: (field=value*)
170 *
171 * @param string $field
172 * @param string $value
173 *
174 * @return string
175 */
176 public function compileStartsWith($field, $value)
177 {
178 return $this->wrap($field.Operator::$equals.$value.Operator::$has);
179 }
180
181 /**
182 * Returns a query string for does not start with.
183 *
184 * Produces: (!(field=*value))
185 *
186 * @param string $field
187 * @param string $value
188 *
189 * @return string
190 */
191 public function compileNotStartsWith($field, $value)
192 {
193 return $this->compileNot($this->compileStartsWith($field, $value));
194 }
195
196 /**
197 * Returns a query string for ends with.
198 *
199 * Produces: (field=*value)
200 *
201 * @param string $field
202 * @param string $value
203 *
204 * @return string
205 */
206 public function compileEndsWith($field, $value)
207 {
208 return $this->wrap($field.Operator::$equals.Operator::$has.$value);
209 }
210
211 /**
212 * Returns a query string for does not end with.
213 *
214 * Produces: (!(field=value*))
215 *
216 * @param string $field
217 * @param string $value
218 *
219 * @return string
220 */
221 public function compileNotEndsWith($field, $value)
222 {
223 return $this->compileNot($this->compileEndsWith($field, $value));
224 }
225
226 /**
227 * Returns a query string for contains.
228 *
229 * Produces: (field=*value*)
230 *
231 * @param string $field
232 * @param string $value
233 *
234 * @return string
235 */
236 public function compileContains($field, $value)
237 {
238 return $this->wrap($field.Operator::$equals.Operator::$has.$value.Operator::$has);
239 }
240
241 /**
242 * Returns a query string for does not contain.
243 *
244 * Produces: (!(field=*value*))
245 *
246 * @param string $field
247 * @param string $value
248 *
249 * @return string
250 */
251 public function compileNotContains($field, $value)
252 {
253 return $this->compileNot($this->compileContains($field, $value));
254 }
255
256 /**
257 * Returns a query string for a where has.
258 *
259 * Produces: (field=*)
260 *
261 * @param string $field
262 *
263 * @return string
264 */
265 public function compileHas($field)
266 {
267 return $this->wrap($field.Operator::$equals.Operator::$has);
268 }
269
270 /**
271 * Returns a query string for a where does not have.
272 *
273 * Produces: (!(field=*))
274 *
275 * @param string $field
276 *
277 * @return string
278 */
279 public function compileNotHas($field)
280 {
281 return $this->compileNot($this->compileHas($field));
282 }
283
284 /**
285 * Wraps the inserted query inside an AND operator.
286 *
287 * Produces: (&query)
288 *
289 * @param string $query
290 *
291 * @return string
292 */
293 public function compileAnd($query)
294 {
295 return $query ? $this->wrap($query, '(&') : '';
296 }
297
298 /**
299 * Wraps the inserted query inside an OR operator.
300 *
301 * Produces: (|query)
302 *
303 * @param string $query
304 *
305 * @return string
306 */
307 public function compileOr($query)
308 {
309 return $query ? $this->wrap($query, '(|') : '';
310 }
311
312 /**
313 * Wraps the inserted query inside an NOT operator.
314 *
315 * @param string $query
316 *
317 * @return string
318 */
319 public function compileNot($query)
320 {
321 return $query ? $this->wrap($query, '(!') : '';
322 }
323
324 /**
325 * Assembles all where clauses in the current wheres property.
326 *
327 * @param array $wheres
328 * @param string $query
329 *
330 * @return string
331 */
332 protected function compileWheres(array $wheres = [], $query = '')
333 {
334 foreach ($wheres as $where) {
335 $query .= $this->compileWhere($where);
336 }
337
338 return $query;
339 }
340
341 /**
342 * Assembles all or where clauses in the current orWheres property.
343 *
344 * @param array $orWheres
345 * @param string $query
346 *
347 * @return string
348 */
349 protected function compileOrWheres(array $orWheres = [], $query = '')
350 {
351 $or = '';
352
353 foreach ($orWheres as $where) {
354 $or .= $this->compileWhere($where);
355 }
356
357 // Make sure we wrap the query in an 'or' if using multiple
358 // orWheres. For example (|(QUERY)(ORWHEREQUERY)).
359 if (($query && count($orWheres) > 0) || count($orWheres) > 1) {
360 $query .= $this->compileOr($or);
361 } else {
362 $query .= $or;
363 }
364
365 return $query;
366 }
367
368 /**
369 * Assembles a single where query based
370 * on its operator and returns it.
371 *
372 * @param array $where
373 *
374 * @return string|null
375 */
376 protected function compileWhere(array $where)
377 {
378 // Get the name of the operator.
379 if ($name = array_search($where['operator'], Operator::all())) {
380 // If the name was found we'll camel case it
381 // to run it through the compile method.
382 $method = 'compile'.ucfirst($name);
383
384 // Make sure the compile method exists for the operator.
385 if (method_exists($this, $method)) {
386 return $this->{$method}($where['field'], $where['value']);
387 }
388 }
389 }
390}