Passed
Push — dev_2x ( fa57a5...f00e1a )
by Adrian
01:48
created

ManyToMany::applyPivotGuards()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
c 0
b 0
f 0
dl 0
loc 8
rs 10
cc 2
nc 2
nop 1
1
<?php
2
3
namespace Sirius\Orm\Relation;
4
5
use Sirius\Orm\Action\BaseAction;
6
use Sirius\Orm\Action\DeletePivotRows;
7
use Sirius\Orm\Collection\Collection;
8
use Sirius\Orm\Contract\EntityInterface;
9
use Sirius\Orm\Contract\Relation\ToManyInterface;
10
use Sirius\Orm\Entity\Tracker;
11
use Sirius\Orm\Helpers\Inflector;
12
use Sirius\Orm\Helpers\QueryHelper;
13
use Sirius\Orm\Query;
14
15
class ManyToMany extends Relation implements ToManyInterface
16
{
17
    use HasAggregates;
18
19
    protected function applyDefaults(): void
20
    {
21
        parent::applyDefaults();
22
23
        $foreignKey = $this->foreignMapper->getConfig()->getPrimaryKey();
24
        if (! isset($this->options[RelationConfig::FOREIGN_KEY])) {
25
            $this->options[RelationConfig::FOREIGN_KEY] = $foreignKey;
26
        }
27
28
        $nativeKey = $this->foreignMapper->getConfig()->getPrimaryKey();
29
        if (! isset($this->options[RelationConfig::NATIVE_KEY])) {
30
            $this->options[RelationConfig::NATIVE_KEY] = $nativeKey;
31
        }
32
33
        if (! isset($this->options[RelationConfig::PIVOT_TABLE])) {
34
            $tables = [$this->foreignMapper->getConfig()->getTable(), $this->nativeMapper->getConfig()->getTable()];
35
            sort($tables);
36
            $this->options[RelationConfig::PIVOT_TABLE] = implode('_', $tables);
37
        }
38
39
        if (! isset($this->options[RelationConfig::PIVOT_NATIVE_COLUMN])) {
40
            $prefix = Inflector::singularize($this->nativeMapper->getConfig()->getTableAlias(true));
41
42
            $this->options[RelationConfig::PIVOT_NATIVE_COLUMN] = $this->getKeyColumn($prefix, $nativeKey);
43
        }
44
45
        if (! isset($this->options[RelationConfig::PIVOT_FOREIGN_COLUMN])) {
46
            $prefix = Inflector::singularize($this->foreignMapper->getConfig()->getTableAlias(true));
47
48
            $this->options[RelationConfig::PIVOT_FOREIGN_COLUMN] = $this->getKeyColumn($prefix, $foreignKey);
49
        }
50
    }
51
52
    public function getQuery(Tracker $tracker)
53
    {
54
        $nativeKey = $this->options[RelationConfig::NATIVE_KEY];
55
        $nativePks = $tracker->pluck($nativeKey, $this->nativeEntityHydrator);
56
57
        if (empty($nativePks)) {
58
            return null;
59
        }
60
61
        $query = $this->foreignMapper->newQuery();
62
63
        $query = $this->joinWithPivotTable($query)
64
                      ->where($this->options[RelationConfig::PIVOT_NATIVE_COLUMN], $nativePks);
65
66
        $query = $this->applyQueryCallback($query);
67
68
        $query = $this->applyForeignGuards($query);
69
70
        $query = $this->applyPivotGuards($query);
71
72
        $query = $this->addPivotColumns($query);
73
74
        return $query;
75
    }
76
77
    protected function joinWithPivotTable($query)
78
    {
79
        $pivotTable      = $this->getOption(RelationConfig::PIVOT_TABLE);
80
        $pivotTableAlias = $this->getOption(RelationConfig::PIVOT_TABLE_ALIAS);
81
        $pivotReference  = QueryHelper::reference($pivotTable, $pivotTableAlias);
82
        $pivotName       = $pivotTableAlias ?? $pivotTable;
83
84
        $pivotColumns     = $this->options[RelationConfig::PIVOT_FOREIGN_COLUMN];
85
        $foreignTableName = $this->foreignMapper->getConfig()->getTableAlias(true);
86
        $foreignKeys      = $this->options[RelationConfig::FOREIGN_KEY];
87
88
        $joinCondition = QueryHelper::joinCondition($foreignTableName, $foreignKeys, $pivotName, $pivotColumns);
89
90
        return $query->join('INNER', $pivotReference, $joinCondition);
91
    }
92
93
    private function addPivotColumns($query)
94
    {
95
        $pivotColumns = $this->getOption(RelationConfig::PIVOT_COLUMNS);
96
97
        $pivotTable      = $this->getOption(RelationConfig::PIVOT_TABLE);
98
        $pivotTableAlias = $this->getOption(RelationConfig::PIVOT_TABLE_ALIAS);
99
        $pivotName       = $pivotTableAlias ?? $pivotTable;
100
101
        if (! empty($pivotColumns)) {
102
            foreach ($pivotColumns as $col => $alias) {
103
                $query->columns("{$pivotName}.{$col} AS {$alias}");
104
            }
105
        }
106
107
        foreach ((array)$this->options[RelationConfig::PIVOT_NATIVE_COLUMN] as $col) {
108
            $query->columns("{$pivotName}.{$col}");
109
        }
110
111
        return $query;
112
    }
113
114
    public function joinSubselect(Query $query, string $reference)
115
    {
116
        $subselect = $this->foreignMapper->newQuery();
0 ignored issues
show
Unused Code introduced by
The assignment to $subselect is dead and can be removed.
Loading history...
117
        $subselect = $query->subSelectForJoinWith($this->foreignMapper)
118
                           ->as($reference);
119
        #$subselect->resetGuards();
120
        #$subselect->setGuards($this->foreignMapper->getConfig()->getGuards());
121
122
        $subselect = $this->joinWithPivotTable($subselect);
123
124
        $subselect = $this->addPivotColumns($subselect);
125
126
        $subselect = $this->applyQueryCallback($subselect);
127
128
        $subselect = $this->applyForeignGuards($subselect);
129
130
        return $query->join('INNER', $subselect->getStatement(), $this->getJoinOnForSubselect());
131
    }
132
133
    protected function getJoinOnForSubselect()
134
    {
135
        return QueryHelper::joinCondition(
136
            $this->nativeMapper->getConfig()->getTableAlias(true),
137
            $this->getOption(RelationConfig::NATIVE_KEY),
138
            $this->name,
139
            $this->getOption(RelationConfig::PIVOT_NATIVE_COLUMN)
140
        );
141
    }
142
143
    protected function computeKeyPairs()
144
    {
145
        $pairs      = [];
146
        $nativeKey  = (array)$this->options[RelationConfig::NATIVE_KEY];
147
        $foreignKey = (array)$this->options[RelationConfig::PIVOT_NATIVE_COLUMN];
148
        foreach ($nativeKey as $k => $v) {
149
            $pairs[$v] = $foreignKey[$k];
150
        }
151
152
        return $pairs;
153
    }
154
155
    public function attachMatchesToEntity(EntityInterface $nativeEntity, array $result)
156
    {
157
        $nativeId = $this->getEntityId($this->nativeMapper, $nativeEntity, array_keys($this->keyPairs));
158
159
        $found = $result[$nativeId] ?? [];
160
161
        $collection = $this->foreignMapper->newCollection($found);
162
        $this->nativeEntityHydrator->set($nativeEntity, $this->name, $collection);
163
    }
164
165
    public function attachEntities(EntityInterface $nativeEntity, EntityInterface $foreignEntity)
166
    {
167
        foreach ($this->keyPairs as $nativeCol => $foreignCol) {
168
            $nativeKeyValue = $this->nativeEntityHydrator->get($nativeEntity, $nativeCol);
169
            $this->foreignEntityHydrator->set($foreignEntity, $foreignCol, $nativeKeyValue);
170
        }
171
    }
172
173
    public function detachEntities(EntityInterface $nativeEntity, EntityInterface $foreignEntity)
174
    {
175
        foreach ($this->keyPairs as $nativeCol => $foreignCol) {
176
            $this->foreignEntityHydrator->set($foreignEntity, $foreignCol, null);
177
        }
178
    }
179
180
    protected function addActionOnDelete(BaseAction $action)
181
    {
182
        $nativeEntity = $action->getEntity();
183
184
        // retrieve them again from the DB since the related collection might not have everything
185
        // for example due to a relation query callback
186
        $foreignEntities = $this->getQuery(new Tracker([$nativeEntity->toArray()]))
187
                                ->get();
188
189
        foreach ($foreignEntities as $entity) {
190
            $deletePivotAction = new DeletePivotRows($this, $nativeEntity, $entity);
191
            $action->append($deletePivotAction);
192
        }
193
    }
194
195
    protected function addActionOnSave(BaseAction $action)
196
    {
197
        if (! $this->relationWasChanged($action->getEntity())) {
198
            return;
199
        }
200
201
        if (! $action->includesRelation($this->name)) {
202
            return;
203
        }
204
205
        $remainingRelations = $this->getRemainingRelations($action->getOption('relations'));
206
207
        /** @var Collection $foreignEntities */
208
        $foreignEntities = $this->nativeEntityHydrator->get($action->getEntity(), $this->name);
209
        if (! $foreignEntities || ! $foreignEntities instanceof Collection || $foreignEntities->isEmpty()) {
0 ignored issues
show
introduced by
$foreignEntities is of type Sirius\Orm\Collection\Collection, thus it always evaluated to true.
Loading history...
introduced by
$foreignEntities is always a sub-type of Sirius\Orm\Collection\Collection.
Loading history...
210
            return;
211
        }
212
213
        $changes = $foreignEntities->getChanges();
214
215
        // save the entities still in the collection
216
        foreach ($foreignEntities as $foreignEntity) {
217
            if (! empty($foreignEntity->getChanges())) {
218
                $saveAction = $this->foreignMapper
219
                    ->newSaveAction($foreignEntity, [
0 ignored issues
show
Bug introduced by
The method newSaveAction() does not exist on Sirius\Orm\Mapper. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

219
                /** @scrutinizer ignore-call */ 
220
                $saveAction = $this->foreignMapper
Loading history...
220
                        'relations' => $remainingRelations
221
                    ]);
222
                $saveAction->addColumns($this->getExtraColumnsForAction());
223
                $action->prepend($saveAction);
224
                $action->append($this->newSyncAction(
225
                    $action->getEntity(),
226
                    $foreignEntity,
227
                    'save'
228
                ));
229
            }
230
        }
231
232
        // save entities that were removed but NOT deleted
233
        foreach ($changes['removed'] as $foreignEntity) {
234
            $deletePivotAction = new DeletePivotRows($this, $action->getEntity(), $foreignEntity);
235
            $action->append($deletePivotAction);
236
        }
237
    }
238
239
    private function applyPivotGuards(Query $query)
240
    {
241
        $guards = $this->getOption(RelationConfig::PIVOT_GUARDS);
242
        if ($guards) {
243
            $query->setGuards($guards);
244
        }
245
246
        return $query;
247
    }
248
}
249