PluginProbe ʕ •ᴥ•ʔ
JetBackup – Backup, Restore & Migrate / trunk
JetBackup – Backup, Restore & Migrate vtrunk
3.1.22.3 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.8.1 1.4.9 1.5.0 1.5.1 1.5.1.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 1.6.10 1.6.11 1.6.12 1.6.13 1.6.15 1.6.5.1 1.6.8.8 1.6.9 1.6.9.1 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7.5 2.0.8.7 2.0.9.11 2.0.9.14 2.0.9.15 2.0.9.6 2.0.9.7 2.0.9.9 3.1.10.7 3.1.11.1 3.1.12.3 3.1.13.4 3.1.14.17 3.1.15.4 3.1.16.1 3.1.17.5 3.1.18.10 3.1.18.8 3.1.18.9 3.1.19.8 3.1.20.3 3.1.21.3 3.1.7.9 3.1.9.2 trunk 1.1.90 1.1.91 1.2.0 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2
backup / src / JetBackup / 3rdparty / SleekDB / QueryBuilder.php
backup / src / JetBackup / 3rdparty / SleekDB Last commit date
Classes 4 months ago Exceptions 1 year ago .htaccess 1 year ago Cache.php 4 months ago Query.php 1 year ago QueryBuilder.php 1 year ago SleekDB.php 1 year ago Store.php 4 months ago autoload.php 1 year ago index.html 1 year ago web.config 1 year ago
QueryBuilder.php
520 lines
1 <?php
2
3 namespace SleekDB;
4
5 use Closure;
6 use SleekDB\Exceptions\InvalidArgumentException;
7
8 class QueryBuilder
9 {
10
11 /**
12 * @var Store
13 */
14 protected $store;
15 /**
16 * @var Cache
17 */
18 protected $cache;
19
20 protected $whereConditions = [];
21
22 protected $skip = 0;
23 protected $limit = 0;
24 protected $orderBy = [];
25 protected $nestedWhere = []; // TODO remove with version 3.0
26 protected $search = [];
27 protected $searchOptions = [
28 "minLength" => 2,
29 "scoreKey" => "searchScore",
30 "mode" => "or",
31 "algorithm" => Query::SEARCH_ALGORITHM["hits"]
32 ];
33
34 protected $fieldsToSelect = [];
35 protected $fieldsToExclude = [];
36 protected $groupBy = [];
37 protected $havingConditions = [];
38
39 protected $listOfJoins = [];
40 protected $distinctFields = [];
41
42 protected $useCache;
43 protected $regenerateCache = false;
44 protected $cacheLifetime;
45
46
47 // will also not be used for cache token
48 protected $propertiesNotUsedInConditionsArray = [
49 "propertiesNotUsedInConditionsArray",
50 "propertiesNotUsedForCacheToken",
51 "store",
52 "cache",
53 ];
54
55 protected $propertiesNotUsedForCacheToken = [
56 "useCache",
57 "regenerateCache",
58 "cacheLifetime"
59 ];
60
61 /**
62 * QueryBuilder constructor.
63 * @param Store $store
64 */
65 public function __construct(Store $store)
66 {
67 $this->store = $store;
68 $this->useCache = $store->_getUseCache();
69 $this->cacheLifetime = $store->_getDefaultCacheLifetime();
70 $this->searchOptions = $store->_getSearchOptions();
71 }
72
73 /**
74 * Select specific fields
75 * @param array $fieldNames
76 * @return QueryBuilder
77 */
78 public function select(array $fieldNames): QueryBuilder
79 {
80 foreach ($fieldNames as $key => $fieldName) {
81 if(is_string($key)){
82 $this->fieldsToSelect[$key] = $fieldName;
83 } else {
84 $this->fieldsToSelect[] = $fieldName;
85 }
86 }
87 return $this;
88 }
89
90 /**
91 * Exclude specific fields
92 * @param string[] $fieldNames
93 * @return QueryBuilder
94 * @throws InvalidArgumentException
95 */
96 public function except(array $fieldNames): QueryBuilder
97 {
98 $errorMsg = "If except is used an array containing strings with fieldNames has to be given";
99 foreach ($fieldNames as $fieldName) {
100 if (empty($fieldName)) {
101 continue;
102 }
103 if (!is_string($fieldName)) {
104 throw new InvalidArgumentException($errorMsg);
105 }
106 $this->fieldsToExclude[] = $fieldName;
107 }
108 return $this;
109 }
110
111 /**
112 * Add conditions to filter data.
113 * @param array $conditions
114 * @return QueryBuilder
115 * @throws InvalidArgumentException
116 */
117 public function where(array $conditions): QueryBuilder
118 {
119 if (empty($conditions)) {
120 throw new InvalidArgumentException("You need to specify a where clause");
121 }
122
123 $this->whereConditions[] = $conditions;
124
125 return $this;
126 }
127
128 /**
129 * Add or-where conditions to filter data.
130 * @param array $conditions array(array(string fieldName, string condition, mixed value) [, array(...)])
131 * @return QueryBuilder
132 * @throws InvalidArgumentException
133 */
134 public function orWhere(array $conditions): QueryBuilder
135 {
136
137 if (empty($conditions)) {
138 throw new InvalidArgumentException("You need to specify a where clause");
139 }
140
141 $this->whereConditions[] = "or";
142 $this->whereConditions[] = $conditions;
143
144 return $this;
145 }
146
147 /**
148 * Set the amount of data record to skip.
149 * @param int|string $skip
150 * @return QueryBuilder
151 * @throws InvalidArgumentException
152 */
153 public function skip($skip = 0): QueryBuilder
154 {
155 if((!is_string($skip) || !is_numeric($skip)) && !is_int($skip)){
156 throw new InvalidArgumentException("Skip has to be an integer or a numeric string");
157 }
158
159 if(!is_int($skip)){
160 $skip = (int) $skip;
161 }
162
163 if($skip < 0){
164 throw new InvalidArgumentException("Skip has to be an integer >= 0");
165 }
166
167 $this->skip = $skip;
168
169 return $this;
170 }
171
172 /**
173 * Set the amount of data record to limit.
174 * @param int|string $limit
175 * @return QueryBuilder
176 * @throws InvalidArgumentException
177 */
178 public function limit($limit = 0): QueryBuilder
179 {
180
181 if((!is_string($limit) || !is_numeric($limit)) && !is_int($limit)){
182 throw new InvalidArgumentException("Limit has to be an integer or a numeric string");
183 }
184
185 if(!is_int($limit)){
186 $limit = (int) $limit;
187 }
188
189 if($limit <= 0){
190 throw new InvalidArgumentException("Limit has to be an integer > 0");
191 }
192
193 $this->limit = $limit;
194
195 return $this;
196 }
197
198 /**
199 * Set the sort order.
200 * @param array $criteria to order by. array($fieldName => $order). $order can be "asc" or "desc"
201 * @return QueryBuilder
202 * @throws InvalidArgumentException
203 */
204 public function orderBy( array $criteria): QueryBuilder
205 {
206 foreach ($criteria as $fieldName => $order){
207
208 if(!is_string($order)) {
209 throw new InvalidArgumentException('Order has to be a string! Please use "asc" or "desc" only.');
210 }
211
212 $order = strtolower($order);
213
214 if(!is_string($fieldName)) {
215 throw new InvalidArgumentException("Field name has to be a string");
216 }
217
218 if (!in_array($order, ['asc', 'desc'])) {
219 throw new InvalidArgumentException('Please use "asc" or "desc" only.');
220 }
221
222 $this->orderBy[] = [
223 'fieldName' => $fieldName,
224 'order' => $order
225 ];
226 }
227
228 return $this;
229 }
230
231 /**
232 * Do a fulltext like search against one or multiple fields.
233 * @param string|array $fields one or multiple fieldNames as an array
234 * @param string $query
235 * @param array $options
236 * @return QueryBuilder
237 * @throws InvalidArgumentException
238 */
239 public function search($fields, string $query, array $options = []): QueryBuilder
240 {
241 if(!is_array($fields) && !is_string($fields)){
242 throw new InvalidArgumentException("Fields to search through have to be either a string or an array.");
243 }
244
245 if(!is_array($fields)){
246 $fields = (array)$fields;
247 }
248
249 if (empty($fields)) {
250 throw new InvalidArgumentException('Cant perform search due to no field name was provided');
251 }
252
253 if(count($fields) > 100){
254 trigger_error('Searching through more than 100 fields is not recommended and can be resource heavy.', E_USER_WARNING);
255 }
256
257 if (!empty($query)) {
258 $this->search = [
259 'fields' => $fields,
260 'query' => $query
261 ];
262 if(!empty($options)){
263 if(array_key_exists("minLength", $options) && is_int($options["minLength"]) && $options["minLength"] > 0){
264 $this->searchOptions["minLength"] = $options["minLength"];
265 }
266 if(array_key_exists("mode", $options) && is_string($options["mode"])){
267 $searchMode = strtolower(trim($options["mode"]));
268 if(in_array($searchMode, ["and", "or"])){
269 $this->searchOptions["mode"] = $searchMode;
270 }
271 }
272 if(array_key_exists("scoreKey", $options) && (is_string($options["scoreKey"]) || is_null($options["scoreKey"]))){
273 $this->searchOptions["scoreKey"] = $options["scoreKey"];
274 }
275 if(array_key_exists("algorithm", $options) && in_array($options["algorithm"], Query::SEARCH_ALGORITHM, true)){
276 $this->searchOptions["algorithm"] = $options["algorithm"];
277 }
278 }
279 }
280 return $this;
281 }
282
283 /**
284 * @param Closure $joinFunction
285 * @param string $propertyName
286 * @return QueryBuilder
287 */
288 public function join(Closure $joinFunction, string $propertyName): QueryBuilder
289 {
290 $this->listOfJoins[] = [
291 'propertyName' => $propertyName,
292 'joinFunction' => $joinFunction
293 ];
294 return $this;
295 }
296
297
298 /**
299 * Return distinct values.
300 * @param array|string $fields
301 * @return QueryBuilder
302 * @throws InvalidArgumentException
303 */
304 public function distinct($fields = []): QueryBuilder
305 {
306 $fieldType = gettype($fields);
307 if ($fieldType === 'array') {
308 if ($fields === array_values($fields)) {
309 // Append fields.
310 $this->distinctFields = array_merge($this->distinctFields, $fields);
311 } else {
312 throw new InvalidArgumentException(
313 'Field value in distinct() method can not be an associative array,
314 please provide a string or a list of string as a non-associative array.'
315 );
316 }
317 } else if ($fieldType === 'string' && !empty($fields)) {
318 $this->distinctFields[] = trim($fields);
319 } else {
320 throw new InvalidArgumentException(
321 'Field value in distinct() is invalid.'
322 );
323 }
324 return $this;
325 }
326
327 /**
328 * Use caching for current query
329 *
330 * @param int|null $lifetime time to live as int in seconds or null to regenerate cache on every insert, update and delete
331 *
332 * @return QueryBuilder
333 * @throws InvalidArgumentException
334 */
335 public function useCache(?int $lifetime = null): QueryBuilder
336 {
337 $this->useCache = true;
338 if((!is_int($lifetime) || $lifetime < 0) && !is_null($lifetime)){
339 throw new InvalidArgumentException("lifetime has to be int >= 0 or null");
340 }
341 $this->cacheLifetime = $lifetime;
342 return $this;
343 }
344
345 /**
346 * Disable cache for the query.
347 * @return QueryBuilder
348 */
349 public function disableCache(): QueryBuilder
350 {
351 $this->useCache = false;
352 return $this;
353 }
354
355 /**
356 * Re-generate the cache for the query.
357 * @return QueryBuilder
358 */
359 public function regenerateCache(): QueryBuilder
360 {
361 $this->regenerateCache = true;
362 return $this;
363 }
364
365 /**
366 * @return Query
367 */
368 public function getQuery(): Query
369 {
370 return new Query($this);
371 }
372
373 /**
374 * @param array $groupByFields
375 * @param string|null $countKeyName
376 * @param bool $allowEmpty
377 *
378 * @return QueryBuilder
379 */
380 public function groupBy(array $groupByFields, ?string $countKeyName = null, bool $allowEmpty = false): QueryBuilder
381 {
382 $this->groupBy = [
383 "groupByFields" => $groupByFields,
384 "countKeyName" => $countKeyName,
385 "allowEmpty" => $allowEmpty
386 ];
387 return $this;
388 }
389
390 /**
391 * Filter result data of groupBy
392 * @param array $criteria
393 * @return QueryBuilder
394 * @throws InvalidArgumentException
395 */
396 public function having(array $criteria): QueryBuilder
397 {
398 if (empty($criteria)) {
399 throw new InvalidArgumentException("You need to specify a having clause");
400 }
401 $this->havingConditions = $criteria;
402 return $this;
403 }
404
405 /**
406 * Returns a an array used to generate a unique token for the current query.
407 * @return array
408 */
409 public function _getCacheTokenArray(): array
410 {
411 $properties = [];
412 $conditionsArray = $this->_getConditionProperties();
413
414 foreach ($conditionsArray as $propertyName => $propertyValue){
415 if(!in_array($propertyName, $this->propertiesNotUsedForCacheToken, true)){
416 $properties[$propertyName] = $propertyValue;
417 }
418 }
419
420 return $properties;
421 }
422
423 /**
424 * Returns an array containing all information needed to execute an query.
425 * @return array
426 */
427 public function _getConditionProperties(): array
428 {
429 $allProperties = get_object_vars($this);
430 $properties = [];
431
432 foreach ($allProperties as $propertyName => $propertyValue){
433 if(!in_array($propertyName, $this->propertiesNotUsedInConditionsArray, true)){
434 $properties[$propertyName] = $propertyValue;
435 }
436 }
437
438 return $properties;
439 }
440
441 /**
442 * Returns the Store object used to create the QueryBuilder object.
443 * @return Store
444 */
445 public function _getStore(): Store{
446 return $this->store;
447 }
448
449 /**
450 * Add "in" condition to filter data.
451 * @param string $fieldName
452 * @param array $values
453 * @return QueryBuilder
454 * @throws InvalidArgumentException
455 * @deprecated since version 2.4, use where and orWhere instead.
456 */
457 public function in(string $fieldName, array $values = []): QueryBuilder
458 {
459 if (empty($fieldName)) {
460 throw new InvalidArgumentException('Field name for in clause can not be empty.');
461 }
462
463 // Add to conditions with "AND" operation
464 $this->whereConditions[] = [$fieldName, "in", $values];
465 return $this;
466 }
467
468 /**
469 * Add "not in" condition to filter data.
470 * @param string $fieldName
471 * @param array $values
472 * @return QueryBuilder
473 * @throws InvalidArgumentException
474 * @deprecated since version 2.4, use where and orWhere instead.
475 */
476 public function notIn(string $fieldName, array $values = []): QueryBuilder
477 {
478 if (empty($fieldName)) {
479 throw new InvalidArgumentException('Field name for notIn clause can not be empty.');
480 }
481
482 // Add to conditions with "AND" operation
483 $this->whereConditions[] = [$fieldName, "not in", $values];
484 return $this;
485 }
486
487 /**
488 * Add a where statement that is nested. ( $x or ($y and $z) )
489 * @param array $conditions
490 * @return QueryBuilder
491 * @throws InvalidArgumentException
492 * @deprecated since version 2.3, use where or orWhere instead.
493 */
494 public function nestedWhere(array $conditions): QueryBuilder
495 {
496 // TODO remove with version 3.0
497 if(empty($conditions)){
498 throw new InvalidArgumentException("You need to specify nested where clauses");
499 }
500
501 if(count($conditions) > 1){
502 throw new InvalidArgumentException("You are not allowed to specify multiple elements at the first depth!");
503 }
504
505 $outerMostOperation = (array_keys($conditions))[0];
506 $outerMostOperation = (is_string($outerMostOperation)) ? strtolower($outerMostOperation) : $outerMostOperation;
507
508 $allowedOuterMostOperations = [0, "and", "or"];
509
510 if(!in_array($outerMostOperation, $allowedOuterMostOperations, true)){
511 throw new InvalidArgumentException("Outer most operation has to one of the following: ( 0 / and / or ) ");
512 }
513
514 $this->nestedWhere = $conditions;
515
516 return $this;
517 }
518
519 }
520