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 / Orm / Collection.php

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

751 lines 18.8 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\Orm;
4
5 use LogicException;
6 use FluentBooking\Framework\Support\Arr;
7 use FluentBooking\Framework\Support\Str;
8 use FluentBooking\Framework\Support\QueueableEntity;
9 use FluentBooking\Framework\Support\QueueableCollection;
10 use FluentBooking\Framework\Support\ArrayableInterface;
11 use FluentBooking\Framework\Support\Collection as BaseCollection;
12
13 class Collection extends BaseCollection implements QueueableCollection
14 {
15 /**
16 * Find a model in the collection by key.
17 *
18 * @param mixed $key
19 * @param mixed $default
20 * @return \FluentBooking\Framework\Database\Orm\Model|static|null
21 */
22 public function find($key, $default = null)
23 {
24 if ($key instanceof Model) {
25 $key = $key->getKey();
26 }
27
28 if ($key instanceof ArrayableInterface) {
29 $key = $key->toArray();
30 }
31
32 if (is_array($key)) {
33 if ($this->isEmpty()) {
34 return new static;
35 }
36
37 return $this->whereIn($this->first()->getKeyName(), $key);
38 }
39
40 return Arr::first($this->items, function ($model) use ($key) {
41 return $model->getKey() == $key;
42 }, $default);
43 }
44
45 /**
46 * Load a set of relationships onto the collection.
47 *
48 * @param array|string $relations
49 * @return $this
50 */
51 public function load($relations)
52 {
53 if ($this->isNotEmpty()) {
54 if (is_string($relations)) {
55 $relations = func_get_args();
56 }
57
58 $query = $this->first()->newQueryWithoutRelationships()->with($relations);
59
60 $this->items = $query->eagerLoadRelations($this->items);
61 }
62
63 return $this;
64 }
65
66 /**
67 * Load a set of aggregations over relationship's column onto the collection.
68 *
69 * @param array|string $relations
70 * @param string $column
71 * @param string $function
72 * @return $this
73 */
74 public function loadAggregate($relations, $column, $function = null)
75 {
76 if ($this->isEmpty()) {
77 return $this;
78 }
79
80 $models = $this->first()->newModelQuery()
81 ->whereKey($this->modelKeys())
82 ->select($this->first()->getKeyName())
83 ->withAggregate($relations, $column, $function)
84 ->get()
85 ->keyBy($this->first()->getKeyName());
86
87 $attributes = Arr::except(
88 array_keys($models->first()->getAttributes()),
89 $models->first()->getKeyName()
90 );
91
92 $this->each(function ($model) use ($models, $attributes) {
93 $extraAttributes = Arr::only($models->get($model->getKey())->getAttributes(), $attributes);
94
95 $model->forceFill($extraAttributes)
96 ->syncOriginalAttributes($attributes)
97 ->mergeCasts($models->get($model->getKey())->getCasts());
98 });
99
100 return $this;
101 }
102
103 /**
104 * Load a set of relationship counts onto the collection.
105 *
106 * @param array|string $relations
107 * @return $this
108 */
109 public function loadCount($relations)
110 {
111 return $this->loadAggregate($relations, '*', 'count');
112 }
113
114 /**
115 * Load a set of relationship's max column values onto the collection.
116 *
117 * @param array|string $relations
118 * @param string $column
119 * @return $this
120 */
121 public function loadMax($relations, $column)
122 {
123 return $this->loadAggregate($relations, $column, 'max');
124 }
125
126 /**
127 * Load a set of relationship's min column values onto the collection.
128 *
129 * @param array|string $relations
130 * @param string $column
131 * @return $this
132 */
133 public function loadMin($relations, $column)
134 {
135 return $this->loadAggregate($relations, $column, 'min');
136 }
137
138 /**
139 * Load a set of relationship's column summations onto the collection.
140 *
141 * @param array|string $relations
142 * @param string $column
143 * @return $this
144 */
145 public function loadSum($relations, $column)
146 {
147 return $this->loadAggregate($relations, $column, 'sum');
148 }
149
150 /**
151 * Load a set of relationship's average column values onto the collection.
152 *
153 * @param array|string $relations
154 * @param string $column
155 * @return $this
156 */
157 public function loadAvg($relations, $column)
158 {
159 return $this->loadAggregate($relations, $column, 'avg');
160 }
161
162 /**
163 * Load a set of related existences onto the collection.
164 *
165 * @param array|string $relations
166 * @return $this
167 */
168 public function loadExists($relations)
169 {
170 return $this->loadAggregate($relations, '*', 'exists');
171 }
172
173 /**
174 * Load a set of relationships onto the collection if they are not already eager loaded.
175 *
176 * @param array|string $relations
177 * @return $this
178 */
179 public function loadMissing($relations)
180 {
181 if (is_string($relations)) {
182 $relations = func_get_args();
183 }
184
185 foreach ($relations as $key => $value) {
186 if (is_numeric($key)) {
187 $key = $value;
188 }
189
190 $segments = explode('.', explode(':', $key)[0]);
191
192 if (Str::contains($key, ':')) {
193 $segments[count($segments) - 1] .= ':'.explode(':', $key)[1];
194 }
195
196 $path = [];
197
198 foreach ($segments as $segment) {
199 $path[] = [$segment => $segment];
200 }
201
202 if (is_callable($value)) {
203 $path[count($segments) - 1][end($segments)] = $value;
204 }
205
206 $this->loadMissingRelation($this, $path);
207 }
208
209 return $this;
210 }
211
212 /**
213 * Load a relationship path if it is not already eager loaded.
214 *
215 * @param \FluentBooking\Framework\Database\Orm\Collection $models
216 * @param array $path
217 * @return void
218 */
219 protected function loadMissingRelation(self $models, array $path)
220 {
221 $relation = array_shift($path);
222
223 $name = explode(':', key($relation))[0];
224
225 if (is_string(reset($relation))) {
226 $relation = reset($relation);
227 }
228
229 $models->filter(function ($model) use ($name) {
230 return ! is_null($model) && ! $model->relationLoaded($name);
231 })->load($relation);
232
233 if (empty($path)) {
234 return;
235 }
236
237 $models = $models->pluck($name)->whereNotNull();
238
239 if ($models->first() instanceof BaseCollection) {
240 $models = $models->collapse();
241 }
242
243 $this->loadMissingRelation(new static($models), $path);
244 }
245
246 /**
247 * Load a set of relationships onto the mixed relationship collection.
248 *
249 * @param string $relation
250 * @param array $relations
251 * @return $this
252 */
253 public function loadMorph($relation, $relations)
254 {
255 $this->pluck($relation)
256 ->filter()
257 ->groupBy(function ($model) {
258 return get_class($model);
259 })
260 ->each(function ($models, $className) use ($relations) {
261 static::make($models)->load($relations[$className] ?? []);
262 });
263
264 return $this;
265 }
266
267 /**
268 * Load a set of relationship counts onto the mixed relationship collection.
269 *
270 * @param string $relation
271 * @param array $relations
272 * @return $this
273 */
274 public function loadMorphCount($relation, $relations)
275 {
276 $this->pluck($relation)
277 ->filter()
278 ->groupBy(function ($model) {
279 return get_class($model);
280 })
281 ->each(function ($models, $className) use ($relations) {
282 static::make($models)->loadCount($relations[$className] ?? []);
283 });
284
285 return $this;
286 }
287
288 /**
289 * Determine if a key exists in the collection.
290 *
291 * @param mixed $key
292 * @param mixed $operator
293 * @param mixed $value
294 * @return bool
295 */
296 public function contains($key, $operator = null, $value = null)
297 {
298 if (func_num_args() > 1 || $this->useAsCallable($key)) {
299 return parent::contains(...func_get_args());
300 }
301
302 if ($key instanceof Model) {
303 return parent::contains(function ($model) use ($key) {
304 return $model->is($key);
305 });
306 }
307
308 return parent::contains(function ($model) use ($key) {
309 return $model->getKey() == $key;
310 });
311 }
312
313 /**
314 * Get the array of primary keys.
315 *
316 * @return array
317 */
318 public function modelKeys()
319 {
320 return array_map(function ($model) {
321 return $model->getKey();
322 }, $this->items);
323 }
324
325 /**
326 * Merge the collection with the given items.
327 *
328 * @param \ArrayAccess|array $items
329 * @return static
330 */
331 public function merge($items)
332 {
333 $dictionary = $this->getDictionary();
334
335 foreach ($items as $item) {
336 $dictionary[$item->getKey()] = $item;
337 }
338
339 return new static(array_values($dictionary));
340 }
341
342 /**
343 * Run a map over each of the items.
344 *
345 * @param callable $callback
346 * @return \FluentBooking\Framework\Support\Collection|static
347 */
348 public function map(callable $callback)
349 {
350 $result = parent::map($callback);
351
352 return $result->contains(function ($item) {
353 return ! $item instanceof Model;
354 }) ? $result->toBase() : $result;
355 }
356
357 /**
358 * Run an associative map over each of the items.
359 *
360 * The callback should return an associative array with a single key / value pair.
361 *
362 * @param callable $callback
363 * @return \FluentBooking\Framework\Support\Collection|static
364 */
365 public function mapWithKeys(callable $callback)
366 {
367 $result = parent::mapWithKeys($callback);
368
369 return $result->contains(function ($item) {
370 return ! $item instanceof Model;
371 }) ? $result->toBase() : $result;
372 }
373
374 /**
375 * Reload a fresh model instance from the database for all the entities.
376 *
377 * @param array|string $with
378 * @return static
379 */
380 public function fresh($with = [])
381 {
382 if ($this->isEmpty()) {
383 return new static;
384 }
385
386 $model = $this->first();
387
388 $freshModels = $model->newQueryWithoutScopes()
389 ->with(is_string($with) ? func_get_args() : $with)
390 ->whereIn($model->getKeyName(), $this->modelKeys())
391 ->get()
392 ->getDictionary();
393
394 return $this->filter(function ($model) use ($freshModels) {
395 return $model->exists && isset($freshModels[$model->getKey()]);
396 })
397 ->map(function ($model) use ($freshModels) {
398 return $freshModels[$model->getKey()];
399 });
400 }
401
402 /**
403 * Diff the collection with the given items.
404 *
405 * @param \ArrayAccess|array $items
406 * @return static
407 */
408 public function diff($items)
409 {
410 $diff = new static;
411
412 $dictionary = $this->getDictionary($items);
413
414 foreach ($this->items as $item) {
415 if (! isset($dictionary[$item->getKey()])) {
416 $diff->add($item);
417 }
418 }
419
420 return $diff;
421 }
422
423 /**
424 * Intersect the collection with the given items.
425 *
426 * @param \ArrayAccess|array $items
427 * @return static
428 */
429 public function intersect($items)
430 {
431 $intersect = new static;
432
433 if (empty($items)) {
434 return $intersect;
435 }
436
437 $dictionary = $this->getDictionary($items);
438
439 foreach ($this->items as $item) {
440 if (isset($dictionary[$item->getKey()])) {
441 $intersect->add($item);
442 }
443 }
444
445 return $intersect;
446 }
447
448 /**
449 * Return only unique items from the collection.
450 *
451 * @param string|callable|null $key
452 * @param bool $strict
453 * @return static
454 */
455 public function unique($key = null, $strict = false)
456 {
457 if (! is_null($key)) {
458 return parent::unique($key, $strict);
459 }
460
461 return new static(array_values($this->getDictionary()));
462 }
463
464 /**
465 * Returns only the models from the collection with the specified keys.
466 *
467 * @param mixed $keys
468 * @return static
469 */
470 public function only($keys)
471 {
472 if (is_null($keys)) {
473 return new static($this->items);
474 }
475
476 $dictionary = Arr::only($this->getDictionary(), $keys);
477
478 return new static(array_values($dictionary));
479 }
480
481 /**
482 * Returns all models in the collection except the models with specified keys.
483 *
484 * @param mixed $keys
485 * @return static
486 */
487 public function except($keys)
488 {
489 $dictionary = Arr::except($this->getDictionary(), $keys);
490
491 return new static(array_values($dictionary));
492 }
493
494 /**
495 * Make the given, typically visible, attributes hidden across the entire collection.
496 *
497 * @param array|string $attributes
498 * @return $this
499 */
500 public function makeHidden($attributes)
501 {
502 return $this->each->makeHidden($attributes);
503 }
504
505 /**
506 * Make the given, typically hidden, attributes visible across the entire collection.
507 *
508 * @param array|string $attributes
509 * @return $this
510 */
511 public function makeVisible($attributes)
512 {
513 return $this->each->makeVisible($attributes);
514 }
515
516 /**
517 * Append an attribute across the entire collection.
518 *
519 * @param array|string $attributes
520 * @return $this
521 */
522 public function append($attributes)
523 {
524 return $this->each->append($attributes);
525 }
526
527 /**
528 * Get a dictionary keyed by primary keys.
529 *
530 * @param \ArrayAccess|array|null $items
531 * @return array
532 */
533 public function getDictionary($items = null)
534 {
535 $items = is_null($items) ? $this->items : $items;
536
537 $dictionary = [];
538
539 foreach ($items as $value) {
540 $dictionary[$value->getKey()] = $value;
541 }
542
543 return $dictionary;
544 }
545
546 /**
547 * The following methods are intercepted to always return base collections.
548 */
549
550 /**
551 * Get an array with the values of a given key.
552 *
553 * @param string|array $value
554 * @param string|null $key
555 * @return \FluentBooking\Framework\Support\Collection
556 */
557 public function pluck($value, $key = null)
558 {
559 return $this->toBase()->pluck($value, $key);
560 }
561
562 /**
563 * Get the keys of the collection items.
564 *
565 * @return \FluentBooking\Framework\Support\Collection
566 */
567 public function keys()
568 {
569 return $this->toBase()->keys();
570 }
571
572 /**
573 * Zip the collection together with one or more arrays.
574 *
575 * @param mixed ...$items
576 * @return \FluentBooking\Framework\Support\Collection
577 */
578 public function zip($items)
579 {
580 return $this->toBase()->zip(...func_get_args());
581 }
582
583 /**
584 * Collapse the collection of items into a single array.
585 *
586 * @return \FluentBooking\Framework\Support\Collection
587 */
588 public function collapse()
589 {
590 return $this->toBase()->collapse();
591 }
592
593 /**
594 * Get a flattened array of the items in the collection.
595 *
596 * @param int $depth
597 * @return \FluentBooking\Framework\Support\Collection
598 */
599 public function flatten($depth = INF)
600 {
601 return $this->toBase()->flatten($depth);
602 }
603
604 /**
605 * Flip the items in the collection.
606 *
607 * @return \FluentBooking\Framework\Support\Collection
608 */
609 public function flip()
610 {
611 return $this->toBase()->flip();
612 }
613
614 /**
615 * Pad collection to the specified length with a value.
616 *
617 * @param int $size
618 * @param mixed $value
619 * @return \FluentBooking\Framework\Support\Collection
620 */
621 public function pad($size, $value)
622 {
623 return $this->toBase()->pad($size, $value);
624 }
625
626 /**
627 * Get the comparison function to detect duplicates.
628 *
629 * @param bool $strict
630 * @return \Closure
631 */
632 protected function duplicateComparator($strict)
633 {
634 return function ($a, $b) {
635 return $a->is($b);
636 };
637 }
638
639 /**
640 * Get the type of the entities being queued.
641 *
642 * @return string|null
643 *
644 * @throws \LogicException
645 */
646 public function getQueueableClass()
647 {
648 if ($this->isEmpty()) {
649 return;
650 }
651
652 $class = get_class($this->first());
653
654 $this->each(function ($model) use ($class) {
655 if (get_class($model) !== $class) {
656 throw new LogicException('Queueing collections with multiple model types is not supported.');
657 }
658 });
659
660 return $class;
661 }
662
663 /**
664 * Get the identifiers for all of the entities.
665 *
666 * @return array
667 */
668 public function getQueueableIds()
669 {
670 if ($this->isEmpty()) {
671 return [];
672 }
673
674 return $this->first() instanceof QueueableEntity
675 ? $this->map->getQueueableId()->all()
676 : $this->modelKeys();
677 }
678
679 /**
680 * Get the relationships of the entities being queued.
681 *
682 * @return array
683 */
684 public function getQueueableRelations()
685 {
686 if ($this->isEmpty()) {
687 return [];
688 }
689
690 $relations = $this->map->getQueueableRelations()->all();
691
692 if (count($relations) === 0 || $relations === [[]]) {
693 return [];
694 } elseif (count($relations) === 1) {
695 return reset($relations);
696 } else {
697 return array_intersect(...array_values($relations));
698 }
699 }
700
701 /**
702 * Get the connection of the entities being queued.
703 *
704 * @return string|null
705 *
706 * @throws \LogicException
707 */
708 public function getQueueableConnection()
709 {
710 if ($this->isEmpty()) {
711 return;
712 }
713
714 $connection = $this->first()->getConnectionName();
715
716 $this->each(function ($model) use ($connection) {
717 if ($model->getConnectionName() !== $connection) {
718 throw new LogicException('Queueing collections with multiple model connections is not supported.');
719 }
720 });
721
722 return $connection;
723 }
724
725 /**
726 * Get the Orm query builder from the collection.
727 *
728 * @return \FluentBooking\Framework\Database\Orm\Builder
729 *
730 * @throws \LogicException
731 */
732 public function toQuery()
733 {
734 $model = $this->first();
735
736 if (! $model) {
737 throw new LogicException('Unable to create query for empty collection.');
738 }
739
740 $class = get_class($model);
741
742 if ($this->filter(function ($model) use ($class) {
743 return ! $model instanceof $class;
744 })->isNotEmpty()) {
745 throw new LogicException('Unable to create query for collection with mixed types.');
746 }
747
748 return $model->newModelQuery()->whereKey($this->modelKeys());
749 }
750 }
751