• 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

93.33
/src/Repository/IdentityMap.php
1
<?php declare(strict_types = 1);
2

3
/**
4
 * This file is part of the Nextras\Orm library.
5
 * This file was inspired by PetrP's ORM library https://github.com/PetrP/Orm/.
6
 * @link       https://github.com/nextras/orm
7
 */
8

9
namespace Nextras\Orm\Repository;
10

11

12
use DateTimeImmutable;
13
use Nextras\Orm\Entity\IEntity;
14
use Nextras\Orm\Entity\IEntityHasPreloadContainer;
15
use Nextras\Orm\Exception\InvalidArgumentException;
16
use ReflectionClass;
17

18

19
/**
20
 * @template E of IEntity
21
 */
22
class IdentityMap
23
{
24
        /** @var array<int|string, \WeakReference<E>|false> */
25
        private array $entities = [];
26

27
        /** @var array<int|string, bool> */
28
        private array $entitiesForRefresh = [];
29

30
        /** @var array<class-string<E>, ReflectionClass<E>> */
31
        private array $entityReflections = [];
32

33

34
        public function __construct(
5 ✔
35
                /** @var IRepository<E> */
36
                private readonly IRepository $repository,
1 ✔
37
        )
38
        {
39
        }
6 ✔
40

41

42
        /**
43
         * @param array|int|mixed $id
44
         */
45
        public function hasById($id): bool
46
        {
47
                $idHash = $this->getIdHash($id);
×
NEW
48
                return isset($this->entities[$idHash])
×
NEW
49
                        && ($this->entities[$idHash] === false || $this->entities[$idHash]->get() !== null);
×
50
        }
51

52

53
        /**
54
         * @param array|int|mixed $id
55
         * @return E|false|null
56
         */
57
        public function getById($id)
58
        {
59
                $idHash = $this->getIdHash($id);
6 ✔
60
                if (
61
                        !isset($this->entities[$idHash])
6 ✔
62
                        || $this->entities[$idHash] === false
6 ✔
63
                        || isset($this->entitiesForRefresh[$idHash])
6 ✔
64
                ) {
65
                        return null;
6 ✔
66
                }
67

68
                $entity = $this->entities[$idHash]->get();
6 ✔
69
                if ($entity instanceof IEntityHasPreloadContainer) {
6 ✔
70
                        $entity->setPreloadContainer(null);
6 ✔
71
                }
72
                return $entity;
6 ✔
73
        }
74

75

76
        /**
77
         * @param E $entity
78
         */
79
        public function add(IEntity $entity): void
80
        {
81
                $id = $this->getIdHash($entity->getPersistedId());
6 ✔
82
                $this->entities[$id] = \WeakReference::create($entity);
6 ✔
83
        }
6 ✔
84

85

86
        /**
87
         * @param array|int|mixed $id
88
         */
89
        public function remove($id): void
90
        {
91
                $idHash = $this->getIdHash($id);
6 ✔
92
                $this->entities[$idHash] = false;
6 ✔
93
                unset($this->entitiesForRefresh[$idHash]);
6 ✔
94
        }
6 ✔
95

96

97
        /**
98
         * @param array<string, mixed> $data
99
         * @return E|null
100
         */
101
        public function create(array $data): IEntity|null
102
        {
103
                $entity = $this->createEntity($data);
6 ✔
104
                $id = $this->getIdHash($entity->getPersistedId());
6 ✔
105

106
                if (isset($this->entities[$id])) {
6 ✔
107
                        if ($this->entities[$id] === false) {
6 ✔
108
                                $this->repository->detach($entity);
×
109
                                return null;
×
110
                        }
111
                        $existingEntity = $this->entities[$id]->get();
6 ✔
112
                        if ($existingEntity === null) {
6 ✔
113
                                // The entity was garbage collected, use the new one
114
                                $this->entities[$id] = \WeakReference::create($entity);
6 ✔
115
                                return $entity;
6 ✔
116
                        }
117
                        $this->repository->detach($entity);
6 ✔
118
                        if (isset($this->entitiesForRefresh[$id])) {
6 ✔
119
                                // entity can be detached because of delete try
120
                                $this->repository->attach($existingEntity);
6 ✔
121
                                $existingEntity->onRefresh($data);
6 ✔
122
                                unset($this->entitiesForRefresh[$id]);
6 ✔
123
                        }
124
                        return $existingEntity;
6 ✔
125
                }
126

127
                $this->entities[$id] = \WeakReference::create($entity);
6 ✔
128
                return $entity;
6 ✔
129
        }
130

131

132
        /**
133
         * @return list<E>
134
         */
135
        public function getAll(): array
136
        {
137
                $all = [];
6 ✔
138
                foreach ($this->entities as $entityRef) {
6 ✔
139
                        if ($entityRef !== false && ($entity = $entityRef->get()) !== null) {
6 ✔
140
                                $all[] = $entity;
6 ✔
141
                        }
142
                }
143
                return $all;
6 ✔
144
        }
145

146

147
        public function check(IEntity $entity): void
148
        {
149
                if (!in_array($entity::class, ($this->repository)::getEntityClassNames(), true)) {
6 ✔
150
                        throw new InvalidArgumentException("Entity '" . $entity::class . "' is not accepted by '" . $this->repository::class . "' repository.");
6 ✔
151
                }
152
        }
6 ✔
153

154

155
        public function destroyAllEntities(): void
156
        {
157
                foreach ($this->entities as $entityRef) {
6 ✔
158
                        if ($entityRef !== false && ($entity = $entityRef->get()) !== null) {
6 ✔
159
                                $this->repository->detach($entity);
6 ✔
160
                                $entity->onFree();
6 ✔
161
                        }
162
                }
163

164
                $this->entities = [];
6 ✔
165
        }
6 ✔
166

167

168
        public function markForRefresh(IEntity $entity): void
169
        {
170
                $id = $this->getIdHash($entity->getPersistedId());
6 ✔
171
                $this->entitiesForRefresh[$id] = true;
6 ✔
172
        }
6 ✔
173

174

175
        public function isMarkedForRefresh(IEntity $entity): bool
176
        {
177
                $id = $this->getIdHash($entity->getPersistedId());
6 ✔
178
                return isset($this->entitiesForRefresh[$id]);
6 ✔
179
        }
180

181

182
        /**
183
         * @param array<string, mixed> $data
184
         * @return E
185
         */
186
        protected function createEntity(array $data): IEntity
187
        {
188
                $entityClass = $this->repository->getEntityClassName($data);
6 ✔
189
                $this->entityReflections[$entityClass] ??= new ReflectionClass($entityClass);
6 ✔
190
                $entity = $this->entityReflections[$entityClass]->newInstanceWithoutConstructor();
6 ✔
191
                $this->repository->attach($entity);
6 ✔
192
                $entity->onLoad($data);
6 ✔
193
                return $entity;
6 ✔
194
        }
195

196

197
        /**
198
         * @param mixed $id
199
         */
200
        protected function getIdHash($id): string
201
        {
202
                if (!is_array($id)) {
6 ✔
203
                        return $id instanceof DateTimeImmutable
6 ✔
204
                                ? $id->format('c.u')
6 ✔
205
                                : (string) $id;
6 ✔
206
                }
207

208
                return implode(
6 ✔
209
                        ',',
4 ✔
210
                        array_map(
6 ✔
211
                                fn($id): string => $id instanceof DateTimeImmutable
6 ✔
212
                                        ? $id->format('c.u')
6 ✔
213
                                        : (string) $id,
6 ✔
214
                                $id,
215
                        ),
216
                );
217
        }
218
}
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