1 | <?php |
||||
2 | |||||
3 | namespace Bdf\Prime\Relations; |
||||
4 | |||||
5 | use Bdf\Prime\Exception\PrimeException; |
||||
6 | use Bdf\Prime\Query\Contract\ReadOperation; |
||||
7 | use Bdf\Prime\Query\Contract\WriteOperation; |
||||
8 | use Bdf\Prime\Query\QueryInterface; |
||||
9 | use Bdf\Prime\Query\ReadCommandInterface; |
||||
10 | |||||
11 | /** |
||||
12 | * EntityRelation |
||||
13 | * |
||||
14 | * Relation wrapper. Link an entity to an relation object |
||||
15 | * |
||||
16 | * @template E as object |
||||
17 | * @template R as object |
||||
18 | * |
||||
19 | * @api |
||||
20 | * |
||||
21 | * @psalm-method QueryInterface<\Bdf\Prime\Connection\ConnectionInterface, R> with(string|string[] $relations) Relations to load |
||||
22 | * @psalm-method QueryInterface<\Bdf\Prime\Connection\ConnectionInterface, R> by(string $attribute, bool $combine = false) Indexing entities by an attribute value. Use combine for multiple entities with same attribute value |
||||
23 | * @psalm-method QueryInterface<\Bdf\Prime\Connection\ConnectionInterface, R> where(string|array|callable $column, mixed|null $operator = null, mixed $value = null) |
||||
24 | * @psalm-method R|null get($pk) Get one entity by its identifier |
||||
25 | * @psalm-method R getOrFail($pk) Get one entity or throws when entity is not found |
||||
26 | * @psalm-method R getOrNew($pk) Get one entity or return a new one if not found in repository |
||||
27 | * @psalm-method int count() |
||||
28 | * |
||||
29 | * @mixin ReadCommandInterface<\Bdf\Prime\Connection\ConnectionInterface, R> |
||||
30 | * |
||||
31 | * @noinspection PhpHierarchyChecksInspection |
||||
32 | */ |
||||
33 | class EntityRelation |
||||
34 | { |
||||
35 | /** |
||||
36 | * The entity owner of the relation |
||||
37 | * |
||||
38 | * @var E |
||||
39 | */ |
||||
40 | protected $owner; |
||||
41 | |||||
42 | /** |
||||
43 | * The relation |
||||
44 | * |
||||
45 | * @var RelationInterface<E, R> |
||||
46 | */ |
||||
47 | protected $relation; |
||||
48 | |||||
49 | /** |
||||
50 | * EntityRelation constructor. |
||||
51 | * |
||||
52 | * @param E $owner The relation owner |
||||
53 | * @param RelationInterface<E, R> $relation The relation |
||||
54 | */ |
||||
55 | 166 | public function __construct($owner, RelationInterface $relation) |
|||
56 | { |
||||
57 | 166 | $this->owner = $owner; |
|||
58 | 166 | $this->relation = $relation; |
|||
59 | } |
||||
60 | |||||
61 | /** |
||||
62 | * Associate an entity to the owner entity |
||||
63 | * |
||||
64 | * This method will set the foreign key value on the owner entity and attach the entity |
||||
65 | * If the relation is detached, only the foreign key will be updated |
||||
66 | * |
||||
67 | * Note: This method will not perform any write operation on database : |
||||
68 | * the related entity id must be generated before, and the owner must be updated manually |
||||
69 | * |
||||
70 | * <code> |
||||
71 | * $entity->relation('foo')->associate($foo); |
||||
72 | * $entity->getFoo() === $foo; // should be true |
||||
73 | * </code> |
||||
74 | * |
||||
75 | * Only foreign key barrier can associate an entity |
||||
76 | * |
||||
77 | * @param R $entity The related entity data |
||||
78 | * |
||||
79 | * @return E Returns the owner entity instance |
||||
80 | */ |
||||
81 | 16 | public function associate($entity) |
|||
82 | { |
||||
83 | 16 | return $this->relation->associate($this->owner, $entity); |
|||
84 | } |
||||
85 | |||||
86 | /** |
||||
87 | * Remove the relation from owner entity |
||||
88 | * This is the reverse operation of associate : will detach the related entity and set the foreign key to null |
||||
89 | * |
||||
90 | * Note: This method will not perform any write operation on database : |
||||
91 | * the related entity id must be generated before, and the owner must be updated manually |
||||
92 | * |
||||
93 | * Only foreign key barrier can dissociate an entity |
||||
94 | * |
||||
95 | * @return E Returns the owner entity instance |
||||
96 | */ |
||||
97 | 17 | public function dissociate() |
|||
98 | { |
||||
99 | 17 | return $this->relation->dissociate($this->owner); |
|||
100 | } |
||||
101 | |||||
102 | /** |
||||
103 | * Add a relation entity on the given entity owner |
||||
104 | * This method will not attach the created entity to the owner |
||||
105 | * |
||||
106 | * Note: This method will not perform any write operation on database, it will only instantiate the entity |
||||
107 | * |
||||
108 | * <code> |
||||
109 | * $foo = $entity->relation('foo')->create(['foo' => 'bar']); |
||||
110 | * $foo->getOwnerId() === $entity->id(); // Should be true |
||||
111 | * $foo->getFoo() === 'bar'; // Should be true |
||||
112 | * </code> |
||||
113 | * |
||||
114 | * Only non foreign key barrier can create an entity |
||||
115 | * |
||||
116 | * @param array $data The related entity data |
||||
117 | * |
||||
118 | * @return R Returns the related entity instance |
||||
119 | */ |
||||
120 | 6 | public function create(array $data = []) |
|||
121 | { |
||||
122 | 6 | return $this->relation->create($this->owner, $data); |
|||
123 | } |
||||
124 | |||||
125 | /** |
||||
126 | * Add a relation entity on the given entity owner |
||||
127 | * This method will set the foreign key value on the related entity but not attach the related entity to the owner |
||||
128 | * |
||||
129 | * Only non foreign key barrier can add an entity |
||||
130 | * |
||||
131 | * <code> |
||||
132 | * $entity->relation('foo')->($entity, $foo); |
||||
133 | * $foo->getOwnerId() === $entity->id(); // Should be true |
||||
134 | * </code> |
||||
135 | * |
||||
136 | * @param R $related |
||||
137 | * |
||||
138 | * @return int |
||||
139 | * @throws PrimeException |
||||
140 | */ |
||||
141 | #[WriteOperation] |
||||
142 | public function add($related): int |
||||
143 | { |
||||
144 | return $this->relation->add($this->owner, $related); |
||||
145 | } |
||||
146 | |||||
147 | /** |
||||
148 | * Check whether the owner has a distant entity relation |
||||
149 | * Note: only works with BelongsToMany relation |
||||
150 | * |
||||
151 | * @param string|R $related |
||||
152 | * |
||||
153 | * @return boolean |
||||
154 | * @throws PrimeException |
||||
155 | */ |
||||
156 | #[ReadOperation] |
||||
157 | 3 | public function has($related): bool |
|||
158 | { |
||||
159 | /** @var BelongsToMany $this->relation */ |
||||
160 | 3 | return $this->relation->has($this->owner, $related); |
|||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
161 | } |
||||
162 | |||||
163 | /** |
||||
164 | * Attach a distant entity to an entity |
||||
165 | * Note: only works with BelongsToMany relation |
||||
166 | * |
||||
167 | * @param string|R[]|R $related |
||||
168 | * |
||||
169 | * @return int |
||||
170 | * @throws PrimeException |
||||
171 | */ |
||||
172 | #[WriteOperation] |
||||
173 | 3 | public function attach($related): int |
|||
174 | { |
||||
175 | /** @var BelongsToMany<E, R> $this->relation */ |
||||
176 | 3 | return $this->relation->attach($this->owner, $related); |
|||
0 ignored issues
–
show
The method
attach() does not exist on Bdf\Prime\Relations\RelationInterface . It seems like you code against a sub-type of Bdf\Prime\Relations\RelationInterface such as Bdf\Prime\Relations\BelongsToMany .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
177 | } |
||||
178 | |||||
179 | /** |
||||
180 | * Detach a distant entity of an entity |
||||
181 | * Note: only works with BelongsToMany relation |
||||
182 | * |
||||
183 | * @param string|R[]|R $related |
||||
184 | * |
||||
185 | * @return int |
||||
186 | */ |
||||
187 | 3 | public function detach($related): int |
|||
188 | { |
||||
189 | /** @var BelongsToMany<E, R> $this->relation */ |
||||
190 | 3 | return $this->relation->detach($this->owner, $related); |
|||
0 ignored issues
–
show
The method
detach() does not exist on Bdf\Prime\Relations\RelationInterface . It seems like you code against a sub-type of Bdf\Prime\Relations\RelationInterface such as Bdf\Prime\Relations\BelongsToMany .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
191 | } |
||||
192 | |||||
193 | /** |
||||
194 | * Gets the relation query builder |
||||
195 | * Note: You can use magic __call method to call query methods directly |
||||
196 | * |
||||
197 | * <code> |
||||
198 | * $entity->relation('foo')->where(['foo' => 'bar'])->get(); |
||||
199 | * </code> |
||||
200 | * |
||||
201 | * @return ReadCommandInterface<\Bdf\Prime\Connection\ConnectionInterface, R> |
||||
202 | */ |
||||
203 | 35 | public function query(): ReadCommandInterface |
|||
204 | { |
||||
205 | 35 | return $this->relation->link($this->owner); |
|||
206 | } |
||||
207 | |||||
208 | /** |
||||
209 | * Save the relation from an entity |
||||
210 | * |
||||
211 | * Note: This method can only works with attached entities |
||||
212 | * |
||||
213 | * @param string|array $relations sub-relation names to save |
||||
214 | * |
||||
215 | * @return int Number of updated / inserted entities |
||||
216 | * @throws PrimeException When cannot save entity |
||||
217 | */ |
||||
218 | #[WriteOperation] |
||||
219 | 14 | public function saveAll($relations = []): int |
|||
220 | { |
||||
221 | 14 | return $this->relation->saveAll($this->owner, (array)$relations); |
|||
222 | } |
||||
223 | |||||
224 | /** |
||||
225 | * Remove the relation from an entity |
||||
226 | * |
||||
227 | * Note: This method can only works |
||||
228 | * |
||||
229 | * @param array $relations sub-relation names to delete |
||||
230 | * |
||||
231 | * @return int Number of deleted entities |
||||
232 | * @throws PrimeException When cannot delete entity |
||||
233 | */ |
||||
234 | #[WriteOperation] |
||||
235 | 12 | public function deleteAll($relations = []): int |
|||
236 | { |
||||
237 | 12 | return $this->relation->deleteAll($this->owner, (array)$relations); |
|||
238 | } |
||||
239 | |||||
240 | /** |
||||
241 | * Check if the relation is loaded on the current entity |
||||
242 | * |
||||
243 | * @return bool |
||||
244 | */ |
||||
245 | 86 | public function isLoaded(): bool |
|||
246 | { |
||||
247 | 86 | return $this->relation->isLoaded($this->owner); |
|||
248 | } |
||||
249 | |||||
250 | /** |
||||
251 | * Redirect every call to the relation query Builder |
||||
252 | * |
||||
253 | * @param string $name |
||||
254 | * @param array $arguments |
||||
255 | * |
||||
256 | * @return mixed |
||||
257 | */ |
||||
258 | 31 | public function __call(string $name, array $arguments) |
|||
259 | { |
||||
260 | 31 | return $this->query()->$name(...$arguments); |
|||
261 | } |
||||
262 | } |
||||
263 |