1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace Doctrine\ORM\Query; |
6
|
|
|
|
7
|
|
|
use Doctrine\DBAL\Types\Type; |
8
|
|
|
use Doctrine\ORM\EntityManagerInterface; |
9
|
|
|
use Doctrine\ORM\Mapping\AssociationMetadata; |
10
|
|
|
use Doctrine\ORM\Mapping\ClassMetadata; |
11
|
|
|
use Doctrine\ORM\Mapping\FieldMetadata; |
12
|
|
|
use Doctrine\ORM\Mapping\InheritanceType; |
13
|
|
|
use Doctrine\ORM\Mapping\JoinColumnMetadata; |
14
|
|
|
use Doctrine\ORM\Mapping\MappingException; |
15
|
|
|
use Doctrine\ORM\Mapping\ToOneAssociationMetadata; |
16
|
|
|
use Doctrine\ORM\Utility\PersisterHelper; |
17
|
|
|
use function explode; |
18
|
|
|
use function in_array; |
19
|
|
|
use function sprintf; |
20
|
|
|
use function strpos; |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* A ResultSetMappingBuilder uses the EntityManager to automatically populate entity fields. |
24
|
|
|
*/ |
25
|
|
|
class ResultSetMappingBuilder extends ResultSetMapping |
26
|
|
|
{ |
27
|
|
|
/** |
28
|
|
|
* Picking this rename mode will register entity columns as is, |
29
|
|
|
* as they are in the database. This can cause clashes when multiple |
30
|
|
|
* entities are fetched that have columns with the same name. |
31
|
|
|
*/ |
32
|
|
|
public const COLUMN_RENAMING_NONE = 1; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* Picking custom renaming allows the user to define the renaming |
36
|
|
|
* of specific columns with a rename array that contains column names as |
37
|
|
|
* keys and result alias as values. |
38
|
|
|
*/ |
39
|
|
|
public const COLUMN_RENAMING_CUSTOM = 2; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* Incremental renaming uses a result set mapping internal counter to add a |
43
|
|
|
* number to each column result, leading to uniqueness. This only works if |
44
|
|
|
* you use {@see generateSelectClause()} to generate the SELECT clause for |
45
|
|
|
* you. |
46
|
|
|
*/ |
47
|
|
|
public const COLUMN_RENAMING_INCREMENT = 3; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* @var int |
51
|
|
|
*/ |
52
|
|
|
private $sqlCounter = 0; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* @var EntityManagerInterface |
56
|
|
|
*/ |
57
|
|
|
private $em; |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* Default column renaming mode. |
61
|
|
|
* |
62
|
|
|
* @var int |
63
|
|
|
*/ |
64
|
|
|
private $defaultRenameMode; |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* @param int $defaultRenameMode |
68
|
|
|
*/ |
69
|
52 |
|
public function __construct(EntityManagerInterface $em, $defaultRenameMode = self::COLUMN_RENAMING_NONE) |
70
|
|
|
{ |
71
|
52 |
|
$this->em = $em; |
72
|
52 |
|
$this->defaultRenameMode = $defaultRenameMode; |
73
|
52 |
|
} |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* Adds a root entity and all of its fields to the result set. |
77
|
|
|
* |
78
|
|
|
* @param string $class The class name of the root entity. |
79
|
|
|
* @param string $alias The unique alias to use for the root entity. |
80
|
|
|
* @param string[] $renamedColumns Columns that have been renamed (tableColumnName => queryColumnName). |
81
|
|
|
* @param int|null $renameMode One of the COLUMN_RENAMING_* constants or array for BC reasons (CUSTOM). |
82
|
|
|
* |
83
|
|
|
*/ |
84
|
43 |
|
public function addRootEntityFromClassMetadata( |
85
|
|
|
string $class, |
86
|
|
|
string $alias, |
87
|
|
|
array $renamedColumns = [], |
88
|
|
|
?int $renameMode = null |
89
|
|
|
) { |
90
|
43 |
|
$renameMode = $renameMode ?: (empty($renamedColumns) ? $this->defaultRenameMode : self::COLUMN_RENAMING_CUSTOM); |
91
|
|
|
|
92
|
43 |
|
$this->addEntityResult($class, $alias); |
93
|
43 |
|
$this->addAllClassFields($class, $alias, $renamedColumns, $renameMode); |
94
|
42 |
|
} |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* Adds a joined entity and all of its fields to the result set. |
98
|
|
|
* |
99
|
|
|
* @param string $class The class name of the joined entity. |
100
|
|
|
* @param string $alias The unique alias to use for the joined entity. |
101
|
|
|
* @param string $parentAlias The alias of the entity result that is the parent of this joined result. |
102
|
|
|
* @param string $relation The association field that connects the parent entity result |
103
|
|
|
* with the joined entity result. |
104
|
|
|
* @param string[] $renamedColumns Columns that have been renamed (tableColumnName => queryColumnName). |
105
|
|
|
* @param int|null $renameMode One of the COLUMN_RENAMING_* constants or array for BC reasons (CUSTOM). |
106
|
|
|
* |
107
|
|
|
*/ |
108
|
11 |
|
public function addJoinedEntityFromClassMetadata( |
109
|
|
|
string $class, |
110
|
|
|
string $alias, |
111
|
|
|
string $parentAlias, |
112
|
|
|
string $relation, |
113
|
|
|
array $renamedColumns = [], |
114
|
|
|
?int $renameMode = null |
115
|
|
|
) { |
116
|
11 |
|
$renameMode = $renameMode ?: (empty($renamedColumns) ? $this->defaultRenameMode : self::COLUMN_RENAMING_CUSTOM); |
117
|
|
|
|
118
|
11 |
|
$this->addJoinedEntityResult($class, $alias, $parentAlias, $relation); |
119
|
11 |
|
$this->addAllClassFields($class, $alias, $renamedColumns, $renameMode); |
120
|
10 |
|
} |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* Adds all fields of the given class to the result set mapping (columns and meta fields). |
124
|
|
|
* |
125
|
|
|
* @param string[] $customRenameColumns |
126
|
|
|
* |
127
|
|
|
* @throws \InvalidArgumentException |
128
|
|
|
*/ |
129
|
43 |
|
protected function addAllClassFields(string $class, string $alias, array $customRenameColumns, int $renameMode) : void |
130
|
|
|
{ |
131
|
|
|
/** @var ClassMetadata $classMetadata */ |
132
|
43 |
|
$classMetadata = $this->em->getClassMetadata($class); |
133
|
43 |
|
$platform = $this->em->getConnection()->getDatabasePlatform(); |
134
|
|
|
|
135
|
43 |
|
if (! $this->isInheritanceSupported($classMetadata)) { |
136
|
1 |
|
throw new \InvalidArgumentException( |
137
|
1 |
|
'ResultSetMapping builder does not currently support your inheritance scheme.' |
138
|
|
|
); |
139
|
|
|
} |
140
|
|
|
|
141
|
42 |
|
foreach ($classMetadata->getDeclaredPropertiesIterator() as $property) { |
142
|
|
|
switch (true) { |
143
|
42 |
|
case ($property instanceof FieldMetadata): |
|
|
|
|
144
|
42 |
|
$columnName = $property->getColumnName(); |
145
|
42 |
|
$columnAlias = $platform->getSQLResultCasing( |
146
|
42 |
|
$this->getColumnAlias($columnName, $renameMode, $customRenameColumns) |
147
|
|
|
); |
148
|
|
|
|
149
|
42 |
|
if (isset($this->fieldMappings[$columnAlias])) { |
150
|
1 |
|
throw new \InvalidArgumentException( |
151
|
1 |
|
sprintf("The column '%s' conflicts with another column in the mapper.", $columnName) |
152
|
|
|
); |
153
|
|
|
} |
154
|
|
|
|
155
|
42 |
|
$this->addFieldResult($alias, $columnAlias, $property->getName()); |
156
|
42 |
|
break; |
157
|
|
|
|
158
|
32 |
|
case ($property instanceof ToOneAssociationMetadata && $property->isOwningSide()): |
159
|
26 |
|
$targetClass = $this->em->getClassMetadata($property->getTargetEntity()); |
|
|
|
|
160
|
|
|
|
161
|
26 |
|
foreach ($property->getJoinColumns() as $joinColumn) { |
|
|
|
|
162
|
|
|
/** @var JoinColumnMetadata $joinColumn */ |
163
|
26 |
|
$columnName = $joinColumn->getColumnName(); |
164
|
26 |
|
$referencedColumnName = $joinColumn->getReferencedColumnName(); |
165
|
26 |
|
$columnAlias = $platform->getSQLResultCasing( |
166
|
26 |
|
$this->getColumnAlias($columnName, $renameMode, $customRenameColumns) |
167
|
|
|
); |
168
|
|
|
|
169
|
26 |
|
if (isset($this->metaMappings[$columnAlias])) { |
170
|
|
|
throw new \InvalidArgumentException( |
171
|
|
|
sprintf("The column '%s' conflicts with another column in the mapper.", $columnName) |
172
|
|
|
); |
173
|
|
|
} |
174
|
|
|
|
175
|
26 |
|
if (! $joinColumn->getType()) { |
176
|
2 |
|
$joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em)); |
|
|
|
|
177
|
|
|
} |
178
|
|
|
|
179
|
26 |
|
$this->addMetaResult($alias, $columnAlias, $columnName, $property->isPrimaryKey(), $joinColumn->getType()); |
180
|
|
|
} |
181
|
42 |
|
break; |
182
|
|
|
} |
183
|
|
|
} |
184
|
42 |
|
} |
185
|
|
|
|
186
|
|
|
/** |
187
|
|
|
* Checks if inheritance if supported. |
188
|
|
|
* |
189
|
|
|
* @return bool |
190
|
|
|
*/ |
191
|
43 |
|
private function isInheritanceSupported(ClassMetadata $metadata) |
192
|
|
|
{ |
193
|
43 |
|
if ($metadata->inheritanceType === InheritanceType::SINGLE_TABLE |
194
|
43 |
|
&& in_array($metadata->getClassName(), $metadata->discriminatorMap, true)) { |
195
|
3 |
|
return true; |
196
|
|
|
} |
197
|
|
|
|
198
|
41 |
|
return ! in_array($metadata->inheritanceType, [InheritanceType::SINGLE_TABLE, InheritanceType::JOINED], true); |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* Gets column alias for a given column. |
203
|
|
|
* |
204
|
|
|
* @param string $columnName |
205
|
|
|
* @param int $mode |
206
|
|
|
* @param string[] $customRenameColumns |
207
|
|
|
* |
208
|
|
|
* @return string |
209
|
|
|
*/ |
210
|
42 |
|
private function getColumnAlias($columnName, $mode, array $customRenameColumns) |
211
|
|
|
{ |
212
|
|
|
switch ($mode) { |
213
|
42 |
|
case self::COLUMN_RENAMING_INCREMENT: |
214
|
3 |
|
return $columnName . $this->sqlCounter++; |
215
|
|
|
|
216
|
39 |
|
case self::COLUMN_RENAMING_CUSTOM: |
217
|
10 |
|
return $customRenameColumns[$columnName] ?? $columnName; |
218
|
|
|
|
219
|
38 |
|
case self::COLUMN_RENAMING_NONE: |
|
|
|
|
220
|
38 |
|
return $columnName; |
221
|
|
|
} |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
/** |
225
|
|
|
* Generates the Select clause from this ResultSetMappingBuilder. |
226
|
|
|
* |
227
|
|
|
* Works only for all the entity results. The select parts for scalar |
228
|
|
|
* expressions have to be written manually. |
229
|
|
|
* |
230
|
|
|
* @param string[] $tableAliases |
231
|
|
|
* |
232
|
|
|
* @return string |
233
|
|
|
*/ |
234
|
18 |
|
public function generateSelectClause($tableAliases = []) |
235
|
|
|
{ |
236
|
18 |
|
$sql = ''; |
237
|
|
|
|
238
|
18 |
|
foreach ($this->columnOwnerMap as $columnName => $dqlAlias) { |
239
|
18 |
|
$tableAlias = $tableAliases[$dqlAlias] ?? $dqlAlias; |
240
|
|
|
|
241
|
18 |
|
if ($sql) { |
242
|
18 |
|
$sql .= ', '; |
243
|
|
|
} |
244
|
|
|
|
245
|
18 |
|
$sql .= $tableAlias . '.'; |
246
|
|
|
|
247
|
18 |
|
if (isset($this->fieldMappings[$columnName])) { |
248
|
18 |
|
$class = $this->em->getClassMetadata($this->declaringClasses[$columnName]); |
249
|
18 |
|
$field = $this->fieldMappings[$columnName]; |
250
|
18 |
|
$sql .= $class->getProperty($field)->getColumnName(); |
|
|
|
|
251
|
12 |
|
} elseif (isset($this->metaMappings[$columnName])) { |
252
|
11 |
|
$sql .= $this->metaMappings[$columnName]; |
253
|
1 |
|
} elseif (isset($this->discriminatorColumns[$dqlAlias])) { |
254
|
1 |
|
$sql .= $this->discriminatorColumns[$dqlAlias]; |
255
|
|
|
} |
256
|
|
|
|
257
|
18 |
|
$sql .= ' AS ' . $columnName; |
258
|
|
|
} |
259
|
|
|
|
260
|
18 |
|
return $sql; |
261
|
|
|
} |
262
|
|
|
|
263
|
|
|
/** |
264
|
|
|
* @return string |
265
|
|
|
*/ |
266
|
1 |
|
public function __toString() |
267
|
|
|
{ |
268
|
1 |
|
return $this->generateSelectClause([]); |
269
|
|
|
} |
270
|
|
|
} |
271
|
|
|
|
As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next
break
.There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.
To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.