BuilderFactoryTest.php 14 KB

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