1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* components |
4
|
|
|
* |
5
|
|
|
* @author Wolfy-J |
6
|
|
|
*/ |
7
|
|
|
namespace Spiral\ORM\Entities\Loaders; |
8
|
|
|
|
9
|
|
|
use Spiral\Database\Builders\SelectQuery; |
10
|
|
|
use Spiral\Database\Injections\Parameter; |
11
|
|
|
use Spiral\ORM\Entities\Loaders\Traits\WhereTrait; |
12
|
|
|
use Spiral\ORM\Entities\Nodes\AbstractNode; |
13
|
|
|
use Spiral\ORM\Entities\Nodes\PivotedNode; |
14
|
|
|
use Spiral\ORM\Record; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* ManyToMany loader will not only load related data, but will include pivot table data into record |
18
|
|
|
* property "@pivot". Loader support WHERE conditions for both related data and pivot table. |
19
|
|
|
* |
20
|
|
|
* It's STRONGLY recommended to load many-to-many data using postload method. However relation still |
21
|
|
|
* can be used to filter query. |
22
|
|
|
*/ |
23
|
|
|
class ManyToManyLoader extends RelationLoader |
24
|
|
|
{ |
25
|
|
|
use WhereTrait; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* Default set of relation options. Child implementation might defined their of default options. |
29
|
|
|
* |
30
|
|
|
* @var array |
31
|
|
|
*/ |
32
|
|
|
protected $options = [ |
33
|
|
|
'method' => self::POSTLOAD, |
34
|
|
|
'minify' => true, |
35
|
|
|
'alias' => null, |
36
|
|
|
'pivotAlias' => null, |
37
|
|
|
'using' => null, |
38
|
|
|
'where' => null, |
39
|
|
|
]; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* {@inheritdoc} |
43
|
|
|
*/ |
44
|
|
|
protected function configureQuery(SelectQuery $query, array $outerKeys = []): SelectQuery |
45
|
|
|
{ |
46
|
|
|
if ($this->isJoined()) { |
47
|
|
|
$query->join( |
48
|
|
|
$this->getMethod() == self::JOIN ? 'INNER' : 'LEFT', |
49
|
|
|
$this->pivotTable() . ' AS ' . $this->pivotAlias(), |
50
|
|
|
[$this->pivotKey(Record::THOUGHT_INNER_KEY) => $this->parentKey(Record::INNER_KEY)] |
51
|
|
|
); |
52
|
|
|
} else { |
53
|
|
|
//TODO: outer keys |
54
|
|
|
$query->join( |
55
|
|
|
$this->getMethod() == self::JOIN ? 'INNER' : 'LEFT', |
56
|
|
|
$this->pivotTable() . ' AS ' . $this->pivotAlias() |
57
|
|
|
)->onWhere( |
58
|
|
|
$this->pivotKey(Record::THOUGHT_INNER_KEY), |
59
|
|
|
new Parameter($outerKeys) |
60
|
|
|
); |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
//pivot where |
64
|
|
|
|
65
|
|
|
// $pivotOuterKey = $this->getPivotKey(RecordEntity::THOUGHT_OUTER_KEY); |
|
|
|
|
66
|
|
|
if ($this->isJoined()) { |
67
|
|
|
$query->join( |
68
|
|
|
$this->getMethod() == self::JOIN ? 'INNER' : 'LEFT', |
69
|
|
|
$this->getTable() . ' AS ' . $this->getAlias(), |
70
|
|
|
[$this->pivotKey(Record::THOUGHT_OUTER_KEY) => $this->localKey(Record::OUTER_KEY)] |
71
|
|
|
); |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
//normal where |
75
|
|
|
|
76
|
|
|
return parent::configureQuery($query); |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* Set columns into SelectQuery. |
81
|
|
|
* |
82
|
|
|
* @param SelectQuery $query |
83
|
|
|
* @param bool $minify Minify column names (will work in case when query parsed in |
84
|
|
|
* FETCH_NUM mode). |
85
|
|
|
* @param string $prefix Prefix to be added for each column name. |
86
|
|
|
* @param bool $overwrite When set to true existed columns will be removed. |
87
|
|
|
*/ |
88
|
|
|
protected function mountColumns( |
89
|
|
|
SelectQuery $query, |
90
|
|
|
bool $minify = false, |
91
|
|
|
string $prefix = '', |
92
|
|
|
bool $overwrite = false |
93
|
|
|
) { |
94
|
|
|
/* |
95
|
|
|
* Configuring pivot columns. |
96
|
|
|
*/ |
97
|
|
|
//Column source alias |
98
|
|
|
$alias = $this->pivotAlias(); |
99
|
|
|
|
100
|
|
|
$columns = $overwrite ? [] : $query->getColumns(); |
101
|
|
|
foreach ($this->pivotColumns() as $name) { |
102
|
|
|
$column = $name; |
103
|
|
|
|
104
|
|
|
if ($minify) { |
105
|
|
|
//Let's use column number instead of full name |
106
|
|
|
$column = 'p_c' . count($columns); |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
$columns[] = "{$alias}.{$name} AS {$prefix}{$column}"; |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
//Updating column set |
113
|
|
|
$query->columns($columns); |
114
|
|
|
|
115
|
|
|
parent::mountColumns($query, $minify, $prefix, $overwrite); |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* {@inheritdoc} |
120
|
|
|
*/ |
121
|
|
|
protected function initNode(): AbstractNode |
122
|
|
|
{ |
123
|
|
|
$node = new PivotedNode( |
124
|
|
|
$this->schema[Record::RELATION_COLUMNS], |
125
|
|
|
$this->schema[Record::PIVOT_COLUMNS], |
126
|
|
|
$this->schema[Record::OUTER_KEY], |
127
|
|
|
$this->schema[Record::THOUGHT_INNER_KEY], |
128
|
|
|
$this->schema[Record::THOUGHT_OUTER_KEY] |
129
|
|
|
); |
130
|
|
|
|
131
|
|
|
return $node->asJoined($this->isJoined()); |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* Pivot table name. |
136
|
|
|
* |
137
|
|
|
* @return string |
138
|
|
|
*/ |
139
|
|
|
protected function pivotTable(): string |
140
|
|
|
{ |
141
|
|
|
return $this->schema[Record::PIVOT_TABLE]; |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
/** |
145
|
|
|
* Pivot table alias, depends on relation table alias. |
146
|
|
|
* |
147
|
|
|
* @return string |
148
|
|
|
*/ |
149
|
|
|
protected function pivotAlias(): string |
150
|
|
|
{ |
151
|
|
|
if (!empty($this->options['pivotAlias'])) { |
152
|
|
|
return $this->options['pivotAlias']; |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
return $this->getAlias() . '_pivot'; |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
/** |
159
|
|
|
* @return array |
160
|
|
|
*/ |
161
|
|
|
protected function pivotColumns(): array |
162
|
|
|
{ |
163
|
|
|
return $this->schema[Record::PIVOT_COLUMNS]; |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
/** |
167
|
|
|
* Key related to pivot table. Must include pivot table alias. |
168
|
|
|
* |
169
|
|
|
* @see pivotKey() |
170
|
|
|
* |
171
|
|
|
* @param string $key |
172
|
|
|
* |
173
|
|
|
* @return null|string |
174
|
|
|
*/ |
175
|
|
|
protected function pivotKey(string $key) |
176
|
|
|
{ |
177
|
|
|
if (!isset($this->schema[$key])) { |
178
|
|
|
return null; |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
return $this->pivotAlias() . '.' . $this->schema[$key]; |
182
|
|
|
} |
183
|
|
|
} |
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.