Complex classes like LazyLoadingGhostFunctionalTest 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 LazyLoadingGhostFunctionalTest, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
63 | class LazyLoadingGhostFunctionalTest extends PHPUnit_Framework_TestCase |
||
64 | { |
||
65 | /** |
||
66 | * @dataProvider getProxyInitializingMethods |
||
67 | * |
||
68 | * @param string $className |
||
69 | * @param object $instance |
||
70 | * @param string $method |
||
71 | * @param mixed[] $params |
||
72 | * @param mixed $expectedValue |
||
73 | */ |
||
74 | public function testMethodCallsThatLazyLoadTheObject( |
||
75 | string $className, |
||
76 | $instance, |
||
77 | string $method, |
||
78 | array $params, |
||
79 | $expectedValue |
||
80 | ) : void { |
||
81 | $proxyName = $this->generateProxy($className); |
||
82 | |||
83 | /* @var $proxy GhostObjectInterface */ |
||
84 | $proxy = $proxyName::staticProxyConstructor($this->createInitializer($className, $instance)); |
||
85 | |||
86 | self::assertFalse($proxy->isProxyInitialized()); |
||
87 | |||
88 | /* @var $callProxyMethod callable */ |
||
89 | $callProxyMethod = [$proxy, $method]; |
||
90 | $parameterValues = array_values($params); |
||
91 | |||
92 | self::assertInternalType('callable', $callProxyMethod); |
||
93 | self::assertSame($expectedValue, $callProxyMethod(...$parameterValues)); |
||
94 | self::assertTrue($proxy->isProxyInitialized()); |
||
95 | } |
||
96 | |||
97 | /** |
||
98 | * @dataProvider getProxyNonInitializingMethods |
||
99 | * |
||
100 | * @param string $className |
||
101 | * @param object $instance |
||
102 | * @param string $method |
||
103 | * @param mixed[] $params |
||
104 | * @param mixed $expectedValue |
||
105 | */ |
||
106 | public function testMethodCallsThatDoNotLazyLoadTheObject( |
||
107 | string $className, |
||
108 | $instance, |
||
109 | string $method, |
||
110 | array $params, |
||
111 | $expectedValue |
||
112 | ) : void { |
||
113 | $proxyName = $this->generateProxy($className); |
||
114 | $initializeMatcher = $this->getMockBuilder(stdClass::class)->setMethods(['__invoke'])->getMock(); |
||
115 | |||
116 | $initializeMatcher->expects(self::never())->method('__invoke'); // should not initialize the proxy |
||
117 | |||
118 | /* @var $proxy GhostObjectInterface */ |
||
119 | $proxy = $proxyName::staticProxyConstructor( |
||
120 | $this->createInitializer($className, $instance, $initializeMatcher) |
||
121 | ); |
||
122 | |||
123 | self::assertFalse($proxy->isProxyInitialized()); |
||
124 | |||
125 | /* @var $callProxyMethod callable */ |
||
126 | $callProxyMethod = [$proxy, $method]; |
||
127 | $parameterValues = array_values($params); |
||
128 | |||
129 | self::assertInternalType('callable', $callProxyMethod); |
||
130 | self::assertSame($expectedValue, $callProxyMethod(...$parameterValues)); |
||
131 | self::assertFalse($proxy->isProxyInitialized()); |
||
132 | } |
||
133 | |||
134 | /** |
||
135 | * @dataProvider getProxyMethods |
||
136 | * |
||
137 | * @param string $className |
||
138 | * @param object $instance |
||
139 | * @param string $method |
||
140 | * @param mixed[] $params |
||
141 | * @param mixed $expectedValue |
||
142 | */ |
||
143 | public function testMethodCallsAfterUnSerialization( |
||
144 | string $className, |
||
145 | $instance, |
||
146 | string $method, |
||
147 | array $params, |
||
148 | $expectedValue |
||
149 | ) : void { |
||
150 | $proxyName = $this->generateProxy($className); |
||
151 | |||
152 | /* @var $proxy GhostObjectInterface */ |
||
153 | $proxy = unserialize(serialize($proxyName::staticProxyConstructor( |
||
154 | $this->createInitializer($className, $instance) |
||
155 | ))); |
||
156 | |||
157 | self::assertTrue($proxy->isProxyInitialized()); |
||
158 | |||
159 | /* @var $callProxyMethod callable */ |
||
160 | $callProxyMethod = [$proxy, $method]; |
||
161 | $parameterValues = array_values($params); |
||
162 | |||
163 | self::assertInternalType('callable', $callProxyMethod); |
||
164 | self::assertSame($expectedValue, $callProxyMethod(...$parameterValues)); |
||
165 | } |
||
166 | |||
167 | /** |
||
168 | * @dataProvider getProxyMethods |
||
169 | * |
||
170 | * @param string $className |
||
171 | * @param object $instance |
||
172 | * @param string $method |
||
173 | * @param mixed[] $params |
||
174 | * @param mixed $expectedValue |
||
175 | */ |
||
176 | public function testMethodCallsAfterCloning( |
||
177 | string $className, |
||
178 | $instance, |
||
179 | string $method, |
||
180 | array $params, |
||
181 | $expectedValue |
||
182 | ) : void { |
||
183 | $proxyName = $this->generateProxy($className); |
||
184 | |||
185 | /* @var $proxy GhostObjectInterface */ |
||
186 | $proxy = $proxyName::staticProxyConstructor($this->createInitializer($className, $instance)); |
||
187 | $cloned = clone $proxy; |
||
188 | |||
189 | self::assertTrue($cloned->isProxyInitialized()); |
||
190 | |||
191 | /* @var $callProxyMethod callable */ |
||
192 | $callProxyMethod = [$proxy, $method]; |
||
193 | $parameterValues = array_values($params); |
||
194 | |||
195 | self::assertInternalType('callable', $callProxyMethod); |
||
196 | self::assertSame($expectedValue, $callProxyMethod(...$parameterValues)); |
||
197 | } |
||
198 | |||
199 | /** |
||
200 | * @dataProvider getPropertyAccessProxies |
||
201 | * |
||
202 | * @param object $instance |
||
203 | * @param GhostObjectInterface $proxy |
||
204 | * @param string $publicProperty |
||
205 | * @param mixed $propertyValue |
||
206 | */ |
||
207 | public function testPropertyReadAccess( |
||
208 | $instance, |
||
|
|||
209 | GhostObjectInterface $proxy, |
||
210 | string $publicProperty, |
||
211 | $propertyValue |
||
212 | ) : void { |
||
213 | self::assertSame($propertyValue, $proxy->$publicProperty); |
||
214 | self::assertTrue($proxy->isProxyInitialized()); |
||
215 | } |
||
216 | |||
217 | /** |
||
218 | * @dataProvider getPropertyAccessProxies |
||
219 | * |
||
220 | * @param object $instance |
||
221 | * @param GhostObjectInterface $proxy |
||
222 | * @param string $publicProperty |
||
223 | */ |
||
224 | public function testPropertyWriteAccess($instance, GhostObjectInterface $proxy, string $publicProperty) : void |
||
232 | |||
233 | /** |
||
234 | * @dataProvider getPropertyAccessProxies |
||
235 | * |
||
236 | * @param object $instance |
||
237 | * @param GhostObjectInterface $proxy |
||
238 | * @param string $publicProperty |
||
239 | */ |
||
240 | public function testPropertyExistence($instance, GhostObjectInterface $proxy, string $publicProperty) : void |
||
245 | |||
246 | /** |
||
247 | * @dataProvider getPropertyAccessProxies |
||
248 | * |
||
249 | * @param object $instance |
||
250 | * @param GhostObjectInterface $proxy |
||
251 | * @param string $publicProperty |
||
252 | */ |
||
253 | public function testPropertyAbsence($instance, GhostObjectInterface $proxy, string $publicProperty) : void |
||
259 | |||
260 | /** |
||
261 | * @dataProvider getPropertyAccessProxies |
||
262 | * |
||
263 | * @param object $instance |
||
264 | * @param GhostObjectInterface $proxy |
||
265 | * @param string $publicProperty |
||
266 | */ |
||
267 | public function testPropertyUnset($instance, GhostObjectInterface $proxy, string $publicProperty) : void |
||
275 | |||
276 | /** |
||
277 | * Verifies that accessing a public property containing an array behaves like in a normal context |
||
278 | */ |
||
279 | public function testCanWriteToArrayKeysInPublicProperty() : void |
||
296 | |||
297 | /** |
||
298 | * Verifies that public properties retrieved via `__get` don't get modified in the object itself |
||
299 | */ |
||
300 | public function testWillNotModifyRetrievedPublicProperties() : void |
||
317 | |||
318 | /** |
||
319 | * Verifies that public properties references retrieved via `__get` modify in the object state |
||
320 | */ |
||
321 | public function testWillModifyByRefRetrievedPublicProperties() : void |
||
338 | |||
339 | public function testKeepsInitializerWhenNotOverwitten() : void |
||
352 | |||
353 | /** |
||
354 | * Verifies that public properties are not being initialized multiple times |
||
355 | */ |
||
356 | public function testKeepsInitializedPublicProperties() : void |
||
376 | |||
377 | /** |
||
378 | * Verifies that properties' default values are preserved |
||
379 | */ |
||
380 | public function testPublicPropertyDefaultWillBePreserved() : void |
||
390 | |||
391 | /** |
||
392 | * Verifies that protected properties' default values are preserved |
||
393 | */ |
||
394 | public function testProtectedPropertyDefaultWillBePreserved() : void |
||
408 | |||
409 | /** |
||
410 | * Verifies that private properties' default values are preserved |
||
411 | */ |
||
412 | public function testPrivatePropertyDefaultWillBePreserved() : void |
||
426 | |||
427 | /** |
||
428 | * @group 159 |
||
429 | * @group 192 |
||
430 | */ |
||
431 | public function testMultiLevelPrivatePropertiesDefaultsWillBePreserved() : void |
||
448 | |||
449 | /** |
||
450 | * @group 159 |
||
451 | * @group 192 |
||
452 | */ |
||
453 | public function testMultiLevelPrivatePropertiesByRefInitialization() : void |
||
475 | |||
476 | /** |
||
477 | * @group 159 |
||
478 | * @group 192 |
||
479 | * |
||
480 | * Test designed to verify that the cached logic does take into account the fact that |
||
481 | * proxies are different instances |
||
482 | */ |
||
483 | public function testGetPropertyFromDifferentProxyInstances() : void |
||
517 | |||
518 | /** |
||
519 | * @group 159 |
||
520 | * @group 192 |
||
521 | * |
||
522 | * Test designed to verify that the cached logic does take into account the fact that |
||
523 | * proxies are different instances |
||
524 | */ |
||
525 | public function testSetPrivatePropertyOnDifferentProxyInstances() : void |
||
548 | |||
549 | /** |
||
550 | * @group 159 |
||
551 | * @group 192 |
||
552 | * |
||
553 | * Test designed to verify that the cached logic does take into account the fact that |
||
554 | * proxies are different instances |
||
555 | */ |
||
556 | public function testIssetPrivatePropertyOnDifferentProxyInstances() : void |
||
580 | |||
581 | /** |
||
582 | * @group 159 |
||
583 | * @group 192 |
||
584 | * |
||
585 | * Test designed to verify that the cached logic does take into account the fact that |
||
586 | * proxies are different instances |
||
587 | */ |
||
588 | public function testUnsetPrivatePropertyOnDifferentProxyInstances() : void |
||
614 | |||
615 | /** |
||
616 | * @group 159 |
||
617 | * @group 192 |
||
618 | * |
||
619 | * Test designed to verify that the cached logic does take into account the fact that |
||
620 | * proxies are different instances |
||
621 | */ |
||
622 | public function testIssetPrivateAndProtectedPropertiesDoesCheckAgainstBooleanFalse() : void |
||
654 | |||
655 | public function testByRefInitialization() : void |
||
682 | |||
683 | /** |
||
684 | * @group 115 |
||
685 | * @group 175 |
||
686 | */ |
||
687 | public function testWillBehaveLikeObjectWithNormalConstructor() : void |
||
708 | |||
709 | public function testInitializeProxyWillReturnTrueOnSuccessfulInitialization() : void |
||
723 | |||
724 | /** |
||
725 | * Generates a proxy for the given class name, and retrieves its class name |
||
726 | * |
||
727 | * @param string $parentClassName |
||
728 | * @param mixed[] $proxyOptions |
||
729 | * |
||
730 | * @return string |
||
731 | */ |
||
732 | private function generateProxy(string $parentClassName, array $proxyOptions = []) : string |
||
746 | |||
747 | /** |
||
748 | * @param string $className |
||
749 | * @param object $realInstance |
||
750 | * @param Mock $initializerMatcher |
||
751 | * |
||
752 | * @return callable |
||
753 | */ |
||
754 | private function createInitializer(string $className, $realInstance, Mock $initializerMatcher = null) : callable |
||
791 | |||
792 | /** |
||
793 | * Generates a list of object | invoked method | parameters | expected result |
||
794 | * |
||
795 | * @return array |
||
796 | */ |
||
797 | public function getProxyMethods() : array |
||
854 | |||
855 | /** |
||
856 | * Generates a list of object | invoked method | parameters | expected result for methods that cause lazy-loading |
||
857 | * of a ghost object |
||
858 | * |
||
859 | * @return array |
||
860 | */ |
||
861 | public function getProxyInitializingMethods() : array |
||
894 | |||
895 | /** |
||
896 | * Generates a list of object | invoked method | parameters | expected result for methods DON'T cause lazy-loading |
||
897 | * |
||
898 | * @return array |
||
899 | */ |
||
900 | public function getProxyNonInitializingMethods() : array |
||
904 | |||
905 | /** |
||
906 | * Generates proxies and instances with a public property to feed to the property accessor methods |
||
907 | * |
||
908 | * @return array |
||
909 | */ |
||
910 | public function getPropertyAccessProxies() : array |
||
934 | |||
935 | /** |
||
936 | * @dataProvider skipPropertiesFixture |
||
937 | * |
||
938 | * @param string $className |
||
939 | * @param string $propertyClass |
||
940 | * @param string $propertyName |
||
941 | * @param mixed $expected |
||
942 | * @param mixed[] $proxyOptions |
||
943 | */ |
||
944 | public function testInitializationIsSkippedForSkippedProperties( |
||
945 | string $className, |
||
946 | string $propertyClass, |
||
947 | string $propertyName, |
||
948 | array $proxyOptions, |
||
949 | $expected |
||
950 | ) : void { |
||
951 | $proxy = $this->generateProxy($className, $proxyOptions); |
||
952 | $ghostObject = $proxy::staticProxyConstructor(function () use ($propertyName) { |
||
953 | self::fail(sprintf('The Property "%s" was not expected to be lazy-loaded', $propertyName)); |
||
954 | }); |
||
955 | |||
956 | $property = new ReflectionProperty($propertyClass, $propertyName); |
||
957 | $property->setAccessible(true); |
||
958 | |||
959 | self::assertSame($expected, $property->getValue($ghostObject)); |
||
960 | } |
||
961 | |||
962 | /** |
||
963 | * @dataProvider skipPropertiesFixture |
||
964 | * |
||
965 | * @param string $className |
||
966 | * @param string $propertyClass |
||
967 | * @param string $propertyName |
||
968 | * @param mixed[] $proxyOptions |
||
969 | */ |
||
970 | public function testSkippedPropertiesAreNotOverwrittenOnInitialization( |
||
971 | string $className, |
||
972 | string $propertyClass, |
||
973 | string $propertyName, |
||
974 | array $proxyOptions |
||
975 | ) : void { |
||
976 | $proxyName = $this->generateProxy($className, $proxyOptions); |
||
977 | /* @var $ghostObject GhostObjectInterface */ |
||
978 | $ghostObject = $proxyName::staticProxyConstructor( |
||
979 | function ($proxy, string $method, $params, & $initializer) : bool { |
||
980 | $initializer = null; |
||
981 | |||
982 | return true; |
||
983 | } |
||
984 | ); |
||
985 | |||
986 | $property = new ReflectionProperty($propertyClass, $propertyName); |
||
987 | |||
988 | $property->setAccessible(true); |
||
989 | |||
990 | $value = uniqid('', true); |
||
991 | |||
992 | $property->setValue($ghostObject, $value); |
||
993 | |||
994 | self::assertTrue($ghostObject->initializeProxy()); |
||
995 | |||
996 | self::assertSame( |
||
997 | $value, |
||
998 | $property->getValue($ghostObject), |
||
999 | 'Property should not be changed by proxy initialization' |
||
1000 | ); |
||
1001 | } |
||
1002 | |||
1003 | /** |
||
1004 | * @group 265 |
||
1005 | */ |
||
1006 | public function testWillForwardVariadicByRefArguments() : void |
||
1023 | |||
1024 | /** |
||
1025 | * @group 265 |
||
1026 | */ |
||
1027 | public function testWillForwardDynamicArguments() : void |
||
1038 | |||
1039 | |||
1040 | /** |
||
1041 | * @return mixed[] in order: |
||
1042 | * - the class to be proxied |
||
1043 | * - the class owning the property to be checked |
||
1044 | * - the property name |
||
1045 | * - the options to be passed to the generator |
||
1046 | * - the expected value of the property |
||
1047 | */ |
||
1048 | public function skipPropertiesFixture() : array |
||
1104 | |||
1105 | /** |
||
1106 | * @group 276 |
||
1107 | * |
||
1108 | * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope |
||
1109 | * |
||
1110 | * @param object $callerObject |
||
1111 | * @param string $method |
||
1112 | * @param string $propertyIndex |
||
1113 | * @param string $expectedValue |
||
1114 | */ |
||
1115 | public function testWillLazyLoadMembersOfOtherProxiesWithTheSamePrivateScope( |
||
1116 | $callerObject, |
||
1117 | string $method, |
||
1118 | string $propertyIndex, |
||
1119 | string $expectedValue |
||
1120 | ) : void { |
||
1121 | $proxyName = $this->generateProxy(get_class($callerObject)); |
||
1122 | /* @var $proxy OtherObjectAccessClass|LazyLoadingInterface */ |
||
1123 | $proxy = $proxyName::staticProxyConstructor( |
||
1124 | function ($proxy, $method, $params, & $initializer, array $props) use ($propertyIndex, $expectedValue) { |
||
1125 | $initializer = null; |
||
1126 | |||
1127 | $props[$propertyIndex] = $expectedValue; |
||
1128 | } |
||
1129 | ); |
||
1130 | |||
1131 | /* @var $accessor callable */ |
||
1132 | $accessor = [$callerObject, $method]; |
||
1133 | |||
1134 | self::assertFalse($proxy->isProxyInitialized()); |
||
1135 | self::assertSame($expectedValue, $accessor($proxy)); |
||
1136 | self::assertTrue($proxy->isProxyInitialized()); |
||
1137 | } |
||
1138 | |||
1139 | /** |
||
1140 | * @group 276 |
||
1141 | * |
||
1142 | * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope |
||
1143 | * |
||
1144 | * @param object $callerObject |
||
1145 | * @param string $method |
||
1146 | * @param string $propertyIndex |
||
1147 | * @param string $expectedValue |
||
1148 | */ |
||
1149 | public function testWillAccessMembersOfOtherDeSerializedProxiesWithTheSamePrivateScope( |
||
1150 | $callerObject, |
||
1151 | string $method, |
||
1152 | string $propertyIndex, |
||
1153 | string $expectedValue |
||
1154 | ) : void { |
||
1155 | $proxyName = $this->generateProxy(get_class($callerObject)); |
||
1156 | /* @var $proxy OtherObjectAccessClass|LazyLoadingInterface */ |
||
1157 | $proxy = unserialize(serialize($proxyName::staticProxyConstructor( |
||
1158 | function ($proxy, $method, $params, & $initializer, array $props) use ($propertyIndex, $expectedValue) { |
||
1159 | $initializer = null; |
||
1160 | |||
1161 | $props[$propertyIndex] = $expectedValue; |
||
1162 | } |
||
1163 | ))); |
||
1164 | |||
1165 | /* @var $accessor callable */ |
||
1166 | $accessor = [$callerObject, $method]; |
||
1167 | |||
1168 | self::assertTrue($proxy->isProxyInitialized()); |
||
1169 | self::assertSame($expectedValue, $accessor($proxy)); |
||
1170 | } |
||
1171 | |||
1172 | /** |
||
1173 | * @group 276 |
||
1174 | * |
||
1175 | * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope |
||
1176 | * |
||
1177 | * @param object $callerObject |
||
1178 | * @param string $method |
||
1179 | * @param string $propertyIndex |
||
1180 | * @param string $expectedValue |
||
1181 | */ |
||
1182 | public function testWillAccessMembersOfOtherClonedProxiesWithTheSamePrivateScope( |
||
1183 | $callerObject, |
||
1184 | string $method, |
||
1185 | string $propertyIndex, |
||
1186 | string $expectedValue |
||
1187 | ) : void { |
||
1188 | $proxyName = $this->generateProxy(get_class($callerObject)); |
||
1189 | /* @var $proxy OtherObjectAccessClass|LazyLoadingInterface */ |
||
1190 | $proxy = clone $proxyName::staticProxyConstructor( |
||
1191 | function ($proxy, $method, $params, & $initializer, array $props) use ($propertyIndex, $expectedValue) { |
||
1192 | $initializer = null; |
||
1193 | |||
1194 | $props[$propertyIndex] = $expectedValue; |
||
1195 | } |
||
1196 | ); |
||
1197 | |||
1198 | /* @var $accessor callable */ |
||
1199 | $accessor = [$callerObject, $method]; |
||
1200 | |||
1201 | self::assertTrue($proxy->isProxyInitialized()); |
||
1202 | self::assertSame($expectedValue, $accessor($proxy)); |
||
1203 | } |
||
1204 | |||
1205 | public function getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope() : array |
||
1254 | |||
1255 | /** |
||
1256 | * @group 276 |
||
1257 | */ |
||
1258 | public function testFriendObjectWillNotCauseLazyLoadingOnSkippedProperty() : void |
||
1298 | |||
1299 | public function testClonedSkippedPropertiesArePreserved() : void |
||
1346 | |||
1347 | /** |
||
1348 | * @group 327 |
||
1349 | */ |
||
1350 | public function testWillExecuteLogicInAVoidMethod() : void |
||
1373 | } |
||
1374 |
This check looks from parameters that have been defined for a function or method, but which are not used in the method body.