PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 2.1.0
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v2.1.0
2.1.0 2.0.15 2.0.12 2.0.10 2.0.4 2.0.1 2.0.0 1.95.3 1.95.2 1.95 1.91.6 trunk 1.11 1.12 1.13 1.20 1.21 1.22 1.23 1.30 1.31 1.32 1.35 1.40 1.41 All 42 releases
fluent-boards / vendor / wpfluent / framework / src / WPFluent / Database / Orm / Collection.php

Collection.php in FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration 2.1.0, at vendor/wpfluent/framework/src/WPFluent/Database/Orm/Collection.php

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