PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 1.5.21
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v1.5.21
2.4.0 2.3.0 2.2.5 2.2.0 2.1.2 2.1.1 trunk 1.10.0 1.10.01 1.10.02 1.5.0 1.5.01 1.5.02 1.5.1 1.5.10 1.5.20 1.5.21 1.5.22 1.5.23 1.5.24 1.5.25 1.6.0 1.7.0 1.7.1 1.7.2 All 33 releases
fluent-booking / vendor / wpfluent / framework / src / WPFluent / Database / Concerns / BuildsQueries.php

BuildsQueries.php in Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution 1.5.21, at vendor/wpfluent/framework/src/WPFluent/Database/Concerns/BuildsQueries.php

499 lines 16.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBooking\Framework\Database\Concerns;
4
5 use RuntimeException;
6 use InvalidArgumentException;
7 use FluentBooking\Framework\Container\Container;
8 use FluentBooking\Framework\Support\Helper;
9 use FluentBooking\Framework\Support\Collection;
10 use FluentBooking\Framework\Support\Conditionable;
11 use FluentBooking\Framework\Support\LazyCollection;
12 use FluentBooking\Framework\Database\Orm\Builder;
13 use FluentBooking\Framework\Database\RecordsNotFoundException;
14 use FluentBooking\Framework\Database\MultipleRecordsFoundException;
15 use FluentBooking\Framework\Pagination\Cursor;
16 use FluentBooking\Framework\Pagination\Paginator;
17 use FluentBooking\Framework\Pagination\CursorPaginator;
18 use FluentBooking\Framework\Pagination\LengthAwarePaginator;
19
20
21 trait BuildsQueries
22 {
23 use Conditionable;
24
25 /**
26 * Chunk the results of the query.
27 *
28 * @param int $count
29 * @param callable $callback
30 * @return bool
31 */
32 public function chunk($count, callable $callback)
33 {
34 $this->enforceOrderBy();
35
36 $page = 1;
37
38 do {
39 // We'll execute the query for the given page and get the results. If there are
40 // no results we can just break and return from here. When there are results
41 // we will call the callback with the current chunk of these results here.
42 $results = $this->forPage($page, $count)->get();
43
44 $countResults = $results->count();
45
46 if ($countResults == 0) {
47 break;
48 }
49
50 // On each chunk result set, we will pass them to the callback and then let the
51 // developer take care of everything within the callback, which allows us to
52 // keep the memory low for spinning through large result sets for working.
53 if ($callback($results, $page) === false) {
54 return false;
55 }
56
57 unset($results);
58
59 $page++;
60 } while ($countResults == $count);
61
62 return true;
63 }
64
65 /**
66 * Run a map over each item while chunking.
67 *
68 * @param callable $callback
69 * @param int $count
70 * @return \FluentBooking\Framework\Support\Collection
71 */
72 public function chunkMap(callable $callback, $count = 1000)
73 {
74 $collection = Collection::make();
75
76 $this->chunk($count, function ($items) use ($collection, $callback) {
77 $items->each(function ($item) use ($collection, $callback) {
78 $collection->push($callback($item));
79 });
80 });
81
82 return $collection;
83 }
84
85 /**
86 * Execute a callback over each item while chunking.
87 *
88 * @param callable $callback
89 * @param int $count
90 * @return bool
91 *
92 * @throws \RuntimeException
93 */
94 public function each(callable $callback, $count = 1000)
95 {
96 return $this->chunk($count, function ($results) use ($callback) {
97 foreach ($results as $key => $value) {
98 if ($callback($value, $key) === false) {
99 return false;
100 }
101 }
102 });
103 }
104
105 /**
106 * Chunk the results of a query by comparing IDs.
107 *
108 * @param int $count
109 * @param callable $callback
110 * @param string|null $column
111 * @param string|null $alias
112 * @return bool
113 */
114 public function chunkById($count, callable $callback, $column = null, $alias = null)
115 {
116 $column = $column ?? $this->defaultKeyName();
117
118 $alias = $alias ?? $column;
119
120 $lastId = null;
121
122 $page = 1;
123
124 do {
125 $clone = clone $this;
126
127 // We'll execute the query for the given page and get the results. If there are
128 // no results we can just break and return from here. When there are results
129 // we will call the callback with the current chunk of these results here.
130 $results = $clone->forPageAfterId($count, $lastId, $column)->get();
131
132 $countResults = $results->count();
133
134 if ($countResults == 0) {
135 break;
136 }
137
138 // On each chunk result set, we will pass them to the callback and then let the
139 // developer take care of everything within the callback, which allows us to
140 // keep the memory low for spinning through large result sets for working.
141 if ($callback($results, $page) === false) {
142 return false;
143 }
144
145 $lastId = $results->last()->{$alias};
146
147 if ($lastId === null) {
148 throw new RuntimeException("The chunkById operation was aborted because the [{$alias}] column is not present in the query result.");
149 }
150
151 unset($results);
152
153 $page++;
154 } while ($countResults == $count);
155
156 return true;
157 }
158
159 /**
160 * Execute a callback over each item while chunking by ID.
161 *
162 * @param callable $callback
163 * @param int $count
164 * @param string|null $column
165 * @param string|null $alias
166 * @return bool
167 */
168 public function eachById(callable $callback, $count = 1000, $column = null, $alias = null)
169 {
170 return $this->chunkById($count, function ($results, $page) use ($callback, $count) {
171 foreach ($results as $key => $value) {
172 if ($callback($value, (($page - 1) * $count) + $key) === false) {
173 return false;
174 }
175 }
176 }, $column, $alias);
177 }
178
179 /**
180 * Query lazily, by chunks of the given size.
181 *
182 * @param int $chunkSize
183 * @return \FluentBooking\Framework\Support\LazyCollection
184 *
185 * @throws \InvalidArgumentException
186 */
187 public function lazy($chunkSize = 1000)
188 {
189 if ($chunkSize < 1) {
190 throw new InvalidArgumentException('The chunk size should be at least 1');
191 }
192
193 $this->enforceOrderBy();
194
195 return LazyCollection::make(function () use ($chunkSize) {
196 $page = 1;
197
198 while (true) {
199 $results = $this->forPage($page++, $chunkSize)->get();
200
201 foreach ($results as $result) {
202 yield $result;
203 }
204
205 if ($results->count() < $chunkSize) {
206 return;
207 }
208 }
209 });
210 }
211
212 /**
213 * Query lazily, by chunking the results of a query by comparing IDs.
214 *
215 * @param int $chunkSize
216 * @param string|null $column
217 * @param string|null $alias
218 * @return \FluentBooking\Framework\Support\LazyCollection
219 *
220 * @throws \InvalidArgumentException
221 */
222 public function lazyById($chunkSize = 1000, $column = null, $alias = null)
223 {
224 return $this->orderedLazyById($chunkSize, $column, $alias);
225 }
226
227 /**
228 * Query lazily, by chunking the results of a query by comparing IDs in descending order.
229 *
230 * @param int $chunkSize
231 * @param string|null $column
232 * @param string|null $alias
233 * @return \FluentBooking\Framework\Support\LazyCollection
234 *
235 * @throws \InvalidArgumentException
236 */
237 public function lazyByIdDesc($chunkSize = 1000, $column = null, $alias = null)
238 {
239 return $this->orderedLazyById($chunkSize, $column, $alias, true);
240 }
241
242 /**
243 * Query lazily, by chunking the results of a query by comparing IDs in a given order.
244 *
245 * @param int $chunkSize
246 * @param string|null $column
247 * @param string|null $alias
248 * @param bool $descending
249 * @return \FluentBooking\Framework\Support\LazyCollection
250 *
251 * @throws \InvalidArgumentException
252 */
253 protected function orderedLazyById($chunkSize = 1000, $column = null, $alias = null, $descending = false)
254 {
255 if ($chunkSize < 1) {
256 throw new InvalidArgumentException('The chunk size should be at least 1');
257 }
258
259 $column = $column ?? $this->defaultKeyName();
260
261 $alias = $alias ?? $column;
262
263 return LazyCollection::make(function () use ($chunkSize, $column, $alias, $descending) {
264 $lastId = null;
265
266 while (true) {
267 $clone = clone $this;
268
269 if ($descending) {
270 $results = $clone->forPageBeforeId($chunkSize, $lastId, $column)->get();
271 } else {
272 $results = $clone->forPageAfterId($chunkSize, $lastId, $column)->get();
273 }
274
275 foreach ($results as $result) {
276 yield $result;
277 }
278
279 if ($results->count() < $chunkSize) {
280 return;
281 }
282
283 $lastId = $results->last()->{$alias};
284 }
285 });
286 }
287
288 /**
289 * Execute the query and get the first result.
290 *
291 * @param array|string $columns
292 * @return \FluentBooking\Framework\Database\Orm\Model|object|static|null
293 */
294 public function first($columns = ['*'])
295 {
296 return $this->take(1)->get($columns)->first();
297 }
298
299 /**
300 * Execute the query and get the first result if it's the sole matching record.
301 *
302 * @param array|string $columns
303 * @return \FluentBooking\Framework\Database\Orm\Model|object|static|null
304 *
305 * @throws \FluentBooking\Framework\Database\RecordsNotFoundException
306 * @throws \FluentBooking\Framework\Database\MultipleRecordsFoundException
307 */
308 public function sole($columns = ['*'])
309 {
310 $result = $this->take(2)->get($columns);
311
312 if ($result->isEmpty()) {
313 throw new RecordsNotFoundException;
314 }
315
316 if ($result->count() > 1) {
317 throw new MultipleRecordsFoundException;
318 }
319
320 return $result->first();
321 }
322
323 /**
324 * Paginate the given query using a cursor paginator.
325 *
326 * @param int $perPage
327 * @param array $columns
328 * @param string $cursorName
329 * @param \FluentBooking\Framework\Pagination\Cursor|string|null $cursor
330 * @return \FluentBooking\Framework\Pagination\CursorPaginator
331 */
332 protected function paginateUsingCursor($perPage, $columns = ['*'], $cursorName = 'cursor', $cursor = null)
333 {
334 if (! $cursor instanceof Cursor) {
335 $cursor = is_string($cursor)
336 ? Cursor::fromEncoded($cursor)
337 : CursorPaginator::resolveCurrentCursor($cursorName, $cursor);
338 }
339
340 $orders = $this->ensureOrderForCursorPagination(! is_null($cursor) && $cursor->pointsToPreviousItems());
341
342 if (! is_null($cursor)) {
343 $addCursorConditions = function (self $builder, $previousColumn, $i) use (&$addCursorConditions, $cursor, $orders) {
344 $unionBuilders = isset($builder->unions) ? Helper::collect($builder->unions)->pluck('query') : Helper::collect();
345
346 if (! is_null($previousColumn)) {
347 $builder->where(
348 $this->getOriginalColumnNameForCursorPagination($this, $previousColumn),
349 '=',
350 $cursor->parameter($previousColumn)
351 );
352
353 $unionBuilders->each(function ($unionBuilder) use ($previousColumn, $cursor) {
354 $unionBuilder->where(
355 $this->getOriginalColumnNameForCursorPagination($this, $previousColumn),
356 '=',
357 $cursor->parameter($previousColumn)
358 );
359
360 $this->addBinding($unionBuilder->getRawBindings()['where'], 'union');
361 });
362 }
363
364 $builder->where(function (self $builder) use ($addCursorConditions, $cursor, $orders, $i, $unionBuilders) {
365 ['column' => $column, 'direction' => $direction] = $orders[$i];
366
367 $builder->where(
368 $this->getOriginalColumnNameForCursorPagination($this, $column),
369 $direction === 'asc' ? '>' : '<',
370 $cursor->parameter($column)
371 );
372
373 if ($i < $orders->count() - 1) {
374 $builder->orWhere(function (self $builder) use ($addCursorConditions, $column, $i) {
375 $addCursorConditions($builder, $column, $i + 1);
376 });
377 }
378
379 $unionBuilders->each(function ($unionBuilder) use ($column, $direction, $cursor, $i, $orders, $addCursorConditions) {
380 $unionBuilder->where(function ($unionBuilder) use ($column, $direction, $cursor, $i, $orders, $addCursorConditions) {
381 $unionBuilder->where(
382 $this->getOriginalColumnNameForCursorPagination($this, $column),
383 $direction === 'asc' ? '>' : '<',
384 $cursor->parameter($column)
385 );
386
387 if ($i < $orders->count() - 1) {
388 $unionBuilder->orWhere(function (self $builder) use ($addCursorConditions, $column, $i) {
389 $addCursorConditions($builder, $column, $i + 1);
390 });
391 }
392
393 $this->addBinding($unionBuilder->getRawBindings()['where'], 'union');
394 });
395 });
396 });
397 };
398
399 $addCursorConditions($this, null, 0);
400 }
401
402 $this->limit($perPage + 1);
403
404 return $this->cursorPaginator($this->get($columns), $perPage, $cursor, [
405 'path' => Paginator::resolveCurrentPath(),
406 'cursorName' => $cursorName,
407 'parameters' => $orders->pluck('column')->toArray(),
408 ]);
409 }
410
411 /**
412 * Get the original column name of the given column, without any aliasing.
413 *
414 * @param \FluentBooking\Framework\Database\Query\Builder|\FluentBooking\Framework\Database\Orm\Builder $builder
415 * @param string $parameter
416 * @return string
417 */
418 protected function getOriginalColumnNameForCursorPagination($builder, string $parameter)
419 {
420 $columns = $builder instanceof Builder ? $builder->getQuery()->columns : $builder->columns;
421
422 if (! is_null($columns)) {
423 foreach ($columns as $column) {
424 if (($position = stripos($column, ' as ')) !== false) {
425 $as = substr($column, $position, 4);
426
427 [$original, $alias] = explode($as, $column);
428
429 if ($parameter === $alias) {
430 return $original;
431 }
432 }
433 }
434 }
435
436 return $parameter;
437 }
438
439 /**
440 * Create a new length-aware paginator instance.
441 *
442 * @param \FluentBooking\Framework\Support\Collection $items
443 * @param int $total
444 * @param int $perPage
445 * @param int $currentPage
446 * @param array $options
447 * @return \FluentBooking\Framework\Pagination\LengthAwarePaginator
448 */
449 protected function paginator($items, $total, $perPage, $currentPage, $options)
450 {
451 return Container::getInstance()->makeWith(LengthAwarePaginator::class, compact(
452 'items', 'total', 'perPage', 'currentPage', 'options'
453 ));
454 }
455
456 /**
457 * Create a new simple paginator instance.
458 *
459 * @param \FluentBooking\Framework\Support\Collection $items
460 * @param int $perPage
461 * @param int $currentPage
462 * @param array $options
463 * @return \FluentBooking\Framework\Pagination\Paginator
464 */
465 protected function simplePaginator($items, $perPage, $currentPage, $options)
466 {
467 return Container::getInstance()->makeWith(Paginator::class, compact(
468 'items', 'perPage', 'currentPage', 'options'
469 ));
470 }
471
472 /**
473 * Create a new cursor paginator instance.
474 *
475 * @param \FluentBooking\Framework\Support\Collection $items
476 * @param int $perPage
477 * @param \FluentBooking\Framework\Pagination\Cursor $cursor
478 * @param array $options
479 * @return \FluentBooking\Framework\Pagination\CursorPaginator
480 */
481 protected function cursorPaginator($items, $perPage, $cursor, $options)
482 {
483 return Container::getInstance()->makeWith(CursorPaginator::class, compact(
484 'items', 'perPage', 'cursor', 'options'
485 ));
486 }
487
488 /**
489 * Pass the query to a given callback.
490 *
491 * @param callable $callback
492 * @return $this|mixed
493 */
494 public function tap($callback)
495 {
496 return $this->when(true, $callback);
497 }
498 }
499