No Description

ClassMirrorTest.php 15KB

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