1 | <?php |
||||||
2 | |||||||
3 | declare(strict_types=1); |
||||||
4 | |||||||
5 | namespace Cycle\ORM\Relation; |
||||||
6 | |||||||
7 | use Cycle\ORM\Collection\Pivoted\PivotedCollectionInterface; |
||||||
8 | use Cycle\ORM\Collection\Pivoted\PivotedStorage; |
||||||
9 | use Cycle\ORM\FactoryInterface; |
||||||
10 | use Cycle\ORM\Heap\HeapInterface; |
||||||
11 | use Cycle\ORM\Heap\Node; |
||||||
12 | use Cycle\ORM\Heap\State; |
||||||
13 | use Cycle\ORM\Iterator; |
||||||
14 | use Cycle\ORM\MapperInterface; |
||||||
15 | use Cycle\ORM\ORMInterface; |
||||||
16 | use Cycle\ORM\Parser\RootNode; |
||||||
17 | use Cycle\ORM\Reference\EmptyReference; |
||||||
18 | use Cycle\ORM\Reference\Reference; |
||||||
19 | use Cycle\ORM\Reference\ReferenceInterface; |
||||||
20 | use Cycle\ORM\Relation; |
||||||
21 | use Cycle\ORM\Select\JoinableLoader; |
||||||
22 | use Cycle\ORM\Select\Loader\ManyToManyLoader; |
||||||
23 | use Cycle\ORM\Select\LoaderInterface; |
||||||
24 | use Cycle\ORM\Select\RootLoader; |
||||||
25 | use Cycle\ORM\Service\EntityFactoryInterface; |
||||||
26 | use Cycle\ORM\Service\SourceProviderInterface; |
||||||
27 | use Cycle\ORM\Transaction\Pool; |
||||||
28 | use Cycle\ORM\Transaction\Tuple; |
||||||
29 | |||||||
30 | /** |
||||||
31 | * @internal |
||||||
32 | */ |
||||||
33 | class ManyToMany extends Relation\AbstractRelation |
||||||
34 | { |
||||||
35 | /** @var string[] */ |
||||||
36 | protected array $throughInnerKeys; |
||||||
37 | |||||||
38 | /** @var string[] */ |
||||||
39 | protected array $throughOuterKeys; |
||||||
40 | |||||||
41 | protected string $pivotRole; |
||||||
42 | protected EntityFactoryInterface $entityFactory; |
||||||
43 | protected SourceProviderInterface $sourceProvider; |
||||||
44 | protected FactoryInterface $factory; |
||||||
45 | private HeapInterface $heap; |
||||||
46 | |||||||
47 | public function __construct( |
||||||
48 | 942 | ORMInterface $orm, |
|||||
49 | private string $role, |
||||||
50 | 942 | string $name, |
|||||
51 | 942 | string $target, |
|||||
52 | 942 | array $schema, |
|||||
53 | 942 | ) { |
|||||
54 | 942 | parent::__construct($orm, $role, $name, $target, $schema); |
|||||
55 | 942 | $this->heap = $orm->getHeap(); |
|||||
56 | $this->sourceProvider = $orm->getService(SourceProviderInterface::class); |
||||||
57 | 942 | $this->entityFactory = $orm->getService(EntityFactoryInterface::class); |
|||||
58 | 942 | $this->factory = $orm->getFactory(); |
|||||
59 | $this->pivotRole = $this->schema[Relation::THROUGH_ENTITY]; |
||||||
60 | |||||||
61 | 346 | $this->throughInnerKeys = (array) $this->schema[Relation::THROUGH_INNER_KEY]; |
|||||
62 | $this->throughOuterKeys = (array) $this->schema[Relation::THROUGH_OUTER_KEY]; |
||||||
63 | 346 | } |
|||||
64 | |||||||
65 | public function prepare(Pool $pool, Tuple $tuple, mixed $related, bool $load = true): void |
||||||
66 | 346 | { |
|||||
67 | 346 | $node = $tuple->node; |
|||||
68 | |||||||
69 | 346 | /** @var PivotedStorage|ReferenceInterface|null $original */ |
|||||
70 | 72 | $original = $node->getRelation($this->getName()); |
|||||
71 | 40 | $tuple->state->setRelation($this->getName(), $related); |
|||||
72 | 40 | ||||||
73 | if ($original instanceof ReferenceInterface) { |
||||||
74 | 56 | if (!$load && $related === $original && !$original->hasValue()) { |
|||||
75 | 56 | $this->finalize($pool, $tuple, $related); |
|||||
76 | 56 | return; |
|||||
77 | } |
||||||
78 | 338 | $this->resolve($original, true); |
|||||
79 | $original = $original->getValue(); |
||||||
80 | 338 | $node->setRelation($this->getName(), $original); |
|||||
81 | } |
||||||
82 | $original = $this->extract($original); |
||||||
83 | |||||||
84 | 338 | if ($related instanceof ReferenceInterface && $this->resolve($related, true) !== null) { |
|||||
85 | $related = $related->getValue(); |
||||||
86 | 338 | $tuple->state->setRelation($this->getName(), $related); |
|||||
87 | } elseif (SpecialValue::isNotSet($related)) { |
||||||
88 | $tuple->state->setRelationStatus($this->getName(), RelationInterface::STATUS_RESOLVED); |
||||||
89 | 338 | return; |
|||||
90 | 192 | } |
|||||
91 | 72 | ||||||
92 | 72 | $related = $this->extractRelated($related, $original); |
|||||
93 | 72 | // $tuple->state->setStorage($this->pivotEntity, $related); |
|||||
94 | $tuple->state->setRelation($this->getName(), $related); |
||||||
95 | |||||||
96 | // un-link old elements |
||||||
97 | 338 | foreach ($original as $item) { |
|||||
98 | if (!$related->has($item)) { |
||||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||||
99 | $pivot = $original->get($item); |
||||||
0 ignored issues
–
show
$item of type array is incompatible with the type object expected by parameter $entity of Cycle\ORM\Collection\Pivoted\PivotedStorage::get() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
100 | $this->deleteChild($pool, $pivot, $item); |
||||||
0 ignored issues
–
show
$item of type array is incompatible with the type object expected by parameter $child of Cycle\ORM\Relation\ManyToMany::deleteChild() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() It seems like
$pivot can also be of type array ; however, parameter $pivot of Cycle\ORM\Relation\ManyToMany::deleteChild() does only seem to accept null|object , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
101 | 338 | $original->getContext()->offsetUnset($item); |
|||||
0 ignored issues
–
show
$item of type array is incompatible with the type object expected by parameter $object of SplObjectStorage::offsetUnset() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
102 | } |
||||||
103 | } |
||||||
104 | 338 | ||||||
105 | 336 | if ($this->inversion === null && \count($related) === 0) { |
|||||
106 | $tuple->state->setRelationStatus($this->getName(), RelationInterface::STATUS_RESOLVED); |
||||||
107 | return; |
||||||
108 | } |
||||||
109 | 338 | $tuple->state->setRelationStatus($this->getName(), RelationInterface::STATUS_PROCESS); |
|||||
110 | |||||||
111 | 338 | // link/sync new and existed elements |
|||||
112 | foreach ($related->getElements() as $item) { |
||||||
113 | 338 | $this->newLink($pool, $tuple, $related, $item); |
|||||
114 | } |
||||||
115 | 338 | } |
|||||
116 | |||||||
117 | public function queue(Pool $pool, Tuple $tuple): void |
||||||
118 | { |
||||||
119 | $related = $tuple->state->getRelation($this->getName()); |
||||||
120 | 338 | ||||||
121 | 338 | if ($related instanceof ReferenceInterface && !$related->hasValue()) { |
|||||
122 | 338 | $tuple->state->setRelationStatus($this->getName(), RelationInterface::STATUS_RESOLVED); |
|||||
123 | 336 | return; |
|||||
124 | 336 | } |
|||||
125 | 336 | ||||||
126 | 336 | $this->finalize($pool, $tuple, $related); |
|||||
127 | 336 | } |
|||||
128 | 336 | ||||||
129 | public function init(EntityFactoryInterface $factory, Node $node, array $data): iterable |
||||||
130 | { |
||||||
131 | 338 | $elements = []; |
|||||
132 | 162 | $pivotData = new \SplObjectStorage(); |
|||||
133 | 162 | ||||||
134 | 160 | $iterator = Iterator::createWithServices( |
|||||
135 | 96 | $this->heap, |
|||||
136 | $this->ormSchema, |
||||||
137 | 64 | $this->entityFactory, |
|||||
138 | 64 | $this->target, |
|||||
139 | $data, |
||||||
140 | 162 | true, |
|||||
141 | ); |
||||||
142 | foreach ($iterator as $pivot => $entity) { |
||||||
143 | if (!\is_array($pivot)) { |
||||||
144 | 480 | // skip partially selected entities (DB level filter) |
|||||
145 | continue; |
||||||
146 | 480 | } |
|||||
147 | 480 | ||||||
148 | $pivotData[$entity] = $factory->make($this->pivotRole, $pivot, Node::MANAGED); |
||||||
149 | 480 | $elements[] = $entity; |
|||||
150 | 480 | } |
|||||
151 | 480 | $collection = new PivotedStorage($elements, $pivotData); |
|||||
152 | 480 | $node->setRelation($this->name, $collection); |
|||||
153 | 480 | ||||||
154 | return $this->collect($collection); |
||||||
155 | } |
||||||
156 | |||||||
157 | 480 | public function cast(?array $data): array |
|||||
158 | 480 | { |
|||||
159 | if (!$data) { |
||||||
0 ignored issues
–
show
The expression
$data of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using ![]() |
|||||||
160 | 24 | return []; |
|||||
161 | } |
||||||
162 | /** |
||||||
163 | 480 | * @var array<non-empty-string, MapperInterface> $targetMappers Target Mappers cache |
|||||
164 | 480 | * @var array<non-empty-string, MapperInterface> $pivotMappers Pivot Mappers cache |
|||||
165 | */ |
||||||
166 | 480 | $pivotMappers = []; |
|||||
167 | 480 | $targetMappers = []; |
|||||
168 | |||||||
169 | 480 | foreach ($data as $key => $pivot) { |
|||||
170 | if (isset($pivot['@'])) { |
||||||
171 | $d = $pivot['@']; |
||||||
172 | 632 | // break link |
|||||
173 | unset($pivot['@']); |
||||||
174 | 632 | ||||||
175 | 40 | $targetRole = $d[LoaderInterface::ROLE_KEY] ?? $this->target; |
|||||
176 | $pivot['@'] = ($targetMappers[$targetRole] ??= $this->mapperProvider->getMapper($targetRole))->cast($d); |
||||||
177 | 632 | } |
|||||
178 | 632 | // break link |
|||||
179 | unset($data[$key]); |
||||||
180 | 632 | ||||||
181 | 632 | $pivotRole = $pivot[LoaderInterface::ROLE_KEY] ?? $this->pivotRole; |
|||||
182 | 632 | $data[$key] = ($pivotMappers[$pivotRole] ??= $this->mapperProvider->getMapper($pivotRole))->cast($pivot); |
|||||
183 | } |
||||||
184 | 632 | ||||||
185 | 632 | return $data; |
|||||
186 | } |
||||||
187 | |||||||
188 | 632 | public function collect(mixed $data): iterable |
|||||
189 | 632 | { |
|||||
190 | return $this->factory->collection( |
||||||
191 | $this->schema[Relation::COLLECTION_TYPE] ?? null, |
||||||
192 | 632 | )->collect($data); |
|||||
193 | } |
||||||
194 | |||||||
195 | 648 | public function extract(?iterable $data): PivotedStorage |
|||||
196 | { |
||||||
197 | 648 | return match (true) { |
|||||
198 | 648 | $data instanceof PivotedStorage => $data, |
|||||
199 | 648 | $data instanceof PivotedCollectionInterface => new PivotedStorage( |
|||||
200 | $data->toArray(), |
||||||
0 ignored issues
–
show
The method
toArray() does not exist on null .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||||
201 | $data->getPivotContext(), |
||||||
202 | 338 | ), |
|||||
203 | $data instanceof \Doctrine\Common\Collections\Collection => new PivotedStorage($data->toArray()), |
||||||
204 | $data === null => new PivotedStorage(), |
||||||
205 | 338 | $data instanceof \Traversable => new PivotedStorage(\iterator_to_array($data)), |
|||||
0 ignored issues
–
show
It seems like
$data can also be of type null ; however, parameter $iterator of iterator_to_array() does only seem to accept Traversable , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
206 | 338 | default => new PivotedStorage((array) $data), |
|||||
207 | 240 | }; |
|||||
208 | 240 | } |
|||||
209 | |||||||
210 | 258 | public function extractRelated(?iterable $data, PivotedStorage $original): PivotedStorage |
|||||
211 | 258 | { |
|||||
212 | 56 | $related = $this->extract($data); |
|||||
213 | 338 | if (\count($original) === 0) { |
|||||
214 | return $related; |
||||||
215 | } |
||||||
216 | // Merge pivots |
||||||
217 | 338 | foreach ($related as $item) { |
|||||
218 | if ($related->hasContext($item)) { |
||||||
0 ignored issues
–
show
$item of type array is incompatible with the type object expected by parameter $entity of Cycle\ORM\Collection\Piv...edStorage::hasContext() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
219 | 338 | continue; |
|||||
220 | 338 | } |
|||||
221 | 322 | if ($original->hasContext($item)) { |
|||||
222 | $related->set($item, $original->getContext()->offsetGet($item)); |
||||||
0 ignored issues
–
show
$item of type array is incompatible with the type object expected by parameter $entity of Cycle\ORM\Collection\Pivoted\PivotedStorage::set() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() $item of type array is incompatible with the type object expected by parameter $object of SplObjectStorage::offsetGet() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
223 | 32 | } |
|||||
224 | 32 | } |
|||||
225 | 32 | return $related; |
|||||
226 | } |
||||||
227 | |||||||
228 | 32 | public function initReference(Node $node): ReferenceInterface |
|||||
229 | { |
||||||
230 | $scope = []; |
||||||
231 | 308 | $nodeData = $node->getData(); |
|||||
232 | foreach ($this->innerKeys as $key) { |
||||||
233 | 308 | if (!isset($nodeData[$key])) { |
|||||
234 | 308 | return new EmptyReference($node->getRole(), new PivotedStorage()); |
|||||
235 | 308 | } |
|||||
236 | 308 | $scope[$key] = $nodeData[$key]; |
|||||
237 | 56 | } |
|||||
238 | |||||||
239 | 268 | return new Reference($this->target, $scope); |
|||||
240 | } |
||||||
241 | |||||||
242 | 268 | public function resolve(ReferenceInterface $reference, bool $load): ?iterable |
|||||
243 | { |
||||||
244 | if ($reference->hasValue()) { |
||||||
245 | 224 | return $reference->getValue(); |
|||||
246 | } |
||||||
247 | 224 | if ($load === false) { |
|||||
248 | 80 | return null; |
|||||
249 | } |
||||||
250 | 192 | $scope = $reference->getScope(); |
|||||
251 | 24 | if ($scope === []) { |
|||||
252 | $result = new PivotedStorage(); |
||||||
253 | 168 | $reference->setValue($result); |
|||||
254 | 168 | return $result; |
|||||
0 ignored issues
–
show
|
|||||||
255 | } |
||||||
256 | |||||||
257 | $source = $this->sourceProvider->getSource($this->target); |
||||||
258 | // getting scoped query |
||||||
259 | $query = (new RootLoader( |
||||||
260 | $this->ormSchema, |
||||||
261 | 168 | $this->sourceProvider, |
|||||
262 | 168 | $this->factory, |
|||||
263 | 168 | $this->target, |
|||||
264 | 168 | loadRelations: false, |
|||||
265 | 168 | ))->buildQuery(); |
|||||
266 | 168 | ||||||
267 | // responsible for all the scoping |
||||||
268 | $loader = new ManyToManyLoader( |
||||||
269 | 168 | $this->ormSchema, |
|||||
270 | 168 | $this->sourceProvider, |
|||||
271 | 168 | $this->factory, |
|||||
272 | 168 | $source->getTable(), |
|||||
273 | 168 | $this->target, |
|||||
274 | 168 | $this->schema, |
|||||
275 | 168 | ); |
|||||
276 | |||||||
277 | /** @var ManyToManyLoader $loader */ |
||||||
278 | $loader = $loader->withContext($loader, [ |
||||||
279 | 168 | 'scope' => $source->getScope(), |
|||||
280 | 168 | 'as' => $this->target, |
|||||
281 | 168 | 'method' => JoinableLoader::POSTLOAD, |
|||||
282 | ]); |
||||||
283 | |||||||
284 | $query = $loader->configureQuery($query, [$scope]); |
||||||
285 | 168 | ||||||
286 | // we are going to add pivot node into virtual root node (only ID) to aggregate the results |
||||||
287 | $root = new RootNode( |
||||||
288 | 168 | (array) $this->schema[Relation::INNER_KEY], |
|||||
289 | 168 | (array) $this->schema[Relation::INNER_KEY], |
|||||
290 | 168 | ); |
|||||
291 | |||||||
292 | $node = $loader->createNode(); |
||||||
293 | 168 | $root->linkNode('output', $node); |
|||||
294 | 168 | ||||||
295 | // emulate presence of parent entity |
||||||
296 | $root->parseRow(0, $this->mapperProvider->getMapper($this->role)->uncast($scope)); |
||||||
297 | 168 | ||||||
298 | $iterator = $query->getIterator(); |
||||||
299 | 168 | foreach ($iterator as $row) { |
|||||
300 | 168 | $node->parseRow(0, $row); |
|||||
301 | 168 | } |
|||||
302 | $iterator->close(); |
||||||
303 | 168 | ||||||
304 | // load all eager relations, forbid loader to re-fetch data (make it think it was joined) |
||||||
305 | $loader->withContext($loader, ['method' => JoinableLoader::INLOAD])->loadData($node); |
||||||
306 | 168 | ||||||
307 | $elements = []; |
||||||
308 | 168 | $pivotData = new \SplObjectStorage(); |
|||||
309 | 168 | $iterator = Iterator::createWithServices( |
|||||
310 | 168 | $this->heap, |
|||||
311 | 168 | $this->ormSchema, |
|||||
312 | 168 | $this->entityFactory, |
|||||
313 | 168 | $this->target, |
|||||
314 | 168 | $root->getResult()[0]['output'], |
|||||
315 | 168 | true, |
|||||
316 | typecast: true, |
||||||
317 | ); |
||||||
318 | foreach ($iterator as $pivot => $entity) { |
||||||
319 | 168 | $pivotData[$entity] = $this->entityFactory->make( |
|||||
320 | 168 | $this->schema[Relation::THROUGH_ENTITY], |
|||||
321 | 168 | $pivot, |
|||||
322 | Node::MANAGED, |
||||||
323 | typecast: true, |
||||||
324 | ); |
||||||
325 | |||||||
326 | $elements[] = $entity; |
||||||
327 | 168 | } |
|||||
328 | $result = new PivotedStorage($elements, $pivotData); |
||||||
329 | 168 | $reference->setValue($result); |
|||||
330 | 168 | return $result; |
|||||
0 ignored issues
–
show
|
|||||||
331 | 168 | } |
|||||
332 | |||||||
333 | protected function applyPivotChanges(State $parentState, State $state): void |
||||||
334 | 336 | { |
|||||
335 | foreach ($this->innerKeys as $i => $innerKey) { |
||||||
336 | 336 | $state->register($this->throughInnerKeys[$i], $parentState->getValue($innerKey)); |
|||||
337 | 336 | } |
|||||
338 | } |
||||||
339 | |||||||
340 | protected function newLink(Pool $pool, Tuple $tuple, PivotedStorage $storage, object $related): void |
||||||
341 | 72 | { |
|||||
342 | $rTuple = $pool->attachStore($related, $this->isCascade()); |
||||||
343 | $this->assertValid($rTuple->node); |
||||||
344 | 72 | ||||||
345 | 72 | $pivot = $storage->get($related); |
|||||
346 | if (!\is_object($pivot)) { |
||||||
347 | 72 | // first time initialization |
|||||
348 | $pivot = $this->initPivot($tuple->entity, $storage, $rTuple, $pivot); |
||||||
349 | $storage->set($related, $pivot); |
||||||
0 ignored issues
–
show
It seems like
$pivot can also be of type null ; however, parameter $pivot of Cycle\ORM\Collection\Pivoted\PivotedStorage::set() does only seem to accept array|object , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
350 | 336 | } |
|||||
351 | |||||||
352 | 336 | $pTuple = $pool->attachStore($pivot, $this->isCascade()); |
|||||
0 ignored issues
–
show
It seems like
$pivot can also be of type null ; however, parameter $entity of Cycle\ORM\Transaction\Pool::attachStore() does only seem to accept object , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
353 | 336 | // $pRelationName = $tuple->node->getRole() . '.' . $this->getName() . ':' . $this->pivotEntity; |
|||||
354 | // $pNode->setRelationStatus($pRelationName, RelationInterface::STATUS_RESOLVED); |
||||||
355 | 336 | ||||||
356 | 336 | foreach ($this->throughInnerKeys as $i => $pInnerKey) { |
|||||
357 | $pTuple->state->register($pInnerKey, $tuple->state->getTransactionData()[$this->innerKeys[$i]] ?? null); |
||||||
358 | 312 | } |
|||||
359 | 312 | ||||||
360 | if ($this->inversion === null) { |
||||||
361 | // send the Pivot into child's State for the ShadowHasMany relation |
||||||
362 | 336 | // $relName = $tuple->node->getRole() . '.' . $this->name . ':' . $this->target; |
|||||
363 | $relName = $this->getTargetRelationName(); |
||||||
364 | $pivots = $rTuple->state->getRelations()[$relName] ?? []; |
||||||
365 | $pivots[] = $pivot; |
||||||
366 | 336 | $rTuple->state->setRelation($relName, $pivots); |
|||||
367 | 336 | } else { |
|||||
368 | $rTuple->state->addToStorage($this->inversion, $pTuple->state); |
||||||
369 | } |
||||||
370 | } |
||||||
371 | |||||||
372 | 336 | /** |
|||||
373 | * Since many to many relation can overlap from two directions we have to properly resolve the pivot entity upon |
||||||
374 | * it's generation. This is achieved using temporary mapping associated with each of the entity states. |
||||||
375 | 176 | */ |
|||||
376 | 176 | protected function initPivot(object $parent, PivotedStorage $storage, Tuple $rTuple, ?array $pivot): ?object |
|||||
377 | 176 | { |
|||||
378 | 176 | if ($this->inversion !== null) { |
|||||
379 | $relatedStorage = $rTuple->state->getRelation($this->inversion); |
||||||
380 | 160 | if ($relatedStorage instanceof PivotedStorage && $relatedStorage->hasContext($parent)) { |
|||||
381 | return $relatedStorage->get($parent); |
||||||
0 ignored issues
–
show
|
|||||||
382 | } |
||||||
383 | } |
||||||
384 | |||||||
385 | $entity = $this->entityFactory->make($this->pivotRole, $pivot ?? []); |
||||||
386 | $storage->set($rTuple->entity, $entity); |
||||||
387 | return $entity; |
||||||
388 | 312 | } |
|||||
389 | |||||||
390 | 312 | private function deleteChild(Pool $pool, ?object $pivot, object $child, ?Node $relatedNode = null): void |
|||||
0 ignored issues
–
show
The parameter
$relatedNode is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||||
391 | 160 | { |
|||||
392 | 160 | // todo: add supporting for nullable pivot entities? |
|||||
393 | 96 | if ($pivot !== null) { |
|||||
394 | $pool->attachDelete($pivot, $this->isCascade()); |
||||||
395 | } |
||||||
396 | $pool->attachStore($child, true); |
||||||
397 | 312 | } |
|||||
398 | 312 | ||||||
399 | 312 | private function finalize(Pool $pool, Tuple $tuple, mixed $related): void |
|||||
400 | { |
||||||
401 | $tuple->state->setRelationStatus($this->getName(), RelationInterface::STATUS_RESOLVED); |
||||||
402 | |||||||
403 | $relationName = $tuple->node->getRole() . '.' . $this->name . ':' . $this->pivotRole; |
||||||
404 | $pStates = []; |
||||||
405 | foreach ($related as $item) { |
||||||
406 | $pivot = $related->get($item); |
||||||
407 | if ($pivot !== null) { |
||||||
408 | $pTuple = $pool->offsetGet($pivot); |
||||||
409 | $this->applyPivotChanges($tuple->state, $pTuple->state); |
||||||
410 | $pStates[] = $pTuple->state; |
||||||
411 | $pTuple->state->setRelationStatus($relationName, RelationInterface::STATUS_RESOLVED); |
||||||
412 | } |
||||||
413 | } |
||||||
414 | if ($this->inversion !== null) { |
||||||
415 | $storage = $tuple->state->getStorage($this->name); |
||||||
416 | foreach ($storage as $pState) { |
||||||
417 | if (\in_array($pState, $pStates, true)) { |
||||||
418 | continue; |
||||||
419 | } |
||||||
420 | $this->applyPivotChanges($tuple->state, $pState); |
||||||
421 | $pState->setRelationStatus($relationName, RelationInterface::STATUS_RESOLVED); |
||||||
422 | } |
||||||
423 | $tuple->state->clearStorage($this->name); |
||||||
424 | } |
||||||
425 | } |
||||||
426 | } |
||||||
427 |