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 YmlMetadataDriver 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 YmlMetadataDriver, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
15 | class YmlMetadataDriver extends FileDriver |
||
16 | { |
||
17 | /** |
||
18 | * Loads the metadata for the specified class into the provided container. |
||
19 | * |
||
20 | * @param string $className |
||
21 | * @param EntityMetadata|ClassMetadata $metadata |
||
22 | * |
||
23 | * @return void |
||
24 | * @throws MappingException |
||
25 | */ |
||
26 | 28 | public function loadMetadataForClass($className, ClassMetadata $metadata) |
|
27 | { |
||
28 | 28 | $element = $this->getElement($className); |
|
29 | |||
30 | 28 | switch ($element['type']) { |
|
31 | 28 | case 'entity': |
|
32 | 28 | if (array_key_exists('repositoryClass', $element)) { |
|
33 | 2 | $metadata->setCustomRepositoryClass($element['repositoryClass']); |
|
34 | 2 | } |
|
35 | 28 | break; |
|
36 | case 'mappedSuperclass': |
||
37 | $metadata->isMappedSuperclass = true; |
||
|
|||
38 | $metadata->setCustomRepositoryClass( |
||
39 | array_key_exists('repositoryClass', $element) ? $element['repositoryClass'] : null |
||
40 | ); |
||
41 | break; |
||
42 | 28 | } |
|
43 | |||
44 | // Configure API |
||
45 | 28 | if (array_key_exists('api', $element)) { |
|
46 | 28 | if (array_key_exists('factory', $element['api'])) { |
|
47 | 28 | $metadata->apiFactory = $element['api']['factory']; |
|
48 | 28 | } |
|
49 | 28 | } |
|
50 | |||
51 | // Evaluate discriminatorColumn |
||
52 | 28 | if (isset($element['discriminatorField'])) { |
|
53 | 4 | $discrColumn = $element['discriminatorField']; |
|
54 | 4 | $metadata->setDiscriminatorField( |
|
55 | [ |
||
56 | 4 | 'name' => isset($discrColumn['name']) ? (string)$discrColumn['name'] : null, |
|
57 | 4 | 'type' => isset($discrColumn['type']) ? (string)$discrColumn['type'] : 'string', |
|
58 | ] |
||
59 | 4 | ); |
|
60 | 4 | } else { |
|
61 | // $metadata->setDiscriminatorField(['name' => 'dtype', 'type' => 'string']); |
||
62 | } |
||
63 | // Evaluate discriminatorMap |
||
64 | 28 | if (isset($element['discriminatorMap'])) { |
|
65 | $metadata->setDiscriminatorMap($element['discriminatorMap']); |
||
66 | } |
||
67 | |||
68 | // Configure Client |
||
69 | 28 | if (array_key_exists('client', $element)) { |
|
70 | 28 | if (array_key_exists('name', $element['client'])) { |
|
71 | 28 | $metadata->clientName = $element['client']['name']; |
|
72 | 28 | } |
|
73 | |||
74 | 28 | $methodProvider = null; |
|
75 | 28 | if (array_key_exists('methods', $element['client'])) { |
|
76 | $methodProvider = new MethodProvider($element['client']['methods']); |
||
77 | } |
||
78 | 28 | if (array_key_exists('entityPath', $element['client'])) { |
|
79 | $pathSeparator = |
||
80 | 28 | array_key_exists('entityPathSeparator', $element['client']) ? |
|
81 | 28 | $element['client']['entityPathSeparator'] : EntityMethodProvider::DEFAULT_PATH_SEPARATOR; |
|
82 | $methodProvider = |
||
83 | 28 | new EntityMethodProvider($element['client']['entityPath'], $pathSeparator, $methodProvider); |
|
84 | 28 | } |
|
85 | |||
86 | 28 | if (null === $methodProvider && null === $metadata->methodProvider) { |
|
87 | throw MappingException::noMethods(); |
||
88 | } |
||
89 | |||
90 | 28 | if (null !== $methodProvider) { |
|
91 | 28 | $metadata->methodProvider = $methodProvider; |
|
92 | 28 | } |
|
93 | 28 | } |
|
94 | |||
95 | // Configure fields |
||
96 | 28 | if (array_key_exists('fields', $element)) { |
|
97 | 28 | foreach ($element['fields'] as $field => $mapping) { |
|
98 | 28 | $mapping = $this->fieldToArray($field, $mapping); |
|
99 | 28 | $metadata->mapField($mapping); |
|
100 | 28 | } |
|
101 | 28 | } |
|
102 | |||
103 | // Configure identifiers |
||
104 | 28 | $associationIds = []; |
|
105 | 28 | if (array_key_exists('id', $element)) { |
|
106 | // Evaluate identifier settings |
||
107 | 28 | $defaults = ['generator' => ['strategy' => 'NATURAL']]; |
|
108 | 28 | foreach ($element['id'] as $name => $idElement) { |
|
109 | 28 | if (isset($idElement['associationKey']) && (bool)$idElement['associationKey'] === true) { |
|
110 | $associationIds[$name] = true; |
||
111 | continue; |
||
112 | } |
||
113 | |||
114 | 28 | $mapping = $this->fieldToArray($name, $idElement); |
|
115 | |||
116 | 28 | $mapping['id'] = true; |
|
117 | 28 | $idElement = array_replace_recursive($defaults, $idElement); |
|
118 | |||
119 | 28 | $mapping['generator']['strategy'] = |
|
120 | 28 | constant(ApiMetadata::class . '::GENERATOR_TYPE_' . $idElement['generator']['strategy']); |
|
121 | |||
122 | 28 | $metadata->mapIdentifier($mapping); |
|
123 | 28 | } |
|
124 | 28 | } |
|
125 | |||
126 | 28 | foreach (['oneToOne', 'manyToOne', 'oneToMany', 'manyToMany'] as $type) { |
|
127 | 28 | if (array_key_exists($type, $element)) { |
|
128 | 18 | $associations = $element[$type]; |
|
129 | 18 | foreach ($associations as $name => $association) { |
|
130 | 18 | $this->mapAssociation($metadata, $type, $name, $association, $associationIds); |
|
131 | 18 | } |
|
132 | 18 | } |
|
133 | 28 | } |
|
134 | 28 | } |
|
135 | |||
136 | /** |
||
137 | * @param EntityMetadata $metadata |
||
138 | * @param string $type |
||
139 | * @param string $name |
||
140 | * @param array $association |
||
141 | * @param int[] $associationIds |
||
142 | */ |
||
143 | 18 | protected function mapAssociation(EntityMetadata $metadata, $type, $name, $association, $associationIds) |
|
200 | |||
201 | /** |
||
202 | * Loads a mapping file with the given name and returns a map |
||
203 | * from class/entity names to their corresponding file driver elements. |
||
204 | * |
||
205 | * @param string $file The mapping file to load. |
||
206 | * |
||
207 | * @return array |
||
208 | * @throws ParseException |
||
209 | */ |
||
210 | 28 | protected function loadMappingFile($file) |
|
214 | |||
215 | 28 | private function fieldToArray($field, $source) |
|
242 | } |
||
243 | |||
244 |
If you access a property on an interface, you most likely code against a concrete implementation of the interface.
Available Fixes
Adding an additional type check:
Changing the type hint: