Completed
Push — master ( a7b476...58257b )
by Adrian
01:51
created

Relation::applyForeignGuards()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 8
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\LazyValueLoader;
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
     * Checks if a native entity belongs and a foreign entity belong together according to this relation
83
     * It verifies if the attributes are properly linked
84
     *
85
     * @param EntityInterface $nativeEntity
86
     * @param EntityInterface $foreignEntity
87
     *
88
     * @return mixed
89
     */
90
    public function entitiesBelongTogether(EntityInterface $nativeEntity, EntityInterface $foreignEntity)
91
    {
92
        foreach ($this->keyPairs as $nativeCol => $foreignCol) {
93
            $nativeKeyValue  = $this->nativeMapper->getEntityAttribute($nativeEntity, $nativeCol);
94
            $foreignKeyValue = $this->foreignMapper->getEntityAttribute($foreignEntity, $foreignCol);
95
            // if both native and foreign key values are present (not unlinked entities) they must be the same
96
            // otherwise we assume that the entities can be linked together
97
            if ($nativeKeyValue && $foreignKeyValue && $nativeKeyValue != $foreignKeyValue) {
98
                return false;
99
            }
100
        }
101
102
        return true;
103
    }
104
105
    public function isEagerLoad()
106
    {
107
        return $this->options[RelationConfig::LOAD_STRATEGY] == RelationConfig::LOAD_EAGER;
108
    }
109
110
    public function isLazyLoad()
111
    {
112
        return $this->options[RelationConfig::LOAD_STRATEGY] == RelationConfig::LOAD_LAZY;
113
    }
114
115
    public function isCascade()
116
    {
117
        return $this->options[RelationConfig::CASCADE] === true;
118
    }
119
120
    protected function getKeyColumn($name, $column)
121
    {
122
        if (is_array($column)) {
123
            $keyColumn = [];
124
            foreach ($column as $col) {
125
                $keyColumn[] = $name . '_' . $col;
126
            }
127
128
            return $keyColumn;
129
        }
130
131
        return $name . '_' . $column;
132
    }
133
134
    public function addActions(BaseAction $action)
135
    {
136
        if (! $this->cascadeIsAllowedForAction($action)) {
137
            return;
138
        }
139
140
        if ($action instanceof Delete) {
141
            $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

141
            $this->/** @scrutinizer ignore-call */ 
142
                   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...
142
        } 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...
143
            $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

143
            $this->/** @scrutinizer ignore-call */ 
144
                   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...
144
        }
145
    }
146
147
    abstract public function attachMatchesToEntity(EntityInterface $nativeEntity, array $queryResult);
148
149
    abstract public function detachEntities(EntityInterface $nativeEntity, EntityInterface $foreignEntity);
150
151
    abstract public function joinSubselect(Query $query, string $reference);
152
153
    public function attachLazyValueToEntity(EntityInterface $entity, Tracker $tracker)
154
    {
155
        $valueLoader = new LazyValueLoader($entity, $tracker, $this);
156
        $this->nativeMapper->setEntityAttribute($entity, $this->name, $valueLoader);
157
    }
158
159
    public function getQuery(Tracker $tracker)
160
    {
161
        $nativeKey = $this->options[RelationConfig::NATIVE_KEY];
162
        $nativePks = $tracker->pluck($nativeKey);
163
164
        $query = $this->foreignMapper
165
            ->newQuery()
166
            ->where($this->foreignMapper->getPrimaryKey(), $nativePks);
167
168
        $query = $this->applyQueryCallback($query);
169
170
        $query = $this->applyForeignGuards($query);
171
172
        return $query;
173
    }
174
175
    /**
176
     * Method used by `entitiesBelongTogether` to check
177
     * if a foreign entity belongs to the native entity
178
     * @return array
179
     */
180
    protected function computeKeyPairs()
181
    {
182
        $pairs      = [];
183
        $nativeKey  = (array)$this->options[RelationConfig::NATIVE_KEY];
184
        $foreignKey = (array)$this->options[RelationConfig::FOREIGN_KEY];
185
        foreach ($nativeKey as $k => $v) {
186
            $pairs[$v] = $foreignKey[$k];
187
        }
188
189
        return $pairs;
190
    }
191
192
    /**
193
     * @param BaseAction $action
194
     *
195
     * @return bool|mixed|null
196
     * @see BaseAction::$options
197
     */
198
    protected function cascadeIsAllowedForAction(BaseAction $action)
199
    {
200
        $relations = $action->getOption('relations');
201
        if (is_array($relations) && ! in_array($this->name, $relations)) {
202
            return false;
203
        }
204
205
        return $relations;
206
    }
207
208
    /**
209
     * Computes the $withRelations value to be passed on to the next related entities
210
     * If an entity receives on delete/save $withRelations = ['category', 'category.images']
211
     * the related 'category' is saved with $withRelations = ['images']
212
     *
213
     * @param $relations
214
     *
215
     * @return array
216
     */
217
    protected function getRemainingRelations($relations)
218
    {
219
        if (! is_array($relations)) {
220
            return $relations;
221
        }
222
223
        $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

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