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 EagerLoadingExtension 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 EagerLoadingExtension, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 41 | final class EagerLoadingExtension implements ContextAwareQueryCollectionExtensionInterface, QueryItemExtensionInterface |
||
| 42 | { |
||
| 43 | use EagerLoadingTrait; |
||
| 44 | |||
| 45 | private $propertyNameCollectionFactory; |
||
| 46 | private $propertyMetadataFactory; |
||
| 47 | private $classMetadataFactory; |
||
| 48 | private $maxJoins; |
||
| 49 | private $serializerContextBuilder; |
||
| 50 | private $requestStack; |
||
| 51 | |||
| 52 | /** |
||
| 53 | * @TODO move $fetchPartial after $forceEager (@soyuka) in 3.0 |
||
| 54 | */ |
||
| 55 | public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, int $maxJoins = 30, bool $forceEager = true, RequestStack $requestStack = null, SerializerContextBuilderInterface $serializerContextBuilder = null, bool $fetchPartial = false, ClassMetadataFactoryInterface $classMetadataFactory = null) |
||
| 56 | { |
||
| 57 | //if (null !== $this->serializerContextBuilder) { |
||
|
|
|||
| 58 | // @trigger_error('Passing an instance of "%s" is deprecated since version 2.2 and will be removed in 3.0. Use the "normalization_context" of the data provider\'s context instead.', E_USER_DEPRECATED); |
||
| 59 | //} |
||
| 60 | |||
| 61 | $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; |
||
| 62 | $this->propertyMetadataFactory = $propertyMetadataFactory; |
||
| 63 | $this->resourceMetadataFactory = $resourceMetadataFactory; |
||
| 64 | $this->classMetadataFactory = $classMetadataFactory; |
||
| 65 | $this->maxJoins = $maxJoins; |
||
| 66 | $this->forceEager = $forceEager; |
||
| 67 | $this->fetchPartial = $fetchPartial; |
||
| 68 | $this->serializerContextBuilder = $serializerContextBuilder; |
||
| 69 | $this->requestStack = $requestStack; |
||
| 70 | } |
||
| 71 | |||
| 72 | /** |
||
| 73 | * {@inheritdoc} |
||
| 74 | */ |
||
| 75 | public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass = null, string $operationName = null, array $context = []) |
||
| 76 | { |
||
| 77 | $this->apply(true, $queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context); |
||
| 78 | } |
||
| 79 | |||
| 80 | /** |
||
| 81 | * The context may contain serialization groups which helps defining joined entities that are readable. |
||
| 82 | */ |
||
| 83 | public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, string $operationName = null, array $context = []) |
||
| 84 | { |
||
| 85 | $this->apply(false, $queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context); |
||
| 86 | } |
||
| 87 | |||
| 88 | private function apply(bool $collection, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass = null, string $operationName = null, array $context) |
||
| 89 | { |
||
| 90 | if (null === $resourceClass) { |
||
| 91 | throw new InvalidArgumentException('The "$resourceClass" parameter must not be null'); |
||
| 92 | } |
||
| 93 | |||
| 94 | $options = []; |
||
| 95 | if (null !== $operationName) { |
||
| 96 | $options[($collection ? 'collection' : 'item').'_operation_name'] = $operationName; |
||
| 97 | } |
||
| 98 | |||
| 99 | $forceEager = $this->shouldOperationForceEager($resourceClass, $options); |
||
| 100 | $fetchPartial = $this->shouldOperationFetchPartial($resourceClass, $options); |
||
| 101 | |||
| 102 | if (!$normalizationContext = $context['normalization_context'] ?? false) { |
||
| 103 | $contextType = isset($context['api_denormalize']) ? 'denormalization_context' : 'normalization_context'; |
||
| 104 | $normalizationContext = $this->getNormalizationContext($context['resource_class'] ?? $resourceClass, $contextType, $options); |
||
| 105 | } |
||
| 106 | |||
| 107 | $this->joinRelations($queryBuilder, $queryNameGenerator, $resourceClass, $forceEager, $fetchPartial, $queryBuilder->getRootAliases()[0], $options, $normalizationContext); |
||
| 108 | } |
||
| 109 | |||
| 110 | /** |
||
| 111 | * Joins relations to eager load. |
||
| 112 | * |
||
| 113 | * @param bool $wasLeftJoin if the relation containing the new one had a left join, we have to force the new one to left join too |
||
| 114 | * @param int $joinCount the number of joins |
||
| 115 | * @param int $currentDepth the current max depth |
||
| 116 | * |
||
| 117 | * @throws RuntimeException when the max number of joins has been reached |
||
| 118 | */ |
||
| 119 | private function joinRelations(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, bool $forceEager, bool $fetchPartial, string $parentAlias, array $options = [], array $normalizationContext = [], bool $wasLeftJoin = false, int &$joinCount = 0, int $currentDepth = null) |
||
| 220 | |||
| 221 | private function addSelect(QueryBuilder $queryBuilder, string $entity, string $associationAlias, array $propertyMetadataOptions) |
||
| 256 | |||
| 257 | /** |
||
| 258 | * Gets serializer context. |
||
| 259 | * |
||
| 260 | * @param string $contextType normalization_context or denormalization_context |
||
| 261 | * @param array $options represents the operation name so that groups are the one of the specific operation |
||
| 262 | */ |
||
| 263 | private function getNormalizationContext(string $resourceClass, string $contextType, array $options): array |
||
| 281 | } |
||
| 282 |
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.