|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace RulerZ\Executor\DoctrineQueryBuilder; |
|
4
|
|
|
|
|
5
|
|
|
use Doctrine\ORM\QueryBuilder; |
|
6
|
|
|
use Doctrine\ORM\EntityManager; |
|
7
|
|
|
|
|
8
|
|
|
class AutoJoin |
|
9
|
|
|
{ |
|
10
|
|
|
const ALIAS_PREFIX = 'rulerz_'; |
|
11
|
|
|
|
|
12
|
|
|
/** |
|
13
|
|
|
* List of root and association entity embeddables |
|
14
|
|
|
* |
|
15
|
|
|
* @var array |
|
16
|
|
|
*/ |
|
17
|
|
|
private $embeddables = null; |
|
18
|
|
|
|
|
19
|
|
|
/** |
|
20
|
|
|
* Associative list of known aliases (selected or joined tables). |
|
21
|
|
|
* |
|
22
|
|
|
* @var array |
|
23
|
|
|
*/ |
|
24
|
|
|
private $knownAliases = []; |
|
25
|
|
|
|
|
26
|
|
|
/** |
|
27
|
|
|
* Associative list of joined tables and their alias. |
|
28
|
|
|
* |
|
29
|
|
|
* @var array |
|
30
|
|
|
*/ |
|
31
|
|
|
private $joinMap = null; |
|
32
|
|
|
|
|
33
|
|
|
/** |
|
34
|
|
|
* @var QueryBuilder |
|
35
|
|
|
*/ |
|
36
|
|
|
private $queryBuilder; |
|
37
|
|
|
|
|
38
|
|
|
/** |
|
39
|
|
|
* @var array |
|
40
|
|
|
*/ |
|
41
|
|
|
private $expectedJoinChains = []; |
|
42
|
|
|
|
|
43
|
|
|
public function __construct(QueryBuilder $queryBuilder, array $expectedJoinChains) |
|
44
|
|
|
{ |
|
45
|
|
|
$this->queryBuilder = $queryBuilder; |
|
46
|
|
|
$this->expectedJoinChains = $expectedJoinChains; |
|
47
|
|
|
} |
|
48
|
|
|
|
|
49
|
|
|
public function getJoinAlias($table) |
|
50
|
|
|
{ |
|
51
|
|
|
if ($this->embeddables === null) { |
|
52
|
|
|
$this->embeddables = $this->analizeEmbeddables($this->queryBuilder); |
|
53
|
|
|
} |
|
54
|
|
|
|
|
55
|
|
|
if ($this->joinMap === null) { |
|
56
|
|
|
$this->joinMap = $this->analizeJoinedTables($this->queryBuilder); |
|
57
|
|
|
$this->knownAliases = array_flip($this->queryBuilder->getRootAliases()) + array_flip($this->joinMap); |
|
58
|
|
|
|
|
59
|
|
|
$this->autoJoin($this->queryBuilder); |
|
60
|
|
|
} |
|
61
|
|
|
|
|
62
|
|
|
// the table is identified as an embeddable |
|
63
|
|
|
if (array_search($table, $this->embeddables) !== false) { |
|
64
|
|
|
$embeddable_dimensions = explode('.', $table); |
|
65
|
|
|
|
|
66
|
|
|
if (count($embeddable_dimensions) === 1) { |
|
67
|
|
|
// the embeddable is not inside an association, so we use the root alias prefix |
|
68
|
|
|
return $this->queryBuilder->getRootAlias() . '.' . $table; |
|
|
|
|
|
|
69
|
|
|
} |
|
70
|
|
|
else { |
|
71
|
|
|
// remove the embeddable's property |
|
72
|
|
|
array_pop($embeddable_dimensions); |
|
73
|
|
|
$table_alias = implode('.', $embeddable_dimensions); |
|
74
|
|
|
|
|
75
|
|
|
if (isset($this->knownAliases[$table_alias])) { |
|
76
|
|
|
return $table; |
|
77
|
|
|
} else { |
|
78
|
|
|
return self::ALIAS_PREFIX . $table; |
|
79
|
|
|
} |
|
80
|
|
|
} |
|
81
|
|
|
} |
|
82
|
|
|
|
|
83
|
|
|
// the table name is a known alias (already join for instance) so we |
|
84
|
|
|
// don't need to do anything. |
|
85
|
|
|
if (isset($this->knownAliases[$table])) { |
|
86
|
|
|
return $table; |
|
87
|
|
|
} |
|
88
|
|
|
|
|
89
|
|
|
// otherwise the table should have automatically been joined, so we use our table prefix |
|
90
|
|
|
if (isset($this->knownAliases[self::ALIAS_PREFIX.$table])) { |
|
91
|
|
|
return self::ALIAS_PREFIX . $table; |
|
92
|
|
|
} |
|
93
|
|
|
|
|
94
|
|
|
throw new \RuntimeException(sprintf('Could not automatically join table "%s"', $table)); |
|
95
|
|
|
} |
|
96
|
|
|
|
|
97
|
|
|
private function traverseAssociationsForEmbeddables(EntityManager $entityManager, array $associations, $fieldNamePrefix = false) |
|
98
|
|
|
{ |
|
99
|
|
|
$associationsEmbeddables = array(); |
|
100
|
|
|
|
|
101
|
|
|
foreach ($associations as $association) { |
|
102
|
|
|
$classMetaData = $entityManager->getClassMetadata($association['targetEntity']); |
|
103
|
|
|
|
|
104
|
|
|
foreach ($classMetaData->embeddedClasses as $embeddedClassKey => $embeddedClass) { |
|
105
|
|
|
$associationsEmbeddables[] = implode('.', array_filter(array($fieldNamePrefix, $association['fieldName'], $embeddedClassKey))); |
|
106
|
|
|
} |
|
107
|
|
|
|
|
108
|
|
|
$associationMappings = $classMetaData->getAssociationMappings(); |
|
109
|
|
|
$associationMappings = array_filter($associationMappings, function($associationMapping) { |
|
110
|
|
|
return $associationMapping['isOwningSide'] === false; |
|
111
|
|
|
}); |
|
112
|
|
|
|
|
113
|
|
|
if (count($associationMappings) !== 0) { |
|
114
|
|
|
$traversedAssociationsEmbeddables = $this->traverseAssociationsForEmbeddables($entityManager, $associationMappings, $association['fieldName']); |
|
115
|
|
|
$associationsEmbeddables = array_merge($associationsEmbeddables, $traversedAssociationsEmbeddables); |
|
116
|
|
|
} |
|
117
|
|
|
} |
|
118
|
|
|
|
|
119
|
|
|
return $associationsEmbeddables; |
|
120
|
|
|
} |
|
121
|
|
|
|
|
122
|
|
|
private function analizeEmbeddables(QueryBuilder $queryBuilder) |
|
123
|
|
|
{ |
|
124
|
|
|
$embeddables = array(); |
|
125
|
|
|
$entityManager = $queryBuilder->getEntityManager(); |
|
126
|
|
|
$rootEntities = $queryBuilder->getRootEntities(); |
|
127
|
|
|
|
|
128
|
|
|
foreach ($rootEntities as $rootEntity) { |
|
129
|
|
|
$classMetaData = $entityManager->getClassMetadata($rootEntity); |
|
130
|
|
|
|
|
131
|
|
|
foreach ($classMetaData->embeddedClasses as $embeddedClassKey => $embeddedClass) { |
|
132
|
|
|
$embeddables[] = $embeddedClassKey; |
|
133
|
|
|
} |
|
134
|
|
|
|
|
135
|
|
|
$traversedAssociationsEmbeddables = $this->traverseAssociationsForEmbeddables($entityManager, $classMetaData->getAssociationMappings()); |
|
136
|
|
|
$embeddables = array_merge($embeddables, $traversedAssociationsEmbeddables); |
|
137
|
|
|
} |
|
138
|
|
|
|
|
139
|
|
|
return $embeddables; |
|
140
|
|
|
} |
|
141
|
|
|
|
|
142
|
|
|
/** |
|
143
|
|
|
* Builds an associative array of already joined tables and their alias. |
|
144
|
|
|
* |
|
145
|
|
|
* @param QueryBuilder $queryBuilder |
|
146
|
|
|
* |
|
147
|
|
|
* @return array |
|
148
|
|
|
*/ |
|
149
|
|
|
private function analizeJoinedTables(QueryBuilder $queryBuilder) |
|
150
|
|
|
{ |
|
151
|
|
|
$joinMap = []; |
|
152
|
|
|
$joins = $queryBuilder->getDQLPart('join'); |
|
153
|
|
|
foreach (array_keys($joins) as $fromTable) { |
|
154
|
|
|
foreach ($joins[$fromTable] as $join) { |
|
155
|
|
|
$joinMap[$join->getJoin()] = $join->getAlias(); |
|
156
|
|
|
} |
|
157
|
|
|
} |
|
158
|
|
|
return $joinMap; |
|
159
|
|
|
} |
|
160
|
|
|
|
|
161
|
|
|
private function autoJoin(QueryBuilder $queryBuilder) |
|
162
|
|
|
{ |
|
163
|
|
|
foreach ($this->expectedJoinChains as $tablesToJoin) { |
|
164
|
|
|
// if the table is an embeddable, the property needs to be removed |
|
165
|
|
|
if (array_search(implode('.', $tablesToJoin), $this->embeddables) !== false) { |
|
166
|
|
|
array_pop($tablesToJoin); |
|
167
|
|
|
} |
|
168
|
|
|
|
|
169
|
|
|
// check if the first dimension is a known alias |
|
170
|
|
|
if (isset($this->knownAliases[$tablesToJoin[0]])) { |
|
171
|
|
|
$joinTo = $tablesToJoin[0]; |
|
172
|
|
|
array_pop($tablesToJoin); |
|
173
|
|
|
} else { // if not, it's the root table |
|
174
|
|
|
$joinTo = $queryBuilder->getRootAliases()[0]; |
|
175
|
|
|
} |
|
176
|
|
|
|
|
177
|
|
|
foreach ($tablesToJoin as $table) { |
|
178
|
|
|
$joinAlias = self::ALIAS_PREFIX . $table; |
|
179
|
|
|
$join = sprintf('%s.%s', $joinTo, $table); |
|
180
|
|
|
|
|
181
|
|
|
if (!isset($this->joinMap[$join])) { |
|
182
|
|
|
$this->joinMap[$join] = $joinAlias; |
|
183
|
|
|
$this->knownAliases[$joinAlias] = true; |
|
184
|
|
|
|
|
185
|
|
|
$queryBuilder->join(sprintf('%s.%s', $joinTo, $table), $joinAlias); |
|
186
|
|
|
} else { |
|
187
|
|
|
$joinAlias = $this->joinMap[$join]; |
|
188
|
|
|
} |
|
189
|
|
|
|
|
190
|
|
|
$joinTo = $joinAlias; |
|
191
|
|
|
} |
|
192
|
|
|
} |
|
193
|
|
|
} |
|
194
|
|
|
} |
|
195
|
|
|
|
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.