Complex classes like Mockery 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 Mockery, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
39 | class Mockery |
||
40 | { |
||
41 | const BLOCKS = 'Mockery_Forward_Blocks'; |
||
42 | |||
43 | /** |
||
44 | * Global container to hold all mocks for the current unit test running. |
||
45 | * |
||
46 | * @var \Mockery\Container |
||
47 | */ |
||
48 | protected static $_container = null; |
||
49 | |||
50 | /** |
||
51 | * Global configuration handler containing configuration options. |
||
52 | * |
||
53 | * @var \Mockery\Configuration |
||
54 | */ |
||
55 | protected static $_config = null; |
||
56 | |||
57 | /** |
||
58 | * @var \Mockery\Generator\Generator |
||
59 | */ |
||
60 | protected static $_generator; |
||
61 | |||
62 | /** |
||
63 | * @var \Mockery\Loader\Loader |
||
64 | */ |
||
65 | protected static $_loader; |
||
66 | |||
67 | /** |
||
68 | * @var array |
||
69 | */ |
||
70 | private static $_filesToCleanUp = []; |
||
71 | |||
72 | /** |
||
73 | * Static shortcut to \Mockery\Container::mock(). |
||
74 | * |
||
75 | * @return \Mockery\MockInterface |
||
76 | */ |
||
77 | 26 | public static function mock() |
|
83 | |||
84 | /** |
||
85 | * Static and semantic shortcut for getting a mock from the container |
||
86 | * and applying the spy's expected behavior into it. |
||
87 | * |
||
88 | * @return \Mockery\MockInterface |
||
89 | */ |
||
90 | 7 | public static function spy() |
|
95 | |||
96 | /** |
||
97 | * Static and Semantic shortcut to \Mockery\Container::mock(). |
||
98 | * |
||
99 | * @return \Mockery\MockInterface |
||
100 | */ |
||
101 | public static function instanceMock() |
||
107 | |||
108 | /** |
||
109 | * Static shortcut to \Mockery\Container::mock(), first argument names the mock. |
||
110 | * |
||
111 | * @return \Mockery\MockInterface |
||
112 | */ |
||
113 | 4 | public static function namedMock() |
|
125 | |||
126 | /** |
||
127 | * Static shortcut to \Mockery\Container::self(). |
||
128 | * |
||
129 | * @throws LogicException |
||
130 | * |
||
131 | * @return \Mockery\MockInterface |
||
132 | */ |
||
133 | 2 | public static function self() |
|
134 | { |
||
135 | 2 | if (is_null(self::$_container)) { |
|
136 | 1 | throw new \LogicException('You have not declared any mocks yet'); |
|
137 | } |
||
138 | |||
139 | 1 | return self::$_container->self(); |
|
140 | } |
||
141 | |||
142 | /** |
||
143 | * Static shortcut to closing up and verifying all mocks in the global |
||
144 | * container, and resetting the container static variable to null. |
||
145 | * |
||
146 | * @return void |
||
147 | */ |
||
148 | 414 | public static function close() |
|
149 | { |
||
150 | 414 | foreach (self::$_filesToCleanUp as $fileName) { |
|
151 | 18 | @unlink($fileName); |
|
152 | 414 | } |
|
153 | 414 | self::$_filesToCleanUp = []; |
|
154 | |||
155 | 414 | if (is_null(self::$_container)) { |
|
156 | return; |
||
157 | } |
||
158 | |||
159 | 414 | self::$_container->mockery_teardown(); |
|
160 | 414 | self::$_container->mockery_close(); |
|
161 | 414 | self::$_container = null; |
|
162 | 414 | } |
|
163 | |||
164 | /** |
||
165 | * Static fetching of a mock associated with a name or explicit class poser. |
||
166 | * |
||
167 | * @param $name |
||
168 | * |
||
169 | * @return \Mockery\Mock |
||
170 | */ |
||
171 | 11 | public static function fetchMock($name) |
|
175 | |||
176 | /** |
||
177 | * Lazy loader and getter for |
||
178 | * the container property. |
||
179 | * |
||
180 | * @return Mockery\Container |
||
181 | */ |
||
182 | 429 | public static function getContainer() |
|
190 | |||
191 | /** |
||
192 | * Setter for the $_generator static propery. |
||
193 | * |
||
194 | * @param \Mockery\Generator\Generator $generator |
||
195 | */ |
||
196 | public static function setGenerator(Generator $generator) |
||
200 | |||
201 | /** |
||
202 | * Lazy loader method and getter for |
||
203 | * the generator property. |
||
204 | * |
||
205 | * @return Generator |
||
206 | */ |
||
207 | 408 | public static function getGenerator() |
|
215 | |||
216 | /** |
||
217 | * Creates and returns a default generator |
||
218 | * used inside this class. |
||
219 | * |
||
220 | * @return CachingGenerator |
||
221 | */ |
||
222 | 409 | public static function getDefaultGenerator() |
|
223 | { |
||
224 | 409 | $generator = new StringManipulationGenerator(array( |
|
225 | 409 | new CallTypeHintPass(), |
|
226 | 409 | new MagicMethodTypeHintsPass(), |
|
227 | 409 | new ClassPass(), |
|
228 | 409 | new ClassNamePass(), |
|
229 | 409 | new InstanceMockPass(), |
|
230 | 409 | new InterfacePass(), |
|
231 | 409 | new MethodDefinitionPass(), |
|
232 | 409 | new RemoveUnserializeForInternalSerializableClassesPass(), |
|
233 | 409 | new RemoveBuiltinMethodsThatAreFinalPass(), |
|
234 | 409 | new RemoveDestructorPass(), |
|
235 | 409 | )); |
|
236 | |||
237 | 409 | return new CachingGenerator($generator); |
|
238 | } |
||
239 | |||
240 | /** |
||
241 | * Setter for the $_loader static property. |
||
242 | * |
||
243 | * @param Loader $loader |
||
244 | */ |
||
245 | public static function setLoader(Loader $loader) |
||
249 | |||
250 | /** |
||
251 | * Lazy loader method and getter for |
||
252 | * the $_loader property. |
||
253 | * |
||
254 | * @return Loader |
||
255 | */ |
||
256 | 408 | public static function getLoader() |
|
264 | |||
265 | /** |
||
266 | * Gets an EvalLoader to be used as default. |
||
267 | * |
||
268 | * @return EvalLoader |
||
269 | */ |
||
270 | 283 | public static function getDefaultLoader() |
|
274 | |||
275 | /** |
||
276 | * Set the container. |
||
277 | * |
||
278 | * @param \Mockery\Container $container |
||
279 | * |
||
280 | * @return \Mockery\Container |
||
281 | */ |
||
282 | 18 | public static function setContainer(Mockery\Container $container) |
|
286 | |||
287 | /** |
||
288 | * Reset the container to null. |
||
289 | * |
||
290 | * @return void |
||
291 | */ |
||
292 | 15 | public static function resetContainer() |
|
296 | |||
297 | /** |
||
298 | * Return instance of ANY matcher. |
||
299 | * |
||
300 | * @return \Mockery\Matcher\Any |
||
301 | */ |
||
302 | 5 | public static function any() |
|
306 | |||
307 | /** |
||
308 | * Return instance of TYPE matcher. |
||
309 | * |
||
310 | * @param $expected |
||
311 | * |
||
312 | * @return \Mockery\Matcher\Type |
||
313 | */ |
||
314 | 48 | public static function type($expected) |
|
318 | |||
319 | /** |
||
320 | * Return instance of DUCKTYPE matcher. |
||
321 | * |
||
322 | * @return \Mockery\Matcher\Ducktype |
||
323 | */ |
||
324 | 3 | public static function ducktype() |
|
328 | |||
329 | /** |
||
330 | * Return instance of SUBSET matcher. |
||
331 | * |
||
332 | * @param array $part |
||
333 | * |
||
334 | * @return \Mockery\Matcher\Subset |
||
335 | */ |
||
336 | 3 | public static function subset(array $part) |
|
340 | |||
341 | /** |
||
342 | * Return instance of CONTAINS matcher. |
||
343 | * |
||
344 | * @return \Mockery\Matcher\Contains |
||
345 | */ |
||
346 | 3 | public static function contains() |
|
350 | |||
351 | /** |
||
352 | * Return instance of HASKEY matcher. |
||
353 | * |
||
354 | * @param $key |
||
355 | * |
||
356 | * @return \Mockery\Matcher\HasKey |
||
357 | */ |
||
358 | 3 | public static function hasKey($key) |
|
362 | |||
363 | /** |
||
364 | * Return instance of HASVALUE matcher. |
||
365 | * |
||
366 | * @param $val |
||
367 | * |
||
368 | * @return \Mockery\Matcher\HasValue |
||
369 | */ |
||
370 | 3 | public static function hasValue($val) |
|
374 | |||
375 | /** |
||
376 | * Return instance of CLOSURE matcher. |
||
377 | * |
||
378 | * @param $closure |
||
379 | * |
||
380 | * @return \Mockery\Matcher\Closure |
||
381 | */ |
||
382 | 7 | public static function on($closure) |
|
386 | |||
387 | /** |
||
388 | * Return instance of MUSTBE matcher. |
||
389 | * |
||
390 | * @param $expected |
||
391 | * |
||
392 | * @return \Mockery\Matcher\MustBe |
||
393 | */ |
||
394 | 9 | public static function mustBe($expected) |
|
398 | |||
399 | /** |
||
400 | * Return instance of NOT matcher. |
||
401 | * |
||
402 | * @param $expected |
||
403 | * |
||
404 | * @return \Mockery\Matcher\Not |
||
405 | */ |
||
406 | 3 | public static function not($expected) |
|
410 | |||
411 | /** |
||
412 | * Return instance of ANYOF matcher. |
||
413 | * |
||
414 | * @return \Mockery\Matcher\AnyOf |
||
415 | */ |
||
416 | 3 | public static function anyOf() |
|
420 | |||
421 | /** |
||
422 | * Return instance of NOTANYOF matcher. |
||
423 | * |
||
424 | * @return \Mockery\Matcher\NotAnyOf |
||
425 | */ |
||
426 | 3 | public static function notAnyOf() |
|
430 | |||
431 | /** |
||
432 | * Lazy loader and Getter for the global |
||
433 | * configuration container. |
||
434 | * |
||
435 | * @return \Mockery\Configuration |
||
436 | */ |
||
437 | 422 | public static function getConfiguration() |
|
445 | |||
446 | /** |
||
447 | * Utility method to format method name and arguments into a string. |
||
448 | * |
||
449 | * @param string $method |
||
450 | * @param array $arguments |
||
451 | * |
||
452 | * @return string |
||
453 | */ |
||
454 | 94 | public static function formatArgs($method, array $arguments = null) |
|
455 | { |
||
456 | 94 | if (is_null($arguments)) { |
|
457 | return $method . '()'; |
||
458 | } |
||
459 | |||
460 | 94 | $formattedArguments = array(); |
|
461 | 94 | foreach ($arguments as $argument) { |
|
462 | 60 | $formattedArguments[] = self::formatArgument($argument); |
|
463 | 94 | } |
|
464 | |||
465 | 94 | return $method . '(' . implode(', ', $formattedArguments) . ')'; |
|
466 | } |
||
467 | |||
468 | /** |
||
469 | * Gets the string representation |
||
470 | * of any passed argument. |
||
471 | * |
||
472 | * @param $argument |
||
473 | * @param $depth |
||
474 | * |
||
475 | * @return string |
||
476 | */ |
||
477 | 60 | private static function formatArgument($argument, $depth = 0) |
|
478 | { |
||
479 | 60 | if (is_object($argument)) { |
|
480 | 10 | return 'object(' . get_class($argument) . ')'; |
|
481 | } |
||
482 | |||
483 | 54 | if (is_int($argument) || is_float($argument)) { |
|
484 | 36 | return $argument; |
|
485 | } |
||
486 | |||
487 | 27 | if (is_array($argument)) { |
|
488 | 13 | if ($depth === 1) { |
|
489 | 2 | $argument = '[...]'; |
|
490 | 2 | } else { |
|
491 | 13 | $sample = array(); |
|
492 | 13 | foreach ($argument as $key => $value) { |
|
493 | 12 | $key = is_int($key) ? $key : "'$key'"; |
|
494 | 12 | $value = self::formatArgument($value, $depth + 1); |
|
495 | 12 | $sample[] = "$key => $value"; |
|
496 | 13 | } |
|
497 | |||
498 | 13 | $argument = "[".implode(", ", $sample)."]"; |
|
499 | } |
||
500 | |||
501 | 13 | return ((strlen($argument) > 1000) ? substr($argument, 0, 1000).'...]' : $argument); |
|
502 | } |
||
503 | |||
504 | 18 | if (is_bool($argument)) { |
|
505 | 1 | return $argument ? 'true' : 'false'; |
|
506 | } |
||
507 | |||
508 | 17 | if (is_resource($argument)) { |
|
509 | 2 | return 'resource(...)'; |
|
510 | } |
||
511 | |||
512 | 15 | if (is_null($argument)) { |
|
513 | 1 | return 'NULL'; |
|
514 | } |
||
515 | |||
516 | 14 | return "'".(string) $argument."'"; |
|
517 | } |
||
518 | |||
519 | /** |
||
520 | * Utility function to format objects to printable arrays. |
||
521 | * |
||
522 | * @param array $objects |
||
523 | * |
||
524 | * @return string |
||
525 | */ |
||
526 | 54 | public static function formatObjects(array $objects = null) |
|
554 | |||
555 | /** |
||
556 | * Utility function to turn public properties and public get* and is* method values into an array. |
||
557 | * |
||
558 | * @param $object |
||
559 | * @param int $nesting |
||
560 | * |
||
561 | * @return array |
||
562 | */ |
||
563 | 7 | private static function objectToArray($object, $nesting = 3) |
|
575 | |||
576 | /** |
||
577 | * Returns all public instance properties. |
||
578 | * |
||
579 | * @param $object |
||
580 | * @param $nesting |
||
581 | * |
||
582 | * @return array |
||
583 | */ |
||
584 | 7 | private static function extractInstancePublicProperties($object, $nesting) |
|
599 | |||
600 | /** |
||
601 | * Returns all object getters. |
||
602 | * |
||
603 | * @param $object |
||
604 | * @param $nesting |
||
605 | * |
||
606 | * @return array |
||
607 | */ |
||
608 | 7 | private static function extractGetters($object, $nesting) |
|
633 | |||
634 | /** |
||
635 | * Utility method used for recursively generating |
||
636 | * an object or array representation. |
||
637 | * |
||
638 | * @param $argument |
||
639 | * @param $nesting |
||
640 | * |
||
641 | * @return mixed |
||
642 | */ |
||
643 | 1 | private static function cleanupNesting($argument, $nesting) |
|
658 | |||
659 | /** |
||
660 | * Utility method for recursively |
||
661 | * gerating a representation |
||
662 | * of the given array. |
||
663 | * |
||
664 | * @param array $argument |
||
665 | * @param int $nesting |
||
666 | * |
||
667 | * @return mixed |
||
668 | */ |
||
669 | 1 | private static function cleanupArray($argument, $nesting = 3) |
|
685 | |||
686 | /** |
||
687 | * Utility function to parse shouldReceive() arguments and generate |
||
688 | * expectations from such as needed. |
||
689 | * |
||
690 | * @param Mockery\MockInterface $mock |
||
691 | * @param array $args |
||
692 | * @param callable $add |
||
693 | * @return \Mockery\CompositeExpectation |
||
694 | */ |
||
695 | 309 | public static function parseShouldReturnArgs(\Mockery\MockInterface $mock, $args, $add) |
|
713 | |||
714 | /** |
||
715 | * Sets up expectations on the members of the CompositeExpectation and |
||
716 | * builds up any demeter chain that was passed to shouldReceive. |
||
717 | * |
||
718 | * @param \Mockery\MockInterface $mock |
||
719 | * @param string $arg |
||
720 | * @param callable $add |
||
721 | * @throws Mockery\Exception |
||
722 | * @return \Mockery\ExpectationInterface |
||
723 | */ |
||
724 | 309 | protected static function buildDemeterChain(\Mockery\MockInterface $mock, $arg, $add) |
|
725 | { |
||
726 | /** @var Mockery\Container $container */ |
||
727 | 309 | $container = $mock->mockery_getContainer(); |
|
728 | 309 | $methodNames = explode('->', $arg); |
|
729 | 309 | reset($methodNames); |
|
730 | |||
731 | 309 | if (!\Mockery::getConfiguration()->mockingNonExistentMethodsAllowed() |
|
732 | 309 | && !$mock->mockery_isAnonymous() |
|
733 | 309 | && !in_array(current($methodNames), $mock->mockery_getMockableMethods()) |
|
734 | 309 | ) { |
|
735 | 4 | throw new \Mockery\Exception( |
|
736 | 'Mockery\'s configuration currently forbids mocking the method ' |
||
737 | 4 | . current($methodNames) . ' as it does not exist on the class or object ' |
|
738 | 4 | . 'being mocked' |
|
739 | 4 | ); |
|
740 | } |
||
741 | |||
742 | /** @var ExpectationInterface|null $expectations */ |
||
743 | 305 | $expectations = null; |
|
744 | |||
745 | /** @var Callable $nextExp */ |
||
746 | $nextExp = function ($method) use ($add) { |
||
747 | 305 | return $add($method); |
|
748 | 305 | }; |
|
749 | |||
750 | 305 | while (true) { |
|
751 | 305 | $method = array_shift($methodNames); |
|
752 | 305 | $expectations = $mock->mockery_getExpectationsFor($method); |
|
753 | |||
754 | 305 | if (is_null($expectations) || self::noMoreElementsInChain($methodNames)) { |
|
755 | 305 | $expectations = $nextExp($method); |
|
756 | 303 | if (self::noMoreElementsInChain($methodNames)) { |
|
757 | 303 | break; |
|
758 | } |
||
759 | |||
760 | 11 | $mock = self::getNewDemeterMock($container, $method, $expectations); |
|
761 | 11 | } else { |
|
762 | 5 | $demeterMockKey = $container->getKeyOfDemeterMockFor($method); |
|
763 | 5 | if ($demeterMockKey) { |
|
764 | 5 | $mock = self::getExistingDemeterMock($container, $demeterMockKey); |
|
765 | 5 | } |
|
766 | } |
||
767 | |||
768 | 11 | $nextExp = function ($n) use ($mock) { |
|
769 | 11 | return $mock->shouldReceive($n); |
|
770 | 11 | }; |
|
771 | 11 | } |
|
772 | |||
773 | 303 | return $expectations; |
|
774 | } |
||
775 | |||
776 | /** |
||
777 | * Gets a new demeter configured |
||
778 | * mock from the container. |
||
779 | * |
||
780 | * @param \Mockery\Container $container |
||
781 | * @param string $method |
||
782 | * @param Mockery\ExpectationInterface $exp |
||
783 | * |
||
784 | * @return \Mockery\Mock |
||
785 | */ |
||
786 | 11 | private static function getNewDemeterMock( |
|
787 | Mockery\Container $container, |
||
788 | $method, |
||
789 | Mockery\ExpectationInterface $exp |
||
790 | ) { |
||
791 | 11 | $mock = $container->mock('demeter_' . $method); |
|
792 | 11 | $exp->andReturn($mock); |
|
793 | |||
794 | 11 | return $mock; |
|
795 | } |
||
796 | |||
797 | /** |
||
798 | * Gets an specific demeter mock from |
||
799 | * the ones kept by the container. |
||
800 | * |
||
801 | * @param \Mockery\Container $container |
||
802 | * @param string $demeterMockKey |
||
803 | * |
||
804 | * @return mixed |
||
805 | */ |
||
806 | 5 | private static function getExistingDemeterMock( |
|
807 | Mockery\Container $container, |
||
808 | $demeterMockKey |
||
809 | ) { |
||
810 | 5 | $mocks = $container->getMocks(); |
|
811 | 5 | $mock = $mocks[$demeterMockKey]; |
|
812 | |||
813 | 5 | return $mock; |
|
814 | } |
||
815 | |||
816 | /** |
||
817 | * Checks if the passed array representing a demeter |
||
818 | * chain with the method names is empty. |
||
819 | * |
||
820 | * @param array $methodNames |
||
821 | * |
||
822 | * @return bool |
||
823 | */ |
||
824 | 303 | private static function noMoreElementsInChain(array $methodNames) |
|
828 | |||
829 | 17 | public static function declareClass($fqn) |
|
830 | { |
||
831 | 17 | return static::declareType($fqn, "class"); |
|
832 | } |
||
833 | |||
834 | 2 | public static function declareInterface($fqn) |
|
835 | { |
||
838 | |||
839 | 18 | private static function declareType($fqn, $type) |
|
865 | |||
866 | /** |
||
867 | * Register a file to be deleted on tearDown. |
||
868 | * |
||
869 | * @param string $fileName |
||
870 | */ |
||
871 | 18 | public static function registerFileForCleanUp($fileName) |
|
875 | } |
||
876 |
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.