Skip to content

Commit

Permalink
Fixed generating PHPDoc for methods with class templates (#1647)
Browse files Browse the repository at this point in the history
* Fixed generating PHPDoc for methods with class templates

* Fix style

* Use getter for alias template names

* Fixed missing class in static analysis
  • Loading branch information
chack1172 authored Dec 31, 2024
1 parent f838156 commit 0bbca8f
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 15 deletions.
53 changes: 41 additions & 12 deletions src/Alias.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Barryvdh\Reflection\DocBlock\ContextFactory;
use Barryvdh\Reflection\DocBlock\Serializer as DocBlockSerializer;
use Barryvdh\Reflection\DocBlock\Tag\MethodTag;
use Barryvdh\Reflection\DocBlock\Tag\TemplateTag;
use Closure;
use Illuminate\Config\Repository as ConfigRepository;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
Expand Down Expand Up @@ -48,6 +49,9 @@ class Alias
/** @var ConfigRepository */
protected $config;

/** @var string[] */
protected $templateNames;

/**
* @param ConfigRepository $config
* @param string $alias
Expand Down Expand Up @@ -347,7 +351,8 @@ protected function addMagicMethods()
$magic,
$this->interfaces,
$this->classAliases,
$this->getReturnTypeNormalizers($class)
$this->getReturnTypeNormalizers($class),
$this->getTemplateNames()
);
}
$this->usedMethods[] = $magic;
Expand Down Expand Up @@ -379,7 +384,8 @@ protected function detectMethods()
$method->name,
$this->interfaces,
$this->classAliases,
$this->getReturnTypeNormalizers($reflection)
$this->getReturnTypeNormalizers($reflection),
$this->getTemplateNames(),
);
}
$this->usedMethods[] = $method->name;
Expand Down Expand Up @@ -477,39 +483,62 @@ public function getDocComment($prefix = "\t\t")
/**
* @param $prefix
* @return string
* @throws \ReflectionException
*/
public function getPhpDocTemplates($prefix = "\t\t")
{
$templateDoc = new DocBlock('');
$serializer = new DocBlockSerializer(1, $prefix);

foreach ($this->getTemplateNames() as $templateName) {
$template = new TemplateTag('template', $templateName);
$template->setBound('static');
$template->setDocBlock($templateDoc);
$templateDoc->appendTag($template);
}

return $serializer->getDocComment($templateDoc);
}

/**
* @return string[]
*/
public function getTemplateNames()
{
if (!isset($this->templateNames)) {
$this->detectTemplateNames();
}
return $this->templateNames;
}

/**
* @return void
* @throws \ReflectionException
*/
protected function detectTemplateNames()
{
$templateNames = [];
foreach ($this->classes as $class) {
$reflection = new ReflectionClass($class);
$traits = collect($reflection->getTraitNames());

$phpdoc = new DocBlock($reflection);
$templates = $phpdoc->getTagsByName('template');
/** @var DocBlock\Tag\TemplateTag $template */
/** @var TemplateTag $template */
foreach ($templates as $template) {
$template->setBound('static');
$template->setDocBlock($templateDoc);
$templateDoc->appendTag($template);
$templateNames[] = $template->getTemplateName();
}

foreach ($traits as $trait) {
$phpdoc = new DocBlock(new ReflectionClass($trait));
$templates = $phpdoc->getTagsByName('template');

/** @var DocBlock\Tag\TemplateTag $template */
/** @var TemplateTag $template */
foreach ($templates as $template) {
$template->setBound('static');
$template->setDocBlock($templateDoc);
$templateDoc->appendTag($template);
$templateNames[] = $template->getTemplateName();
}
}
}
return $serializer->getDocComment($templateDoc);
$this->templateNames = $templateNames;
}

/**
Expand Down
11 changes: 8 additions & 3 deletions src/Method.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ class Method
protected $classAliases;
protected $returnTypeNormalizers;

/** @var string[] */
protected $templateNames = [];

/**
* @param \ReflectionMethod|\ReflectionFunctionAbstract $method
* @param string $alias
Expand All @@ -47,15 +50,17 @@ class Method
* @param array $interfaces
* @param array $classAliases
* @param array $returnTypeNormalizers
* @param string[] $templateNames
*/
public function __construct($method, $alias, $class, $methodName = null, $interfaces = [], array $classAliases = [], array $returnTypeNormalizers = [])
public function __construct($method, $alias, $class, $methodName = null, $interfaces = [], array $classAliases = [], array $returnTypeNormalizers = [], array $templateNames = [])
{
$this->method = $method;
$this->interfaces = $interfaces;
$this->classAliases = $classAliases;
$this->returnTypeNormalizers = $returnTypeNormalizers;
$this->name = $methodName ?: $method->name;
$this->real_name = $method->isClosure() ? $this->name : $method->name;
$this->templateNames = $templateNames;
$this->initClassDefinedProperties($method, $class);

//Reference the 'real' function in the declaring class
Expand Down Expand Up @@ -84,7 +89,7 @@ public function __construct($method, $alias, $class, $methodName = null, $interf
*/
protected function initPhpDoc($method)
{
$this->phpdoc = new DocBlock($method, new Context($this->namespace, $this->classAliases));
$this->phpdoc = new DocBlock($method, new Context($this->namespace, $this->classAliases, generics: $this->templateNames));
}

/**
Expand Down Expand Up @@ -386,7 +391,7 @@ protected function getInheritDoc($reflectionMethod)
}
if ($method) {
$namespace = $method->getDeclaringClass()->getNamespaceName();
$phpdoc = new DocBlock($method, new Context($namespace, $this->classAliases));
$phpdoc = new DocBlock($method, new Context($namespace, $this->classAliases, generics: $this->templateNames));

if (strpos($phpdoc->getText(), '{@inheritdoc}') !== false) {
//Not at the end yet, try another parent/interface..
Expand Down
16 changes: 16 additions & 0 deletions tests/AliasTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,21 @@ function () {
$this->assertNotNull($this->getAliasMacro($alias, EloquentBuilder::class, $macro));
}

/**
* @covers ::detectTemplateNames
*/
public function testTemplateNamesAreDetected(): void
{
// Mock
$alias = new AliasMock();

// Prepare
$alias->setClasses([EloquentBuilder::class]);

// Test
$this->assertSame(['TModel', 'TValue'], $alias->getTemplateNames());
}

protected function getAliasMacro(Alias $alias, string $class, string $method): ?Macro
{
return Arr::first(
Expand All @@ -82,6 +97,7 @@ function ($macro) use ($class, $method) {
/**
* @internal
* @noinspection PhpMultipleClassesDeclarationsInOneFile
* @template TValue
*/
class AliasMock extends Alias
{
Expand Down
23 changes: 23 additions & 0 deletions tests/MethodTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,29 @@ public function testClassAliases()
$this->assertSame([], $method->getParamsWithDefault(false));
$this->assertTrue($method->shouldReturn());
}

public function testEloquentBuilderWithTemplates()
{
$reflectionClass = new \ReflectionClass(EloquentBuilder::class);
$reflectionMethod = $reflectionClass->getMethod('firstOr');

$method = new Method($reflectionMethod, 'Builder', $reflectionClass, null, [], [], [], ['TModel']);

$output = <<<'DOC'
/**
* Execute the query and get the first result or call a callback.
*
* @template TValue
* @param (\Closure(): TValue)|list<string> $columns
* @param (\Closure(): TValue)|null $callback
* @return TModel|TValue
* @static
*/
DOC;
$this->assertSame($output, $method->getDocComment(''));
$this->assertSame('firstOr', $method->getName());
$this->assertSame('\\' . EloquentBuilder::class, $method->getDeclaringClass());
}
}

class ExampleClass
Expand Down

0 comments on commit 0bbca8f

Please sign in to comment.