Relation::setOptionIfMissing()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 2
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 4
rs 10
1
<?php
2
declare(strict_types=1);
3
4
namespace Sirius\Orm\Relation;
5
6
use Sirius\Orm\Action\AttachEntities;
7
use Sirius\Orm\Action\BaseAction;
8
use Sirius\Orm\Action\Delete;
9
use Sirius\Orm\Action\DetachEntities;
10
use Sirius\Orm\Action\Update;
11
use Sirius\Orm\Entity\EntityInterface;
12
use Sirius\Orm\Entity\LazyRelation;
13
use Sirius\Orm\Entity\Tracker;
14
use Sirius\Orm\Helpers\Arr;
15
use Sirius\Orm\Helpers\QueryHelper;
16
use Sirius\Orm\Mapper;
17
use Sirius\Orm\Query;
18
use Sirius\Sql\Select;
19
20
abstract class Relation
21
{
22
    /**
23
     * Name of the relation (used to infer defaults)
24
     * @var
25
     */
26
    protected $name;
27
28
    /**
29
     * @var Mapper
30
     */
31
    protected $nativeMapper;
32
33
    /**
34
     * @var Mapper
35
     */
36
    protected $foreignMapper;
37
38
    /**
39
     * @var array
40
     */
41
    protected $options = [];
42
43
    /**
44
     * Stores the nativeColumn-foreignColumn pairs to be used on queries
45
     * @var array
46
     */
47
    protected $keyPairs;
48
49
    public function __construct($name, Mapper $nativeMapper, Mapper $foreignMapper, array $options = [])
50
    {
51
        $this->name          = $name;
52
        $this->nativeMapper  = $nativeMapper;
53
        $this->foreignMapper = $foreignMapper;
54
        $this->options       = $options;
55
        $this->applyDefaults();
56
        $this->keyPairs = $this->computeKeyPairs();
57
    }
58
59
    protected function applyDefaults(): void
60
    {
61
        $this->setOptionIfMissing(RelationConfig::LOAD_STRATEGY, RelationConfig::LOAD_LAZY);
62
        $this->setOptionIfMissing(RelationConfig::CASCADE, false);
63
    }
64
65
    protected function setOptionIfMissing($name, $value)
66
    {
67
        if (! isset($this->options[$name])) {
68
            $this->options[$name] = $value;
69
        }
70
    }
71
72
    public function getOption($name)
73
    {
74
        if ($name == 'name') {
75
            return $this->name;
76
        }
77
78
        return $this->options[$name] ?? null;
79
    }
80
81
    /**
82
     * @return array
83
     */
84
    public function getKeyPairs(): array
85
    {
86
        return $this->keyPairs;
87
    }
88
89
    public function isEagerLoad()
90
    {
91
        return $this->options[RelationConfig::LOAD_STRATEGY] == RelationConfig::LOAD_EAGER;
92
    }
93
94
    public function isLazyLoad()
95
    {
96
        return $this->options[RelationConfig::LOAD_STRATEGY] == RelationConfig::LOAD_LAZY;
97
    }
98
99
    public function isCascade()
100
    {
101
        return $this->options[RelationConfig::CASCADE] === true;
102
    }
103
104
    protected function getKeyColumn($name, $column)
105
    {
106
        if (!is_array($column)) {
107
            return $name . '_' . $column;
108
        }
109
110
        $keyColumn = [];
111
        foreach ($column as $col) {
112
            $keyColumn[] = $name . '_' . $col;
113
        }
114
115
        return $keyColumn;
116
    }
117
118
    public function addActions(BaseAction $action)
119
    {
120
        if ($action instanceof Delete) {
121
            $this->addActionOnDelete($action);
122
        } elseif ($action instanceof Insert || $action instanceof Update) {
0 ignored issues
show
Bug introduced by
The type Sirius\Orm\Relation\Insert was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
123
            $this->addActionOnSave($action);
124
        }
125
    }
126
127
    abstract protected function addActionOnSave(BaseAction $action);
128
129
    abstract protected function addActionOnDelete(BaseAction $action);
130
131
    abstract public function attachMatchesToEntity(EntityInterface $nativeEntity, array $queryResult);
132
133
    abstract public function attachEntities(EntityInterface $nativeEntity, EntityInterface $foreignEntity);
134
135
    abstract public function detachEntities(EntityInterface $nativeEntity, EntityInterface $foreignEntity);
136
137
    abstract public function joinSubselect(Query $query, string $reference);
138
139
    public function attachLazyRelationToEntity(EntityInterface $entity, Tracker $tracker)
140
    {
141
        $valueLoader = new LazyRelation($entity, $tracker, $this);
142
        $this->nativeMapper->setEntityAttribute($entity, $this->name, $valueLoader);
143
    }
144
145
    public function getQuery(Tracker $tracker)
146
    {
147
        $nativeKey = $this->options[RelationConfig::NATIVE_KEY];
148
        $nativePks = $tracker->pluck($nativeKey);
149
150
        $query = $this->foreignMapper
151
            ->newQuery()
152
            ->where($this->foreignMapper->getPrimaryKey(), $nativePks);
153
154
        $query = $this->applyQueryCallback($query);
155
156
        $query = $this->applyForeignGuards($query);
157
158
        return $query;
159
    }
160
161
    public function indexQueryResults(array $entities)
162
    {
163
        $result = [];
164
165
        foreach ($entities as $entity) {
166
            $entityId = $this->getEntityId($this->foreignMapper, $entity, array_values($this->keyPairs));
167
            if (!isset($result[$entityId])) {
168
                $result[$entityId] = [];
169
            }
170
            $result[$entityId][] = $entity;
171
        }
172
173
        return $result;
174
    }
175
176
    protected function getEntityId(Mapper $mapper, EntityInterface $entity, array $keyColumns)
177
    {
178
        $entityKeys = [];
179
        foreach ($keyColumns as $col) {
180
            $entityKeys[] = $mapper->getEntityAttribute($entity, $col);
181
        }
182
        return implode('-', $entityKeys);
183
    }
184
185
    /**
186
     * Method used by `entitiesBelongTogether` to check
187
     * if a foreign entity belongs to the native entity
188
     * @return array
189
     */
190
    protected function computeKeyPairs()
191
    {
192
        $pairs      = [];
193
        $nativeKey  = (array)$this->options[RelationConfig::NATIVE_KEY];
194
        $foreignKey = (array)$this->options[RelationConfig::FOREIGN_KEY];
195
        foreach ($nativeKey as $k => $v) {
196
            $pairs[$v] = $foreignKey[$k];
197
        }
198
199
        return $pairs;
200
    }
201
202
    /**
203
     * Computes the $withRelations value to be passed on to the next related entities
204
     * If an entity receives on delete/save $withRelations = ['category', 'category.images']
205
     * the related 'category' is saved with $withRelations = ['images']
206
     *
207
     * @param $relations
208
     *
209
     * @return array
210
     */
211
    protected function getRemainingRelations($relations)
212
    {
213
        if (! is_array($relations)) {
214
            return $relations;
215
        }
216
217
        $arr = array_combine($relations, $relations);
218
        if (is_array($arr)) {
219
            $children = Arr::getChildren($arr, $this->name);
220
221
            return array_keys($children);
222
        }
223
224
        return [];
225
    }
226
227
    protected function getExtraColumnsForAction()
228
    {
229
        $cols   = [];
230
        $guards = $this->getOption(RelationConfig::FOREIGN_GUARDS);
231
        if (is_array($guards)) {
232
            foreach ($guards as $col => $val) {
233
                // guards that are strings (eg: 'deleted_at is null') can't be used as extra columns
234
                if (! is_int($col)) {
235
                    $cols[$col] = $val;
236
                }
237
            }
238
        }
239
240
        return $cols;
241
    }
242
243
    protected function newSyncAction(EntityInterface $nativeEntity, EntityInterface $foreignEntity, string $actionType)
244
    {
245
        if ($actionType == 'delete') {
246
            return new DetachEntities(
247
                $this->nativeMapper,
248
                $nativeEntity,
249
                $this->foreignMapper,
250
                $foreignEntity,
251
                $this,
252
                'save'
253
            );
254
        }
255
256
        return new AttachEntities(
257
            $this->nativeMapper,
258
            $nativeEntity,
259
            $this->foreignMapper,
260
            $foreignEntity,
261
            $this,
262
            'save'
263
        );
264
    }
265
266
    protected function relationWasChanged(EntityInterface $entity)
267
    {
268
        $changes = $entity->getChanges();
269
270
        return isset($changes[$this->name]) && $changes[$this->name];
271
    }
272
273
    protected function applyQueryCallback(Select $query)
274
    {
275
        $queryCallback = $this->getOption(RelationConfig::QUERY_CALLBACK);
276
        if ($queryCallback && is_callable($queryCallback)) {
277
            $query = $queryCallback($query);
278
        }
279
280
        return $query;
281
    }
282
283
    protected function applyForeignGuards(Select $query)
284
    {
285
        $guards = $this->getOption(RelationConfig::FOREIGN_GUARDS);
286
        if ($guards) {
287
            $query->setGuards($guards);
288
        }
289
290
        return $query;
291
    }
292
293
    protected function getJoinOnForSubselect()
294
    {
295
        return QueryHelper::joinCondition(
296
            $this->nativeMapper->getTableAlias(true),
297
            $this->getOption(RelationConfig::NATIVE_KEY),
298
            $this->name,
299
            $this->getOption(RelationConfig::FOREIGN_KEY)
300
        );
301
    }
302
303
    /**
304
     * @return Mapper
305
     */
306
    public function getNativeMapper(): Mapper
307
    {
308
        return $this->nativeMapper;
309
    }
310
311
    /**
312
     * @return Mapper
313
     */
314
    public function getForeignMapper(): Mapper
315
    {
316
        return $this->foreignMapper;
317
    }
318
}
319