From 36c4406b3a6ae1fd6480ee03eb81551696a324b6 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev Date: Mon, 21 Dec 2020 14:15:47 +0400 Subject: [PATCH] `Alias` will grab macros from `\Illuminate\Database\Eloquent\Builder` too (#1118) * `Alias` will grab macros from `\Illuminate\Database\Eloquent\Builder` too. * `@return` will contains `|static` for Eloquent Builder macros which return Eloquent Builder instance. * `Macro` tests. * `Alias::detectMethods()` tests. * Mock classes rename. --- src/Alias.php | 4 +- src/Macro.php | 9 +++- tests/AliasTest.php | 105 ++++++++++++++++++++++++++++++++++++++++++++ tests/MacroTest.php | 79 +++++++++++++++++++++++++++++++++ 4 files changed, 194 insertions(+), 3 deletions(-) create mode 100644 tests/AliasTest.php create mode 100644 tests/MacroTest.php diff --git a/src/Alias.php b/src/Alias.php index 7b834f5..26193e7 100644 --- a/src/Alias.php +++ b/src/Alias.php @@ -17,6 +17,7 @@ use Barryvdh\Reflection\DocBlock\Serializer as DocBlockSerializer; use Barryvdh\Reflection\DocBlock\Tag\MethodTag; use Closure; use Illuminate\Config\Repository as ConfigRepository; +use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Support\Facades\Facade; use ReflectionClass; @@ -368,8 +369,9 @@ class Alias } // Check if the class is macroable + // (Eloquent\Builder is also macroable but doesn't use Macroable trait) $traits = collect($reflection->getTraitNames()); - if ($traits->contains('Illuminate\Support\Traits\Macroable')) { + if ($traits->contains('Illuminate\Support\Traits\Macroable') || $class === EloquentBuilder::class) { $properties = $reflection->getStaticProperties(); $macros = isset($properties['macros']) ? $properties['macros'] : []; foreach ($macros as $macro_name => $macro_func) { diff --git a/src/Macro.php b/src/Macro.php index 088557e..0a4b630 100644 --- a/src/Macro.php +++ b/src/Macro.php @@ -4,6 +4,7 @@ namespace Barryvdh\LaravelIdeHelper; use Barryvdh\Reflection\DocBlock; use Barryvdh\Reflection\DocBlock\Tag; +use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Support\Collection; class Macro extends Method @@ -49,8 +50,12 @@ class Macro extends Method // Add macro return type if ($method->hasReturnType()) { - $type = $method->getReturnType()->getName(); - $type .= $method->getReturnType()->allowsNull() ? '|null' : ''; + $builder = EloquentBuilder::class; + $return = $method->getReturnType(); + + $type = $return->getName(); + $type .= $this->root === "\\{$builder}" && $return->getName() === $builder ? '|static' : ''; + $type .= $return->allowsNull() ? '|null' : ''; $this->phpdoc->appendTag(Tag::createInstance("@return {$type}")); } diff --git a/tests/AliasTest.php b/tests/AliasTest.php new file mode 100644 index 0000000..39d221c --- /dev/null +++ b/tests/AliasTest.php @@ -0,0 +1,105 @@ +setClasses([Builder::class]); + $alias->detectMethods(); + + // Test + $this->assertNotNull($this->getAliasMacro($alias, Builder::class, $macro)); + } + + /** + * @covers ::detectMethods + */ + public function testDetectMethodsEloquentBuilderMacros(): void + { + // Mock + $macro = __FUNCTION__; + $alias = new AliasMock(); + + // Macros + EloquentBuilder::macro( + $macro, + function () { + // empty + } + ); + + // Prepare + $alias->setClasses([EloquentBuilder::class]); + $alias->detectMethods(); + + // Test + $this->assertNotNull($this->getAliasMacro($alias, EloquentBuilder::class, $macro)); + } + + protected function getAliasMacro(Alias $alias, string $class, string $method): ?Macro + { + return Arr::first( + $alias->getMethods(), + function ($macro) use ($class, $method) { + return $macro instanceof Macro + && $macro->getDeclaringClass() === "\\{$class}" + && $macro->getName() === $method; + } + ); + } +} + +/** + * @internal + * @noinspection PhpMultipleClassesDeclarationsInOneFile + */ +class AliasMock extends Alias +{ + public function __construct() + { + // no need to call parent + } + + /** + * @param string[] $classes + */ + public function setClasses(array $classes) + { + $this->classes = $classes; + } + + public function detectMethods() + { + parent::detectMethods(); + } +} diff --git a/tests/MacroTest.php b/tests/MacroTest.php new file mode 100644 index 0000000..6c9a486 --- /dev/null +++ b/tests/MacroTest.php @@ -0,0 +1,79 @@ +getPhpDoc( + new ReflectionFunction( + function (): EloquentBuilder { + return $this; + } + ), + $class + ); + + $this->assertNotNull($phpdoc); + $this->assertEquals( + '@return \Illuminate\Database\Eloquent\Builder|static', + $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; + } +} + +/** + * @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; + } +}