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 AbstractHydrator 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 AbstractHydrator, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
38 | abstract class AbstractHydrator |
||
39 | { |
||
40 | /** |
||
41 | * The ResultSetMapping. |
||
42 | * |
||
43 | * @var \Doctrine\ORM\Query\ResultSetMapping |
||
44 | */ |
||
45 | protected $_rsm; |
||
46 | |||
47 | /** |
||
48 | * The EntityManager instance. |
||
49 | * |
||
50 | * @var EntityManagerInterface |
||
51 | */ |
||
52 | protected $_em; |
||
53 | |||
54 | /** |
||
55 | * The dbms Platform instance. |
||
56 | * |
||
57 | * @var \Doctrine\DBAL\Platforms\AbstractPlatform |
||
58 | */ |
||
59 | protected $_platform; |
||
60 | |||
61 | /** |
||
62 | * The UnitOfWork of the associated EntityManager. |
||
63 | * |
||
64 | * @var \Doctrine\ORM\UnitOfWork |
||
65 | */ |
||
66 | protected $_uow; |
||
67 | |||
68 | /** |
||
69 | * Local ClassMetadata cache to avoid going to the EntityManager all the time. |
||
70 | * |
||
71 | * @var ClassMetadata[]|LazyPropertyMap indexed by class name |
||
72 | */ |
||
73 | protected $_metadataCache; |
||
74 | |||
75 | /** |
||
76 | * The cache used during row-by-row hydration. |
||
77 | * |
||
78 | * @var array |
||
79 | */ |
||
80 | protected $_cache = []; |
||
81 | |||
82 | /** |
||
83 | * The statement that provides the data to hydrate. |
||
84 | * |
||
85 | * @var \Doctrine\DBAL\Driver\Statement |
||
86 | */ |
||
87 | protected $_stmt; |
||
88 | |||
89 | /** |
||
90 | * The query hints. |
||
91 | * |
||
92 | * @var array |
||
93 | */ |
||
94 | protected $_hints; |
||
95 | |||
96 | /** |
||
97 | * Initializes a new instance of a class derived from <tt>AbstractHydrator</tt>. |
||
98 | * |
||
99 | * @param EntityManagerInterface $em The EntityManager to use. |
||
100 | 1020 | */ |
|
101 | public function __construct(EntityManagerInterface $em) |
||
102 | 1020 | { |
|
103 | 1020 | $this->_em = $em; |
|
104 | 1020 | $this->_platform = $em->getConnection()->getDatabasePlatform(); |
|
105 | 1020 | $this->_uow = $em->getUnitOfWork(); |
|
106 | $this->_metadataCache = new LazyPropertyMap([$em, 'getClassMetadata']); |
||
107 | } |
||
108 | |||
109 | /** |
||
110 | * Initiates a row-by-row hydration. |
||
111 | * |
||
112 | * @param object $stmt |
||
113 | * @param object $resultSetMapping |
||
114 | * @param array $hints |
||
115 | * |
||
116 | 12 | * @return IterableResult |
|
117 | */ |
||
118 | 12 | public function iterate($stmt, $resultSetMapping, array $hints = []) |
|
119 | 12 | { |
|
120 | 12 | $this->_stmt = $stmt; |
|
121 | $this->_rsm = $resultSetMapping; |
||
122 | 12 | $this->_hints = $hints; |
|
123 | |||
124 | 12 | $evm = $this->_em->getEventManager(); |
|
125 | |||
126 | 12 | $evm->addEventListener([Events::onClear], $this); |
|
127 | |||
128 | 12 | $this->prepare(); |
|
129 | |||
130 | return new IterableResult($this); |
||
131 | } |
||
132 | |||
133 | /** |
||
134 | * Hydrates all rows returned by the passed statement instance at once. |
||
135 | * |
||
136 | * @param object $stmt |
||
137 | * @param object $resultSetMapping |
||
138 | * @param array $hints |
||
139 | * |
||
140 | 1008 | * @return array |
|
141 | */ |
||
142 | 1008 | public function hydrateAll($stmt, $resultSetMapping, array $hints = []) |
|
143 | 1008 | { |
|
144 | 1008 | $this->_stmt = $stmt; |
|
145 | $this->_rsm = $resultSetMapping; |
||
146 | 1008 | $this->_hints = $hints; |
|
147 | |||
148 | 1008 | $this->_em->getEventManager()->addEventListener([Events::onClear], $this); |
|
149 | |||
150 | 1007 | $this->prepare(); |
|
151 | |||
152 | 997 | $result = $this->hydrateAllData(); |
|
153 | |||
154 | 997 | $this->cleanup(); |
|
155 | |||
156 | return $result; |
||
157 | } |
||
158 | |||
159 | /** |
||
160 | * Hydrates a single row returned by the current statement instance during |
||
161 | * row-by-row hydration with {@link iterate()}. |
||
162 | * |
||
163 | 11 | * @return mixed |
|
164 | */ |
||
165 | 11 | View Code Duplication | public function hydrateRow() |
166 | { |
||
167 | 11 | $row = $this->_stmt->fetch(PDO::FETCH_ASSOC); |
|
168 | 8 | ||
169 | if ( ! $row) { |
||
170 | 8 | $this->cleanup(); |
|
171 | |||
172 | return false; |
||
173 | 10 | } |
|
174 | |||
175 | 10 | $result = []; |
|
176 | |||
177 | 10 | $this->hydrateRowData($row, $result); |
|
178 | |||
179 | return $result; |
||
180 | } |
||
181 | |||
182 | /** |
||
183 | * When executed in a hydrate() loop we have to clear internal state to |
||
184 | * decrease memory consumption. |
||
185 | * |
||
186 | * @param mixed $eventArgs |
||
187 | * |
||
188 | 8 | * @return void |
|
189 | */ |
||
190 | 8 | public function onClear($eventArgs) |
|
191 | { |
||
192 | } |
||
193 | |||
194 | /** |
||
195 | * Executes one-time preparation tasks, once each time hydration is started |
||
196 | * through {@link hydrateAll} or {@link iterate()}. |
||
197 | * |
||
198 | 106 | * @return void |
|
199 | */ |
||
200 | 106 | protected function prepare() |
|
201 | { |
||
202 | } |
||
203 | |||
204 | /** |
||
205 | * Executes one-time cleanup tasks at the end of a hydration that was initiated |
||
206 | * through {@link hydrateAll} or {@link iterate()}. |
||
207 | * |
||
208 | 1005 | * @return void |
|
209 | */ |
||
210 | 1005 | protected function cleanup() |
|
211 | { |
||
212 | 1005 | $this->_stmt->closeCursor(); |
|
213 | 1005 | ||
214 | 1005 | $this->_stmt = null; |
|
215 | 1005 | $this->_rsm = null; |
|
216 | $this->_cache = []; |
||
217 | $this->_metadataCache = new LazyPropertyMap([$this->_em, 'getClassMetadata']); |
||
218 | 1005 | ||
219 | 1005 | $this |
|
220 | 1005 | ->_em |
|
221 | 1005 | ->getEventManager() |
|
222 | ->removeEventListener([Events::onClear], $this); |
||
223 | } |
||
224 | |||
225 | /** |
||
226 | * Hydrates a single row from the current statement instance. |
||
227 | * |
||
228 | * Template method. |
||
229 | * |
||
230 | * @param array $data The row data. |
||
231 | * @param array $result The result to fill. |
||
232 | * |
||
233 | * @return void |
||
234 | * |
||
235 | * @throws HydrationException |
||
236 | */ |
||
237 | protected function hydrateRowData(array $data, array &$result) |
||
238 | { |
||
239 | throw new HydrationException("hydrateRowData() not implemented by this hydrator."); |
||
240 | } |
||
241 | |||
242 | /** |
||
243 | * Hydrates all rows from the current statement instance at once. |
||
244 | * |
||
245 | * @return array |
||
246 | */ |
||
247 | abstract protected function hydrateAllData(); |
||
248 | |||
249 | /** |
||
250 | * Processes a row of the result set. |
||
251 | * |
||
252 | * Used for identity-based hydration (HYDRATE_OBJECT and HYDRATE_ARRAY). |
||
253 | * Puts the elements of a result row into a new array, grouped by the dql alias |
||
254 | * they belong to. The column names in the result set are mapped to their |
||
255 | * field names during this procedure as well as any necessary conversions on |
||
256 | * the values applied. Scalar values are kept in a specific key 'scalars'. |
||
257 | * |
||
258 | * @param array $data SQL Result Row. |
||
259 | * @param array &$id Dql-Alias => ID-Hash. |
||
260 | * @param array &$nonemptyComponents Does this DQL-Alias has at least one non NULL value? |
||
261 | * |
||
262 | * @return array An array with all the fields (name => value) of the data row, |
||
263 | 709 | * grouped by their component alias. |
|
264 | */ |
||
265 | 709 | protected function gatherRowData(array $data, array &$id, array &$nonemptyComponents) |
|
331 | |||
332 | /** |
||
333 | * Processes a row of the result set. |
||
334 | * |
||
335 | * Used for HYDRATE_SCALAR. This is a variant of _gatherRowData() that |
||
336 | * simply converts column names to field names and properly converts the |
||
337 | * values according to their types. The resulting row has the same number |
||
338 | * of elements as before. |
||
339 | * |
||
340 | * @param array $data |
||
341 | * |
||
342 | 98 | * @return array The processed row. |
|
343 | */ |
||
344 | 98 | protected function gatherScalarRowData(&$data) |
|
371 | |||
372 | /** |
||
373 | * Retrieve column information from ResultSetMapping. |
||
374 | * |
||
375 | * @param string $key Column name |
||
376 | * |
||
377 | 960 | * @return array|null |
|
378 | */ |
||
379 | 960 | protected function hydrateColumnInfo($key) |
|
459 | |||
460 | /** |
||
461 | * Register entity as managed in UnitOfWork. |
||
462 | * |
||
463 | * @param ClassMetadata $class |
||
464 | 920 | * @param object $entity |
|
465 | * @param array $data |
||
466 | 920 | * |
|
467 | 920 | * @return void |
|
468 | * |
||
469 | * @todo The "$id" generation is the same of UnitOfWork#createEntity. Remove this duplication somehow |
||
470 | 920 | */ |
|
471 | protected function registerManaged(ClassMetadata $class, $entity, array $data) |
||
492 | } |
||
493 |
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.