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