Complex classes like OverrideGraph 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 OverrideGraph, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
66 | class OverrideGraph |
||
67 | { |
||
68 | /** |
||
69 | * Stores the names of all modules (vertices) as keys. |
||
70 | * |
||
71 | * @var array |
||
72 | */ |
||
73 | private $moduleNames = array(); |
||
74 | |||
75 | /** |
||
76 | * Stores the edges in the keys of a multi-dimensional array. |
||
77 | * |
||
78 | * The first dimension stores the targets, the second dimension the origins |
||
79 | * of the edges. |
||
80 | * |
||
81 | * @var array |
||
82 | */ |
||
83 | private $edges = array(); |
||
84 | |||
85 | /** |
||
86 | * Creates an override graph for the given modules. |
||
87 | * |
||
88 | * @param ModuleCollection $modules The modules to load. |
||
89 | * |
||
90 | * @return static The created override graph. |
||
91 | */ |
||
92 | 93 | public static function forModules(ModuleCollection $modules) |
|
93 | { |
||
94 | 93 | $graph = new static($modules->getModuleNames()); |
|
95 | |||
96 | 93 | foreach ($modules as $module) { |
|
97 | 93 | if (null === $module->getModuleFile()) { |
|
98 | 2 | continue; |
|
99 | } |
||
100 | |||
101 | 93 | foreach ($module->getModuleFile()->getOverriddenModules() as $overriddenModule) { |
|
102 | 35 | if ($graph->hasModuleName($overriddenModule)) { |
|
103 | 93 | $graph->addEdge($overriddenModule, $module->getName()); |
|
104 | } |
||
105 | } |
||
106 | } |
||
107 | |||
108 | // Do we have a root module? |
||
109 | 93 | if (null === $modules->getRootModule()) { |
|
110 | 20 | return $graph; |
|
111 | } |
||
112 | |||
113 | // Make sure we have numeric, ascending keys here |
||
114 | 73 | $moduleOrder = array_values($modules->getRootModule()->getModuleFile()->getOverrideOrder()); |
|
115 | |||
116 | // Each module overrides the previous one in the list |
||
117 | 73 | for ($i = 1, $l = count($moduleOrder); $i < $l; ++$i) { |
|
118 | 1 | $overriddenModule = $moduleOrder[$i - 1]; |
|
119 | 1 | $overridingModule = $moduleOrder[$i]; |
|
120 | |||
121 | 1 | if ($graph->hasModuleName($overriddenModule)) { |
|
122 | 1 | $graph->addEdge($overriddenModule, $overridingModule); |
|
123 | } |
||
124 | } |
||
125 | |||
126 | 73 | return $graph; |
|
127 | } |
||
128 | |||
129 | /** |
||
130 | * Creates a new graph. |
||
131 | * |
||
132 | * @param string[] $moduleNames The module names stored in the nodes of |
||
133 | * the graph. |
||
134 | */ |
||
135 | 132 | public function __construct(array $moduleNames = array()) |
|
136 | { |
||
137 | 132 | $this->addModuleNames($moduleNames); |
|
138 | 132 | } |
|
139 | |||
140 | /** |
||
141 | * Adds a module name to the graph. |
||
142 | * |
||
143 | * @param string $moduleName The module name. |
||
144 | * |
||
145 | * @throws RuntimeException If the module name already exists. |
||
146 | */ |
||
147 | 131 | public function addModuleName($moduleName) |
|
148 | { |
||
149 | 131 | if (isset($this->moduleNames[$moduleName])) { |
|
150 | 1 | throw new RuntimeException(sprintf( |
|
151 | 1 | 'The module "%s" was added to the graph twice.', |
|
152 | $moduleName |
||
153 | )); |
||
154 | } |
||
155 | |||
156 | 131 | $this->moduleNames[$moduleName] = true; |
|
157 | 131 | $this->edges[$moduleName] = array(); |
|
158 | 131 | } |
|
159 | |||
160 | /** |
||
161 | * Adds a list of module names to the graph. |
||
162 | * |
||
163 | * @param string[] $moduleNames The module names. |
||
164 | * |
||
165 | * @throws RuntimeException If a module name already exists. |
||
166 | */ |
||
167 | 132 | public function addModuleNames(array $moduleNames) |
|
168 | { |
||
169 | 132 | foreach ($moduleNames as $moduleName) { |
|
170 | 105 | $this->addModuleName($moduleName); |
|
171 | } |
||
172 | 132 | } |
|
173 | |||
174 | /** |
||
175 | * Returns whether a module name exists in the graph. |
||
176 | * |
||
177 | * @param string $moduleName The module name. |
||
178 | * |
||
179 | * @return bool Whether the module name exists. |
||
180 | */ |
||
181 | 40 | public function hasModuleName($moduleName) |
|
182 | { |
||
183 | 40 | return isset($this->moduleNames[$moduleName]); |
|
184 | } |
||
185 | |||
186 | /** |
||
187 | * Adds a directed edge from one to another module. |
||
188 | * |
||
189 | * @param string $from The start module name. |
||
190 | * @param string $to The end module name. |
||
191 | * |
||
192 | * @throws RuntimeException If any of the modules does not exist in the |
||
193 | * graph. Each module must have been added first. |
||
194 | * @throws CyclicDependencyException If adding the edge would create a cycle. |
||
195 | */ |
||
196 | 67 | public function addEdge($from, $to) |
|
197 | { |
||
198 | 67 | if (!isset($this->moduleNames[$from])) { |
|
199 | 1 | throw new RuntimeException(sprintf( |
|
200 | 1 | 'The module "%s" does not exist in the graph.', |
|
201 | $from |
||
202 | )); |
||
203 | } |
||
204 | |||
205 | 66 | if (!isset($this->moduleNames[$to])) { |
|
206 | 1 | throw new RuntimeException(sprintf( |
|
207 | 1 | 'The module "%s" does not exist in the graph.', |
|
208 | $to |
||
209 | )); |
||
210 | } |
||
211 | |||
212 | 65 | if (null !== ($path = $this->getPath($to, $from))) { |
|
213 | 1 | $last = array_pop($path); |
|
214 | |||
215 | 1 | throw new CyclicDependencyException(sprintf( |
|
216 | 'A cyclic dependency was discovered between the modules "%s" '. |
||
217 | 'and "%s". Please check the "override" keys defined in these '. |
||
218 | 1 | 'modules.', |
|
219 | 1 | implode('", "', $path), |
|
220 | $last |
||
221 | )); |
||
222 | } |
||
223 | |||
224 | 65 | $this->edges[$to][$from] = true; |
|
225 | 65 | } |
|
226 | |||
227 | 2 | public function removeEdge($from, $to) |
|
231 | |||
232 | /** |
||
233 | * Returns whether an edge exists between two modules. |
||
234 | * |
||
235 | * @param string $from The start module name. |
||
236 | * @param string $to The end module name. |
||
237 | * |
||
238 | * @return bool Whether an edge exists from the origin to the target module. |
||
239 | */ |
||
240 | 40 | public function hasEdge($from, $to) |
|
244 | |||
245 | /** |
||
246 | * Returns whether a path exists from one to another module. |
||
247 | * |
||
248 | * @param string $from The start module name. |
||
249 | * @param string $to The end module name. |
||
250 | * |
||
251 | * @return bool Whether a path exists from the origin to the target module. |
||
252 | */ |
||
253 | 9 | public function hasPath($from, $to) |
|
274 | |||
275 | /** |
||
276 | * Returns the path from one to another module. |
||
277 | * |
||
278 | * @param string $from The start module name. |
||
279 | * @param string $to The end module name. |
||
280 | * |
||
281 | * @return string[]|null The path of module names or `null`, if no path |
||
|
|||
282 | * was found. |
||
283 | */ |
||
284 | 65 | public function getPath($from, $to) |
|
292 | |||
293 | /** |
||
294 | * Returns all module names in the graph. |
||
295 | * |
||
296 | * @return array All module names in the graph. |
||
297 | */ |
||
298 | public function getModuleNames() |
||
299 | { |
||
300 | return $this->moduleNames; |
||
301 | } |
||
302 | |||
303 | /** |
||
304 | * Sorts module names according to the defined edges. |
||
305 | * |
||
306 | * The names are sorted such that if two modules p1 and p2 have an edge |
||
307 | * (p1, p2) in the graph, then p1 comes before p2 in the sorted set. |
||
308 | * |
||
309 | * If no module names are passed, all names are sorted. |
||
310 | * |
||
311 | * @param string[] $namesToSort The module names which should be sorted. |
||
312 | * |
||
313 | * @return string[] The sorted module names. |
||
314 | * |
||
315 | * @throws RuntimeException If any of the passed module names does not |
||
316 | * exist in the graph. |
||
317 | */ |
||
318 | 83 | public function getSortedModuleNames(array $namesToSort = array()) |
|
319 | { |
||
320 | 83 | if (count($namesToSort) > 0) { |
|
321 | 54 | $namesToSort = array_flip($namesToSort); |
|
322 | |||
323 | 54 | foreach ($namesToSort as $module => $_) { |
|
324 | 54 | if (!isset($this->moduleNames[$module])) { |
|
325 | 1 | throw new RuntimeException(sprintf( |
|
326 | 54 | 'The module "%s" does not exist in the graph.', |
|
327 | $module |
||
328 | )); |
||
329 | } |
||
330 | } |
||
331 | } else { |
||
332 | 29 | $namesToSort = $this->moduleNames; |
|
333 | } |
||
334 | |||
335 | 82 | $sorted = array(); |
|
336 | |||
337 | // Do a topologic sort |
||
338 | // Start with any module and process until no more are left |
||
339 | 82 | while (false !== reset($namesToSort)) { |
|
340 | 82 | $this->sortModulesDFS(key($namesToSort), $namesToSort, $sorted); |
|
341 | } |
||
342 | |||
343 | 82 | return $sorted; |
|
344 | } |
||
345 | |||
346 | /** |
||
347 | * Finds a path between modules using Depth-First Search. |
||
348 | * |
||
349 | * @param string $from The start module name. |
||
350 | * @param string $to The end module name. |
||
351 | * @param array $reversePath The path in reverse order. |
||
352 | * |
||
353 | * @return bool Whether a path was found. |
||
354 | */ |
||
355 | 65 | private function getPathDFS($from, $to, &$reversePath = array()) |
|
380 | |||
381 | /** |
||
382 | * Topologically sorts the given module name into the output array. |
||
383 | * |
||
384 | * The resulting array is sorted such that all predecessors of the module |
||
385 | * come before the module (and their predecessors before them, and so on). |
||
386 | * |
||
387 | * @param string $currentName The current module name to sort. |
||
388 | * @param array $namesToSort The module names yet to be sorted. |
||
389 | * @param array $output The output array. |
||
390 | */ |
||
391 | 82 | private function sortModulesDFS($currentName, array &$namesToSort, array &$output) |
|
409 | } |
||
410 |
This check compares the return type specified in the
@return
annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.If the return type contains the type array, this check recommends the use of a more specific type like
String[]
orarray<String>
.