ClassMirrorTest.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. <?php
  2. namespace Tests\Prophecy\Doubler\Generator;
  3. use PHPUnit\Framework\TestCase;
  4. use Prophecy\Doubler\Generator\ClassMirror;
  5. class ClassMirrorTest extends TestCase
  6. {
  7. /**
  8. * @test
  9. */
  10. public function it_reflects_allowed_magic_methods()
  11. {
  12. $class = new \ReflectionClass('Fixtures\Prophecy\SpecialMethods');
  13. $mirror = new ClassMirror();
  14. $node = $mirror->reflect($class, array());
  15. $this->assertCount(7, $node->getMethods());
  16. }
  17. /**
  18. * @test
  19. */
  20. public function it_reflects_protected_abstract_methods()
  21. {
  22. $class = new \ReflectionClass('Fixtures\Prophecy\WithProtectedAbstractMethod');
  23. $mirror = new ClassMirror();
  24. $classNode = $mirror->reflect($class, array());
  25. $this->assertEquals('Fixtures\Prophecy\WithProtectedAbstractMethod', $classNode->getParentClass());
  26. $methodNodes = $classNode->getMethods();
  27. $this->assertCount(1, $methodNodes);
  28. $this->assertEquals('protected', $methodNodes['innerDetail']->getVisibility());
  29. }
  30. /**
  31. * @test
  32. */
  33. public function it_reflects_public_static_methods()
  34. {
  35. $class = new \ReflectionClass('Fixtures\Prophecy\WithStaticMethod');
  36. $mirror = new ClassMirror();
  37. $classNode = $mirror->reflect($class, array());
  38. $this->assertEquals('Fixtures\Prophecy\WithStaticMethod', $classNode->getParentClass());
  39. $methodNodes = $classNode->getMethods();
  40. $this->assertCount(1, $methodNodes);
  41. $this->assertTrue($methodNodes['innerDetail']->isStatic());
  42. }
  43. /**
  44. * @test
  45. */
  46. public function it_marks_required_args_without_types_as_not_optional()
  47. {
  48. $class = new \ReflectionClass('Fixtures\Prophecy\WithArguments');
  49. $mirror = new ClassMirror();
  50. $classNode = $mirror->reflect($class, array());
  51. $methodNode = $classNode->getMethod('methodWithoutTypeHints');
  52. $argNodes = $methodNode->getArguments();
  53. $this->assertCount(1, $argNodes);
  54. $this->assertEquals('arg', $argNodes[0]->getName());
  55. $this->assertNull($argNodes[0]->getTypeHint());
  56. $this->assertFalse($argNodes[0]->isOptional());
  57. $this->assertNull($argNodes[0]->getDefault());
  58. $this->assertFalse($argNodes[0]->isPassedByReference());
  59. $this->assertFalse($argNodes[0]->isVariadic());
  60. }
  61. /**
  62. * @test
  63. */
  64. public function it_properly_reads_methods_arguments_with_types()
  65. {
  66. $class = new \ReflectionClass('Fixtures\Prophecy\WithArguments');
  67. $mirror = new ClassMirror();
  68. $classNode = $mirror->reflect($class, array());
  69. $methodNode = $classNode->getMethod('methodWithArgs');
  70. $argNodes = $methodNode->getArguments();
  71. $this->assertCount(3, $argNodes);
  72. $this->assertEquals('arg_1', $argNodes[0]->getName());
  73. $this->assertEquals('array', $argNodes[0]->getTypeHint());
  74. $this->assertTrue($argNodes[0]->isOptional());
  75. $this->assertEquals(array(), $argNodes[0]->getDefault());
  76. $this->assertFalse($argNodes[0]->isPassedByReference());
  77. $this->assertFalse($argNodes[0]->isVariadic());
  78. $this->assertEquals('arg_2', $argNodes[1]->getName());
  79. $this->assertEquals('ArrayAccess', $argNodes[1]->getTypeHint());
  80. $this->assertFalse($argNodes[1]->isOptional());
  81. $this->assertEquals('arg_3', $argNodes[2]->getName());
  82. $this->assertEquals('ArrayAccess', $argNodes[2]->getTypeHint());
  83. $this->assertTrue($argNodes[2]->isOptional());
  84. $this->assertNull($argNodes[2]->getDefault());
  85. $this->assertFalse($argNodes[2]->isPassedByReference());
  86. $this->assertFalse($argNodes[2]->isVariadic());
  87. }
  88. /**
  89. * @test
  90. * @requires PHP 5.4
  91. */
  92. public function it_properly_reads_methods_arguments_with_callable_types()
  93. {
  94. $class = new \ReflectionClass('Fixtures\Prophecy\WithCallableArgument');
  95. $mirror = new ClassMirror();
  96. $classNode = $mirror->reflect($class, array());
  97. $methodNode = $classNode->getMethod('methodWithArgs');
  98. $argNodes = $methodNode->getArguments();
  99. $this->assertCount(2, $argNodes);
  100. $this->assertEquals('arg_1', $argNodes[0]->getName());
  101. $this->assertEquals('callable', $argNodes[0]->getTypeHint());
  102. $this->assertFalse($argNodes[0]->isOptional());
  103. $this->assertFalse($argNodes[0]->isPassedByReference());
  104. $this->assertFalse($argNodes[0]->isVariadic());
  105. $this->assertEquals('arg_2', $argNodes[1]->getName());
  106. $this->assertEquals('callable', $argNodes[1]->getTypeHint());
  107. $this->assertTrue($argNodes[1]->isOptional());
  108. $this->assertNull($argNodes[1]->getDefault());
  109. $this->assertFalse($argNodes[1]->isPassedByReference());
  110. $this->assertFalse($argNodes[1]->isVariadic());
  111. }
  112. /**
  113. * @test
  114. * @requires PHP 5.6
  115. */
  116. public function it_properly_reads_methods_variadic_arguments()
  117. {
  118. $class = new \ReflectionClass('Fixtures\Prophecy\WithVariadicArgument');
  119. $mirror = new ClassMirror();
  120. $classNode = $mirror->reflect($class, array());
  121. $methodNode = $classNode->getMethod('methodWithArgs');
  122. $argNodes = $methodNode->getArguments();
  123. $this->assertCount(1, $argNodes);
  124. $this->assertEquals('args', $argNodes[0]->getName());
  125. $this->assertNull($argNodes[0]->getTypeHint());
  126. $this->assertFalse($argNodes[0]->isOptional());
  127. $this->assertFalse($argNodes[0]->isPassedByReference());
  128. $this->assertTrue($argNodes[0]->isVariadic());
  129. }
  130. /**
  131. * @test
  132. * @requires PHP 5.6
  133. */
  134. public function it_properly_reads_methods_typehinted_variadic_arguments()
  135. {
  136. if (defined('HHVM_VERSION_ID')) {
  137. $this->markTestSkipped('HHVM does not support typehints on variadic arguments.');
  138. }
  139. $class = new \ReflectionClass('Fixtures\Prophecy\WithTypehintedVariadicArgument');
  140. $mirror = new ClassMirror();
  141. $classNode = $mirror->reflect($class, array());
  142. $methodNode = $classNode->getMethod('methodWithTypeHintedArgs');
  143. $argNodes = $methodNode->getArguments();
  144. $this->assertCount(1, $argNodes);
  145. $this->assertEquals('args', $argNodes[0]->getName());
  146. $this->assertEquals('array', $argNodes[0]->getTypeHint());
  147. $this->assertFalse($argNodes[0]->isOptional());
  148. $this->assertFalse($argNodes[0]->isPassedByReference());
  149. $this->assertTrue($argNodes[0]->isVariadic());
  150. }
  151. /**
  152. * @test
  153. */
  154. public function it_marks_passed_by_reference_args_as_passed_by_reference()
  155. {
  156. $class = new \ReflectionClass('Fixtures\Prophecy\WithReferences');
  157. $mirror = new ClassMirror();
  158. $classNode = $mirror->reflect($class, array());
  159. $this->assertTrue($classNode->hasMethod('methodWithReferenceArgument'));
  160. $argNodes = $classNode->getMethod('methodWithReferenceArgument')->getArguments();
  161. $this->assertCount(2, $argNodes);
  162. $this->assertTrue($argNodes[0]->isPassedByReference());
  163. $this->assertTrue($argNodes[1]->isPassedByReference());
  164. }
  165. /**
  166. * @test
  167. * @expectedException Prophecy\Exception\Doubler\ClassMirrorException
  168. */
  169. public function it_throws_an_exception_if_class_is_final()
  170. {
  171. $class = new \ReflectionClass('Fixtures\Prophecy\FinalClass');
  172. $mirror = new ClassMirror();
  173. $mirror->reflect($class, array());
  174. }
  175. /**
  176. * @test
  177. */
  178. public function it_ignores_final_methods()
  179. {
  180. $class = new \ReflectionClass('Fixtures\Prophecy\WithFinalMethod');
  181. $mirror = new ClassMirror();
  182. $classNode = $mirror->reflect($class, array());
  183. $this->assertCount(0, $classNode->getMethods());
  184. }
  185. /**
  186. * @test
  187. */
  188. public function it_marks_final_methods_as_unextendable()
  189. {
  190. $class = new \ReflectionClass('Fixtures\Prophecy\WithFinalMethod');
  191. $mirror = new ClassMirror();
  192. $classNode = $mirror->reflect($class, array());
  193. $this->assertCount(1, $classNode->getUnextendableMethods());
  194. $this->assertFalse($classNode->isExtendable('finalImplementation'));
  195. }
  196. /**
  197. * @test
  198. * @expectedException Prophecy\Exception\InvalidArgumentException
  199. */
  200. public function it_throws_an_exception_if_interface_provided_instead_of_class()
  201. {
  202. $class = new \ReflectionClass('Fixtures\Prophecy\EmptyInterface');
  203. $mirror = new ClassMirror();
  204. $mirror->reflect($class, array());
  205. }
  206. /**
  207. * @test
  208. */
  209. public function it_reflects_all_interfaces_methods()
  210. {
  211. $mirror = new ClassMirror();
  212. $classNode = $mirror->reflect(null, array(
  213. new \ReflectionClass('Fixtures\Prophecy\Named'),
  214. new \ReflectionClass('Fixtures\Prophecy\ModifierInterface'),
  215. ));
  216. $this->assertEquals('stdClass', $classNode->getParentClass());
  217. $this->assertEquals(array(
  218. 'Prophecy\Doubler\Generator\ReflectionInterface',
  219. 'Fixtures\Prophecy\ModifierInterface',
  220. 'Fixtures\Prophecy\Named',
  221. ), $classNode->getInterfaces());
  222. $this->assertCount(3, $classNode->getMethods());
  223. $this->assertTrue($classNode->hasMethod('getName'));
  224. $this->assertTrue($classNode->hasMethod('isAbstract'));
  225. $this->assertTrue($classNode->hasMethod('getVisibility'));
  226. }
  227. /**
  228. * @test
  229. */
  230. public function it_ignores_virtually_private_methods()
  231. {
  232. $class = new \ReflectionClass('Fixtures\Prophecy\WithVirtuallyPrivateMethod');
  233. $mirror = new ClassMirror();
  234. $classNode = $mirror->reflect($class, array());
  235. $this->assertCount(2, $classNode->getMethods());
  236. $this->assertTrue($classNode->hasMethod('isAbstract'));
  237. $this->assertTrue($classNode->hasMethod('__toString'));
  238. $this->assertFalse($classNode->hasMethod('_getName'));
  239. }
  240. /**
  241. * @test
  242. */
  243. public function it_does_not_throw_exception_for_virtually_private_finals()
  244. {
  245. $class = new \ReflectionClass('Fixtures\Prophecy\WithFinalVirtuallyPrivateMethod');
  246. $mirror = new ClassMirror();
  247. $classNode = $mirror->reflect($class, array());
  248. $this->assertCount(0, $classNode->getMethods());
  249. }
  250. /**
  251. * @test
  252. * @requires PHP 7
  253. */
  254. public function it_reflects_return_typehints()
  255. {
  256. $class = new \ReflectionClass('Fixtures\Prophecy\WithReturnTypehints');
  257. $mirror = new ClassMirror();
  258. $classNode = $mirror->reflect($class, array());
  259. $this->assertCount(3, $classNode->getMethods());
  260. $this->assertTrue($classNode->hasMethod('getName'));
  261. $this->assertTrue($classNode->hasMethod('getSelf'));
  262. $this->assertTrue($classNode->hasMethod('getParent'));
  263. $this->assertEquals('string', $classNode->getMethod('getName')->getReturnType());
  264. $this->assertEquals('\Fixtures\Prophecy\WithReturnTypehints', $classNode->getMethod('getSelf')->getReturnType());
  265. $this->assertEquals('\Fixtures\Prophecy\EmptyClass', $classNode->getMethod('getParent')->getReturnType());
  266. }
  267. /**
  268. * @test
  269. * @expectedException InvalidArgumentException
  270. */
  271. public function it_throws_an_exception_if_class_provided_in_interfaces_list()
  272. {
  273. $class = new \ReflectionClass('Fixtures\Prophecy\EmptyClass');
  274. $mirror = new ClassMirror();
  275. $mirror->reflect(null, array($class));
  276. }
  277. /**
  278. * @test
  279. * @expectedException InvalidArgumentException
  280. */
  281. public function it_throws_an_exception_if_not_reflection_provided_as_interface()
  282. {
  283. $mirror = new ClassMirror();
  284. $mirror->reflect(null, array(null));
  285. }
  286. /**
  287. * @test
  288. */
  289. public function it_doesnt_use_scalar_typehints()
  290. {
  291. $mirror = new ClassMirror();
  292. $classNode = $mirror->reflect(new \ReflectionClass('ReflectionMethod'), array());
  293. $method = $classNode->getMethod('export');
  294. $arguments = $method->getArguments();
  295. $this->assertNull($arguments[0]->getTypeHint());
  296. $this->assertNull($arguments[1]->getTypeHint());
  297. $this->assertNull($arguments[2]->getTypeHint());
  298. }
  299. /**
  300. * @test
  301. */
  302. public function it_doesnt_fail_to_typehint_nonexistent_FQCN()
  303. {
  304. $mirror = new ClassMirror();
  305. $classNode = $mirror->reflect(new \ReflectionClass('Fixtures\Prophecy\OptionalDepsClass'), array());
  306. $method = $classNode->getMethod('iHaveAStrangeTypeHintedArg');
  307. $arguments = $method->getArguments();
  308. $this->assertEquals('I\Simply\Am\Nonexistent', $arguments[0]->getTypeHint());
  309. }
  310. /**
  311. * @test
  312. * @requires PHP 7.1
  313. */
  314. public function it_doesnt_fail_on_array_nullable_parameter_with_not_null_default_value()
  315. {
  316. $mirror = new ClassMirror();
  317. $classNode = $mirror->reflect(new \ReflectionClass('Fixtures\Prophecy\NullableArrayParameter'), array());
  318. $method = $classNode->getMethod('iHaveNullableArrayParameterWithNotNullDefaultValue');
  319. $arguments = $method->getArguments();
  320. $this->assertSame('array', $arguments[0]->getTypeHint());
  321. $this->assertTrue($arguments[0]->isNullable());
  322. }
  323. /**
  324. * @test
  325. */
  326. public function it_doesnt_fail_to_typehint_nonexistent_RQCN()
  327. {
  328. $mirror = new ClassMirror();
  329. $classNode = $mirror->reflect(new \ReflectionClass('Fixtures\Prophecy\OptionalDepsClass'), array());
  330. $method = $classNode->getMethod('iHaveAnEvenStrangerTypeHintedArg');
  331. $arguments = $method->getArguments();
  332. $this->assertEquals('I\Simply\Am\Not', $arguments[0]->getTypeHint());
  333. }
  334. /**
  335. * @test
  336. * @requires PHP 7.2
  337. */
  338. function it_doesnt_fail_when_method_is_extended_with_more_params()
  339. {
  340. $mirror = new ClassMirror();
  341. $classNode = $mirror->reflect(
  342. new \ReflectionClass('Fixtures\Prophecy\MethodWithAdditionalParam'),
  343. array(new \ReflectionClass('Fixtures\Prophecy\Named'))
  344. );
  345. $method = $classNode->getMethod('getName');
  346. $this->assertCount(1, $method->getArguments());
  347. $method = $classNode->getMethod('methodWithoutTypeHints');
  348. $this->assertCount(2, $method->getArguments());
  349. }
  350. /**
  351. * @test
  352. */
  353. function it_changes_argument_names_if_they_are_varying()
  354. {
  355. // Use test doubles in this test, as arguments named ... in the Reflection API can only happen for internal classes
  356. $class = $this->prophesize('ReflectionClass');
  357. $method = $this->prophesize('ReflectionMethod');
  358. $parameter = $this->prophesize('ReflectionParameter');
  359. $class->getName()->willReturn('Custom\ClassName');
  360. $class->isInterface()->willReturn(false);
  361. $class->isFinal()->willReturn(false);
  362. $class->getMethods(\ReflectionMethod::IS_PUBLIC)->willReturn(array($method));
  363. $class->getMethods(\ReflectionMethod::IS_ABSTRACT)->willReturn(array());
  364. $method->getParameters()->willReturn(array($parameter));
  365. $method->getName()->willReturn('methodName');
  366. $method->isFinal()->willReturn(false);
  367. $method->isProtected()->willReturn(false);
  368. $method->isStatic()->willReturn(false);
  369. $method->returnsReference()->willReturn(false);
  370. if (version_compare(PHP_VERSION, '7.0', '>=')) {
  371. $method->hasReturnType()->willReturn(false);
  372. }
  373. $parameter->getName()->willReturn('...');
  374. $parameter->isDefaultValueAvailable()->willReturn(true);
  375. $parameter->getDefaultValue()->willReturn(null);
  376. $parameter->isPassedByReference()->willReturn(false);
  377. $parameter->allowsNull()->willReturn(true);
  378. $parameter->getClass()->willReturn($class);
  379. if (version_compare(PHP_VERSION, '5.6', '>=')) {
  380. $parameter->isVariadic()->willReturn(false);
  381. }
  382. $mirror = new ClassMirror();
  383. $classNode = $mirror->reflect($class->reveal(), array());
  384. $methodNodes = $classNode->getMethods();
  385. $argumentNodes = $methodNodes['methodName']->getArguments();
  386. $argumentNode = $argumentNodes[0];
  387. $this->assertEquals('__dot_dot_dot__', $argumentNode->getName());
  388. }
  389. }