• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nextras / orm / 40780907175

28 Oct 2026 10:33PM UTC coverage: 92.01% (+0.01%) from 91.998%
40780907175

Pull #842

github

web-flow
Merge 6ce4bf857 into 10fe91984
Pull Request #842: Setup DressCode

253 of 306 new or added lines in 64 files covered. (82.68%)

13 existing lines in 9 files now uncovered.

4399 of 4781 relevant lines covered (92.01%)

5.4 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.78
/src/Collection/ArrayCollection.php
1
<?php declare(strict_types = 1);
2

3
namespace Nextras\Orm\Collection;
4

5

6
use Closure;
7
use Countable;
8
use Iterator;
9
use Nette\Utils\Arrays;
10
use Nextras\Orm\Collection\Functions\Result\ArrayExpressionResult;
11
use Nextras\Orm\Collection\Helpers\ArrayCollectionHelper;
12
use Nextras\Orm\Collection\Helpers\FetchPairsHelper;
13
use Nextras\Orm\Entity\IEntity;
14
use Nextras\Orm\Exception\InvalidArgumentException;
15
use Nextras\Orm\Exception\MemberAccessException;
16
use Nextras\Orm\Exception\NoResultException;
17
use Nextras\Orm\Mapper\IRelationshipMapper;
18
use Nextras\Orm\Repository\IRepository;
19
use function array_values;
20

21

22
/**
23
 * @template E of IEntity
24
 * @implements ICollection<E>
25
 * @implements MemoryCollection<E>
26
 */
27
class ArrayCollection implements ICollection, MemoryCollection
28
{
29
        /** @var list<callable(\Traversable<E> $entities): void> */
30
        public array $onEntityFetch = [];
31

32
        /** @var list<E> */
33
        protected array $data;
34

35
        protected IRelationshipMapper|null $relationshipMapper = null;
36
        protected IEntity|null $relationshipParent = null;
37

38
        /** @var Iterator<E>|null */
39
        protected Iterator|null $fetchIterator = null;
40

41
        protected ArrayCollectionHelper|null $helper = null;
42

43
        /** @var array<Closure(E): ArrayExpressionResult> */
44
        protected array $collectionFilter = [];
45

46
        /** @var list<array{mixed, string}> */
47
        protected array $collectionSorter = [];
48

49
        /** @var array{int, int|null}|null */
50
        protected array|null $collectionLimit = null;
51
        protected bool $entityFetchEventTriggered = false;
52

53

54
        /**
55
         * @param list<E> $entities
56
         */
57
        public function __construct(
5 ✔
58
                array $entities,
59
                /** @var IRepository<E> */
60
                protected readonly IRepository $repository,
1 ✔
61
        )
62
        {
63
                if (!Arrays::isList($entities)) {
6 ✔
64
                        throw new InvalidArgumentException('Entities has to be passed as a list.');
6 ✔
65
                }
66
                $this->data = $entities;
6 ✔
67
        }
6 ✔
68

69

70
        #[\NoDiscard]
71
        public function getBy(array $conds): IEntity|null
72
        {
73
                return $this->findBy($conds)->fetch();
6 ✔
74
        }
75

76

77
        #[\NoDiscard]
78
        public function getByChecked(array $conds): IEntity
79
        {
80
                return $this->findBy($conds)->fetchChecked();
6 ✔
81
        }
82

83

84
        #[\NoDiscard]
85
        public function getById($id): IEntity|null
86
        {
87
                return $this->getBy(['id' => $id]);
6 ✔
88
        }
89

90

91
        #[\NoDiscard]
92
        public function getByIdChecked($id): IEntity
93
        {
94
                $entity = $this->getById($id);
6 ✔
95
                if ($entity === null) {
6 ✔
96
                        throw new NoResultException();
6 ✔
97
                }
98
                return $entity;
6 ✔
99
        }
100

101

102
        #[\NoDiscard('Method returns a new collection instance, the original collection is not modified.')]
103
        public function findBy(array $conds): ICollection
104
        {
105
                $collection = clone $this;
6 ✔
106
                $collection->collectionFilter[] = $this->getHelper()->createFilter($conds, null);
6 ✔
107
                return $collection;
6 ✔
108
        }
109

110

111
        #[\NoDiscard('Method returns a new collection instance, the original collection is not modified.')]
112
        public function orderBy($expression, string $direction = self::ASC): ICollection
113
        {
114
                $collection = clone $this;
6 ✔
115
                if (is_array($expression) && !isset($expression[0])) {
6 ✔
116
                        foreach ($expression as $subExpression => $subDirection) {
6 ✔
117
                                $collection->collectionSorter[] = [$subExpression, $subDirection];
6 ✔
118
                        }
119
                } else {
120
                        $collection->collectionSorter[] = [$expression, $direction];
6 ✔
121
                }
122
                return $collection;
6 ✔
123
        }
124

125

126
        #[\NoDiscard('Method returns a new collection instance, the original collection is not modified.')]
127
        public function resetOrderBy(): ICollection
128
        {
129
                $collection = clone $this;
6 ✔
130
                $collection->collectionSorter = [];
6 ✔
131
                return $collection;
6 ✔
132
        }
133

134

135
        #[\NoDiscard('Method returns a new collection instance, the original collection is not modified.')]
136
        public function limitBy(int $limit, int|null $offset = null): ICollection
137
        {
138
                $collection = clone $this;
6 ✔
139
                $collection->collectionLimit = [$limit, $offset];
6 ✔
140
                return $collection;
6 ✔
141
        }
142

143

144
        #[\NoDiscard]
145
        public function fetch(): IEntity|null
146
        {
147
                if ($this->fetchIterator === null) {
6 ✔
148
                        $this->fetchIterator = $this->getIterator();
6 ✔
149
                }
150

151
                if ($this->fetchIterator->valid()) {
6 ✔
152
                        $current = $this->fetchIterator->current();
6 ✔
153
                        $this->fetchIterator->next();
6 ✔
154
                        return $current;
6 ✔
155
                }
156

157
                return null;
6 ✔
158
        }
159

160

161
        #[\NoDiscard]
162
        public function fetchChecked(): IEntity
163
        {
164
                $entity = $this->fetch();
6 ✔
165
                if ($entity === null) {
6 ✔
166
                        throw new NoResultException();
6 ✔
167
                }
168
                return $entity;
6 ✔
169
        }
170

171

172
        #[\NoDiscard]
173
        public function fetchAll(): array
174
        {
175
                return iterator_to_array($this->getIterator(), preserve_keys: false);
6 ✔
176
        }
177

178

179
        #[\NoDiscard]
180
        public function fetchPairs(string|null $key = null, string|null $value = null): array
181
        {
182
                return FetchPairsHelper::process($this->getIterator(), $key, $value);
6 ✔
183
        }
184

185

186
        /**
187
         * @param mixed[] $args
188
         * @return never
189
         * @throws MemberAccessException
190
         */
191
        public function __call(string $name, array $args)
192
        {
NEW
193
                $class = static::class;
×
194
                throw new MemberAccessException("Call to undefined method $class::$name().");
×
195
        }
196

197

198
        /**
199
         * @return Iterator<int, E>
200
         */
201
        #[\NoDiscard]
202
        public function getIterator(): Iterator
203
        {
204
                if ($this->relationshipParent !== null && $this->relationshipMapper !== null) {
6 ✔
205
                        $collection = clone $this;
6 ✔
206
                        $collection->relationshipMapper = null;
6 ✔
207
                        $collection->relationshipParent = null;
6 ✔
208
                        /** @var Iterator<E> $entityIterator */
209
                        $entityIterator = $this->relationshipMapper->getIterator($this->relationshipParent, $collection);
6 ✔
210
                } else {
211
                        $this->processData();
6 ✔
212
                        /** @var Iterator<E> $entityIterator */
213
                        $entityIterator = new EntityIterator($this->data);
6 ✔
214
                }
215

216
                if (!$this->entityFetchEventTriggered) {
6 ✔
217
                        foreach ($this->onEntityFetch as $entityFetchCallback) {
6 ✔
218
                                $entityFetchCallback($entityIterator);
6 ✔
219
                        }
220
                        $entityIterator->rewind();
6 ✔
221
                        $this->entityFetchEventTriggered = true;
6 ✔
222
                }
223

224
                return $entityIterator;
6 ✔
225
        }
226

227

228
        #[\NoDiscard]
229
        public function count(): int
230
        {
231
                $iterator = $this->getIterator();
6 ✔
232
                assert($iterator instanceof Countable);
233
                return count($iterator);
6 ✔
234
        }
235

236

237
        #[\NoDiscard]
238
        public function countStored(): int
239
        {
240
                return $this->count();
6 ✔
241
        }
242

243

244
        #[\NoDiscard]
245
        public function toMemoryCollection(): MemoryCollection
246
        {
247
                return clone $this;
6 ✔
248
        }
249

250

251
        public function setRelationshipMapper(IRelationshipMapper|null $mapper): ICollection
252
        {
253
                $this->relationshipMapper = $mapper;
6 ✔
254
                return $this;
6 ✔
255
        }
256

257

258
        #[\NoDiscard]
259
        public function getRelationshipMapper(): IRelationshipMapper|null
260
        {
261
                return $this->relationshipMapper;
6 ✔
262
        }
263

264

265
        #[\NoDiscard('Method returns a new collection instance, the original collection is not modified.')]
266
        public function setRelationshipParent(IEntity $parent): ICollection
267
        {
268
                $collection = clone $this;
6 ✔
269
                $collection->relationshipParent = $parent;
6 ✔
270
                return $collection;
6 ✔
271
        }
272

273

274
        public function subscribeOnEntityFetch(callable $callback): void
275
        {
276
                $this->onEntityFetch[] = $callback;
6 ✔
277
        }
6 ✔
278

279

280
        public function __clone()
281
        {
282
                $this->fetchIterator = null;
6 ✔
283
                $this->entityFetchEventTriggered = false;
6 ✔
284
        }
6 ✔
285

286

287
        protected function processData(): void
288
        {
289
                if (
290
                        count($this->collectionFilter) > 0
6 ✔
291
                        || count($this->collectionSorter) > 0
6 ✔
292
                        || $this->collectionLimit !== null
6 ✔
293
                ) {
294
                        $data = $this->data;
6 ✔
295

296
                        foreach ($this->collectionFilter as $filter) {
6 ✔
297
                                $data = array_filter($data, fn($value) => $filter($value)->value);
6 ✔
298
                        }
299

300
                        if (count($this->collectionSorter) > 0) {
6 ✔
301
                                $sorter = $this->getHelper()->createSorter($this->collectionSorter);
6 ✔
302
                                usort($data, $sorter);
6 ✔
303
                        }
304

305
                        if ($this->collectionLimit !== null) {
6 ✔
306
                                $data = array_slice($data, $this->collectionLimit[1] ?? 0, $this->collectionLimit[0]);
6 ✔
307
                        }
308

309
                        $this->collectionFilter = [];
6 ✔
310
                        $this->collectionSorter = [];
6 ✔
311
                        $this->collectionLimit = null;
6 ✔
312
                        $this->data = array_values($data);
6 ✔
313
                }
314
        }
6 ✔
315

316

317
        protected function getHelper(): ArrayCollectionHelper
318
        {
319
                if ($this->helper === null) {
6 ✔
320
                        $this->helper = new ArrayCollectionHelper($this->repository);
6 ✔
321
                }
322

323
                return $this->helper;
6 ✔
324
        }
325
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc