DocParserTest.php 54 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621
  1. <?php
  2. namespace EasySwoole\DoctrineAnnotation\Tests;
  3. use EasySwoole\DoctrineAnnotation\Annotation;
  4. use EasySwoole\DoctrineAnnotation\Annotation\Target;
  5. use EasySwoole\DoctrineAnnotation\AnnotationException;
  6. use EasySwoole\DoctrineAnnotation\AnnotationRegistry;
  7. use EasySwoole\DoctrineAnnotation\DocParser;
  8. use EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationTargetAll;
  9. use EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationWithConstants;
  10. use EasySwoole\DoctrineAnnotation\Tests\Fixtures\ClassWithConstants;
  11. use EasySwoole\DoctrineAnnotation\Tests\Fixtures\InterfaceWithConstants;
  12. use InvalidArgumentException;
  13. use PHPUnit\Framework\TestCase;
  14. use ReflectionClass;
  15. use function array_column;
  16. use function array_combine;
  17. use function assert;
  18. use function class_exists;
  19. use function extension_loaded;
  20. use function ini_get;
  21. use function sprintf;
  22. use function ucfirst;
  23. use const PHP_EOL;
  24. class DocParserTest extends TestCase
  25. {
  26. public function testNestedArraysWithNestedAnnotation(): void
  27. {
  28. $parser = $this->createTestParser();
  29. // Nested arrays with nested annotations
  30. $result = $parser->parse('@Name(foo={1,2, {"key"=@Name}})');
  31. $annot = $result[0];
  32. self::assertInstanceOf(Name::class, $annot);
  33. self::assertNull($annot->value);
  34. self::assertCount(3, $annot->foo);
  35. self::assertEquals(1, $annot->foo[0]);
  36. self::assertEquals(2, $annot->foo[1]);
  37. self::assertIsArray($annot->foo[2]);
  38. $nestedArray = $annot->foo[2];
  39. self::assertTrue(isset($nestedArray['key']));
  40. self::assertInstanceOf(Name::class, $nestedArray['key']);
  41. }
  42. public function testBasicAnnotations(): void
  43. {
  44. $parser = $this->createTestParser();
  45. // Marker annotation
  46. $result = $parser->parse('@Name');
  47. $annot = $result[0];
  48. self::assertInstanceOf(Name::class, $annot);
  49. self::assertNull($annot->value);
  50. self::assertNull($annot->foo);
  51. // Associative arrays
  52. $result = $parser->parse('@Name(foo={"key1" = "value1"})');
  53. $annot = $result[0];
  54. self::assertNull($annot->value);
  55. self::assertIsArray($annot->foo);
  56. self::assertTrue(isset($annot->foo['key1']));
  57. // Numerical arrays
  58. $result = $parser->parse('@Name({2="foo", 4="bar"})');
  59. $annot = $result[0];
  60. self::assertIsArray($annot->value);
  61. self::assertEquals('foo', $annot->value[2]);
  62. self::assertEquals('bar', $annot->value[4]);
  63. self::assertFalse(isset($annot->value[0]));
  64. self::assertFalse(isset($annot->value[1]));
  65. self::assertFalse(isset($annot->value[3]));
  66. // Multiple values
  67. $result = $parser->parse('@Name(@Name, @Name)');
  68. $annot = $result[0];
  69. self::assertInstanceOf(Name::class, $annot);
  70. self::assertIsArray($annot->value);
  71. self::assertInstanceOf(Name::class, $annot->value[0]);
  72. self::assertInstanceOf(Name::class, $annot->value[1]);
  73. // Multiple types as values
  74. $result = $parser->parse('@Name(foo="Bar", @Name, {"key1"="value1", "key2"="value2"})');
  75. $annot = $result[0];
  76. self::assertInstanceOf(Name::class, $annot);
  77. self::assertIsArray($annot->value);
  78. self::assertInstanceOf(Name::class, $annot->value[0]);
  79. self::assertIsArray($annot->value[1]);
  80. self::assertEquals('value1', $annot->value[1]['key1']);
  81. self::assertEquals('value2', $annot->value[1]['key2']);
  82. // Complete docblock
  83. $docblock = <<<DOCBLOCK
  84. /**
  85. * Some nifty class.
  86. *
  87. * @author Mr.X
  88. * @Name(foo="bar")
  89. */
  90. DOCBLOCK;
  91. $result = $parser->parse($docblock);
  92. self::assertCount(1, $result);
  93. $annot = $result[0];
  94. self::assertInstanceOf(Name::class, $annot);
  95. self::assertEquals('bar', $annot->foo);
  96. self::assertNull($annot->value);
  97. }
  98. public function testDefaultValueAnnotations(): void
  99. {
  100. $parser = $this->createTestParser();
  101. // Array as first value
  102. $result = $parser->parse('@Name({"key1"="value1"})');
  103. $annot = $result[0];
  104. self::assertInstanceOf(Name::class, $annot);
  105. self::assertIsArray($annot->value);
  106. self::assertEquals('value1', $annot->value['key1']);
  107. // Array as first value and additional values
  108. $result = $parser->parse('@Name({"key1"="value1"}, foo="bar")');
  109. $annot = $result[0];
  110. self::assertInstanceOf(Name::class, $annot);
  111. self::assertIsArray($annot->value);
  112. self::assertEquals('value1', $annot->value['key1']);
  113. self::assertEquals('bar', $annot->foo);
  114. }
  115. public function testNamespacedAnnotations(): void
  116. {
  117. $parser = new DocParser();
  118. $parser->setIgnoreNotImportedAnnotations(true);
  119. $docblock = <<<DOCBLOCK
  120. /**
  121. * Some nifty class.
  122. *
  123. * @package foo
  124. * @subpackage bar
  125. * @author Mr.X <mr@x.com>
  126. * @EasySwoole\DoctrineAnnotation\Tests\Name(foo="bar")
  127. * @ignore
  128. */
  129. DOCBLOCK;
  130. $result = $parser->parse($docblock);
  131. self::assertCount(1, $result);
  132. $annot = $result[0];
  133. self::assertInstanceOf(Name::class, $annot);
  134. self::assertEquals('bar', $annot->foo);
  135. }
  136. /**
  137. * @group debug
  138. */
  139. public function testTypicalMethodDocBlock(): void
  140. {
  141. $parser = $this->createTestParser();
  142. $docblock = <<<DOCBLOCK
  143. /**
  144. * Some nifty method.
  145. *
  146. * @since 2.0
  147. * @EasySwoole\DoctrineAnnotation\Tests\Name(foo="bar")
  148. * @param string \$foo This is foo.
  149. * @param mixed \$bar This is bar.
  150. * @return string Foo and bar.
  151. * @This is irrelevant
  152. * @Marker
  153. */
  154. DOCBLOCK;
  155. $result = $parser->parse($docblock);
  156. self::assertCount(2, $result);
  157. self::assertTrue(isset($result[0]));
  158. self::assertTrue(isset($result[1]));
  159. $annot = $result[0];
  160. self::assertInstanceOf(Name::class, $annot);
  161. self::assertEquals('bar', $annot->foo);
  162. $marker = $result[1];
  163. self::assertInstanceOf(Marker::class, $marker);
  164. }
  165. public function testAnnotationWithoutConstructor(): void
  166. {
  167. $parser = $this->createTestParser();
  168. $docblock = <<<DOCBLOCK
  169. /**
  170. * @SomeAnnotationClassNameWithoutConstructor("Some data")
  171. */
  172. DOCBLOCK;
  173. $result = $parser->parse($docblock);
  174. self::assertCount(1, $result);
  175. $annot = $result[0];
  176. self::assertInstanceOf(SomeAnnotationClassNameWithoutConstructor::class, $annot);
  177. self::assertNull($annot->name);
  178. self::assertNotNull($annot->data);
  179. self::assertEquals($annot->data, 'Some data');
  180. $docblock = <<<DOCBLOCK
  181. /**
  182. * @SomeAnnotationClassNameWithoutConstructor(name="Some Name", data = "Some data")
  183. */
  184. DOCBLOCK;
  185. $result = $parser->parse($docblock);
  186. self::assertCount(1, $result);
  187. $annot = $result[0];
  188. self::assertNotNull($annot);
  189. self::assertInstanceOf(SomeAnnotationClassNameWithoutConstructor::class, $annot);
  190. self::assertEquals($annot->name, 'Some Name');
  191. self::assertEquals($annot->data, 'Some data');
  192. $docblock = <<<DOCBLOCK
  193. /**
  194. * @SomeAnnotationClassNameWithoutConstructor(data = "Some data")
  195. */
  196. DOCBLOCK;
  197. $result = $parser->parse($docblock);
  198. self::assertCount(1, $result);
  199. $annot = $result[0];
  200. self::assertEquals($annot->data, 'Some data');
  201. self::assertNull($annot->name);
  202. $docblock = <<<DOCBLOCK
  203. /**
  204. * @SomeAnnotationClassNameWithoutConstructor(name = "Some name")
  205. */
  206. DOCBLOCK;
  207. $result = $parser->parse($docblock);
  208. self::assertCount(1, $result);
  209. $annot = $result[0];
  210. self::assertEquals($annot->name, 'Some name');
  211. self::assertNull($annot->data);
  212. $docblock = <<<DOCBLOCK
  213. /**
  214. * @SomeAnnotationClassNameWithoutConstructor("Some data")
  215. */
  216. DOCBLOCK;
  217. $result = $parser->parse($docblock);
  218. self::assertCount(1, $result);
  219. $annot = $result[0];
  220. self::assertEquals($annot->data, 'Some data');
  221. self::assertNull($annot->name);
  222. $docblock = <<<DOCBLOCK
  223. /**
  224. * @SomeAnnotationClassNameWithoutConstructor("Some data",name = "Some name")
  225. */
  226. DOCBLOCK;
  227. $result = $parser->parse($docblock);
  228. self::assertCount(1, $result);
  229. $annot = $result[0];
  230. self::assertEquals($annot->name, 'Some name');
  231. self::assertEquals($annot->data, 'Some data');
  232. $docblock = <<<DOCBLOCK
  233. /**
  234. * @SomeAnnotationWithConstructorWithoutParams(name = "Some name")
  235. */
  236. DOCBLOCK;
  237. $result = $parser->parse($docblock);
  238. self::assertCount(1, $result);
  239. $annot = $result[0];
  240. self::assertEquals($annot->name, 'Some name');
  241. self::assertEquals($annot->data, 'Some data');
  242. $docblock = <<<DOCBLOCK
  243. /**
  244. * @SomeAnnotationClassNameWithoutConstructorAndProperties()
  245. */
  246. DOCBLOCK;
  247. $result = $parser->parse($docblock);
  248. self::assertCount(1, $result);
  249. self::assertInstanceOf(SomeAnnotationClassNameWithoutConstructorAndProperties::class, $result[0]);
  250. }
  251. public function testAnnotationTarget(): void
  252. {
  253. $parser = new DocParser();
  254. $parser->setImports(['__NAMESPACE__' => 'EasySwoole\DoctrineAnnotation\Tests\Fixtures']);
  255. $class = new ReflectionClass(Fixtures\ClassWithValidAnnotationTarget::class);
  256. $context = 'class ' . $class->getName();
  257. $docComment = $class->getDocComment();
  258. $parser->setTarget(Target::TARGET_CLASS);
  259. self::assertNotNull($parser->parse($docComment, $context));
  260. $property = $class->getProperty('foo');
  261. $docComment = $property->getDocComment();
  262. $context = 'property ' . $class->getName() . '::$' . $property->getName();
  263. $parser->setTarget(Target::TARGET_PROPERTY);
  264. self::assertNotNull($parser->parse($docComment, $context));
  265. $method = $class->getMethod('someFunction');
  266. $docComment = $property->getDocComment();
  267. $context = 'method ' . $class->getName() . '::' . $method->getName() . '()';
  268. $parser->setTarget(Target::TARGET_METHOD);
  269. self::assertNotNull($parser->parse($docComment, $context));
  270. try {
  271. $class = new ReflectionClass(Fixtures\ClassWithInvalidAnnotationTargetAtClass::class);
  272. $context = 'class ' . $class->getName();
  273. $docComment = $class->getDocComment();
  274. $parser->setTarget(Target::TARGET_CLASS);
  275. $parser->parse($docComment, $context);
  276. $this->fail();
  277. } catch (AnnotationException $exc) {
  278. self::assertNotNull($exc->getMessage());
  279. }
  280. try {
  281. $class = new ReflectionClass(Fixtures\ClassWithInvalidAnnotationTargetAtMethod::class);
  282. $method = $class->getMethod('functionName');
  283. $docComment = $method->getDocComment();
  284. $context = 'method ' . $class->getName() . '::' . $method->getName() . '()';
  285. $parser->setTarget(Target::TARGET_METHOD);
  286. $parser->parse($docComment, $context);
  287. $this->fail();
  288. } catch (AnnotationException $exc) {
  289. self::assertNotNull($exc->getMessage());
  290. }
  291. try {
  292. $class = new ReflectionClass(Fixtures\ClassWithInvalidAnnotationTargetAtProperty::class);
  293. $property = $class->getProperty('foo');
  294. $docComment = $property->getDocComment();
  295. $context = 'property ' . $class->getName() . '::$' . $property->getName();
  296. $parser->setTarget(Target::TARGET_PROPERTY);
  297. $parser->parse($docComment, $context);
  298. $this->fail();
  299. } catch (AnnotationException $exc) {
  300. self::assertNotNull($exc->getMessage());
  301. }
  302. }
  303. /**
  304. * @phpstan-return list<array{string, string}>
  305. */
  306. public function getAnnotationVarTypeProviderValid()
  307. {
  308. //({attribute name}, {attribute value})
  309. return [
  310. // mixed type
  311. ['mixed', '"String Value"'],
  312. ['mixed', 'true'],
  313. ['mixed', 'false'],
  314. ['mixed', '1'],
  315. ['mixed', '1.2'],
  316. ['mixed', '@EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationTargetAll'],
  317. // boolean type
  318. ['boolean', 'true'],
  319. ['boolean', 'false'],
  320. // alias for internal type boolean
  321. ['bool', 'true'],
  322. ['bool', 'false'],
  323. // integer type
  324. ['integer', '0'],
  325. ['integer', '1'],
  326. ['integer', '123456789'],
  327. ['integer', '9223372036854775807'],
  328. // alias for internal type double
  329. ['float', '0.1'],
  330. ['float', '1.2'],
  331. ['float', '123.456'],
  332. // string type
  333. ['string', '"String Value"'],
  334. ['string', '"true"'],
  335. ['string', '"123"'],
  336. // array type
  337. ['array', '{@AnnotationExtendsAnnotationTargetAll}'],
  338. ['array', '{@AnnotationExtendsAnnotationTargetAll,@AnnotationExtendsAnnotationTargetAll}'],
  339. ['arrayOfIntegers', '1'],
  340. ['arrayOfIntegers', '{1}'],
  341. ['arrayOfIntegers', '{1,2,3,4}'],
  342. ['arrayOfAnnotations', '@AnnotationExtendsAnnotationTargetAll'],
  343. ['arrayOfAnnotations', '{@EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationTargetAll}'],
  344. [
  345. 'arrayOfAnnotations',
  346. '{
  347. @AnnotationExtendsAnnotationTargetAll,
  348. @EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationTargetAll
  349. }',
  350. ],
  351. // annotation instance
  352. ['annotation', '@EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationTargetAll'],
  353. ['annotation', '@AnnotationExtendsAnnotationTargetAll'],
  354. ];
  355. }
  356. /**
  357. * @phpstan-return list<array{string, string, string, string}>
  358. */
  359. public function getAnnotationVarTypeProviderInvalid(): array
  360. {
  361. //({attribute name}, {type declared type}, {attribute value} , {given type or class})
  362. return [
  363. // boolean type
  364. ['boolean','boolean','1','integer'],
  365. ['boolean','boolean','1.2','double'],
  366. ['boolean','boolean','"str"','string'],
  367. ['boolean','boolean','{1,2,3}','array'],
  368. ['boolean','boolean','@Name', 'an instance of EasySwoole\DoctrineAnnotation\Tests\Name'],
  369. // alias for internal type boolean
  370. ['bool','bool', '1','integer'],
  371. ['bool','bool', '1.2','double'],
  372. ['bool','bool', '"str"','string'],
  373. ['bool','bool', '{"str"}','array'],
  374. // integer type
  375. ['integer','integer', 'true','boolean'],
  376. ['integer','integer', 'false','boolean'],
  377. ['integer','integer', '1.2','double'],
  378. ['integer','integer', '"str"','string'],
  379. ['integer','integer', '{"str"}','array'],
  380. ['integer','integer', '{1,2,3,4}','array'],
  381. // alias for internal type double
  382. ['float','float', 'true','boolean'],
  383. ['float','float', 'false','boolean'],
  384. ['float','float', '123','integer'],
  385. ['float','float', '"str"','string'],
  386. ['float','float', '{"str"}','array'],
  387. ['float','float', '{12.34}','array'],
  388. ['float','float', '{1,2,3}','array'],
  389. // string type
  390. ['string','string', 'true','boolean'],
  391. ['string','string', 'false','boolean'],
  392. ['string','string', '12','integer'],
  393. ['string','string', '1.2','double'],
  394. ['string','string', '{"str"}','array'],
  395. ['string','string', '{1,2,3,4}','array'],
  396. // annotation instance
  397. ['annotation', AnnotationTargetAll::class, 'true','boolean'],
  398. ['annotation', AnnotationTargetAll::class, 'false','boolean'],
  399. ['annotation', AnnotationTargetAll::class, '12','integer'],
  400. ['annotation', AnnotationTargetAll::class, '1.2','double'],
  401. ['annotation', AnnotationTargetAll::class, '{"str"}','array'],
  402. ['annotation', AnnotationTargetAll::class, '{1,2,3,4}','array'],
  403. [
  404. 'annotation',
  405. AnnotationTargetAll::class,
  406. '@Name',
  407. 'an instance of EasySwoole\DoctrineAnnotation\Tests\Name',
  408. ],
  409. ];
  410. }
  411. /**
  412. * @phpstan-return list<array{string, string, string, string}>
  413. */
  414. public function getAnnotationVarTypeArrayProviderInvalid()
  415. {
  416. //({attribute name}, {type declared type}, {attribute value} , {given type or class})
  417. return [
  418. ['arrayOfIntegers', 'integer', 'true', 'boolean'],
  419. ['arrayOfIntegers', 'integer', 'false', 'boolean'],
  420. ['arrayOfIntegers', 'integer', '{true,true}', 'boolean'],
  421. ['arrayOfIntegers', 'integer', '{1,true}', 'boolean'],
  422. ['arrayOfIntegers', 'integer', '{1,2,1.2}', 'double'],
  423. ['arrayOfIntegers', 'integer', '{1,2,"str"}', 'string'],
  424. ['arrayOfStrings', 'string', 'true', 'boolean'],
  425. ['arrayOfStrings', 'string', 'false', 'boolean'],
  426. ['arrayOfStrings', 'string', '{true,true}', 'boolean'],
  427. ['arrayOfStrings', 'string', '{"foo",true}', 'boolean'],
  428. ['arrayOfStrings', 'string', '{"foo","bar",1.2}', 'double'],
  429. ['arrayOfStrings', 'string', '1', 'integer'],
  430. ['arrayOfAnnotations', AnnotationTargetAll::class, 'true', 'boolean'],
  431. ['arrayOfAnnotations', AnnotationTargetAll::class, 'false', 'boolean'],
  432. [
  433. 'arrayOfAnnotations',
  434. AnnotationTargetAll::class,
  435. '{@EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationTargetAll,true}',
  436. 'boolean',
  437. ],
  438. [
  439. 'arrayOfAnnotations',
  440. AnnotationTargetAll::class,
  441. '{@EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationTargetAll,true}',
  442. 'boolean',
  443. ],
  444. [
  445. 'arrayOfAnnotations',
  446. AnnotationTargetAll::class,
  447. '{@EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationTargetAll,1.2}',
  448. 'double',
  449. ],
  450. [
  451. 'arrayOfAnnotations',
  452. AnnotationTargetAll::class,
  453. '{
  454. @EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationTargetAll,
  455. @AnnotationExtendsAnnotationTargetAll,
  456. "str"
  457. }',
  458. 'string',
  459. ],
  460. ];
  461. }
  462. /**
  463. * @dataProvider getAnnotationVarTypeProviderValid
  464. */
  465. public function testAnnotationWithVarType(string $attribute, string $value): void
  466. {
  467. $parser = $this->createTestParser();
  468. $context = 'property SomeClassName::$invalidProperty.';
  469. $docblock = sprintf(
  470. '@EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationWithVarType(%s = %s)',
  471. $attribute,
  472. $value
  473. );
  474. $parser->setTarget(Target::TARGET_PROPERTY);
  475. $result = $parser->parse($docblock, $context);
  476. self::assertCount(1, $result);
  477. self::assertInstanceOf(Fixtures\AnnotationWithVarType::class, $result[0]);
  478. self::assertNotNull($result[0]->$attribute);
  479. }
  480. /**
  481. * @dataProvider getAnnotationVarTypeProviderInvalid
  482. */
  483. public function testAnnotationWithVarTypeError(
  484. string $attribute,
  485. string $type,
  486. string $value,
  487. string $given
  488. ): void {
  489. $parser = $this->createTestParser();
  490. $context = 'property SomeClassName::invalidProperty.';
  491. $docblock = sprintf(
  492. '@EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationWithVarType(%s = %s)',
  493. $attribute,
  494. $value
  495. );
  496. $parser->setTarget(Target::TARGET_PROPERTY);
  497. try {
  498. $parser->parse($docblock, $context);
  499. $this->fail();
  500. } catch (AnnotationException $exc) {
  501. self::assertStringMatchesFormat(
  502. '[Type Error] Attribute "' . $attribute .
  503. '" of @EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationWithVarType' .
  504. ' declared on property SomeClassName::invalidProperty. expects a(n) %A' .
  505. $type . ', but got ' . $given . '.',
  506. $exc->getMessage()
  507. );
  508. }
  509. }
  510. /**
  511. * @dataProvider getAnnotationVarTypeArrayProviderInvalid
  512. */
  513. public function testAnnotationWithVarTypeArrayError(
  514. string $attribute,
  515. string $type,
  516. string $value,
  517. string $given
  518. ): void {
  519. $parser = $this->createTestParser();
  520. $context = 'property SomeClassName::invalidProperty.';
  521. $docblock = sprintf(
  522. '@EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationWithVarType(%s = %s)',
  523. $attribute,
  524. $value
  525. );
  526. $parser->setTarget(Target::TARGET_PROPERTY);
  527. try {
  528. $parser->parse($docblock, $context);
  529. $this->fail();
  530. } catch (AnnotationException $exc) {
  531. self::assertStringMatchesFormat(
  532. '[Type Error] Attribute "' . $attribute .
  533. '" of @EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationWithVarType' .
  534. ' declared on property SomeClassName::invalidProperty. expects either a(n) %A' .
  535. $type . ', or an array of %A' . $type . 's, but got ' . $given . '.',
  536. $exc->getMessage()
  537. );
  538. }
  539. }
  540. /**
  541. * @dataProvider getAnnotationVarTypeProviderValid
  542. */
  543. public function testAnnotationWithAttributes(string $attribute, string $value): void
  544. {
  545. $parser = $this->createTestParser();
  546. $context = 'property SomeClassName::$invalidProperty.';
  547. $docblock = sprintf(
  548. '@EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationWithAttributes(%s = %s)',
  549. $attribute,
  550. $value
  551. );
  552. $parser->setTarget(Target::TARGET_PROPERTY);
  553. $result = $parser->parse($docblock, $context);
  554. self::assertCount(1, $result);
  555. self::assertInstanceOf(Fixtures\AnnotationWithAttributes::class, $result[0]);
  556. $getter = 'get' . ucfirst($attribute);
  557. self::assertNotNull($result[0]->$getter());
  558. }
  559. /**
  560. * @dataProvider getAnnotationVarTypeProviderInvalid
  561. */
  562. public function testAnnotationWithAttributesError(
  563. string $attribute,
  564. string $type,
  565. string $value,
  566. string $given
  567. ): void {
  568. $parser = $this->createTestParser();
  569. $context = 'property SomeClassName::invalidProperty.';
  570. $docblock = sprintf(
  571. '@EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationWithAttributes(%s = %s)',
  572. $attribute,
  573. $value
  574. );
  575. $parser->setTarget(Target::TARGET_PROPERTY);
  576. try {
  577. $parser->parse($docblock, $context);
  578. $this->fail();
  579. } catch (AnnotationException $exc) {
  580. self::assertStringContainsString(sprintf(
  581. '[Type Error] Attribute "%s" of @EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationWithAttributes' .
  582. ' declared on property SomeClassName::invalidProperty. expects a(n) %s, but got %s.',
  583. $attribute,
  584. $type,
  585. $given
  586. ), $exc->getMessage());
  587. }
  588. }
  589. /**
  590. * @dataProvider getAnnotationVarTypeArrayProviderInvalid
  591. */
  592. public function testAnnotationWithAttributesWithVarTypeArrayError(
  593. string $attribute,
  594. string $type,
  595. string $value,
  596. string $given
  597. ): void {
  598. $parser = $this->createTestParser();
  599. $context = 'property SomeClassName::invalidProperty.';
  600. $docblock = sprintf(
  601. '@EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationWithAttributes(%s = %s)',
  602. $attribute,
  603. $value
  604. );
  605. $parser->setTarget(Target::TARGET_PROPERTY);
  606. try {
  607. $parser->parse($docblock, $context);
  608. $this->fail();
  609. } catch (AnnotationException $exc) {
  610. self::assertStringContainsString(sprintf(
  611. '[Type Error] Attribute "%s" of' .
  612. ' @EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationWithAttributes declared' .
  613. ' on property SomeClassName::invalidProperty. expects either a(n) %s, or an array of %ss, but got %s.',
  614. $attribute,
  615. $type,
  616. $type,
  617. $given
  618. ), $exc->getMessage());
  619. }
  620. }
  621. public function testAnnotationWithRequiredAttributes(): void
  622. {
  623. $parser = $this->createTestParser();
  624. $context = 'property SomeClassName::invalidProperty.';
  625. $parser->setTarget(Target::TARGET_PROPERTY);
  626. $docblock = '@EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationWithRequiredAttributes' .
  627. '("Some Value", annot = @EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationTargetAnnotation)';
  628. $result = $parser->parse($docblock);
  629. self::assertCount(1, $result);
  630. $annotation = $result[0];
  631. assert($annotation instanceof Fixtures\AnnotationWithRequiredAttributes);
  632. self::assertInstanceOf(Fixtures\AnnotationWithRequiredAttributes::class, $annotation);
  633. self::assertEquals('Some Value', $annotation->getValue());
  634. self::assertInstanceOf(Fixtures\AnnotationTargetAnnotation::class, $annotation->getAnnot());
  635. $docblock = '@EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationWithRequiredAttributes("Some Value")';
  636. try {
  637. $parser->parse($docblock, $context);
  638. $this->fail();
  639. } catch (AnnotationException $exc) {
  640. self::assertStringContainsString(
  641. 'Attribute "annot" of @EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationWithRequiredAttributes' .
  642. ' declared on property SomeClassName::invalidProperty. expects a(n)' .
  643. ' EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationTargetAnnotation.' .
  644. ' This value should not be null.',
  645. $exc->getMessage()
  646. );
  647. }
  648. $docblock = '@EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationWithRequiredAttributes' .
  649. '(annot = @EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationTargetAnnotation)';
  650. try {
  651. $parser->parse($docblock, $context);
  652. $this->fail();
  653. } catch (AnnotationException $exc) {
  654. self::assertStringContainsString(
  655. 'Attribute "value" of @EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationWithRequiredAttributes' .
  656. ' declared on property SomeClassName::invalidProperty. expects a(n) string.' .
  657. ' This value should not be null.',
  658. $exc->getMessage()
  659. );
  660. }
  661. }
  662. public function testAnnotationWithRequiredAttributesWithoutConstructor(): void
  663. {
  664. $parser = $this->createTestParser();
  665. $context = 'property SomeClassName::invalidProperty.';
  666. $parser->setTarget(Target::TARGET_PROPERTY);
  667. $docblock = '@EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationWithRequiredAttributesWithoutConstructor' .
  668. '("Some Value", annot = @EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationTargetAnnotation)';
  669. $result = $parser->parse($docblock);
  670. self::assertCount(1, $result);
  671. self::assertInstanceOf(Fixtures\AnnotationWithRequiredAttributesWithoutConstructor::class, $result[0]);
  672. self::assertEquals('Some Value', $result[0]->value);
  673. self::assertInstanceOf(Fixtures\AnnotationTargetAnnotation::class, $result[0]->annot);
  674. $docblock = <<<'ANNOTATION'
  675. @EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationWithRequiredAttributesWithoutConstructor("Some Value")
  676. ANNOTATION;
  677. try {
  678. $parser->parse($docblock, $context);
  679. $this->fail();
  680. } catch (AnnotationException $exc) {
  681. self::assertStringContainsString(
  682. 'Attribute "annot" of' .
  683. ' @EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationWithRequiredAttributesWithoutConstructor' .
  684. ' declared on property SomeClassName::invalidProperty. expects a(n)' .
  685. ' \EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationTargetAnnotation.' .
  686. ' This value should not be null.',
  687. $exc->getMessage()
  688. );
  689. }
  690. $docblock = '@EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationWithRequiredAttributesWithoutConstructor' .
  691. '(annot = @EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationTargetAnnotation)';
  692. try {
  693. $parser->parse($docblock, $context);
  694. $this->fail();
  695. } catch (AnnotationException $exc) {
  696. self::assertStringContainsString(
  697. 'Attribute "value" of' .
  698. ' @EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationWithRequiredAttributesWithoutConstructor' .
  699. ' declared on property SomeClassName::invalidProperty. expects a(n) string.' .
  700. ' This value should not be null.',
  701. $exc->getMessage()
  702. );
  703. }
  704. }
  705. public function testAnnotationEnumeratorException(): void
  706. {
  707. $parser = $this->createTestParser();
  708. $context = 'property SomeClassName::invalidProperty.';
  709. $docblock = '@EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationEnum("FOUR")';
  710. $parser->setIgnoreNotImportedAnnotations(false);
  711. $parser->setTarget(Target::TARGET_PROPERTY);
  712. $this->expectException(AnnotationException::class);
  713. $this->expectExceptionMessage(
  714. 'Attribute "value" of @EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationEnum declared' .
  715. ' on property SomeClassName::invalidProperty. accepts only [ONE, TWO, THREE], but got FOUR.'
  716. );
  717. $parser->parse($docblock, $context);
  718. }
  719. public function testAnnotationEnumeratorLiteralException(): void
  720. {
  721. $parser = $this->createTestParser();
  722. $context = 'property SomeClassName::invalidProperty.';
  723. $docblock = '@EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationEnumLiteral(4)';
  724. $parser->setIgnoreNotImportedAnnotations(false);
  725. $parser->setTarget(Target::TARGET_PROPERTY);
  726. $this->expectException(AnnotationException::class);
  727. $this->expectExceptionMessage(
  728. 'Attribute "value" of @EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationEnumLiteral declared' .
  729. ' on property SomeClassName::invalidProperty. accepts only' .
  730. ' [AnnotationEnumLiteral::ONE, AnnotationEnumLiteral::TWO, AnnotationEnumLiteral::THREE], but got 4.'
  731. );
  732. $parser->parse($docblock, $context);
  733. }
  734. public function testAnnotationEnumInvalidTypeDeclarationException(): void
  735. {
  736. $parser = $this->createTestParser();
  737. $docblock = '@EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationEnumInvalid("foo")';
  738. $parser->setIgnoreNotImportedAnnotations(false);
  739. $this->expectException(InvalidArgumentException::class);
  740. $this->expectExceptionMessage('@Enum supports only scalar values "array" given.');
  741. $parser->parse($docblock);
  742. }
  743. public function testAnnotationEnumInvalidLiteralDeclarationException(): void
  744. {
  745. $parser = $this->createTestParser();
  746. $docblock = '@EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationEnumLiteralInvalid("foo")';
  747. $parser->setIgnoreNotImportedAnnotations(false);
  748. $this->expectException(InvalidArgumentException::class);
  749. $this->expectExceptionMessage('Undefined enumerator value "3" for literal "AnnotationEnumLiteral::THREE".');
  750. $parser->parse($docblock);
  751. }
  752. /**
  753. * @phpstan-return array<string, array{string, mixed}>
  754. */
  755. public function getConstantsProvider(): array
  756. {
  757. $provider = [];
  758. $provider[] = [
  759. '@AnnotationWithConstants(PHP_EOL)',
  760. PHP_EOL,
  761. ];
  762. $provider[] = [
  763. '@AnnotationWithConstants(AnnotationWithConstants::INTEGER)',
  764. AnnotationWithConstants::INTEGER,
  765. ];
  766. $provider[] = [
  767. '@EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationWithConstants(AnnotationWithConstants::STRING)',
  768. AnnotationWithConstants::STRING,
  769. ];
  770. $provider[] = [
  771. '@AnnotationWithConstants(EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationWithConstants::FLOAT)',
  772. AnnotationWithConstants::FLOAT,
  773. ];
  774. $provider[] = [
  775. '@AnnotationWithConstants(ClassWithConstants::SOME_VALUE)',
  776. ClassWithConstants::SOME_VALUE,
  777. ];
  778. $provider[] = [
  779. '@AnnotationWithConstants(ClassWithConstants::OTHER_KEY_)',
  780. ClassWithConstants::OTHER_KEY_,
  781. ];
  782. $provider[] = [
  783. '@AnnotationWithConstants(ClassWithConstants::OTHER_KEY_2)',
  784. ClassWithConstants::OTHER_KEY_2,
  785. ];
  786. $provider[] = [
  787. '@AnnotationWithConstants(EasySwoole\DoctrineAnnotation\Tests\Fixtures\ClassWithConstants::SOME_VALUE)',
  788. ClassWithConstants::SOME_VALUE,
  789. ];
  790. $provider[] = [
  791. '@AnnotationWithConstants(InterfaceWithConstants::SOME_VALUE)',
  792. InterfaceWithConstants::SOME_VALUE,
  793. ];
  794. $provider[] = [
  795. '@AnnotationWithConstants(\EasySwoole\DoctrineAnnotation\Tests\Fixtures\InterfaceWithConstants::SOME_VALUE)',
  796. InterfaceWithConstants::SOME_VALUE,
  797. ];
  798. $provider[] = [<<<'ANNOTATION'
  799. @AnnotationWithConstants({
  800. AnnotationWithConstants::STRING,
  801. AnnotationWithConstants::INTEGER,
  802. AnnotationWithConstants::FLOAT
  803. })
  804. ANNOTATION
  805. ,
  806. [AnnotationWithConstants::STRING, AnnotationWithConstants::INTEGER, AnnotationWithConstants::FLOAT],
  807. ];
  808. $provider[] = [
  809. '@AnnotationWithConstants({
  810. AnnotationWithConstants::STRING = AnnotationWithConstants::INTEGER
  811. })',
  812. [AnnotationWithConstants::STRING => AnnotationWithConstants::INTEGER],
  813. ];
  814. $provider[] = [<<<'ANNOTATION'
  815. @AnnotationWithConstants({
  816. EasySwoole\DoctrineAnnotation\Tests\Fixtures\InterfaceWithConstants::SOME_KEY = AnnotationWithConstants::INTEGER
  817. })
  818. ANNOTATION
  819. ,
  820. [InterfaceWithConstants::SOME_KEY => AnnotationWithConstants::INTEGER],
  821. ];
  822. $provider[] = [<<<'ANNOTATION'
  823. @AnnotationWithConstants({
  824. \EasySwoole\DoctrineAnnotation\Tests\Fixtures\InterfaceWithConstants::SOME_KEY = AnnotationWithConstants::INTEGER
  825. })
  826. ANNOTATION
  827. ,
  828. [InterfaceWithConstants::SOME_KEY => AnnotationWithConstants::INTEGER],
  829. ];
  830. $provider[] = [<<<'ANNOTATION'
  831. @AnnotationWithConstants({
  832. AnnotationWithConstants::STRING = AnnotationWithConstants::INTEGER,
  833. ClassWithConstants::SOME_KEY = ClassWithConstants::SOME_VALUE,
  834. EasySwoole\DoctrineAnnotation\Tests\Fixtures\InterfaceWithConstants::SOME_KEY = InterfaceWithConstants::SOME_VALUE
  835. })
  836. ANNOTATION
  837. ,
  838. [
  839. AnnotationWithConstants::STRING => AnnotationWithConstants::INTEGER,
  840. ClassWithConstants::SOME_KEY => ClassWithConstants::SOME_VALUE,
  841. InterfaceWithConstants::SOME_KEY => InterfaceWithConstants::SOME_VALUE,
  842. ],
  843. ];
  844. $provider[] = [
  845. '@AnnotationWithConstants(AnnotationWithConstants::class)',
  846. AnnotationWithConstants::class,
  847. ];
  848. $provider[] = [
  849. '@AnnotationWithConstants({AnnotationWithConstants::class = AnnotationWithConstants::class})',
  850. [AnnotationWithConstants::class => AnnotationWithConstants::class],
  851. ];
  852. $provider[] = [
  853. '@AnnotationWithConstants(EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationWithConstants::class)',
  854. AnnotationWithConstants::class,
  855. ];
  856. $provider[] = [
  857. '@EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationWithConstants' .
  858. '(EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationWithConstants::class)',
  859. AnnotationWithConstants::class,
  860. ];
  861. return array_combine(array_column($provider, 0), $provider);
  862. }
  863. /**
  864. * @param mixed $expected
  865. *
  866. * @dataProvider getConstantsProvider
  867. */
  868. public function testSupportClassConstants(string $docblock, $expected): void
  869. {
  870. $parser = $this->createTestParser();
  871. $parser->setImports([
  872. 'classwithconstants' => ClassWithConstants::class,
  873. 'interfacewithconstants' => InterfaceWithConstants::class,
  874. 'annotationwithconstants' => AnnotationWithConstants::class,
  875. ]);
  876. $result = $parser->parse($docblock);
  877. self::assertInstanceOf(AnnotationWithConstants::class, $annotation = $result[0]);
  878. self::assertEquals($expected, $annotation->value);
  879. }
  880. public function testWithoutConstructorWhenIsNotDefaultValue(): void
  881. {
  882. $parser = $this->createTestParser();
  883. $docblock = <<<DOCBLOCK
  884. /**
  885. * @SomeAnnotationClassNameWithoutConstructorAndProperties("Foo")
  886. */
  887. DOCBLOCK;
  888. $parser->setTarget(Target::TARGET_CLASS);
  889. $this->expectException(AnnotationException::class);
  890. $this->expectExceptionMessage(
  891. 'The annotation @SomeAnnotationClassNameWithoutConstructorAndProperties declared on ' .
  892. ' does not accept any values, but got {"value":"Foo"}.'
  893. );
  894. $parser->parse($docblock);
  895. }
  896. public function testWithoutConstructorWhenHasNoProperties(): void
  897. {
  898. $parser = $this->createTestParser();
  899. $docblock = <<<DOCBLOCK
  900. /**
  901. * @SomeAnnotationClassNameWithoutConstructorAndProperties(value = "Foo")
  902. */
  903. DOCBLOCK;
  904. $parser->setTarget(Target::TARGET_CLASS);
  905. $this->expectException(AnnotationException::class);
  906. $this->expectExceptionMessage(
  907. 'The annotation @SomeAnnotationClassNameWithoutConstructorAndProperties declared on ' .
  908. ' does not accept any values, but got {"value":"Foo"}.'
  909. );
  910. $parser->parse($docblock);
  911. }
  912. public function testAnnotationTargetSyntaxError(): void
  913. {
  914. $parser = $this->createTestParser();
  915. $context = 'class SomeClassName';
  916. $docblock = <<<DOCBLOCK
  917. /**
  918. * @EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationWithTargetSyntaxError()
  919. */
  920. DOCBLOCK;
  921. $parser->setTarget(Target::TARGET_CLASS);
  922. $this->expectException(AnnotationException::class);
  923. $this->expectExceptionMessage(
  924. "Expected namespace separator or identifier, got ')' at position 24" .
  925. ' in class @EasySwoole\DoctrineAnnotation\Tests\Fixtures\AnnotationWithTargetSyntaxError.'
  926. );
  927. $parser->parse($docblock, $context);
  928. }
  929. public function testAnnotationWithInvalidTargetDeclarationError(): void
  930. {
  931. $parser = $this->createTestParser();
  932. $context = 'class SomeClassName';
  933. $docblock = <<<DOCBLOCK
  934. /**
  935. * @AnnotationWithInvalidTargetDeclaration()
  936. */
  937. DOCBLOCK;
  938. $parser->setTarget(Target::TARGET_CLASS);
  939. $this->expectException(InvalidArgumentException::class);
  940. $this->expectExceptionMessage(
  941. 'Invalid Target "Foo". Available targets: [ALL, CLASS, METHOD, PROPERTY, ANNOTATION]'
  942. );
  943. $parser->parse($docblock, $context);
  944. }
  945. public function testAnnotationWithTargetEmptyError(): void
  946. {
  947. $parser = $this->createTestParser();
  948. $context = 'class SomeClassName';
  949. $docblock = <<<DOCBLOCK
  950. /**
  951. * @AnnotationWithTargetEmpty()
  952. */
  953. DOCBLOCK;
  954. $parser->setTarget(Target::TARGET_CLASS);
  955. $this->expectException(InvalidArgumentException::class);
  956. $this->expectExceptionMessage('@Target expects either a string value, or an array of strings, "NULL" given.');
  957. $parser->parse($docblock, $context);
  958. }
  959. /**
  960. * @group DDC-575
  961. */
  962. public function testRegressionDDC575(): void
  963. {
  964. $parser = $this->createTestParser();
  965. $docblock = <<<DOCBLOCK
  966. /**
  967. * @Name
  968. *
  969. * Will trigger error.
  970. */
  971. DOCBLOCK;
  972. $result = $parser->parse($docblock);
  973. self::assertInstanceOf(Name::class, $result[0]);
  974. $docblock = <<<DOCBLOCK
  975. /**
  976. * @Name
  977. * @Marker
  978. *
  979. * Will trigger error.
  980. */
  981. DOCBLOCK;
  982. $result = $parser->parse($docblock);
  983. self::assertInstanceOf(Name::class, $result[0]);
  984. }
  985. /**
  986. * @group DDC-77
  987. */
  988. public function testAnnotationWithoutClassIsIgnoredWithoutWarning(): void
  989. {
  990. $parser = new DocParser();
  991. $parser->setIgnoreNotImportedAnnotations(true);
  992. $result = $parser->parse('@param');
  993. self::assertEmpty($result);
  994. }
  995. /**
  996. * Tests if it's possible to ignore whole namespaces
  997. *
  998. * @param string $ignoreAnnotationName annotation/namespace to ignore
  999. * @param string $input annotation/namespace from the docblock
  1000. *
  1001. * @dataProvider provideTestIgnoreWholeNamespaces
  1002. * @group 45
  1003. */
  1004. public function testIgnoreWholeNamespaces($ignoreAnnotationName, $input): void
  1005. {
  1006. $parser = new DocParser();
  1007. $parser->setIgnoredAnnotationNamespaces([$ignoreAnnotationName => true]);
  1008. $result = $parser->parse($input);
  1009. self::assertEmpty($result);
  1010. }
  1011. /**
  1012. * @phpstan-return list<array{string, string}>
  1013. */
  1014. public function provideTestIgnoreWholeNamespaces(): array
  1015. {
  1016. return [
  1017. ['Namespace', '@Namespace'],
  1018. ['Namespace\\', '@Namespace'],
  1019. ['Namespace', '@Namespace\Subnamespace'],
  1020. ['Namespace\\', '@Namespace\Subnamespace'],
  1021. ['Namespace', '@Namespace\Subnamespace\SubSubNamespace'],
  1022. ['Namespace\\', '@Namespace\Subnamespace\SubSubNamespace'],
  1023. ['Namespace\Subnamespace', '@Namespace\Subnamespace'],
  1024. ['Namespace\Subnamespace\\', '@Namespace\Subnamespace'],
  1025. ['Namespace\Subnamespace', '@Namespace\Subnamespace\SubSubNamespace'],
  1026. ['Namespace\Subnamespace\\', '@Namespace\Subnamespace\SubSubNamespace'],
  1027. ['Namespace\Subnamespace\SubSubNamespace', '@Namespace\Subnamespace\SubSubNamespace'],
  1028. ['Namespace\Subnamespace\SubSubNamespace\\', '@Namespace\Subnamespace\SubSubNamespace'],
  1029. ];
  1030. }
  1031. /**
  1032. * @group DCOM-168
  1033. */
  1034. public function testNotAnAnnotationClassIsIgnoredWithoutWarning(): void
  1035. {
  1036. $parser = new DocParser();
  1037. $parser->setIgnoredAnnotationNames([TestCase::class => true]);
  1038. $result = $parser->parse('@\PHPUnit\Framework\TestCase');
  1039. self::assertEmpty($result);
  1040. }
  1041. public function testNotAnAnnotationClassIsIgnoredWithoutWarningWithoutCheating(): void
  1042. {
  1043. $parser = new DocParser();
  1044. $parser->setIgnoreNotImportedAnnotations(true);
  1045. $result = $parser->parse('@\PHPUnit\Framework\TestCase');
  1046. self::assertEmpty($result);
  1047. }
  1048. public function testAnnotationDontAcceptSingleQuotes(): void
  1049. {
  1050. $parser = $this->createTestParser();
  1051. $this->expectException(AnnotationException::class);
  1052. $this->expectExceptionMessage("Expected PlainValue, got ''' at position 10.");
  1053. $parser->parse("@Name(foo='bar')");
  1054. }
  1055. /**
  1056. * @group DCOM-41
  1057. */
  1058. public function testAnnotationDoesntThrowExceptionWhenAtSignIsNotFollowedByIdentifier(): void
  1059. {
  1060. $parser = new DocParser();
  1061. $result = $parser->parse("'@'");
  1062. self::assertEmpty($result);
  1063. }
  1064. /**
  1065. * @group DCOM-41
  1066. */
  1067. public function testAnnotationThrowsExceptionWhenAtSignIsNotFollowedByIdentifierInNestedAnnotation(): void
  1068. {
  1069. $parser = new DocParser();
  1070. $this->expectException(AnnotationException::class);
  1071. $parser->parse("@EasySwoole\DoctrineAnnotation\Tests\Name(@')");
  1072. }
  1073. /**
  1074. * @group DCOM-56
  1075. */
  1076. public function testAutoloadAnnotation(): void
  1077. {
  1078. self::assertFalse(
  1079. class_exists('EasySwoole\DoctrineAnnotation\Tests\Fixture\Annotation\Autoload', false),
  1080. 'Pre-condition: EasySwoole\DoctrineAnnotation\Tests\Fixture\Annotation\Autoload not allowed to be loaded.'
  1081. );
  1082. $parser = new DocParser();
  1083. AnnotationRegistry::registerAutoloadNamespace(
  1084. 'EasySwoole\DoctrineAnnotation\Tests\Fixtures\Annotation',
  1085. __DIR__ . '/../../../../'
  1086. );
  1087. $parser->setImports([
  1088. 'autoload' => Fixtures\Annotation\Autoload::class,
  1089. ]);
  1090. $annotations = $parser->parse('@Autoload');
  1091. self::assertCount(1, $annotations);
  1092. self::assertInstanceOf(Fixtures\Annotation\Autoload::class, $annotations[0]);
  1093. }
  1094. public function createTestParser(): DocParser
  1095. {
  1096. $parser = new DocParser();
  1097. $parser->setIgnoreNotImportedAnnotations(true);
  1098. $parser->setImports([
  1099. 'name' => Name::class,
  1100. '__NAMESPACE__' => 'EasySwoole\DoctrineAnnotation\Tests',
  1101. ]);
  1102. return $parser;
  1103. }
  1104. /**
  1105. * @group DDC-78
  1106. */
  1107. public function testSyntaxErrorWithContextDescription(): void
  1108. {
  1109. $parser = $this->createTestParser();
  1110. $this->expectException(AnnotationException::class);
  1111. $this->expectExceptionMessage(
  1112. "Expected PlainValue, got ''' at position 10 in class \EasySwoole\DoctrineAnnotation\Tests\Name"
  1113. );
  1114. $parser->parse("@Name(foo='bar')", 'class \EasySwoole\DoctrineAnnotation\Tests\Name');
  1115. }
  1116. /**
  1117. * @group DDC-183
  1118. */
  1119. public function testSyntaxErrorWithUnknownCharacters(): void
  1120. {
  1121. $docblock = <<<DOCBLOCK
  1122. /**
  1123. * @test at.
  1124. */
  1125. class A {
  1126. }
  1127. DOCBLOCK;
  1128. //$lexer = new \EasySwoole\DoctrineAnnotation\Lexer();
  1129. //$lexer->setInput(trim($docblock, '/ *'));
  1130. //var_dump($lexer);
  1131. try {
  1132. $parser = $this->createTestParser();
  1133. self::assertEmpty($parser->parse($docblock));
  1134. } catch (AnnotationException $e) {
  1135. $this->fail($e->getMessage());
  1136. }
  1137. }
  1138. /**
  1139. * @group DCOM-14
  1140. */
  1141. public function testIgnorePHPDocThrowTag(): void
  1142. {
  1143. $docblock = <<<DOCBLOCK
  1144. /**
  1145. * @throws \RuntimeException
  1146. */
  1147. class A {
  1148. }
  1149. DOCBLOCK;
  1150. try {
  1151. $parser = $this->createTestParser();
  1152. self::assertEmpty($parser->parse($docblock));
  1153. } catch (AnnotationException $e) {
  1154. $this->fail($e->getMessage());
  1155. }
  1156. }
  1157. /**
  1158. * @group DCOM-38
  1159. */
  1160. public function testCastInt(): void
  1161. {
  1162. $parser = $this->createTestParser();
  1163. $result = $parser->parse('@Name(foo=1234)');
  1164. $annot = $result[0];
  1165. self::assertIsInt($annot->foo);
  1166. }
  1167. /**
  1168. * @group DCOM-38
  1169. */
  1170. public function testCastNegativeInt(): void
  1171. {
  1172. $parser = $this->createTestParser();
  1173. $result = $parser->parse('@Name(foo=-1234)');
  1174. $annot = $result[0];
  1175. self::assertIsInt($annot->foo);
  1176. }
  1177. /**
  1178. * @group DCOM-38
  1179. */
  1180. public function testCastFloat(): void
  1181. {
  1182. $parser = $this->createTestParser();
  1183. $result = $parser->parse('@Name(foo=1234.345)');
  1184. $annot = $result[0];
  1185. self::assertIsFloat($annot->foo);
  1186. }
  1187. /**
  1188. * @group DCOM-38
  1189. */
  1190. public function testCastNegativeFloat(): void
  1191. {
  1192. $parser = $this->createTestParser();
  1193. $result = $parser->parse('@Name(foo=-1234.345)');
  1194. $annot = $result[0];
  1195. self::assertIsFloat($annot->foo);
  1196. $result = $parser->parse('@Marker(-1234.345)');
  1197. $annot = $result[0];
  1198. self::assertIsFloat($annot->value);
  1199. }
  1200. public function testSetValuesException(): void
  1201. {
  1202. $docblock = <<<DOCBLOCK
  1203. /**
  1204. * @SomeAnnotationClassNameWithoutConstructor(invalidaProperty = "Some val")
  1205. */
  1206. DOCBLOCK;
  1207. $this->expectException(AnnotationException::class);
  1208. $this->expectExceptionMessage(
  1209. '[Creation Error] The annotation @SomeAnnotationClassNameWithoutConstructor declared' .
  1210. ' on some class does not have a property named "invalidaProperty".
  1211. Available properties: data, name'
  1212. );
  1213. $this->createTestParser()->parse($docblock, 'some class');
  1214. }
  1215. public function testInvalidIdentifierInAnnotation(): void
  1216. {
  1217. $parser = $this->createTestParser();
  1218. $this->expectException(AnnotationException::class);
  1219. $this->expectExceptionMessage('[Syntax Error] Expected EasySwoole\DoctrineAnnotation\DocLexer::T_IDENTIFIER' .
  1220. ' or EasySwoole\DoctrineAnnotation\DocLexer::T_TRUE' .
  1221. ' or EasySwoole\DoctrineAnnotation\DocLexer::T_FALSE' .
  1222. " or EasySwoole\DoctrineAnnotation\DocLexer::T_NULL, got '3.42' at position 5.");
  1223. $parser->parse('@Foo\3.42');
  1224. }
  1225. public function testTrailingCommaIsAllowed(): void
  1226. {
  1227. $parser = $this->createTestParser();
  1228. $annots = $parser->parse('@Name({
  1229. "Foo",
  1230. "Bar",
  1231. })');
  1232. self::assertCount(1, $annots);
  1233. self::assertEquals(['Foo', 'Bar'], $annots[0]->value);
  1234. }
  1235. public function testTabPrefixIsAllowed(): void
  1236. {
  1237. $docblock = <<<DOCBLOCK
  1238. /**
  1239. * @Name
  1240. */
  1241. DOCBLOCK;
  1242. $parser = $this->createTestParser();
  1243. $result = $parser->parse($docblock);
  1244. self::assertCount(1, $result);
  1245. self::assertInstanceOf(Name::class, $result[0]);
  1246. }
  1247. public function testArrayWithColon(): void
  1248. {
  1249. $parser = $this->createTestParser();
  1250. $annots = $parser->parse('@Name({"foo": "bar"})');
  1251. self::assertCount(1, $annots);
  1252. self::assertEquals(['foo' => 'bar'], $annots[0]->value);
  1253. }
  1254. public function testInvalidContantName(): void
  1255. {
  1256. $parser = $this->createTestParser();
  1257. $this->expectException(AnnotationException::class);
  1258. $this->expectExceptionMessage("[Semantical Error] Couldn't find constant foo.");
  1259. $parser->parse('@Name(foo: "bar")');
  1260. }
  1261. /**
  1262. * Tests parsing empty arrays.
  1263. */
  1264. public function testEmptyArray(): void
  1265. {
  1266. $parser = $this->createTestParser();
  1267. $annots = $parser->parse('@Name({"foo": {}})');
  1268. self::assertCount(1, $annots);
  1269. self::assertEquals(['foo' => []], $annots[0]->value);
  1270. }
  1271. public function testKeyHasNumber(): void
  1272. {
  1273. $parser = $this->createTestParser();
  1274. $annots = $parser->parse('@SettingsAnnotation(foo="test", bar2="test")');
  1275. self::assertCount(1, $annots);
  1276. self::assertEquals(['foo' => 'test', 'bar2' => 'test'], $annots[0]->settings);
  1277. }
  1278. /**
  1279. * @group 44
  1280. */
  1281. public function testSupportsEscapedQuotedValues(): void
  1282. {
  1283. $result = $this->createTestParser()->parse('@EasySwoole\DoctrineAnnotation\Tests\Name(foo="""bar""")');
  1284. self::assertCount(1, $result);
  1285. self::assertInstanceOf(Name::class, $result[0]);
  1286. self::assertEquals('"bar"', $result[0]->foo);
  1287. }
  1288. /**
  1289. * @see http://php.net/manual/en/mbstring.configuration.php
  1290. * mbstring.func_overload can be changed only in php.ini
  1291. * so for testing this case instead of skipping it you need to manually configure your php installation
  1292. */
  1293. public function testMultiByteAnnotation(): void
  1294. {
  1295. $overloadStringFunctions = 2;
  1296. if (! extension_loaded('mbstring') || (ini_get('mbstring.func_overload') & $overloadStringFunctions) === 0) {
  1297. $this->markTestSkipped('This test requires mbstring function overloading is turned on');
  1298. }
  1299. $docblock = <<<DOCBLOCK
  1300. /**
  1301. * Мультибайтовый текст ломал парсер при оверлоадинге строковых функций
  1302. * @EasySwoole\DoctrineAnnotation\Tests\Name
  1303. */
  1304. DOCBLOCK;
  1305. $docParser = $this->createTestParser();
  1306. $result = $docParser->parse($docblock);
  1307. self::assertCount(1, $result);
  1308. }
  1309. public function testWillNotParseAnnotationSucceededByAnImmediateDash(): void
  1310. {
  1311. $parser = $this->createTestParser();
  1312. self::assertEmpty($parser->parse('@SomeAnnotationClassNameWithoutConstructorAndProperties-'));
  1313. }
  1314. public function testWillParseAnnotationSucceededByANonImmediateDash(): void
  1315. {
  1316. $result = $this
  1317. ->createTestParser()
  1318. ->parse('@SomeAnnotationClassNameWithoutConstructorAndProperties -');
  1319. self::assertCount(1, $result);
  1320. self::assertInstanceOf(SomeAnnotationClassNameWithoutConstructorAndProperties::class, $result[0]);
  1321. }
  1322. }
  1323. /** @Annotation */
  1324. class SettingsAnnotation
  1325. {
  1326. /** @var mixed[] */
  1327. public $settings;
  1328. /**
  1329. * @param mixed[] $settings
  1330. */
  1331. public function __construct($settings)
  1332. {
  1333. $this->settings = $settings;
  1334. }
  1335. }
  1336. /** @Annotation */
  1337. class SomeAnnotationClassNameWithoutConstructor
  1338. {
  1339. /** @var mixed */
  1340. public $data;
  1341. /** @var mixed */
  1342. public $name;
  1343. }
  1344. /** @Annotation */
  1345. class SomeAnnotationWithConstructorWithoutParams
  1346. {
  1347. public function __construct()
  1348. {
  1349. $this->data = 'Some data';
  1350. }
  1351. /** @var mixed */
  1352. public $data;
  1353. /** @var mixed */
  1354. public $name;
  1355. }
  1356. /** @Annotation */
  1357. class SomeAnnotationClassNameWithoutConstructorAndProperties
  1358. {
  1359. }
  1360. /**
  1361. * @Annotation
  1362. * @Target("Foo")
  1363. */
  1364. class AnnotationWithInvalidTargetDeclaration
  1365. {
  1366. }
  1367. /**
  1368. * @Annotation
  1369. * @Target
  1370. */
  1371. class AnnotationWithTargetEmpty
  1372. {
  1373. }
  1374. /** @Annotation */
  1375. class AnnotationExtendsAnnotationTargetAll extends AnnotationTargetAll
  1376. {
  1377. }
  1378. /** @Annotation */
  1379. class Name extends Annotation
  1380. {
  1381. /** @var mixed */
  1382. public $foo;
  1383. }
  1384. /** @Annotation */
  1385. class Marker
  1386. {
  1387. /** @var mixed */
  1388. public $value;
  1389. }
  1390. namespace EasySwoole\DoctrineAnnotation\Tests\FooBar;
  1391. use EasySwoole\DoctrineAnnotation\Annotation;
  1392. /** @Annotation */
  1393. class Name extends Annotation
  1394. {
  1395. }