getPhpDoc( new ReflectionFunction( function (): EloquentBuilder { return $this; } ), $class ); $this->assertNotNull($phpdoc); $this->assertEquals( '@return \Illuminate\Database\Eloquent\Builder|static', $this->tagsToString($phpdoc, 'return') ); } /** * @covers ::initPhpDoc * @throws \ReflectionException */ public function testInitPhpDocClosureWithoutDocBlock(): void { $phpdoc = (new MacroMock())->getPhpDoc( new ReflectionFunction( function (?int $a = null): int { return 0; } ) ); $this->assertNotNull($phpdoc); $this->assertEmpty($phpdoc->getText()); $this->assertEquals('@param int|null $a', $this->tagsToString($phpdoc, 'param')); $this->assertEquals('@return int', $this->tagsToString($phpdoc, 'return')); $this->assertTrue($phpdoc->hasTag('see')); } /** * @covers ::initPhpDoc * @throws \ReflectionException */ public function testInitPhpDocClosureWithArgsAndReturnType(): void { $phpdoc = (new MacroMock())->getPhpDoc( new ReflectionFunction( /** * Test docblock. */ function (?int $a = null): int { return 0; } ) ); $this->assertNotNull($phpdoc); $this->assertStringContainsString('Test docblock', $phpdoc->getText()); $this->assertEquals('@param int|null $a', $this->tagsToString($phpdoc, 'param')); $this->assertEquals('@return int', $this->tagsToString($phpdoc, 'return')); $this->assertTrue($phpdoc->hasTag('see')); } /** * @covers ::initPhpDoc * @throws \ReflectionException */ public function testInitPhpDocClosureWithArgs(): void { $phpdoc = (new MacroMock())->getPhpDoc( new ReflectionFunction( /** * Test docblock. */ function (?int $a = null) { return 0; } ) ); $this->assertNotNull($phpdoc); $this->assertStringContainsString('Test docblock', $phpdoc->getText()); $this->assertEquals('@param int|null $a', $this->tagsToString($phpdoc, 'param')); $this->assertFalse($phpdoc->hasTag('return')); $this->assertTrue($phpdoc->hasTag('see')); } /** * @covers ::initPhpDoc * @throws \ReflectionException */ public function testInitPhpDocClosureWithReturnType(): void { $phpdoc = (new MacroMock())->getPhpDoc( new ReflectionFunction( /** * Test docblock. */ function (): int { return 0; } ) ); $this->assertNotNull($phpdoc); $this->assertStringContainsString('Test docblock', $phpdoc->getText()); $this->assertFalse($phpdoc->hasTag('param')); $this->assertEquals('@return int', $this->tagsToString($phpdoc, 'return')); $this->assertTrue($phpdoc->hasTag('see')); } /** * @covers ::initPhpDoc */ public function testInitPhpDocParamsAddedOnlyNotPresent(): void { $phpdoc = (new MacroMock())->getPhpDoc( new ReflectionFunction( /** * Test docblock. * * @param \stdClass|null $a aaaaa */ function ($a = null): int { return 0; } ) ); $this->assertNotNull($phpdoc); $this->assertStringContainsString('Test docblock', $phpdoc->getText()); $this->assertEquals('@param \stdClass|null $a aaaaa', $this->tagsToString($phpdoc, 'param')); $this->assertEquals('@return int', $this->tagsToString($phpdoc, 'return')); } /** * @covers ::initPhpDoc */ public function testInitPhpDocReturnAddedOnlyNotPresent(): void { $phpdoc = (new MacroMock())->getPhpDoc( new ReflectionFunction( /** * Test docblock. * * @return \stdClass|null rrrrrrr */ function ($a = null): int { return 0; } ) ); $this->assertNotNull($phpdoc); $this->assertStringContainsString('Test docblock', $phpdoc->getText()); $this->assertEquals('@param mixed $a', $this->tagsToString($phpdoc, 'param')); $this->assertEquals('@return \stdClass|null rrrrrrr', $this->tagsToString($phpdoc, 'return')); } public function testInitPhpDocParamsWithUnionTypes(): void { $phpdoc = (new MacroMock())->getPhpDoc(eval(<<<'PHP' return new ReflectionFunction( /** * Test docblock. */ function (\Stringable|string $a = null): \Stringable|string|null { return $a; } ); PHP)); $this->assertNotNull($phpdoc); $this->assertStringContainsString('Test docblock', $phpdoc->getText()); $this->assertEquals('@param \Stringable|string|null $a', $this->tagsToString($phpdoc, 'param')); $this->assertEquals('@return \Stringable|string|null', $this->tagsToString($phpdoc, 'return')); } public function testInitPhpDocParamsWithDnfTypes(): void { $phpdoc = (new MacroMock())->getPhpDoc(eval(<<<'PHP' return new ReflectionFunction( /** * Test docblock with DNF types. */ function ((\Countable&\Iterator)|null $a): (\Countable&\Iterator)|\stdClass|null { return $a; } ); PHP)); $this->assertNotNull($phpdoc); $this->assertStringContainsString('Test docblock with DNF types', $phpdoc->getText()); $this->assertEquals('@param (Countable&Iterator)|null $a', $this->tagsToString($phpdoc, 'param')); $this->assertEquals('@return (Countable&Iterator)|\stdClass|null', $this->tagsToString($phpdoc, 'return')); } public function testInitPhpDocParamsWithPureIntersectionType(): void { $phpdoc = (new MacroMock())->getPhpDoc(eval(<<<'PHP' return new ReflectionFunction( /** * Test docblock with pure intersection type. */ function (\Countable&\Iterator $a): \Countable&\Iterator { return $a; } ); PHP)); $this->assertNotNull($phpdoc); $this->assertStringContainsString('Test docblock with pure intersection type', $phpdoc->getText()); $this->assertEquals('@param (Countable&Iterator) $a', $this->tagsToString($phpdoc, 'param')); $this->assertEquals('@return (Countable&Iterator)', $this->tagsToString($phpdoc, 'return')); } public function testInitPhpDocParamsWithMultipleIntersections(): void { $phpdoc = (new MacroMock())->getPhpDoc(eval(<<<'PHP' return new ReflectionFunction( /** * Test docblock with multiple intersection segments. */ function ((\Countable&\Iterator)|(\ArrayAccess&\Stringable) $a): (\Countable&\Iterator)|(\ArrayAccess&\Stringable)|null { return $a; } ); PHP)); $this->assertNotNull($phpdoc); $this->assertStringContainsString('Test docblock with multiple intersection segments', $phpdoc->getText()); $this->assertEquals('@param (Countable&Iterator)|(ArrayAccess&Stringable) $a', $this->tagsToString($phpdoc, 'param')); $this->assertEquals('@return (Countable&Iterator)|(ArrayAccess&Stringable)|null', $this->tagsToString($phpdoc, 'return')); } protected function tagsToString(DocBlock $docBlock, string $name) { $tags = $docBlock->getTagsByName($name); $tags = array_map( function (Tag $tag) { return trim((string)$tag); }, $tags ); $tags = implode(PHP_EOL, $tags); return $tags; } /** * Test that we can actually instantiate the class */ public function testCanInstantiate() { $reflectionMethod = new \ReflectionMethod(UrlGeneratorMacroClass::class, '__invoke'); $macro = new Macro($reflectionMethod, UrlGenerator::class, new ReflectionClass(UrlGenerator::class), 'macroName'); $this->assertInstanceOf(Macro::class, $macro); } /** * Test the output of a class */ public function testOutput() { $reflectionMethod = new \ReflectionMethod(UrlGeneratorMacroClass::class, '__invoke'); $macro = new Macro($reflectionMethod, 'URL', new ReflectionClass(UrlGenerator::class), 'macroName'); $output = <<<'DOC' /** * @param string $foo * @param int $bar * @return string * @see \Barryvdh\LaravelIdeHelper\Tests\UrlGeneratorMacroClass::__invoke() * @static */ DOC; $this->assertSame($output, $macro->getDocComment('')); $this->assertSame('__invoke', $macro->getRealName()); $this->assertSame('\\' . UrlGenerator::class, $macro->getDeclaringClass()); $this->assertSame('$foo, $bar', $macro->getParams(true)); $this->assertSame(['$foo', '$bar'], $macro->getParams(false)); $this->assertSame('$foo, $bar = 0', $macro->getParamsWithDefault(true)); $this->assertSame(['$foo', '$bar = 0'], $macro->getParamsWithDefault(false)); $this->assertTrue($macro->shouldReturn()); $this->assertSame('$instance->__invoke($foo, $bar)', $macro->getRootMethodCall()); } } /** * @internal * @noinspection PhpMultipleClassesDeclarationsInOneFile */ class MacroMock extends Macro { public function __construct() { // no need to call parent } public function getPhpDoc(ReflectionFunctionAbstract $method, ?ReflectionClass $class = null): DocBlock { return (new Macro($method, '', $class ?? $method->getClosureScopeClass()))->phpdoc; } } /** * Example of an invokable class to be used as a macro. */ class UrlGeneratorMacroClass { /** * @param string $foo * @param int $bar * @return string */ public function __invoke(string $foo, int $bar = 0): string { return ''; } }