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 EntityManager 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 EntityManager, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
48 | final class EntityManager implements EntityManagerInterface |
||
49 | { |
||
50 | /** |
||
51 | * The used Configuration. |
||
52 | * |
||
53 | * @var \Doctrine\ORM\Configuration |
||
54 | */ |
||
55 | private $config; |
||
56 | |||
57 | /** |
||
58 | * The database connection used by the EntityManager. |
||
59 | * |
||
60 | * @var \Doctrine\DBAL\Connection |
||
61 | */ |
||
62 | private $conn; |
||
63 | |||
64 | /** |
||
65 | * The metadata factory, used to retrieve the ORM metadata of entity classes. |
||
66 | * |
||
67 | * @var \Doctrine\ORM\Mapping\ClassMetadataFactory |
||
68 | */ |
||
69 | private $metadataFactory; |
||
70 | |||
71 | /** |
||
72 | * The UnitOfWork used to coordinate object-level transactions. |
||
73 | * |
||
74 | * @var \Doctrine\ORM\UnitOfWork |
||
75 | */ |
||
76 | private $unitOfWork; |
||
77 | |||
78 | /** |
||
79 | * The event manager that is the central point of the event system. |
||
80 | * |
||
81 | * @var \Doctrine\Common\EventManager |
||
82 | */ |
||
83 | private $eventManager; |
||
84 | |||
85 | /** |
||
86 | * The proxy factory used to create dynamic proxies. |
||
87 | * |
||
88 | * @var \Doctrine\ORM\Proxy\Factory\ProxyFactory |
||
89 | */ |
||
90 | private $proxyFactory; |
||
91 | |||
92 | /** |
||
93 | * The repository factory used to create dynamic repositories. |
||
94 | * |
||
95 | * @var \Doctrine\ORM\Repository\RepositoryFactory |
||
96 | */ |
||
97 | private $repositoryFactory; |
||
98 | |||
99 | /** |
||
100 | * The expression builder instance used to generate query expressions. |
||
101 | * |
||
102 | * @var \Doctrine\ORM\Query\Expr |
||
103 | */ |
||
104 | private $expressionBuilder; |
||
105 | |||
106 | /** |
||
107 | * The IdentifierFlattener used for manipulating identifiers |
||
108 | * |
||
109 | * @var \Doctrine\ORM\Utility\IdentifierFlattener |
||
110 | */ |
||
111 | private $identifierFlattener; |
||
112 | |||
113 | /** |
||
114 | * Whether the EntityManager is closed or not. |
||
115 | * |
||
116 | * @var bool |
||
117 | */ |
||
118 | private $closed = false; |
||
119 | |||
120 | /** |
||
121 | * Collection of query filters. |
||
122 | * |
||
123 | * @var \Doctrine\ORM\Query\FilterCollection |
||
124 | */ |
||
125 | private $filterCollection; |
||
126 | |||
127 | /** |
||
128 | * @var \Doctrine\ORM\Cache The second level cache regions API. |
||
129 | */ |
||
130 | private $cache; |
||
131 | |||
132 | /** |
||
133 | * Creates a new EntityManager that operates on the given database connection |
||
134 | * and uses the given Configuration and EventManager implementations. |
||
135 | * |
||
136 | * @param \Doctrine\DBAL\Connection $conn |
||
137 | * @param \Doctrine\ORM\Configuration $config |
||
138 | * @param \Doctrine\Common\EventManager $eventManager |
||
139 | */ |
||
140 | protected function __construct(Connection $conn, Configuration $config, EventManager $eventManager) |
||
164 | 2290 | ||
165 | 2290 | /** |
|
166 | 2290 | * {@inheritDoc} |
|
167 | */ |
||
168 | public function getConnection() |
||
172 | 277 | ||
173 | /** |
||
174 | 2290 | * Gets the metadata factory used to gather the metadata of classes. |
|
175 | * |
||
176 | * @return \Doctrine\ORM\Mapping\ClassMetadataFactory |
||
177 | */ |
||
178 | public function getMetadataFactory() |
||
182 | |||
183 | /** |
||
184 | * {@inheritDoc} |
||
185 | */ |
||
186 | public function getExpressionBuilder() |
||
194 | |||
195 | /** |
||
196 | * @return IdentifierFlattener |
||
197 | 17 | */ |
|
198 | public function getIdentifierFlattener() : IdentifierFlattener |
||
202 | |||
203 | 17 | /** |
|
204 | * {@inheritDoc} |
||
205 | */ |
||
206 | public function beginTransaction() |
||
210 | |||
211 | 2 | /** |
|
212 | 2 | * {@inheritDoc} |
|
213 | */ |
||
214 | public function getCache() |
||
218 | |||
219 | 211 | /** |
|
220 | * {@inheritDoc} |
||
221 | */ |
||
222 | public function transactional(callable $func) |
||
240 | |||
241 | /** |
||
242 | * {@inheritDoc} |
||
243 | */ |
||
244 | public function commit() |
||
248 | |||
249 | /** |
||
250 | * {@inheritDoc} |
||
251 | 1 | */ |
|
252 | public function rollback() |
||
256 | |||
257 | /** |
||
258 | * Returns the ORM metadata descriptor for a class. |
||
259 | 1 | * |
|
260 | * The class name must be the fully-qualified class name without a leading backslash |
||
261 | 1 | * (as it is returned by get_class($obj)) or an aliased class name. |
|
262 | 1 | * |
|
263 | * Examples: |
||
264 | * MyProject\Domain\User |
||
265 | * sales:PriceRequest |
||
266 | * |
||
267 | * Internal note: Performance-sensitive method. |
||
268 | * |
||
269 | * @param string $className |
||
270 | * |
||
271 | * @return Mapping\ClassMetadata |
||
272 | */ |
||
273 | public function getClassMetadata($className) : Mapping\ClassMetadata |
||
277 | |||
278 | /** |
||
279 | * {@inheritDoc} |
||
280 | 1898 | */ |
|
281 | public function createQuery($dql = '') |
||
291 | |||
292 | 924 | /** |
|
293 | 919 | * {@inheritDoc} |
|
294 | */ |
||
295 | public function createNamedQuery($name) |
||
299 | |||
300 | /** |
||
301 | * {@inheritDoc} |
||
302 | 1 | */ |
|
303 | public function createNativeQuery($sql, ResultSetMapping $rsm) |
||
312 | 21 | ||
313 | /** |
||
314 | 21 | * {@inheritDoc} |
|
315 | 21 | */ |
|
316 | public function createNamedNativeQuery($name) |
||
322 | |||
323 | 1 | /** |
|
324 | * {@inheritDoc} |
||
325 | 1 | */ |
|
326 | public function createQueryBuilder() |
||
330 | |||
331 | /** |
||
332 | * {@inheritDoc} |
||
333 | 112 | * |
|
334 | * @deprecated |
||
335 | 112 | */ |
|
336 | public function merge($object) |
||
340 | |||
341 | /** |
||
342 | * {@inheritDoc} |
||
343 | * |
||
344 | * @deprecated |
||
345 | */ |
||
346 | public function detach($object) |
||
350 | |||
351 | /** |
||
352 | * Flushes all changes to objects that have been queued up to now to the database. |
||
353 | * This effectively synchronizes the in-memory state of managed objects with the |
||
354 | 1012 | * database. |
|
355 | * |
||
356 | 1012 | * If an entity is explicitly passed to this method only this entity and |
|
357 | * the cascade-persist semantics + scheduled inserts/removals are synchronized. |
||
358 | 1011 | * |
|
359 | 1002 | * @return void |
|
360 | * |
||
361 | * @throws \Doctrine\ORM\OptimisticLockException If a version check on an entity that |
||
362 | * makes use of optimistic locking fails. |
||
363 | * @throws ORMException |
||
364 | */ |
||
365 | public function flush() |
||
371 | |||
372 | /** |
||
373 | * Finds an Entity by its identifier. |
||
374 | * |
||
375 | * @param string $entityName The class name of the entity to find. |
||
376 | * @param mixed $id The identity of the entity to find. |
||
377 | * @param integer|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants |
||
378 | * or NULL if no specific lock mode should be used |
||
379 | 410 | * during the search. |
|
380 | * @param integer|null $lockVersion The version of the entity to find when using |
||
381 | 410 | * optimistic locking. |
|
382 | * |
||
383 | 410 | * @return object|null The entity instance or NULL if the entity can not be found. |
|
384 | 365 | * |
|
385 | * @throws OptimisticLockException |
||
386 | * @throws ORMInvalidArgumentException |
||
387 | * @throws TransactionRequiredException |
||
388 | 365 | * @throws ORMException |
|
389 | */ |
||
390 | public function find($entityName, $id, $lockMode = null, $lockVersion = null) |
||
478 | 91 | ||
479 | /** |
||
480 | 91 | * {@inheritDoc} |
|
481 | 91 | */ |
|
482 | public function getReference($entityName, $id) |
||
483 | { |
||
484 | $class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\')); |
||
485 | 91 | $className = $class->getClassName(); |
|
486 | 91 | ||
487 | View Code Duplication | if ( ! is_array($id)) { |
|
488 | if ($class->isIdentifierComposite()) { |
||
489 | 91 | throw ORMInvalidArgumentException::invalidCompositeIdentifier(); |
|
490 | 1 | } |
|
491 | |||
492 | $id = [$class->identifier[0] => $id]; |
||
493 | } |
||
494 | 90 | ||
495 | 27 | $scalarId = []; |
|
496 | |||
497 | foreach ($id as $i => $value) { |
||
498 | 86 | $scalarId[$i] = $value; |
|
499 | 2 | ||
500 | if (is_object($value) && $this->metadataFactory->hasMetadataFor(StaticClassNameConverter::getClass($value))) { |
||
501 | $scalarId[$i] = $this->unitOfWork->getSingleIdentifierValue($value); |
||
502 | 86 | ||
503 | if ($scalarId[$i] === null) { |
||
504 | 86 | throw ORMInvalidArgumentException::invalidIdentifierBindingEntity(); |
|
505 | } |
||
506 | 86 | } |
|
507 | } |
||
508 | |||
509 | $sortedId = []; |
||
510 | |||
511 | View Code Duplication | foreach ($class->identifier as $identifier) { |
|
512 | 4 | if ( ! isset($scalarId[$identifier])) { |
|
513 | throw ORMException::missingIdentifierField($className, $identifier); |
||
514 | 4 | } |
|
515 | |||
516 | $sortedId[$identifier] = $scalarId[$identifier]; |
||
517 | 4 | unset($scalarId[$identifier]); |
|
518 | 1 | } |
|
519 | |||
520 | if ($scalarId) { |
||
521 | 3 | throw ORMException::unrecognizedIdentifierFields($className, array_keys($scalarId)); |
|
522 | 3 | } |
|
523 | |||
524 | // Check identity map first, if its already in there just return it. |
||
525 | 3 | View Code Duplication | if (($entity = $this->unitOfWork->tryGetById($sortedId, $class->getRootClassName())) !== false) { |
526 | return ($entity instanceof $className) ? $entity : null; |
||
527 | 3 | } |
|
528 | |||
529 | 3 | if ($class->getSubClasses()) { |
|
530 | 3 | return $this->find($entityName, $sortedId); |
|
531 | } |
||
532 | 3 | ||
533 | $entity = $this->proxyFactory->getProxy($class, $id); |
||
534 | |||
535 | $this->unitOfWork->registerManaged($entity, $sortedId, []); |
||
536 | |||
537 | if ($entity instanceof EntityManagerAware) { |
||
538 | $entity->injectEntityManager($this, $class); |
||
539 | } |
||
540 | |||
541 | return $entity; |
||
542 | } |
||
543 | 1217 | ||
544 | /** |
||
545 | 1217 | * {@inheritDoc} |
|
546 | 1217 | */ |
|
547 | public function getPartialReference($entityName, $id) |
||
548 | { |
||
549 | $class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\')); |
||
550 | $className = $class->getClassName(); |
||
551 | 19 | ||
552 | View Code Duplication | if ( ! is_array($id)) { |
|
553 | 19 | if ($class->isIdentifierComposite()) { |
|
554 | throw ORMInvalidArgumentException::invalidCompositeIdentifier(); |
||
555 | 19 | } |
|
556 | 19 | ||
557 | $id = [$class->identifier[0] => $id]; |
||
558 | } |
||
559 | |||
560 | View Code Duplication | foreach ($id as $i => $value) { |
|
561 | if (is_object($value) && $this->metadataFactory->hasMetadataFor(StaticClassNameConverter::getClass($value))) { |
||
562 | $id[$i] = $this->unitOfWork->getSingleIdentifierValue($value); |
||
563 | |||
564 | if ($id[$i] === null) { |
||
565 | throw ORMInvalidArgumentException::invalidIdentifierBindingEntity(); |
||
566 | } |
||
567 | } |
||
568 | } |
||
569 | |||
570 | $sortedId = []; |
||
571 | |||
572 | View Code Duplication | foreach ($class->identifier as $identifier) { |
|
573 | if ( ! isset($id[$identifier])) { |
||
574 | 1006 | throw ORMException::missingIdentifierField($className, $identifier); |
|
575 | } |
||
576 | 1006 | ||
577 | 1 | $sortedId[$identifier] = $id[$identifier]; |
|
578 | unset($id[$identifier]); |
||
579 | } |
||
580 | 1005 | ||
581 | if ($id) { |
||
582 | 1004 | throw ORMException::unrecognizedIdentifierFields($className, array_keys($id)); |
|
583 | 1003 | } |
|
584 | |||
585 | // Check identity map first, if its already in there just return it. |
||
586 | View Code Duplication | if (($entity = $this->unitOfWork->tryGetById($sortedId, $class->getRootClassName())) !== false) { |
|
587 | return ($entity instanceof $className) ? $entity : null; |
||
588 | } |
||
589 | |||
590 | $persister = $this->unitOfWork->getEntityPersister($class->getClassName()); |
||
591 | $entity = $this->unitOfWork->newInstance($class); |
||
592 | |||
593 | $persister->setIdentifier($entity, $sortedId); |
||
594 | |||
595 | $this->unitOfWork->registerManaged($entity, $sortedId, []); |
||
596 | $this->unitOfWork->markReadOnly($entity); |
||
597 | |||
598 | 50 | return $entity; |
|
599 | } |
||
600 | 50 | ||
601 | 1 | /** |
|
602 | * Clears the EntityManager. All entities that are currently managed |
||
603 | * by this EntityManager become detached. |
||
604 | 49 | * |
|
605 | * @param null $entityName Unused. @todo Remove from ObjectManager. |
||
606 | 48 | * |
|
607 | 48 | * @return void |
|
608 | */ |
||
609 | public function clear($entityName = null) |
||
610 | { |
||
611 | $this->unitOfWork->clear(); |
||
612 | } |
||
613 | |||
614 | /** |
||
615 | * {@inheritDoc} |
||
616 | */ |
||
617 | public function close() |
||
618 | { |
||
619 | $this->clear(); |
||
620 | 18 | ||
621 | $this->closed = true; |
||
622 | 18 | } |
|
623 | 1 | ||
624 | /** |
||
625 | * Tells the EntityManager to make an instance managed and persistent. |
||
626 | 17 | * |
|
627 | * The entity will be entered into the database at or before transaction |
||
628 | 16 | * commit or as a result of the flush operation. |
|
629 | 16 | * |
|
630 | * NOTE: The persist operation always considers entities that are not yet known to |
||
631 | * this EntityManager as NEW. Do not pass detached entities to the persist operation. |
||
632 | * |
||
633 | * @param object $entity The instance to make managed and persistent. |
||
634 | * |
||
635 | * @return void |
||
636 | * |
||
637 | * @throws ORMInvalidArgumentException |
||
638 | * @throws ORMException |
||
639 | */ |
||
640 | View Code Duplication | public function persist($entity) |
|
641 | { |
||
642 | if ( ! is_object($entity)) { |
||
643 | throw ORMInvalidArgumentException::invalidObject('EntityManager#persist()', $entity); |
||
644 | 13 | } |
|
645 | |||
646 | 13 | $this->errorIfClosed(); |
|
647 | 1 | ||
648 | $this->unitOfWork->persist($entity); |
||
649 | } |
||
650 | 12 | ||
651 | 12 | /** |
|
652 | * Removes an entity instance. |
||
653 | * |
||
654 | * A removed entity will be removed from the database at or before transaction commit |
||
655 | * or as a result of the flush operation. |
||
656 | * |
||
657 | * @param object $entity The entity instance to remove. |
||
658 | * |
||
659 | * @return void |
||
660 | * |
||
661 | * @throws ORMInvalidArgumentException |
||
662 | * @throws ORMException |
||
663 | */ |
||
664 | View Code Duplication | public function remove($entity) |
|
665 | 42 | { |
|
666 | if ( ! is_object($entity)) { |
||
667 | 42 | throw ORMInvalidArgumentException::invalidObject('EntityManager#remove()', $entity); |
|
668 | 1 | } |
|
669 | |||
670 | $this->errorIfClosed(); |
||
671 | 41 | ||
672 | $this->unitOfWork->remove($entity); |
||
673 | 40 | } |
|
674 | |||
675 | /** |
||
676 | * Refreshes the persistent state of an entity from the database, |
||
677 | * overriding any local changes that have not yet been persisted. |
||
678 | * |
||
679 | * @param object $entity The entity to refresh. |
||
680 | * |
||
681 | * @return void |
||
682 | * |
||
683 | * @throws ORMInvalidArgumentException |
||
684 | * @throws ORMException |
||
685 | */ |
||
686 | View Code Duplication | public function refresh($entity) |
|
687 | { |
||
688 | if ( ! is_object($entity)) { |
||
689 | throw ORMInvalidArgumentException::invalidObject('EntityManager#refresh()', $entity); |
||
690 | 10 | } |
|
691 | |||
692 | 10 | $this->errorIfClosed(); |
|
693 | 3 | ||
694 | $this->unitOfWork->refresh($entity); |
||
695 | } |
||
696 | |||
697 | /** |
||
698 | * {@inheritDoc} |
||
699 | */ |
||
700 | public function lock($entity, $lockMode, $lockVersion = null) |
||
701 | { |
||
702 | 142 | $this->unitOfWork->lock($entity, $lockMode, $lockVersion); |
|
703 | } |
||
704 | 142 | ||
705 | /** |
||
706 | * Gets the repository for an entity class. |
||
707 | * |
||
708 | * @param string $entityName The name of the entity. |
||
709 | * |
||
710 | * @return \Doctrine\ORM\EntityRepository The repository class. |
||
711 | */ |
||
712 | public function getRepository($entityName) |
||
713 | { |
||
714 | 22 | return $this->repositoryFactory->getRepository($this, $entityName); |
|
715 | } |
||
716 | 22 | ||
717 | 20 | /** |
|
718 | 22 | * Determines whether an entity instance is managed in this EntityManager. |
|
719 | * |
||
720 | * @param object $entity |
||
721 | * |
||
722 | * @return boolean TRUE if this EntityManager currently manages the given entity, FALSE otherwise. |
||
723 | */ |
||
724 | 2290 | public function contains($entity) |
|
725 | { |
||
726 | 2290 | return $this->unitOfWork->isScheduledForInsert($entity) |
|
727 | || ($this->unitOfWork->isInIdentityMap($entity) && !$this->unitOfWork->isScheduledForDelete($entity)); |
||
728 | } |
||
729 | |||
730 | /** |
||
731 | * {@inheritDoc} |
||
732 | 2290 | */ |
|
733 | public function getEventManager() |
||
737 | |||
738 | /** |
||
739 | * {@inheritDoc} |
||
740 | */ |
||
741 | public function getConfiguration() |
||
745 | |||
746 | 1025 | /** |
|
747 | 5 | * Throws an exception if the EntityManager is closed or currently not active. |
|
748 | * |
||
749 | 1020 | * @return void |
|
750 | * |
||
751 | * @throws ORMException If the EntityManager is closed. |
||
752 | */ |
||
753 | private function errorIfClosed() |
||
759 | |||
760 | /** |
||
761 | * {@inheritDoc} |
||
762 | 2290 | */ |
|
763 | public function isOpen() |
||
767 | |||
768 | /** |
||
769 | * {@inheritDoc} |
||
770 | */ |
||
771 | public function getUnitOfWork() |
||
772 | { |
||
773 | return $this->unitOfWork; |
||
774 | } |
||
775 | |||
776 | /** |
||
777 | * {@inheritDoc} |
||
778 | 879 | */ |
|
779 | public function getHydrator($hydrationMode) |
||
783 | |||
784 | 535 | /** |
|
785 | 34 | * {@inheritDoc} |
|
786 | */ |
||
787 | 506 | public function newHydrator($hydrationMode) |
|
813 | |||
814 | /** |
||
815 | * {@inheritDoc} |
||
816 | */ |
||
817 | public function getProxyFactory() |
||
821 | |||
822 | /** |
||
823 | * {@inheritDoc} |
||
824 | */ |
||
825 | public function initializeObject($obj) |
||
829 | |||
830 | /** |
||
831 | * Factory method to create EntityManager instances. |
||
832 | * |
||
833 | 1229 | * @param array|Connection $connection An array with the connection parameters or an existing Connection instance. |
|
834 | * @param Configuration $config The Configuration instance to use. |
||
835 | 1229 | * @param EventManager $eventManager The EventManager instance to use. |
|
836 | * |
||
837 | * @return EntityManager The created EntityManager. |
||
838 | * |
||
839 | 1229 | * @throws \InvalidArgumentException |
|
840 | * @throws ORMException |
||
841 | 1229 | */ |
|
842 | public static function create($connection, Configuration $config, EventManager $eventManager = null) |
||
852 | |||
853 | /** |
||
854 | * Factory method to create Connection instances. |
||
855 | * |
||
856 | 1229 | * @param array|Connection $connection An array with the connection parameters or an existing Connection instance. |
|
857 | * @param Configuration $config The Configuration instance to use. |
||
858 | 1229 | * @param EventManager $eventManager The EventManager instance to use. |
|
859 | * |
||
860 | * @return Connection |
||
861 | * |
||
862 | 1229 | * @throws \InvalidArgumentException |
|
863 | * @throws ORMException |
||
864 | */ |
||
865 | protected static function createConnection($connection, Configuration $config, EventManager $eventManager = null) |
||
887 | |||
888 | 36 | /** |
|
889 | * {@inheritDoc} |
||
890 | 36 | */ |
|
891 | public function getFilters() |
||
899 | |||
900 | /** |
||
901 | * {@inheritDoc} |
||
902 | */ |
||
903 | public function isFiltersStateClean() |
||
907 | |||
908 | /** |
||
909 | * {@inheritDoc} |
||
910 | */ |
||
911 | public function hasFilters() |
||
915 | } |
||
916 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.