Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like ResultSetMappingBuilder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use ResultSetMappingBuilder, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
33 | class ResultSetMappingBuilder extends ResultSetMapping |
||
34 | { |
||
35 | /** |
||
36 | * Picking this rename mode will register entity columns as is, |
||
37 | * as they are in the database. This can cause clashes when multiple |
||
38 | * entities are fetched that have columns with the same name. |
||
39 | * |
||
40 | * @var int |
||
41 | */ |
||
42 | const COLUMN_RENAMING_NONE = 1; |
||
43 | |||
44 | /** |
||
45 | * Picking custom renaming allows the user to define the renaming |
||
46 | * of specific columns with a rename array that contains column names as |
||
47 | * keys and result alias as values. |
||
48 | * |
||
49 | * @var int |
||
50 | */ |
||
51 | const COLUMN_RENAMING_CUSTOM = 2; |
||
52 | |||
53 | /** |
||
54 | * Incremental renaming uses a result set mapping internal counter to add a |
||
55 | * number to each column result, leading to uniqueness. This only works if |
||
56 | * you use {@see generateSelectClause()} to generate the SELECT clause for |
||
57 | * you. |
||
58 | * |
||
59 | * @var int |
||
60 | */ |
||
61 | const COLUMN_RENAMING_INCREMENT = 3; |
||
62 | |||
63 | /** |
||
64 | * @var int |
||
65 | */ |
||
66 | private $sqlCounter = 0; |
||
67 | |||
68 | /** |
||
69 | * @var EntityManagerInterface |
||
70 | */ |
||
71 | private $em; |
||
72 | |||
73 | /** |
||
74 | * Default column renaming mode. |
||
75 | * |
||
76 | * @var int |
||
77 | */ |
||
78 | private $defaultRenameMode; |
||
79 | |||
80 | /** |
||
81 | * @param EntityManagerInterface $em |
||
82 | * @param integer $defaultRenameMode |
||
83 | */ |
||
84 | 58 | public function __construct(EntityManagerInterface $em, $defaultRenameMode = self::COLUMN_RENAMING_NONE) |
|
89 | |||
90 | /** |
||
91 | * Adds a root entity and all of its fields to the result set. |
||
92 | * |
||
93 | * @param string $class The class name of the root entity. |
||
94 | * @param string $alias The unique alias to use for the root entity. |
||
95 | * @param array $renamedColumns Columns that have been renamed (tableColumnName => queryColumnName). |
||
96 | * @param int|null $renameMode One of the COLUMN_RENAMING_* constants or array for BC reasons (CUSTOM). |
||
97 | * |
||
98 | * @return void |
||
99 | */ |
||
100 | 39 | View Code Duplication | public function addRootEntityFromClassMetadata($class, $alias, $renamedColumns = [], $renameMode = null) |
101 | { |
||
102 | 39 | $renameMode = $renameMode ?: $this->defaultRenameMode; |
|
103 | 39 | $columnAliasMap = $this->getColumnAliasMap($class, $renameMode, $renamedColumns); |
|
104 | |||
105 | 39 | $this->addEntityResult($class, $alias); |
|
106 | 39 | $this->addAllClassFields($class, $alias, $columnAliasMap); |
|
107 | 38 | } |
|
108 | |||
109 | /** |
||
110 | * Adds a joined entity and all of its fields to the result set. |
||
111 | * |
||
112 | * @param string $class The class name of the joined entity. |
||
113 | * @param string $alias The unique alias to use for the joined entity. |
||
114 | * @param string $parentAlias The alias of the entity result that is the parent of this joined result. |
||
115 | * @param string $relation The association field that connects the parent entity result |
||
116 | * with the joined entity result. |
||
117 | * @param array $renamedColumns Columns that have been renamed (tableColumnName => queryColumnName). |
||
118 | * @param int|null $renameMode One of the COLUMN_RENAMING_* constants or array for BC reasons (CUSTOM). |
||
119 | * |
||
120 | * @return void |
||
121 | */ |
||
122 | 12 | View Code Duplication | public function addJoinedEntityFromClassMetadata($class, $alias, $parentAlias, $relation, $renamedColumns = [], $renameMode = null) |
123 | { |
||
124 | 12 | $renameMode = $renameMode ?: $this->defaultRenameMode; |
|
125 | 12 | $columnAliasMap = $this->getColumnAliasMap($class, $renameMode, $renamedColumns); |
|
126 | |||
127 | 12 | $this->addJoinedEntityResult($class, $alias, $parentAlias, $relation); |
|
128 | 12 | $this->addAllClassFields($class, $alias, $columnAliasMap); |
|
129 | 11 | } |
|
130 | |||
131 | /** |
||
132 | * Adds all fields of the given class to the result set mapping (columns and meta fields). |
||
133 | * |
||
134 | * @param string $class |
||
135 | * @param string $alias |
||
136 | * @param array $columnAliasMap |
||
137 | * |
||
138 | * @return void |
||
139 | * |
||
140 | * @throws \InvalidArgumentException |
||
141 | */ |
||
142 | 39 | protected function addAllClassFields($class, $alias, $columnAliasMap = []) |
|
143 | { |
||
144 | 39 | $classMetadata = $this->em->getClassMetadata($class); |
|
145 | 39 | $platform = $this->em->getConnection()->getDatabasePlatform(); |
|
146 | |||
147 | 39 | if ( ! $this->isInheritanceSupported($classMetadata)) { |
|
|
|||
148 | 1 | throw new \InvalidArgumentException('ResultSetMapping builder does not currently support your inheritance scheme.'); |
|
149 | } |
||
150 | |||
151 | |||
152 | 38 | foreach ($classMetadata->getColumnNames() as $columnName) { |
|
153 | 38 | $propertyName = $classMetadata->getFieldName($columnName); |
|
154 | 38 | $columnAlias = $platform->getSQLResultCasing($columnAliasMap[$columnName]); |
|
155 | |||
156 | 38 | if (isset($this->fieldMappings[$columnAlias])) { |
|
157 | 1 | throw new \InvalidArgumentException("The column '$columnName' conflicts with another column in the mapper."); |
|
158 | } |
||
159 | |||
160 | 38 | $this->addFieldResult($alias, $columnAlias, $propertyName); |
|
161 | } |
||
162 | |||
163 | 38 | foreach ($classMetadata->associationMappings as $associationMapping) { |
|
164 | 28 | if ($associationMapping['isOwningSide'] && $associationMapping['type'] & ClassMetadataInfo::TO_ONE) { |
|
165 | 22 | $targetClass = $this->em->getClassMetadata($associationMapping['targetEntity']); |
|
166 | 22 | $isIdentifier = isset($associationMapping['id']) && $associationMapping['id'] === true; |
|
167 | |||
168 | 22 | foreach ($associationMapping['joinColumns'] as $joinColumn) { |
|
169 | 22 | $columnName = $joinColumn['name']; |
|
170 | 22 | $columnAlias = $platform->getSQLResultCasing($columnAliasMap[$columnName]); |
|
171 | 22 | $columnType = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em); |
|
172 | |||
173 | 22 | if (isset($this->metaMappings[$columnAlias])) { |
|
174 | throw new \InvalidArgumentException("The column '$columnAlias' conflicts with another column in the mapper."); |
||
175 | } |
||
176 | |||
177 | 28 | $this->addMetaResult($alias, $columnAlias, $columnName, $isIdentifier, $columnType); |
|
178 | } |
||
179 | } |
||
180 | } |
||
181 | 38 | } |
|
182 | |||
183 | 39 | private function isInheritanceSupported(ClassMetadataInfo $classMetadata) |
|
192 | |||
193 | /** |
||
194 | * Gets column alias for a given column. |
||
195 | * |
||
196 | * @param string $columnName |
||
197 | * @param int $mode |
||
198 | * @param array $customRenameColumns |
||
199 | * |
||
200 | * @return string |
||
201 | */ |
||
202 | 39 | private function getColumnAlias($columnName, $mode, array $customRenameColumns) |
|
203 | { |
||
204 | switch ($mode) { |
||
205 | 39 | case self::COLUMN_RENAMING_INCREMENT: |
|
206 | 3 | return $columnName . $this->sqlCounter++; |
|
207 | |||
208 | 36 | case self::COLUMN_RENAMING_CUSTOM: |
|
209 | 11 | return $customRenameColumns[$columnName] ?? $columnName; |
|
210 | |||
211 | 35 | case self::COLUMN_RENAMING_NONE: |
|
212 | 35 | return $columnName; |
|
213 | |||
214 | } |
||
215 | } |
||
216 | |||
217 | /** |
||
218 | * Retrieves a class columns and join columns aliases that are used in the SELECT clause. |
||
219 | * |
||
220 | * This depends on the renaming mode selected by the user. |
||
221 | * |
||
222 | * @param string $className |
||
223 | * @param int $mode |
||
224 | * @param array $customRenameColumns |
||
225 | * |
||
226 | * @return array |
||
227 | */ |
||
228 | 39 | private function getColumnAliasMap($className, $mode, array $customRenameColumns) |
|
229 | { |
||
230 | 39 | if ($customRenameColumns) { // for BC with 2.2-2.3 API |
|
231 | 11 | $mode = self::COLUMN_RENAMING_CUSTOM; |
|
232 | } |
||
233 | |||
234 | 39 | $columnAlias = []; |
|
235 | 39 | $class = $this->em->getClassMetadata($className); |
|
236 | |||
237 | 39 | foreach ($class->getColumnNames() as $columnName) { |
|
238 | 39 | $columnAlias[$columnName] = $this->getColumnAlias($columnName, $mode, $customRenameColumns); |
|
239 | } |
||
240 | |||
241 | 39 | foreach ($class->associationMappings as $associationMapping) { |
|
242 | 29 | if ($associationMapping['isOwningSide'] && $associationMapping['type'] & ClassMetadataInfo::TO_ONE) { |
|
243 | 23 | foreach ($associationMapping['joinColumns'] as $joinColumn) { |
|
244 | 23 | $columnName = $joinColumn['name']; |
|
245 | 29 | $columnAlias[$columnName] = $this->getColumnAlias($columnName, $mode, $customRenameColumns); |
|
246 | } |
||
247 | } |
||
248 | } |
||
249 | |||
250 | 39 | return $columnAlias; |
|
251 | } |
||
252 | |||
253 | /** |
||
254 | * Adds the mappings of the results of native SQL queries to the result set. |
||
255 | * |
||
256 | * @param ClassMetadataInfo $class |
||
257 | * @param array $queryMapping |
||
258 | * |
||
259 | * @return ResultSetMappingBuilder |
||
260 | */ |
||
261 | 10 | public function addNamedNativeQueryMapping(ClassMetadataInfo $class, array $queryMapping) |
|
269 | |||
270 | /** |
||
271 | * Adds the class mapping of the results of native SQL queries to the result set. |
||
272 | * |
||
273 | * @param ClassMetadataInfo $class |
||
274 | * @param string $resultClassName |
||
275 | * |
||
276 | * @return ResultSetMappingBuilder |
||
277 | */ |
||
278 | 3 | public function addNamedNativeQueryResultClassMapping(ClassMetadataInfo $class, $resultClassName) |
|
314 | |||
315 | /** |
||
316 | * Adds the result set mapping of the results of native SQL queries to the result set. |
||
317 | * |
||
318 | * @param ClassMetadataInfo $class |
||
319 | * @param string $resultSetMappingName |
||
320 | * |
||
321 | * @return ResultSetMappingBuilder |
||
322 | */ |
||
323 | 8 | public function addNamedNativeQueryResultSetMapping(ClassMetadataInfo $class, $resultSetMappingName) |
|
365 | |||
366 | /** |
||
367 | * Adds the entity result mapping of the results of native SQL queries to the result set. |
||
368 | * |
||
369 | * @param ClassMetadataInfo $classMetadata |
||
370 | * @param array $entityMapping |
||
371 | * @param string $alias |
||
372 | * |
||
373 | * @return ResultSetMappingBuilder |
||
374 | * |
||
375 | * @throws MappingException |
||
376 | * @throws \InvalidArgumentException |
||
377 | */ |
||
378 | 8 | public function addNamedNativeQueryEntityResultMapping(ClassMetadataInfo $classMetadata, array $entityMapping, $alias) |
|
427 | |||
428 | /** |
||
429 | * Generates the Select clause from this ResultSetMappingBuilder. |
||
430 | * |
||
431 | * Works only for all the entity results. The select parts for scalar |
||
432 | * expressions have to be written manually. |
||
433 | * |
||
434 | * @param array $tableAliases |
||
435 | * |
||
436 | * @return string |
||
437 | */ |
||
438 | 13 | public function generateSelectClause($tableAliases = []) |
|
465 | |||
466 | /** |
||
467 | * @return string |
||
468 | */ |
||
469 | 1 | public function __toString() |
|
473 | } |
||
474 |
This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.
Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.