Issues (590)

src/Relations/OneOrMany.php (2 issues)

Labels
Severity
1
<?php
2
3
namespace Bdf\Prime\Relations;
4
5
use Bdf\Prime\Query\Contract\EntityJoinable;
6
use Bdf\Prime\Query\Contract\ReadOperation;
7
use Bdf\Prime\Query\Contract\WriteOperation;
8
use Bdf\Prime\Query\ReadCommandInterface;
9
use Bdf\Prime\Repository\RepositoryInterface;
10
use InvalidArgumentException;
11
12
/**
13
 * OneOrMany
14
 *
15
 * @package Bdf\Prime\Relations
16
 *
17
 * @todo possibilité de désactiver les constraints globales
18
 *
19
 * @template L as object
20
 * @template R as object
21
 *
22
 * @extends Relation<L, R>
23
 */
24
abstract class OneOrMany extends Relation
25
{
26
    /**
27
     * {@inheritdoc}
28
     */
29 84
    public function relationRepository(): RepositoryInterface
30
    {
31 84
        return $this->distant;
32
    }
33
34
    /**
35
     * {@inheritdoc}
36
     */
37 240
    protected function applyConstraints(ReadCommandInterface $query, $constraints = [], $context = null): ReadCommandInterface
38
    {
39 240
        parent::applyConstraints($query, $constraints, $context);
40
41
        // Si le dépot distant possède la foreign key, on estime qu'il possède le discriminator
42
        // On applique donc la contrainte de relation sur le discriminitor
43 240
        if ($this->isPolymorphic() && $this->isForeignKeyBarrier($this->distant->entityClass())) {
44 9
            $query->where(
0 ignored issues
show
The method where() does not exist on Bdf\Prime\Query\ReadCommandInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Bdf\Prime\Query\ReadCommandInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

44
            $query->/** @scrutinizer ignore-call */ 
45
                    where(
Loading history...
45 9
                $this->applyContext($context, [$this->discriminator => $this->discriminatorValue])
46 9
            );
47
        }
48
49 240
        return $query;
50
    }
51
52
    /**
53
     * {@inheritdoc}
54
     */
55 77
    public function join(EntityJoinable $query, string $alias): void
56
    {
57
        // @fixme ?
58
//        if ($alias === null) {
59
//            $alias = $this->attributeAim;
60
//        }
61
62 77
        $query->joinEntity($this->distant->entityName(), $this->distantKey, $this->getLocalAlias($query).$this->localKey, $alias);
63
64
        // apply relation constraints
65 77
        $this->applyConstraints($query, [], '$'.$alias);
66
    }
67
68
    /**
69
     * {@inheritdoc}
70
     */
71 63
    public function joinRepositories(EntityJoinable $query, string $alias, $discriminator = null): array
72
    {
73 63
        return [
74 63
            $alias => $this->relationRepository()
75 63
        ];
76
    }
77
78
    /**
79
     * {@inheritdoc}
80
     */
81
    #[ReadOperation]
82 216
    protected function relations($keys, $with, $constraints, $without): array
83
    {
84
        /** @var R[] */
85 216
        return $this->relationQuery($keys, $constraints)
86 216
            ->with($with)
0 ignored issues
show
The method with() does not exist on Bdf\Prime\Query\ReadCommandInterface. It seems like you code against a sub-type of said class. However, the method does not exist in Bdf\Prime\Query\Contract...\KeyValueQueryInterface or Bdf\Prime\Query\QueryInterface or Bdf\Prime\Query\SqlQueryInterface. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

86
            ->/** @scrutinizer ignore-call */ with($with)
Loading history...
87 216
            ->without($without)
88 216
            ->all();
89
    }
90
91
    /**
92
     * {@inheritdoc}
93
     */
94 213
    protected function match($collection, $relations): void
95
    {
96 213
        foreach ($relations as $key => $distant) {
97 203
            foreach ($collection[$key] as $local) {
98 203
                $this->setRelation($local, $distant);
99
            }
100
        }
101
    }
102
103
    /**
104
     * {@inheritdoc}
105
     */
106 28
    public function link($owner): ReadCommandInterface
107
    {
108 28
        return $this->query($this->getLocalKeyValue($owner));
109
    }
110
111
    /**
112
     * {@inheritdoc}
113
     */
114 13
    public function associate($owner, $entity)
115
    {
116 13
        if (!$this->isForeignKeyBarrier($owner)) {
117 6
            throw new InvalidArgumentException('The local entity is not the foreign key barrier.');
118
        }
119
120 7
        if ($this->isPolymorphic()) {
121 4
            $this->discriminatorValue = $this->discriminator(get_class($entity));
122
        }
123
124 7
        $this->setForeignKeyValue($owner, $this->getDistantKeyValue($entity));
125 7
        $this->setRelation($owner, $entity);
126
127 7
        return $owner;
128
    }
129
130
    /**
131
     * {@inheritdoc}
132
     */
133 13
    public function dissociate($owner)
134
    {
135 13
        if (!$this->isForeignKeyBarrier($owner)) {
136 6
            throw new InvalidArgumentException('The local entity is not the foreign key barrier.');
137
        }
138
139 7
        if ($this->isPolymorphic()) {
140 4
            $this->discriminatorValue = null;
141
        }
142
143
        // TODO Dont update key if it is embedded in the relation object
144 7
        $this->setForeignKeyValue($owner, null);
145 7
        $this->setRelation($owner, null);
146
147 7
        return $owner;
148
    }
149
150
    /**
151
     * {@inheritdoc}
152
     */
153 15
    public function create($owner, array $data = [])
154
    {
155 15
        if ($this->isForeignKeyBarrier($owner)) {
156 6
            throw new InvalidArgumentException('The local entity is not the primary key barrier.');
157
        }
158
159 9
        $entity = $this->distant->entity($data);
160
161 9
        $this->setForeignKeyValue($entity, $this->getLocalKeyValue($owner));
162
163 9
        return $entity;
164
    }
165
166
    /**
167
     * {@inheritdoc}
168
     */
169
    #[WriteOperation]
170
    public function add($owner, $related): int
171
    {
172
        if ($this->isForeignKeyBarrier($owner)) {
173
            throw new InvalidArgumentException('The local entity is not the primary key barrier.');
174
        }
175
176
        $this->setForeignKeyValue($related, $this->getLocalKeyValue($owner));
177
178
        return $this->distant->save($related);
179
    }
180
181
    /**
182
     * {@inheritdoc}
183
     */
184
    #[WriteOperation]
185 30
    public function saveAll($owner, array $relations = []): int
186
    {
187 30
        $entities = $this->getRelation($owner);
188
189 30
        if (empty($entities)) {
190 4
            return 0;
191
        }
192
193 26
        $id = $this->getLocalKeyValue($owner);
194
195
        //Detach all relations
196 26
        if ($this->saveStrategy === self::SAVE_STRATEGY_REPLACE) {
197 4
            $this->query($id)->delete();
198
        }
199
200 26
        if (!is_array($entities)) {
201 22
            $entities = [$entities];
202
        }
203
204
        // Save new relations
205 26
        $nb = 0;
206
207 26
        foreach ($entities as $entity) {
208 26
            $this->setForeignKeyValue($entity, $id);
209 26
            $nb += $this->distant->saveAll($entity, $relations);
210
        }
211
212 26
        return (int) $nb;
213
    }
214
215
    /**
216
     * {@inheritdoc}
217
     */
218
    #[WriteOperation]
219 25
    public function deleteAll($owner, array $relations = []): int
220
    {
221 25
        $entities = $this->getRelation($owner);
222
223 25
        if (empty($entities)) {
224 2
            return 0;
225
        }
226
227 24
        if (!is_array($entities)) {
228 19
            $entities = [$entities];
229
        }
230
231 24
        $nb = 0;
232
233 24
        foreach ($entities as $entity) {
234 24
            $nb += $this->distant->deleteAll($entity, $relations);
235
        }
236
237 24
        return (int) $nb;
238
    }
239
240
    /**
241
     * Get the repository that owns the foreign key and the key name
242
     *
243
     * @return array{0:RepositoryInterface,1:string}
244
     */
245
    abstract protected function getForeignInfos(): array;
246
247
    /**
248
     * Get the query used to load relations
249
     *
250
     * @param array $keys The owner keys
251
     * @param array $constraints Constraints to apply on the query
252
     *
253
     * @return ReadCommandInterface
254
     */
255
    abstract protected function relationQuery($keys, $constraints): ReadCommandInterface;
256
257
    /**
258
     * Check if the entity is the foreign key barrier
259
     *
260
     * @param string|object $entity
261
     *
262
     * @return bool
263
     */
264 161
    private function isForeignKeyBarrier($entity): bool
265
    {
266 161
        list($repository) = $this->getForeignInfos();
267
268 161
        if (!is_string($entity)) {
269 41
            $entity = get_class($entity);
270
        }
271
272 161
        return $repository->entityClass() === $entity;
273
    }
274
275
    /**
276
     * Set the foreign key value on an entity
277
     *
278
     * @param object $entity
279
     * @param mixed  $id
280
     */
281 49
    private function setForeignKeyValue($entity, $id): void
282
    {
283
        /**
284
         * @var RepositoryInterface $repository
285
         * @var string $key
286
         */
287 49
        list($repository, $key) = $this->getForeignInfos();
288
289 49
        if ($repository->entityClass() === get_class($entity)) {
290 30
            $repository->mapper()->hydrateOne($entity, $key, $id);
291
292 30
            if ($this->isPolymorphic()) {
293 11
                $repository->mapper()->hydrateOne($entity, $this->discriminator, $this->discriminatorValue);
294
            }
295
        }
296
    }
297
}
298