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 HydratorFactory 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 HydratorFactory, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
40 | class HydratorFactory |
||
41 | { |
||
42 | /** |
||
43 | * The DocumentManager this factory is bound to. |
||
44 | * |
||
45 | * @var \Doctrine\ODM\MongoDB\DocumentManager |
||
46 | */ |
||
47 | private $dm; |
||
48 | |||
49 | /** |
||
50 | * The UnitOfWork used to coordinate object-level transactions. |
||
51 | * |
||
52 | * @var \Doctrine\ODM\MongoDB\UnitOfWork |
||
53 | */ |
||
54 | private $unitOfWork; |
||
55 | |||
56 | /** |
||
57 | * The EventManager associated with this Hydrator |
||
58 | * |
||
59 | * @var \Doctrine\Common\EventManager |
||
60 | */ |
||
61 | private $evm; |
||
62 | |||
63 | /** |
||
64 | * Which algorithm to use to automatically (re)generate hydrator classes. |
||
65 | * |
||
66 | * @var integer |
||
67 | */ |
||
68 | private $autoGenerate; |
||
69 | |||
70 | /** |
||
71 | * The namespace that contains all hydrator classes. |
||
72 | * |
||
73 | * @var string |
||
74 | */ |
||
75 | private $hydratorNamespace; |
||
76 | |||
77 | /** |
||
78 | * The directory that contains all hydrator classes. |
||
79 | * |
||
80 | * @var string |
||
81 | */ |
||
82 | private $hydratorDir; |
||
83 | |||
84 | /** |
||
85 | * Array of instantiated document hydrators. |
||
86 | * |
||
87 | * @var array |
||
88 | */ |
||
89 | private $hydrators = array(); |
||
90 | |||
91 | /** |
||
92 | * @param DocumentManager $dm |
||
93 | * @param EventManager $evm |
||
94 | * @param string $hydratorDir |
||
95 | * @param string $hydratorNs |
||
96 | * @param integer $autoGenerate |
||
97 | * @throws HydratorException |
||
98 | */ |
||
99 | 929 | public function __construct(DocumentManager $dm, EventManager $evm, $hydratorDir, $hydratorNs, $autoGenerate) |
|
113 | |||
114 | /** |
||
115 | * Sets the UnitOfWork instance. |
||
116 | * |
||
117 | * @param UnitOfWork $uow |
||
118 | */ |
||
119 | 929 | public function setUnitOfWork(UnitOfWork $uow) |
|
123 | |||
124 | /** |
||
125 | * Gets the hydrator object for the given document class. |
||
126 | * |
||
127 | * @param string $className |
||
128 | * @return \Doctrine\ODM\MongoDB\Hydrator\HydratorInterface $hydrator |
||
129 | */ |
||
130 | 380 | public function getHydratorFor($className) |
|
166 | |||
167 | /** |
||
168 | * Generates hydrator classes for all given classes. |
||
169 | * |
||
170 | * @param array $classes The classes (ClassMetadata instances) for which to generate hydrators. |
||
171 | * @param string $toDir The target directory of the hydrator classes. If not specified, the |
||
172 | * directory configured on the Configuration of the DocumentManager used |
||
173 | * by this factory is used. |
||
174 | */ |
||
175 | public function generateHydratorClasses(array $classes, $toDir = null) |
||
185 | |||
186 | /** |
||
187 | * @param ClassMetadata $class |
||
188 | * @param string $hydratorClassName |
||
189 | * @param string $fileName |
||
190 | */ |
||
191 | 168 | private function generateHydratorClass(ClassMetadata $class, $hydratorClassName, $fileName) |
|
192 | { |
||
193 | 168 | $code = ''; |
|
194 | |||
195 | 168 | foreach ($class->fieldMappings as $fieldName => $mapping) { |
|
196 | 168 | if (isset($mapping['alsoLoadFields'])) { |
|
197 | 4 | foreach ($mapping['alsoLoadFields'] as $name) { |
|
198 | 4 | $code .= sprintf(<<<EOF |
|
199 | |||
200 | /** @AlsoLoad("$name") */ |
||
201 | 4 | if (!array_key_exists('%1\$s', \$data) && array_key_exists('$name', \$data)) { |
|
202 | 4 | \$data['%1\$s'] = \$data['$name']; |
|
203 | } |
||
204 | |||
205 | EOF |
||
206 | 4 | , |
|
207 | 4 | $mapping['name'] |
|
208 | 4 | ); |
|
209 | 4 | } |
|
210 | 4 | } |
|
211 | |||
212 | 168 | if ($mapping['type'] === 'date') { |
|
213 | 9 | $code .= sprintf(<<<EOF |
|
214 | |||
215 | /** @Field(type="date") */ |
||
216 | if (isset(\$data['%1\$s'])) { |
||
217 | \$value = \$data['%1\$s']; |
||
218 | %3\$s |
||
219 | \$this->class->reflFields['%2\$s']->setValue(\$document, clone \$return); |
||
220 | \$hydratedData['%2\$s'] = \$return; |
||
221 | } |
||
222 | |||
223 | EOF |
||
224 | 9 | , |
|
225 | 9 | $mapping['name'], |
|
226 | 9 | $mapping['fieldName'], |
|
227 | 9 | Type::getType($mapping['type'])->closureToPHP() |
|
228 | 9 | ); |
|
229 | |||
230 | |||
231 | 168 | } elseif ( ! isset($mapping['association'])) { |
|
232 | 168 | $code .= sprintf(<<<EOF |
|
233 | |||
234 | 168 | /** @Field(type="{$mapping['type']}") */ |
|
235 | if (isset(\$data['%1\$s'])) { |
||
236 | \$value = \$data['%1\$s']; |
||
237 | %3\$s |
||
238 | \$this->class->reflFields['%2\$s']->setValue(\$document, \$return); |
||
239 | \$hydratedData['%2\$s'] = \$return; |
||
240 | } |
||
241 | |||
242 | EOF |
||
243 | 168 | , |
|
244 | 168 | $mapping['name'], |
|
245 | 168 | $mapping['fieldName'], |
|
246 | 168 | Type::getType($mapping['type'])->closureToPHP() |
|
247 | 168 | ); |
|
248 | 168 | View Code Duplication | } elseif ($mapping['association'] === ClassMetadata::REFERENCE_ONE && $mapping['isOwningSide']) { |
249 | 46 | $code .= sprintf(<<<EOF |
|
250 | |||
251 | /** @ReferenceOne */ |
||
252 | if (isset(\$data['%1\$s'])) { |
||
253 | \$reference = \$data['%1\$s']; |
||
254 | if (isset(\$this->class->fieldMappings['%2\$s']['simple']) && \$this->class->fieldMappings['%2\$s']['simple']) { |
||
255 | \$className = \$this->class->fieldMappings['%2\$s']['targetDocument']; |
||
256 | \$mongoId = \$reference; |
||
257 | } else { |
||
258 | \$className = \$this->unitOfWork->getClassNameForAssociation(\$this->class->fieldMappings['%2\$s'], \$reference); |
||
259 | \$mongoId = \$reference['\$id']; |
||
260 | } |
||
261 | \$targetMetadata = \$this->dm->getClassMetadata(\$className); |
||
262 | \$id = \$targetMetadata->getPHPIdentifierValue(\$mongoId); |
||
263 | \$return = \$this->dm->getReference(\$className, \$id); |
||
264 | \$this->class->reflFields['%2\$s']->setValue(\$document, \$return); |
||
265 | \$hydratedData['%2\$s'] = \$return; |
||
266 | } |
||
267 | |||
268 | EOF |
||
269 | 46 | , |
|
270 | 46 | $mapping['name'], |
|
271 | 46 | $mapping['fieldName'] |
|
272 | 46 | ); |
|
273 | 111 | } elseif ($mapping['association'] === ClassMetadata::REFERENCE_ONE && $mapping['isInverseSide']) { |
|
274 | 6 | if (isset($mapping['repositoryMethod']) && $mapping['repositoryMethod']) { |
|
275 | 1 | $code .= sprintf(<<<EOF |
|
276 | |||
277 | \$className = \$this->class->fieldMappings['%2\$s']['targetDocument']; |
||
278 | \$return = \$this->dm->getRepository(\$className)->%3\$s(\$document); |
||
279 | \$this->class->reflFields['%2\$s']->setValue(\$document, \$return); |
||
280 | \$hydratedData['%2\$s'] = \$return; |
||
281 | |||
282 | EOF |
||
283 | 1 | , |
|
284 | 1 | $mapping['name'], |
|
285 | 1 | $mapping['fieldName'], |
|
286 | 1 | $mapping['repositoryMethod'] |
|
287 | 1 | ); |
|
288 | 1 | } else { |
|
289 | 6 | $code .= sprintf(<<<EOF |
|
290 | |||
291 | \$mapping = \$this->class->fieldMappings['%2\$s']; |
||
292 | \$className = \$mapping['targetDocument']; |
||
293 | \$targetClass = \$this->dm->getClassMetadata(\$mapping['targetDocument']); |
||
294 | \$mappedByMapping = \$targetClass->fieldMappings[\$mapping['mappedBy']]; |
||
295 | \$mappedByFieldName = isset(\$mappedByMapping['simple']) && \$mappedByMapping['simple'] ? \$mapping['mappedBy'] : \$mapping['mappedBy'].'.\$id'; |
||
296 | \$criteria = array_merge( |
||
297 | array(\$mappedByFieldName => \$data['_id']), |
||
298 | isset(\$this->class->fieldMappings['%2\$s']['criteria']) ? \$this->class->fieldMappings['%2\$s']['criteria'] : array() |
||
299 | ); |
||
300 | \$sort = isset(\$this->class->fieldMappings['%2\$s']['sort']) ? \$this->class->fieldMappings['%2\$s']['sort'] : array(); |
||
301 | \$return = \$this->unitOfWork->getDocumentPersister(\$className)->load(\$criteria, null, array(), 0, \$sort); |
||
302 | \$this->class->reflFields['%2\$s']->setValue(\$document, \$return); |
||
303 | \$hydratedData['%2\$s'] = \$return; |
||
304 | |||
305 | EOF |
||
306 | 6 | , |
|
307 | 6 | $mapping['name'], |
|
308 | 6 | $mapping['fieldName'] |
|
309 | 6 | ); |
|
310 | } |
||
311 | 92 | View Code Duplication | } elseif ($mapping['association'] === ClassMetadata::REFERENCE_MANY || $mapping['association'] === ClassMetadata::EMBED_MANY) { |
312 | 71 | $code .= sprintf(<<<EOF |
|
313 | |||
314 | /** @Many */ |
||
315 | \$mongoData = isset(\$data['%1\$s']) ? \$data['%1\$s'] : null; |
||
316 | \$return = \$this->unitOfWork->getPersistentCollectionFactory()->create(\$this->class->fieldMappings['%2\$s']); |
||
317 | \$return->setHints(\$hints); |
||
318 | \$return->setOwner(\$document, \$this->class->fieldMappings['%2\$s']); |
||
319 | \$return->setInitialized(false); |
||
320 | if (\$mongoData) { |
||
321 | \$return->setMongoData(\$mongoData); |
||
322 | } |
||
323 | \$this->class->reflFields['%2\$s']->setValue(\$document, \$return); |
||
324 | \$hydratedData['%2\$s'] = \$return; |
||
325 | |||
326 | EOF |
||
327 | 71 | , |
|
328 | 71 | $mapping['name'], |
|
329 | 71 | $mapping['fieldName'] |
|
330 | 71 | ); |
|
331 | 91 | } elseif ($mapping['association'] === ClassMetadata::EMBED_ONE) { |
|
332 | 40 | $code .= sprintf(<<<EOF |
|
333 | |||
334 | /** @EmbedOne */ |
||
335 | if (isset(\$data['%1\$s'])) { |
||
336 | \$embeddedDocument = \$data['%1\$s']; |
||
337 | \$className = \$this->unitOfWork->getClassNameForAssociation(\$this->class->fieldMappings['%2\$s'], \$embeddedDocument); |
||
338 | \$embeddedMetadata = \$this->dm->getClassMetadata(\$className); |
||
339 | \$return = \$embeddedMetadata->newInstance(); |
||
340 | |||
341 | \$this->unitOfWork->setParentAssociation(\$return, \$this->class->fieldMappings['%2\$s'], \$document, '%1\$s'); |
||
342 | |||
343 | \$embeddedData = \$this->dm->getHydratorFactory()->hydrate(\$return, \$embeddedDocument, \$hints); |
||
344 | \$embeddedId = \$embeddedMetadata->identifier && isset(\$embeddedData[\$embeddedMetadata->identifier]) ? \$embeddedData[\$embeddedMetadata->identifier] : null; |
||
345 | |||
346 | \$this->unitOfWork->registerManaged(\$return, \$embeddedId, \$embeddedData); |
||
347 | |||
348 | \$this->class->reflFields['%2\$s']->setValue(\$document, \$return); |
||
349 | \$hydratedData['%2\$s'] = \$return; |
||
350 | } |
||
351 | |||
352 | EOF |
||
353 | 40 | , |
|
354 | 40 | $mapping['name'], |
|
355 | 40 | $mapping['fieldName'] |
|
356 | 40 | ); |
|
357 | 40 | } |
|
358 | 168 | } |
|
359 | |||
360 | 168 | $namespace = $this->hydratorNamespace; |
|
361 | 168 | $code = sprintf(<<<EOF |
|
362 | <?php |
||
363 | |||
364 | namespace $namespace; |
||
365 | |||
366 | use Doctrine\ODM\MongoDB\DocumentManager; |
||
367 | use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; |
||
368 | use Doctrine\ODM\MongoDB\Hydrator\HydratorInterface; |
||
369 | use Doctrine\ODM\MongoDB\UnitOfWork; |
||
370 | |||
371 | /** |
||
372 | * THIS CLASS WAS GENERATED BY THE DOCTRINE ODM. DO NOT EDIT THIS FILE. |
||
373 | */ |
||
374 | 168 | class $hydratorClassName implements HydratorInterface |
|
375 | { |
||
376 | private \$dm; |
||
377 | private \$unitOfWork; |
||
378 | private \$class; |
||
379 | |||
380 | public function __construct(DocumentManager \$dm, UnitOfWork \$uow, ClassMetadata \$class) |
||
381 | { |
||
382 | \$this->dm = \$dm; |
||
383 | \$this->unitOfWork = \$uow; |
||
384 | \$this->class = \$class; |
||
385 | } |
||
386 | |||
387 | public function hydrate(\$document, \$data, array \$hints = array()) |
||
388 | { |
||
389 | \$hydratedData = array(); |
||
390 | %s return \$hydratedData; |
||
391 | } |
||
392 | 168 | } |
|
393 | EOF |
||
394 | 168 | , |
|
395 | $code |
||
396 | 168 | ); |
|
397 | |||
398 | 168 | if ($fileName === false) { |
|
399 | if ( ! class_exists($namespace . '\\' . $hydratorClassName)) { |
||
400 | eval(substr($code, 5)); |
||
401 | } |
||
402 | View Code Duplication | } else { |
|
403 | 168 | $parentDirectory = dirname($fileName); |
|
404 | |||
405 | 168 | if ( ! is_dir($parentDirectory) && (false === @mkdir($parentDirectory, 0775, true))) { |
|
406 | throw HydratorException::hydratorDirectoryNotWritable(); |
||
407 | } |
||
408 | |||
409 | 168 | if ( ! is_writable($parentDirectory)) { |
|
410 | throw HydratorException::hydratorDirectoryNotWritable(); |
||
411 | } |
||
412 | |||
413 | 168 | $tmpFileName = $fileName . '.' . uniqid('', true); |
|
414 | 168 | file_put_contents($tmpFileName, $code); |
|
415 | 168 | rename($tmpFileName, $fileName); |
|
416 | 168 | chmod($fileName, 0664); |
|
417 | } |
||
418 | 168 | } |
|
419 | |||
420 | /** |
||
421 | * Hydrate array of MongoDB document data into the given document object. |
||
422 | * |
||
423 | * @param object $document The document object to hydrate the data into. |
||
424 | * @param array $data The array of document data. |
||
425 | * @param array $hints Any hints to account for during reconstitution/lookup of the document. |
||
426 | * @return array $values The array of hydrated values. |
||
427 | */ |
||
428 | 380 | public function hydrate($document, $data, array $hints = array()) |
|
468 | } |
||
469 |
It seems like the type of the argument is not accepted by the function/method which you are calling.
In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.
We suggest to add an explicit type cast like in the following example: