BuilderFactoryTest.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. <?php declare(strict_types=1);
  2. namespace PhpParser;
  3. use PhpParser\Node\Arg;
  4. use PhpParser\Node\Attribute;
  5. use PhpParser\Node\Expr;
  6. use PhpParser\Node\Expr\BinaryOp\Concat;
  7. use PhpParser\Node\Identifier;
  8. use PhpParser\Node\Name;
  9. use PhpParser\Node\Scalar\LNumber;
  10. use PhpParser\Node\Scalar\String_;
  11. class BuilderFactoryTest extends \PHPUnit\Framework\TestCase
  12. {
  13. /**
  14. * @dataProvider provideTestFactory
  15. */
  16. public function testFactory($methodName, $className) {
  17. $factory = new BuilderFactory;
  18. $this->assertInstanceOf($className, $factory->$methodName('test'));
  19. }
  20. public function provideTestFactory() {
  21. return [
  22. ['namespace', Builder\Namespace_::class],
  23. ['class', Builder\Class_::class],
  24. ['interface', Builder\Interface_::class],
  25. ['trait', Builder\Trait_::class],
  26. ['enum', Builder\Enum_::class],
  27. ['method', Builder\Method::class],
  28. ['function', Builder\Function_::class],
  29. ['property', Builder\Property::class],
  30. ['param', Builder\Param::class],
  31. ['use', Builder\Use_::class],
  32. ['useFunction', Builder\Use_::class],
  33. ['useConst', Builder\Use_::class],
  34. ['enumCase', Builder\EnumCase::class],
  35. ];
  36. }
  37. public function testFactoryClassConst() {
  38. $factory = new BuilderFactory;
  39. $this->assertInstanceOf(Builder\ClassConst::class, $factory->classConst('TEST',1));
  40. }
  41. public function testAttribute() {
  42. $factory = new BuilderFactory();
  43. $this->assertEquals(
  44. new Attribute(new Name('AttributeName'), [new Arg(
  45. new String_('bar'), false, false, [], new Identifier('foo')
  46. )]),
  47. $factory->attribute('AttributeName', ['foo' => 'bar'])
  48. );
  49. }
  50. public function testVal() {
  51. // This method is a wrapper around BuilderHelpers::normalizeValue(),
  52. // which is already tested elsewhere
  53. $factory = new BuilderFactory();
  54. $this->assertEquals(
  55. new String_("foo"),
  56. $factory->val("foo")
  57. );
  58. }
  59. public function testConcat() {
  60. $factory = new BuilderFactory();
  61. $varA = new Expr\Variable('a');
  62. $varB = new Expr\Variable('b');
  63. $varC = new Expr\Variable('c');
  64. $this->assertEquals(
  65. new Concat($varA, $varB),
  66. $factory->concat($varA, $varB)
  67. );
  68. $this->assertEquals(
  69. new Concat(new Concat($varA, $varB), $varC),
  70. $factory->concat($varA, $varB, $varC)
  71. );
  72. $this->assertEquals(
  73. new Concat(new Concat(new String_("a"), $varB), new String_("c")),
  74. $factory->concat("a", $varB, "c")
  75. );
  76. }
  77. public function testConcatOneError() {
  78. $this->expectException(\LogicException::class);
  79. $this->expectExceptionMessage('Expected at least two expressions');
  80. (new BuilderFactory())->concat("a");
  81. }
  82. public function testConcatInvalidExpr() {
  83. $this->expectException(\LogicException::class);
  84. $this->expectExceptionMessage('Expected string or Expr');
  85. (new BuilderFactory())->concat("a", 42);
  86. }
  87. public function testArgs() {
  88. $factory = new BuilderFactory();
  89. $unpack = new Arg(new Expr\Variable('c'), false, true);
  90. $this->assertEquals(
  91. [
  92. new Arg(new Expr\Variable('a')),
  93. new Arg(new String_('b')),
  94. $unpack
  95. ],
  96. $factory->args([new Expr\Variable('a'), 'b', $unpack])
  97. );
  98. }
  99. public function testNamedArgs() {
  100. $factory = new BuilderFactory();
  101. $this->assertEquals(
  102. [
  103. new Arg(new String_('foo')),
  104. new Arg(new String_('baz'), false, false, [], new Identifier('bar')),
  105. ],
  106. $factory->args(['foo', 'bar' => 'baz'])
  107. );
  108. }
  109. public function testCalls() {
  110. $factory = new BuilderFactory();
  111. // Simple function call
  112. $this->assertEquals(
  113. new Expr\FuncCall(
  114. new Name('var_dump'),
  115. [new Arg(new String_('str'))]
  116. ),
  117. $factory->funcCall('var_dump', ['str'])
  118. );
  119. // Dynamic function call
  120. $this->assertEquals(
  121. new Expr\FuncCall(new Expr\Variable('fn')),
  122. $factory->funcCall(new Expr\Variable('fn'))
  123. );
  124. // Simple method call
  125. $this->assertEquals(
  126. new Expr\MethodCall(
  127. new Expr\Variable('obj'),
  128. new Identifier('method'),
  129. [new Arg(new LNumber(42))]
  130. ),
  131. $factory->methodCall(new Expr\Variable('obj'), 'method', [42])
  132. );
  133. // Explicitly pass Identifier node
  134. $this->assertEquals(
  135. new Expr\MethodCall(
  136. new Expr\Variable('obj'),
  137. new Identifier('method')
  138. ),
  139. $factory->methodCall(new Expr\Variable('obj'), new Identifier('method'))
  140. );
  141. // Dynamic method call
  142. $this->assertEquals(
  143. new Expr\MethodCall(
  144. new Expr\Variable('obj'),
  145. new Expr\Variable('method')
  146. ),
  147. $factory->methodCall(new Expr\Variable('obj'), new Expr\Variable('method'))
  148. );
  149. // Simple static method call
  150. $this->assertEquals(
  151. new Expr\StaticCall(
  152. new Name\FullyQualified('Foo'),
  153. new Identifier('bar'),
  154. [new Arg(new Expr\Variable('baz'))]
  155. ),
  156. $factory->staticCall('\Foo', 'bar', [new Expr\Variable('baz')])
  157. );
  158. // Dynamic static method call
  159. $this->assertEquals(
  160. new Expr\StaticCall(
  161. new Expr\Variable('foo'),
  162. new Expr\Variable('bar')
  163. ),
  164. $factory->staticCall(new Expr\Variable('foo'), new Expr\Variable('bar'))
  165. );
  166. // Simple new call
  167. $this->assertEquals(
  168. new Expr\New_(new Name\FullyQualified('stdClass')),
  169. $factory->new('\stdClass')
  170. );
  171. // Dynamic new call
  172. $this->assertEquals(
  173. new Expr\New_(
  174. new Expr\Variable('foo'),
  175. [new Arg(new String_('bar'))]
  176. ),
  177. $factory->new(new Expr\Variable('foo'), ['bar'])
  178. );
  179. }
  180. public function testConstFetches() {
  181. $factory = new BuilderFactory();
  182. $this->assertEquals(
  183. new Expr\ConstFetch(new Name('FOO')),
  184. $factory->constFetch('FOO')
  185. );
  186. $this->assertEquals(
  187. new Expr\ClassConstFetch(new Name('Foo'), new Identifier('BAR')),
  188. $factory->classConstFetch('Foo', 'BAR')
  189. );
  190. $this->assertEquals(
  191. new Expr\ClassConstFetch(new Expr\Variable('foo'), new Identifier('BAR')),
  192. $factory->classConstFetch(new Expr\Variable('foo'), 'BAR')
  193. );
  194. $this->assertEquals(
  195. new Expr\ClassConstFetch(new Name('Foo'), new Expr\Variable('foo')),
  196. $factory->classConstFetch('Foo', $factory->var('foo'))
  197. );
  198. }
  199. public function testVar() {
  200. $factory = new BuilderFactory();
  201. $this->assertEquals(
  202. new Expr\Variable("foo"),
  203. $factory->var("foo")
  204. );
  205. $this->assertEquals(
  206. new Expr\Variable(new Expr\Variable("foo")),
  207. $factory->var($factory->var("foo"))
  208. );
  209. }
  210. public function testPropertyFetch() {
  211. $f = new BuilderFactory();
  212. $this->assertEquals(
  213. new Expr\PropertyFetch(new Expr\Variable('foo'), 'bar'),
  214. $f->propertyFetch($f->var('foo'), 'bar')
  215. );
  216. $this->assertEquals(
  217. new Expr\PropertyFetch(new Expr\Variable('foo'), 'bar'),
  218. $f->propertyFetch($f->var('foo'), new Identifier('bar'))
  219. );
  220. $this->assertEquals(
  221. new Expr\PropertyFetch(new Expr\Variable('foo'), new Expr\Variable('bar')),
  222. $f->propertyFetch($f->var('foo'), $f->var('bar'))
  223. );
  224. }
  225. public function testInvalidIdentifier() {
  226. $this->expectException(\LogicException::class);
  227. $this->expectExceptionMessage('Expected string or instance of Node\Identifier');
  228. (new BuilderFactory())->classConstFetch('Foo', new Name('foo'));
  229. }
  230. public function testInvalidIdentifierOrExpr() {
  231. $this->expectException(\LogicException::class);
  232. $this->expectExceptionMessage('Expected string or instance of Node\Identifier or Node\Expr');
  233. (new BuilderFactory())->staticCall('Foo', new Name('bar'));
  234. }
  235. public function testInvalidNameOrExpr() {
  236. $this->expectException(\LogicException::class);
  237. $this->expectExceptionMessage('Name must be a string or an instance of Node\Name or Node\Expr');
  238. (new BuilderFactory())->funcCall(new Node\Stmt\Return_());
  239. }
  240. public function testInvalidVar() {
  241. $this->expectException(\LogicException::class);
  242. $this->expectExceptionMessage('Variable name must be string or Expr');
  243. (new BuilderFactory())->var(new Node\Stmt\Return_());
  244. }
  245. public function testIntegration() {
  246. $factory = new BuilderFactory;
  247. $node = $factory->namespace('Name\Space')
  248. ->addStmt($factory->use('Foo\Bar\SomeOtherClass'))
  249. ->addStmt($factory->use('Foo\Bar')->as('A'))
  250. ->addStmt($factory->useFunction('strlen'))
  251. ->addStmt($factory->useConst('PHP_VERSION'))
  252. ->addStmt($factory
  253. ->class('SomeClass')
  254. ->extend('SomeOtherClass')
  255. ->implement('A\Few', '\Interfaces')
  256. ->addAttribute($factory->attribute('ClassAttribute', ['repository' => 'fqcn']))
  257. ->makeAbstract()
  258. ->addStmt($factory->useTrait('FirstTrait'))
  259. ->addStmt($factory->useTrait('SecondTrait', 'ThirdTrait')
  260. ->and('AnotherTrait')
  261. ->with($factory->traitUseAdaptation('foo')->as('bar'))
  262. ->with($factory->traitUseAdaptation('AnotherTrait', 'baz')->as('test'))
  263. ->with($factory->traitUseAdaptation('AnotherTrait', 'func')->insteadof('SecondTrait')))
  264. ->addStmt($factory->method('firstMethod')
  265. ->addAttribute($factory->attribute('Route', ['/index', 'name' => 'homepage']))
  266. )
  267. ->addStmt($factory->method('someMethod')
  268. ->makePublic()
  269. ->makeAbstract()
  270. ->addParam($factory->param('someParam')->setType('SomeClass'))
  271. ->setDocComment('/**
  272. * This method does something.
  273. *
  274. * @param SomeClass And takes a parameter
  275. */'))
  276. ->addStmt($factory->method('anotherMethod')
  277. ->makeProtected()
  278. ->addParam($factory->param('someParam')
  279. ->setDefault('test')
  280. ->addAttribute($factory->attribute('TaggedIterator', ['app.handlers']))
  281. )
  282. ->addStmt(new Expr\Print_(new Expr\Variable('someParam'))))
  283. ->addStmt($factory->property('someProperty')->makeProtected())
  284. ->addStmt($factory->property('anotherProperty')
  285. ->makePrivate()
  286. ->setDefault([1, 2, 3]))
  287. ->addStmt($factory->property('integerProperty')
  288. ->setType('int')
  289. ->addAttribute($factory->attribute('Column', ['options' => ['unsigned' => true]]))
  290. ->setDefault(1))
  291. ->addStmt($factory->classConst('CONST_WITH_ATTRIBUTE', 1)
  292. ->makePublic()
  293. ->addAttribute($factory->attribute('ConstAttribute'))
  294. )
  295. ->addStmt($factory->classConst("FIRST_CLASS_CONST", 1)
  296. ->addConst("SECOND_CLASS_CONST",2)
  297. ->makePrivate()))
  298. ->getNode()
  299. ;
  300. $expected = <<<'EOC'
  301. <?php
  302. namespace Name\Space;
  303. use Foo\Bar\SomeOtherClass;
  304. use Foo\Bar as A;
  305. use function strlen;
  306. use const PHP_VERSION;
  307. #[ClassAttribute(repository: 'fqcn')]
  308. abstract class SomeClass extends SomeOtherClass implements A\Few, \Interfaces
  309. {
  310. use FirstTrait;
  311. use SecondTrait, ThirdTrait, AnotherTrait {
  312. foo as bar;
  313. AnotherTrait::baz as test;
  314. AnotherTrait::func insteadof SecondTrait;
  315. }
  316. #[ConstAttribute]
  317. public const CONST_WITH_ATTRIBUTE = 1;
  318. private const FIRST_CLASS_CONST = 1, SECOND_CLASS_CONST = 2;
  319. protected $someProperty;
  320. private $anotherProperty = array(1, 2, 3);
  321. #[Column(options: array('unsigned' => true))]
  322. public int $integerProperty = 1;
  323. #[Route('/index', name: 'homepage')]
  324. function firstMethod()
  325. {
  326. }
  327. /**
  328. * This method does something.
  329. *
  330. * @param SomeClass And takes a parameter
  331. */
  332. public abstract function someMethod(SomeClass $someParam);
  333. protected function anotherMethod(#[TaggedIterator('app.handlers')] $someParam = 'test')
  334. {
  335. print $someParam;
  336. }
  337. }
  338. EOC;
  339. $stmts = [$node];
  340. $prettyPrinter = new PrettyPrinter\Standard();
  341. $generated = $prettyPrinter->prettyPrintFile($stmts);
  342. $this->assertEquals(
  343. str_replace("\r\n", "\n", $expected),
  344. str_replace("\r\n", "\n", $generated)
  345. );
  346. }
  347. }