ManyToManyLoader::configureQuery()   B
last analyzed

Complexity

Conditions 8
Paths 17

Size

Total Lines 72

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 72
rs 7.3664
c 0
b 0
f 0
cc 8
nc 17
nop 3

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Spiral, Core Components
4
 *
5
 * @author Wolfy-J
6
 */
7
8
namespace Spiral\ORM\Entities\Loaders;
9
10
use Spiral\Database\Builders\SelectQuery;
11
use Spiral\Database\Injections\Parameter;
12
use Spiral\ORM\Entities\Loaders\Traits\ConstrainTrait;
13
use Spiral\ORM\Entities\Loaders\Traits\WhereTrait;
14
use Spiral\ORM\Entities\Nodes\AbstractNode;
15
use Spiral\ORM\Entities\Nodes\PivotedNode;
16
use Spiral\ORM\ORMInterface;
17
use Spiral\ORM\Record;
18
use Spiral\ORM\RecordEntity;
19
20
/**
21
 * ManyToMany loader will not only load related data, but will include pivot table data into record
22
 * property "@pivot". Loader support WHERE conditions for both related data and pivot table.
23
 *
24
 * It's STRONGLY recommended to load many-to-many data using postload method. However relation still
25
 * can be used to filter query.
26
 */
27
class ManyToManyLoader extends RelationLoader
28
{
29
    use WhereTrait, ConstrainTrait;
30
31
    /**
32
     * When target role is null parent role to be used. Redefine this variable to revert behaviour
33
     * of ManyToMany relation.
34
     *
35
     * @see ManyToMorphedRelation
36
     * @var string|null
37
     */
38
    private $targetRole = null;
39
40
    /**
41
     * Default set of relation options. Child implementation might defined their of default options.
42
     *
43
     * @var array
44
     */
45
    protected $options = [
46
        'method'     => self::POSTLOAD,
47
        'minify'     => true,
48
        'alias'      => null,
49
        'pivotAlias' => null,
50
        'using'      => null,
51
        'where'      => null,
52
        'wherePivot' => null,
53
        'orderBy'    => [],
54
        'limit'      => 0
55
    ];
56
57
    /**
58
     * @param string                   $class
59
     * @param string                   $relation
60
     * @param array                    $schema
61
     * @param \Spiral\ORM\ORMInterface $orm
62
     * @param string|null              $targetRole
63
     */
64
    public function __construct(
65
        $class,
66
        $relation,
67
        array $schema,
68
        ORMInterface $orm,
69
        string $targetRole = null
70
    ) {
71
        parent::__construct($class, $relation, $schema, $orm);
72
        $this->targetRole = $targetRole;
73
74
        if (!empty($schema[RecordEntity::ORDER_BY])) {
75
            //Default sorting direction
76
            $this->options['orderBy'] = $schema[RecordEntity::ORDER_BY];
77
        }
78
    }
79
80
    /**
81
     * {@inheritdoc}
82
     *
83
     * Visibility up.
84
     */
85
    public function configureQuery(SelectQuery $query, bool $loadColumns = true, array $outerKeys = []): SelectQuery
86
    {
87
        if (!empty($this->options['using'])) {
88
            //Use pre-defined query
89
            return parent::configureQuery($query, $loadColumns, $outerKeys);
90
        }
91
92
        if ($this->isJoined()) {
93
            $query->join(
94
                $this->getMethod() == self::JOIN ? 'INNER' : 'LEFT',
95
                $this->pivotTable() . ' AS ' . $this->pivotAlias())
96
                ->on(
97
                    $this->pivotKey(Record::THOUGHT_INNER_KEY),
98
                    $this->parentKey(Record::INNER_KEY)
99
                );
100
        } else {
101
            $query->innerJoin(
102
                $this->pivotTable() . ' AS ' . $this->pivotAlias())
103
                ->on(
104
                    $this->pivotKey(Record::THOUGHT_OUTER_KEY),
105
                    $this->localKey(Record::OUTER_KEY)
106
                )->where(
107
                    $this->pivotKey(Record::THOUGHT_INNER_KEY),
108
                    new Parameter($outerKeys)
109
                );
110
111
            $this->configureWindow($query, $this->options['orderBy'], $this->options['limit']);
112
        }
113
114
        //When relation is joined we will use ON statements, when not - normal WHERE
115
        $whereTarget = $this->isJoined() ? 'onWhere' : 'where';
116
117
        //Pivot conditions specified in relation schema
118
        $this->setWhere(
119
            $query,
120
            $this->pivotAlias(),
121
            $whereTarget,
122
            $this->schema[Record::WHERE_PIVOT]
123
        );
124
125
        //Additional morphed conditions
126
        if (!empty($this->schema[Record::MORPH_KEY])) {
127
            $this->setWhere(
128
                $query,
129
                $this->pivotAlias(),
130
                'onWhere',
131
                [$this->pivotKey(Record::MORPH_KEY) => $this->targetRole()]
132
            );
133
        }
134
135
        //Pivot conditions specified by user
136
        $this->setWhere($query, $this->pivotAlias(), $whereTarget, $this->options['wherePivot']);
137
138
        if ($this->isJoined()) {
139
            //Actual data is always INNER join
140
            $query->join(
141
                $this->getMethod() == self::JOIN ? 'INNER' : 'LEFT',
142
                $this->getTable() . ' AS ' . $this->getAlias()
143
            )->on(
144
                $this->localKey(Record::OUTER_KEY),
145
                $this->pivotKey(Record::THOUGHT_OUTER_KEY)
146
            );
147
        }
148
149
        //Where conditions specified in relation definition
150
        $this->setWhere($query, $this->getAlias(), $whereTarget, $this->schema[Record::WHERE]);
151
152
        //User specified WHERE conditions
153
        $this->setWhere($query, $this->getAlias(), $whereTarget, $this->options['where']);
154
155
        return parent::configureQuery($query, $loadColumns);
156
    }
157
158
    /**
159
     * Set columns into SelectQuery.
160
     *
161
     * @param SelectQuery $query
162
     * @param bool        $minify    Minify column names (will work in case when query parsed in
163
     *                               FETCH_NUM mode).
164
     * @param string      $prefix    Prefix to be added for each column name.
165
     * @param bool        $overwrite When set to true existed columns will be removed.
166
     */
167
    protected function mountColumns(
168
        SelectQuery $query,
169
        bool $minify = false,
170
        string $prefix = '',
171
        bool $overwrite = false
172
    ) {
173
        //Pivot table source alias
174
        $alias = $this->pivotAlias();
175
176
        $columns = $overwrite ? [] : $query->getColumns();
177
        foreach ($this->pivotColumns() as $name) {
178
            $column = $name;
179
180
            if ($minify) {
181
                //Let's use column number instead of full name
182
                $column = 'p_c' . count($columns);
183
            }
184
185
            $columns[] = "{$alias}.{$name} AS {$prefix}{$column}";
186
        }
187
188
        //Updating column set
189
        $query->columns($columns);
190
191
        parent::mountColumns($query, $minify, $prefix, false);
192
    }
193
194
    /**
195
     * {@inheritdoc}
196
     */
197
    protected function initNode(): AbstractNode
198
    {
199
        $node = new PivotedNode(
200
            $this->schema[Record::RELATION_COLUMNS],
201
            $this->schema[Record::PIVOT_COLUMNS],
202
            $this->schema[Record::OUTER_KEY],
203
            $this->schema[Record::THOUGHT_INNER_KEY],
204
            $this->schema[Record::THOUGHT_OUTER_KEY]
205
        );
206
207
        return $node->asJoined($this->isJoined());
208
    }
209
210
    /**
211
     * Pivot table name.
212
     *
213
     * @return string
214
     */
215
    protected function pivotTable(): string
216
    {
217
        return $this->schema[Record::PIVOT_TABLE];
218
    }
219
220
    /**
221
     * Pivot table alias, depends on relation table alias.
222
     *
223
     * @return string
224
     */
225
    protected function pivotAlias(): string
226
    {
227
        if (!empty($this->options['pivotAlias'])) {
228
            return $this->options['pivotAlias'];
229
        }
230
231
        return $this->getAlias() . '_pivot';
232
    }
233
234
    /**
235
     * @return array
236
     */
237
    protected function pivotColumns(): array
238
    {
239
        return $this->schema[Record::PIVOT_COLUMNS];
240
    }
241
242
    /**
243
     * Key related to pivot table. Must include pivot table alias.
244
     *
245
     * @see pivotKey()
246
     *
247
     * @param string $key
248
     *
249
     * @return null|string
250
     */
251
    protected function pivotKey(string $key)
252
    {
253
        if (!isset($this->schema[$key])) {
254
            return null;
255
        }
256
257
        return $this->pivotAlias() . '.' . $this->schema[$key];
258
    }
259
260
    /**
261
     * Defined role to be used in morphed relations.
262
     *
263
     * @return string
264
     */
265
    private function targetRole(): string
266
    {
267
        return $this->targetRole ?? $this->orm->define(
268
                $this->parent->getClass(),
269
                ORMInterface::R_ROLE_NAME
270
            );
271
    }
272
}