Complex classes like MockConfiguration 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 MockConfiguration, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
27 | class MockConfiguration |
||
28 | { |
||
29 | protected static $mockCounter = 0; |
||
30 | |||
31 | /** |
||
32 | * A class that we'd like to mock |
||
33 | */ |
||
34 | protected $targetClass; |
||
35 | protected $targetClassName; |
||
36 | |||
37 | /** |
||
38 | * A number of interfaces we'd like to mock, keyed by name to attempt to |
||
39 | * keep unique |
||
40 | */ |
||
41 | protected $targetInterfaces = array(); |
||
42 | protected $targetInterfaceNames = array(); |
||
43 | |||
44 | /** |
||
45 | * A number of traits we'd like to mock, keyed by name to attempt to |
||
46 | * keep unique |
||
47 | */ |
||
48 | protected $targetTraits = array(); |
||
49 | protected $targetTraitNames = array(); |
||
50 | |||
51 | /** |
||
52 | * An object we'd like our mock to proxy to |
||
53 | */ |
||
54 | protected $targetObject; |
||
55 | |||
56 | /** |
||
57 | * The class name we'd like to use for a generated mock |
||
58 | */ |
||
59 | protected $name; |
||
60 | |||
61 | /** |
||
62 | * Methods that should specifically not be mocked |
||
63 | * |
||
64 | * This is currently populated with stuff we don't know how to deal with, |
||
65 | * should really be somewhere else |
||
66 | */ |
||
67 | protected $blackListedMethods = array(); |
||
68 | |||
69 | /** |
||
70 | * If not empty, only these methods will be mocked |
||
71 | */ |
||
72 | protected $whiteListedMethods = array(); |
||
73 | |||
74 | /** |
||
75 | * An instance mock is where we override the original class before it's |
||
76 | * autoloaded |
||
77 | */ |
||
78 | protected $instanceMock = false; |
||
79 | |||
80 | /** |
||
81 | * Param overrides |
||
82 | */ |
||
83 | protected $parameterOverrides = array(); |
||
84 | |||
85 | /** |
||
86 | * Instance cache of all methods |
||
87 | */ |
||
88 | protected $allMethods; |
||
89 | |||
90 | /** |
||
91 | * If true, overrides original class destructor |
||
92 | */ |
||
93 | protected $mockOriginalDestructor = false; |
||
94 | |||
95 | 467 | public function __construct( |
|
112 | |||
113 | /** |
||
114 | * Attempt to create a hash of the configuration, in order to allow caching |
||
115 | * |
||
116 | * @TODO workout if this will work |
||
117 | * |
||
118 | * @return string |
||
119 | */ |
||
120 | 447 | public function getHash() |
|
136 | |||
137 | /** |
||
138 | * Gets a list of methods from the classes, interfaces and objects and |
||
139 | * filters them appropriately. Lot's of filtering going on, perhaps we could |
||
140 | * have filter classes to iterate through |
||
141 | */ |
||
142 | 415 | public function getMethodsToMock() |
|
190 | |||
191 | /** |
||
192 | * We declare the __call method to handle undefined stuff, if the class |
||
193 | * we're mocking has also defined it, we need to comply with their interface |
||
194 | */ |
||
195 | 408 | public function requiresCallTypeHintRemoval() |
|
206 | |||
207 | /** |
||
208 | * We declare the __callStatic method to handle undefined stuff, if the class |
||
209 | * we're mocking has also defined it, we need to comply with their interface |
||
210 | */ |
||
211 | 407 | public function requiresCallStaticTypeHintRemoval() |
|
222 | |||
223 | 407 | public function rename($className) |
|
224 | { |
||
225 | 407 | $targets = array(); |
|
226 | |||
227 | 407 | if ($this->targetClassName) { |
|
228 | 383 | $targets[] = $this->targetClassName; |
|
229 | } |
||
230 | |||
231 | 407 | if ($this->targetInterfaceNames) { |
|
232 | 19 | $targets = array_merge($targets, $this->targetInterfaceNames); |
|
233 | } |
||
234 | |||
235 | 407 | if ($this->targetTraitNames) { |
|
|
|||
236 | 3 | $targets = array_merge($targets, $this->targetTraitNames); |
|
237 | } |
||
238 | |||
239 | 407 | if ($this->targetObject) { |
|
240 | 14 | $targets[] = $this->targetObject; |
|
241 | } |
||
242 | |||
243 | 407 | return new self( |
|
244 | $targets, |
||
245 | 407 | $this->blackListedMethods, |
|
246 | 407 | $this->whiteListedMethods, |
|
247 | $className, |
||
248 | 407 | $this->instanceMock, |
|
249 | 407 | $this->parameterOverrides, |
|
250 | 407 | $this->mockOriginalDestructor |
|
251 | ); |
||
252 | } |
||
253 | |||
254 | 435 | protected function addTarget($target) |
|
255 | { |
||
256 | 435 | if (is_object($target)) { |
|
257 | 14 | $this->setTargetObject($target); |
|
258 | 14 | $this->setTargetClassName(get_class($target)); |
|
259 | 14 | return $this; |
|
260 | } |
||
261 | |||
262 | 435 | if ($target[0] !== "\\") { |
|
263 | 432 | $target = "\\" . $target; |
|
264 | } |
||
265 | |||
266 | 435 | if (class_exists($target)) { |
|
267 | 406 | $this->setTargetClassName($target); |
|
268 | 406 | return $this; |
|
269 | } |
||
270 | |||
271 | 43 | if (interface_exists($target)) { |
|
272 | 24 | $this->addTargetInterfaceName($target); |
|
273 | 24 | return $this; |
|
274 | } |
||
275 | |||
276 | 20 | if (trait_exists($target)) { |
|
277 | 3 | $this->addTargetTraitName($target); |
|
278 | 3 | return $this; |
|
279 | } |
||
280 | |||
281 | /** |
||
282 | * Default is to set as class, or interface if class already set |
||
283 | * |
||
284 | * Don't like this condition, can't remember what the default |
||
285 | * targetClass is for |
||
286 | */ |
||
287 | 17 | if ($this->getTargetClassName()) { |
|
288 | 1 | $this->addTargetInterfaceName($target); |
|
289 | 1 | return $this; |
|
290 | } |
||
291 | |||
292 | 17 | $this->setTargetClassName($target); |
|
293 | 17 | } |
|
294 | |||
295 | 467 | protected function addTargets($interfaces) |
|
301 | |||
302 | 17 | public function getTargetClassName() |
|
306 | |||
307 | 418 | public function getTargetClass() |
|
337 | |||
338 | 416 | public function getTargetTraits() |
|
339 | { |
||
340 | 416 | if (!empty($this->targetTraits)) { |
|
341 | 3 | return $this->targetTraits; |
|
342 | } |
||
343 | |||
344 | 416 | foreach ($this->targetTraitNames as $targetTrait) { |
|
345 | 3 | $this->targetTraits[] = DefinedTargetClass::factory($targetTrait); |
|
346 | } |
||
347 | |||
348 | 416 | $this->targetTraits = array_unique($this->targetTraits); // just in case |
|
349 | 416 | return $this->targetTraits; |
|
350 | } |
||
351 | |||
352 | 420 | public function getTargetInterfaces() |
|
400 | |||
401 | 454 | public function getTargetObject() |
|
405 | |||
406 | 453 | public function getName() |
|
410 | |||
411 | /** |
||
412 | * Generate a suitable name based on the config |
||
413 | */ |
||
414 | 387 | public function generateName() |
|
435 | |||
436 | 411 | public function getShortName() |
|
441 | |||
442 | 411 | public function getNamespaceName() |
|
453 | |||
454 | 397 | public function getBlackListedMethods() |
|
458 | |||
459 | 415 | public function getWhiteListedMethods() |
|
463 | |||
464 | 408 | public function isInstanceMock() |
|
468 | |||
469 | 117 | public function getParameterOverrides() |
|
473 | |||
474 | 383 | public function isMockOriginalDestructor() |
|
478 | |||
479 | 416 | protected function setTargetClassName($targetClassName) |
|
483 | |||
484 | 416 | protected function getAllMethods() |
|
485 | { |
||
486 | 416 | if ($this->allMethods) { |
|
487 | 133 | return $this->allMethods; |
|
488 | } |
||
489 | |||
490 | 416 | $classes = $this->getTargetInterfaces(); |
|
491 | |||
492 | 416 | if ($this->getTargetClass()) { |
|
493 | 390 | $classes[] = $this->getTargetClass(); |
|
494 | } |
||
495 | |||
496 | 416 | $methods = array(); |
|
497 | 416 | foreach ($classes as $class) { |
|
498 | 402 | $methods = array_merge($methods, $class->getMethods()); |
|
499 | } |
||
500 | |||
501 | 416 | foreach ($this->getTargetTraits() AS $trait) { |
|
502 | 3 | foreach ($trait->getMethods() as $method) { |
|
503 | 3 | if ($method->isAbstract()) { |
|
504 | 3 | $methods[] = $method; |
|
505 | } |
||
506 | } |
||
507 | } |
||
508 | |||
509 | 416 | $names = array(); |
|
510 | 416 | $methods = array_filter($methods, function ($method) use (&$names) { |
|
511 | 141 | if (in_array($method->getName(), $names)) { |
|
512 | 3 | return false; |
|
513 | } |
||
514 | |||
515 | 141 | $names[] = $method->getName(); |
|
516 | 141 | return true; |
|
517 | 416 | }); |
|
518 | |||
519 | 416 | return $this->allMethods = $methods; |
|
520 | } |
||
521 | |||
522 | /** |
||
523 | * If we attempt to implement Traversable, we must ensure we are also |
||
524 | * implementing either Iterator or IteratorAggregate, and that whichever one |
||
525 | * it is comes before Traversable in the list of implements. |
||
526 | */ |
||
527 | 24 | protected function addTargetInterfaceName($targetInterface) |
|
531 | |||
532 | 3 | protected function addTargetTraitName($targetTraitName) |
|
533 | { |
||
534 | 3 | $this->targetTraitNames[] = $targetTraitName; |
|
535 | 3 | } |
|
536 | |||
537 | 14 | protected function setTargetObject($object) |
|
541 | } |
||
542 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.