Passed
Push — master ( ea0818...92b4a6 )
by Adrian
01:28
created

Relation::attachLazyValueToEntity()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
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\LazyLoader;
0 ignored issues
show
Bug introduced by
The type Sirius\Orm\LazyLoader 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...
17
use Sirius\Orm\Mapper;
18
use Sirius\Orm\Query;
19
use Sirius\Sql\Select;
20
21
abstract class Relation
22
{
23
    /**
24
     * Name of the relation (used to infer defaults)
25
     * @var
26
     */
27
    protected $name;
28
29
    /**
30
     * @var Mapper
31
     */
32
    protected $nativeMapper;
33
34
    /**
35
     * @var Mapper
36
     */
37
    protected $foreignMapper;
38
39
    /**
40
     * @var array
41
     */
42
    protected $options = [];
43
44
    /**
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
    /**
90
     * Checks if a native entity belongs and a foreign entity belong together according to this relation
91
     * It verifies if the attributes are properly linked
92
     *
93
     * @param EntityInterface $nativeEntity
94
     * @param EntityInterface $foreignEntity
95
     *
96
     * @return mixed
97
     */
98
    public function entitiesBelongTogether(EntityInterface $nativeEntity, EntityInterface $foreignEntity)
99
    {
100
        /**
101
         * @todo make this method protected
102
         */
103
        foreach ($this->keyPairs as $nativeCol => $foreignCol) {
104
            $nativeKeyValue  = $this->nativeMapper->getEntityAttribute($nativeEntity, $nativeCol);
105
            $foreignKeyValue = $this->foreignMapper->getEntityAttribute($foreignEntity, $foreignCol);
106
            // if both native and foreign key values are present (not unlinked entities) they must be the same
107
            // otherwise we assume that the entities can be linked together
108
            if ($nativeKeyValue && $foreignKeyValue && $nativeKeyValue != $foreignKeyValue) {
109
                return false;
110
            }
111
        }
112
113
        return true;
114
    }
115
116
    public function isEagerLoad()
117
    {
118
        return $this->options[RelationConfig::LOAD_STRATEGY] == RelationConfig::LOAD_EAGER;
119
    }
120
121
    public function isLazyLoad()
122
    {
123
        return $this->options[RelationConfig::LOAD_STRATEGY] == RelationConfig::LOAD_LAZY;
124
    }
125
126
    public function isCascade()
127
    {
128
        return $this->options[RelationConfig::CASCADE] === true;
129
    }
130
131
    protected function getKeyColumn($name, $column)
132
    {
133
        if (is_array($column)) {
134
            $keyColumn = [];
135
            foreach ($column as $col) {
136
                $keyColumn[] = $name . '_' . $col;
137
            }
138
139
            return $keyColumn;
140
        }
141
142
        return $name . '_' . $column;
143
    }
144
145
    public function addActions(BaseAction $action)
146
    {
147
        if (! $this->cascadeIsAllowedForAction($action)) {
148
            return;
149
        }
150
151
        if ($action instanceof Delete) {
152
            $this->addActionOnDelete($action);
0 ignored issues
show
Bug introduced by
The method addActionOnDelete() does not exist on Sirius\Orm\Relation\Relation. Did you maybe mean addActions()? ( Ignorable by Annotation )

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

152
            $this->/** @scrutinizer ignore-call */ 
153
                   addActionOnDelete($action);

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.

Loading history...
153
        } 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...
154
            $this->addActionOnSave($action);
0 ignored issues
show
Bug introduced by
The method addActionOnSave() does not exist on Sirius\Orm\Relation\Relation. Did you maybe mean addActions()? ( Ignorable by Annotation )

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

154
            $this->/** @scrutinizer ignore-call */ 
155
                   addActionOnSave($action);

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.

Loading history...
155
        }
156
    }
157
158
    abstract public function attachMatchesToEntity(EntityInterface $nativeEntity, array $queryResult);
159
160
    abstract public function detachEntities(EntityInterface $nativeEntity, EntityInterface $foreignEntity);
161
162
    abstract public function joinSubselect(Query $query, string $reference);
163
164
    public function attachLazyRelationToEntity(EntityInterface $entity, Tracker $tracker)
165
    {
166
        $valueLoader = new LazyRelation($entity, $tracker, $this);
167
        $this->nativeMapper->setEntityAttribute($entity, $this->name, $valueLoader);
168
    }
169
170
    public function getQuery(Tracker $tracker)
171
    {
172
        $nativeKey = $this->options[RelationConfig::NATIVE_KEY];
173
        $nativePks = $tracker->pluck($nativeKey);
174
175
        $query = $this->foreignMapper
176
            ->newQuery()
177
            ->where($this->foreignMapper->getPrimaryKey(), $nativePks);
178
179
        $query = $this->applyQueryCallback($query);
180
181
        $query = $this->applyForeignGuards($query);
182
183
        return $query;
184
    }
185
186
    /**
187
     * Method used by `entitiesBelongTogether` to check
188
     * if a foreign entity belongs to the native entity
189
     * @return array
190
     */
191
    protected function computeKeyPairs()
192
    {
193
        $pairs      = [];
194
        $nativeKey  = (array)$this->options[RelationConfig::NATIVE_KEY];
195
        $foreignKey = (array)$this->options[RelationConfig::FOREIGN_KEY];
196
        foreach ($nativeKey as $k => $v) {
197
            $pairs[$v] = $foreignKey[$k];
198
        }
199
200
        return $pairs;
201
    }
202
203
    /**
204
     * @param BaseAction $action
205
     *
206
     * @return bool|mixed|null
207
     * @see BaseAction::$options
208
     */
209
    protected function cascadeIsAllowedForAction(BaseAction $action)
210
    {
211
        $relations = $action->getOption('relations');
212
        if (is_array($relations) && ! in_array($this->name, $relations)) {
213
            return false;
214
        }
215
216
        return $relations;
217
    }
218
219
    /**
220
     * Computes the $withRelations value to be passed on to the next related entities
221
     * If an entity receives on delete/save $withRelations = ['category', 'category.images']
222
     * the related 'category' is saved with $withRelations = ['images']
223
     *
224
     * @param $relations
225
     *
226
     * @return array
227
     */
228
    protected function getRemainingRelations($relations)
229
    {
230
        if (! is_array($relations)) {
231
            return $relations;
232
        }
233
234
        $children = Arr::getChildren(array_combine($relations, $relations), $this->name);
0 ignored issues
show
Bug introduced by
It seems like array_combine($relations, $relations) can also be of type false; however, parameter $arr of Sirius\Orm\Helpers\Arr::getChildren() does only seem to accept array, 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 ignore-type  annotation

234
        $children = Arr::getChildren(/** @scrutinizer ignore-type */ array_combine($relations, $relations), $this->name);
Loading history...
235
236
        return array_keys($children);
237
    }
238
239
    protected function getExtraColumnsForAction()
240
    {
241
        $cols   = [];
242
        $guards = $this->getOption(RelationConfig::FOREIGN_GUARDS);
243
        if (is_array($guards)) {
244
            foreach ($guards as $col => $val) {
245
                // guards that are strings (eg: 'deleted_at is null') can't be used as extra columns
246
                if (! is_int($col)) {
247
                    $cols[$col] = $val;
248
                }
249
            }
250
        }
251
252
        return $cols;
253
    }
254
255
    protected function newSyncAction(EntityInterface $nativeEntity, EntityInterface $foreignEntity, string $actionType)
256
    {
257
        if ($actionType == 'delete') {
258
            return new DetachEntities(
259
                $nativeEntity,
260
                $foreignEntity,
261
                $this,
262
                'save'
263
            );
264
        }
265
266
        return new AttachEntities(
267
            $nativeEntity,
268
            $foreignEntity,
269
            $this,
270
            'save'
271
        );
272
    }
273
274
    protected function relationWasChanged(EntityInterface $entity)
275
    {
276
        $changes = $entity->getChanges();
277
278
        return isset($changes[$this->name]) && $changes[$this->name];
279
    }
280
281
    protected function applyQueryCallback(Select $query)
282
    {
283
        $queryCallback = $this->getOption(RelationConfig::QUERY_CALLBACK);
284
        if ($queryCallback && is_callable($queryCallback)) {
285
            $query = $queryCallback($query);
286
        }
287
288
        return $query;
289
    }
290
291
    protected function applyForeignGuards(Select $query)
292
    {
293
        $guards = $this->getOption(RelationConfig::FOREIGN_GUARDS);
294
        if ($guards) {
295
            $query->setGuards($guards);
296
        }
297
298
        return $query;
299
    }
300
301
    protected function getJoinOnForSubselect()
302
    {
303
        return QueryHelper::joinCondition(
304
            $this->nativeMapper->getTableAlias(true),
305
            $this->getOption(RelationConfig::NATIVE_KEY),
306
            $this->name,
307
            $this->getOption(RelationConfig::FOREIGN_KEY)
308
        );
309
    }
310
311
    /**
312
     * @return Mapper
313
     */
314
    public function getNativeMapper(): Mapper
315
    {
316
        return $this->nativeMapper;
317
    }
318
319
    /**
320
     * @return Mapper
321
     */
322
    public function getForeignMapper(): Mapper
323
    {
324
        return $this->foreignMapper;
325
    }
326
}
327