Completed
Push — master ( 667eb8...379582 )
by Anton
04:04
created

ManyToManyLoader::configureQuery()   C

Complexity

Conditions 8
Paths 17

Size

Total Lines 70
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 42
nc 17
nop 2
dl 0
loc 70
rs 6.4909
c 0
b 0
f 0

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