Skip to content

Commit e3ec773

Browse files
pjiolaravel-ide-helper
andauthored
Improve replacement of return type for methods from Query\Builder (#1575)
* Add return type `|static` to Query\Builder methods (#1574) * Replace return type Query\Builder with Eloquent\Builder (#1574) * Replace return type Query\Builder only for facade Eloquent (#1574) * Add special return type replacement for Macros of \Eloquent * Restrict special return type to methods from Eloquent\Builder and Query\Builder * Do not overwrite return type in normalizeReturn() with conditional call of setType() * Use generic return type for builder methods in \Eloquent * composer fix-style --------- Co-authored-by: laravel-ide-helper <[email protected]>
1 parent 64588af commit e3ec773

File tree

4 files changed

+135
-23
lines changed

4 files changed

+135
-23
lines changed

src/Alias.php

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Closure;
1919
use Illuminate\Config\Repository as ConfigRepository;
2020
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
21+
use Illuminate\Database\Query\Builder as QueryBuilder;
2122
use Illuminate\Support\Facades\Facade;
2223
use ReflectionClass;
2324
use Throwable;
@@ -333,7 +334,15 @@ protected function addMagicMethods()
333334

334335
if (!in_array($magic, $this->usedMethods)) {
335336
if ($class !== $this->root) {
336-
$this->methods[] = new Method($method, $this->alias, $class, $magic, $this->interfaces, $this->classAliases);
337+
$this->methods[] = new Method(
338+
$method,
339+
$this->alias,
340+
$class,
341+
$magic,
342+
$this->interfaces,
343+
$this->classAliases,
344+
$this->getReturnTypeNormalizers($class)
345+
);
337346
}
338347
$this->usedMethods[] = $magic;
339348
}
@@ -363,7 +372,8 @@ protected function detectMethods()
363372
$reflection,
364373
$method->name,
365374
$this->interfaces,
366-
$this->classAliases
375+
$this->classAliases,
376+
$this->getReturnTypeNormalizers($reflection)
367377
);
368378
}
369379
$this->usedMethods[] = $method->name;
@@ -386,7 +396,8 @@ protected function detectMethods()
386396
$reflection,
387397
$macro_name,
388398
$this->interfaces,
389-
$this->classAliases
399+
$this->classAliases,
400+
$this->getReturnTypeNormalizers($reflection)
390401
);
391402
$this->usedMethods[] = $macro_name;
392403
}
@@ -395,6 +406,21 @@ protected function detectMethods()
395406
}
396407
}
397408

409+
/**
410+
* @param ReflectionClass $class
411+
* @return array<string, string>
412+
*/
413+
protected function getReturnTypeNormalizers($class)
414+
{
415+
if ($this->alias === 'Eloquent' && in_array($class->getName(), [EloquentBuilder::class, QueryBuilder::class])) {
416+
return [
417+
'$this' => '\\' . EloquentBuilder::class . ($this->config->get('ide-helper.use_generics_annotations') ? '<static>' : '|static'),
418+
];
419+
}
420+
421+
return [];
422+
}
423+
398424
/**
399425
* @param $macro_func
400426
*

src/Macro.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,18 @@ class Macro extends Method
1818
* @param null $methodName
1919
* @param array $interfaces
2020
* @param array $classAliases
21+
* @param array $returnTypeNormalizers
2122
*/
2223
public function __construct(
2324
$method,
2425
$alias,
2526
$class,
2627
$methodName = null,
2728
$interfaces = [],
28-
$classAliases = []
29+
$classAliases = [],
30+
$returnTypeNormalizers = []
2931
) {
30-
parent::__construct($method, $alias, $class, $methodName, $interfaces, $classAliases);
32+
parent::__construct($method, $alias, $class, $methodName, $interfaces, $classAliases, $returnTypeNormalizers);
3133
}
3234

3335
/**

src/Method.php

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
use Barryvdh\Reflection\DocBlock\Tag;
1818
use Barryvdh\Reflection\DocBlock\Tag\ParamTag;
1919
use Barryvdh\Reflection\DocBlock\Tag\ReturnTag;
20-
use Illuminate\Database\Eloquent\Builder;
21-
use Illuminate\Support\Str;
2220

2321
class Method
2422
{
@@ -39,6 +37,7 @@ class Method
3937
protected $return = null;
4038
protected $root;
4139
protected $classAliases;
40+
protected $returnTypeNormalizers;
4241

4342
/**
4443
* @param \ReflectionMethod|\ReflectionFunctionAbstract $method
@@ -47,12 +46,14 @@ class Method
4746
* @param string|null $methodName
4847
* @param array $interfaces
4948
* @param array $classAliases
49+
* @param array $returnTypeNormalizers
5050
*/
51-
public function __construct($method, $alias, $class, $methodName = null, $interfaces = [], array $classAliases = [])
51+
public function __construct($method, $alias, $class, $methodName = null, $interfaces = [], array $classAliases = [], array $returnTypeNormalizers = [])
5252
{
5353
$this->method = $method;
5454
$this->interfaces = $interfaces;
5555
$this->classAliases = $classAliases;
56+
$this->returnTypeNormalizers = $returnTypeNormalizers;
5657
$this->name = $methodName ?: $method->name;
5758
$this->real_name = $method->isClosure() ? $this->name : $method->name;
5859
$this->initClassDefinedProperties($method, $class);
@@ -180,6 +181,25 @@ public function getParams($implode = true)
180181
return $implode ? implode(', ', $this->params) : $this->params;
181182
}
182183

184+
/**
185+
* @param DocBlock|null $phpdoc
186+
* @return ReturnTag|null
187+
*/
188+
public function getReturnTag($phpdoc = null)
189+
{
190+
if ($phpdoc === null) {
191+
$phpdoc = $this->phpdoc;
192+
}
193+
194+
$returnTags = $phpdoc->getTagsByName('return');
195+
196+
if (count($returnTags) === 0) {
197+
return null;
198+
}
199+
200+
return reset($returnTags);
201+
}
202+
183203
/**
184204
* Get the parameters for this method including default values
185205
*
@@ -248,25 +268,31 @@ protected function normalizeParams(DocBlock $phpdoc)
248268
}
249269

250270
/**
251-
* Normalize the return tag (make full namespace, replace interfaces)
271+
* Normalize the return tag (make full namespace, replace interfaces, resolve $this)
252272
*
253273
* @param DocBlock $phpdoc
254274
*/
255275
protected function normalizeReturn(DocBlock $phpdoc)
256276
{
257277
//Get the return type and adjust them for better autocomplete
258-
$returnTags = $phpdoc->getTagsByName('return');
278+
$tag = $this->getReturnTag($phpdoc);
259279

260-
if (count($returnTags) === 0) {
280+
if ($tag === null) {
261281
$this->return = null;
262282
return;
263283
}
264284

265-
/** @var ReturnTag $tag */
266-
$tag = reset($returnTags);
267285
// Get the expanded type
268286
$returnValue = $tag->getType();
269287

288+
if (array_key_exists($returnValue, $this->returnTypeNormalizers)) {
289+
$returnValue = $this->returnTypeNormalizers[$returnValue];
290+
}
291+
292+
if ($returnValue === '$this') {
293+
$returnValue = $this->root;
294+
}
295+
270296
// Replace the interfaces
271297
foreach ($this->interfaces as $interface => $real) {
272298
$returnValue = str_replace($interface, $real, $returnValue);
@@ -275,12 +301,6 @@ protected function normalizeReturn(DocBlock $phpdoc)
275301
// Set the changed content
276302
$tag->setContent($returnValue . ' ' . $tag->getDescription());
277303
$this->return = $returnValue;
278-
279-
if ($tag->getType() === '$this') {
280-
Str::contains($this->root, Builder::class)
281-
? $tag->setType($this->root . '|static')
282-
: $tag->setType($this->root);
283-
}
284304
}
285305

286306
/**

tests/MethodTest.php

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
namespace Barryvdh\LaravelIdeHelper\Tests;
66

77
use Barryvdh\LaravelIdeHelper\Method;
8-
use Illuminate\Database\Eloquent\Builder;
8+
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
9+
use Illuminate\Database\Query\Builder as QueryBuilder;
910
use PHPUnit\Framework\TestCase;
1011

1112
class MethodTest extends TestCase
@@ -54,11 +55,11 @@ public function testOutput()
5455
}
5556

5657
/**
57-
* Test the output of a class
58+
* Test the output of Illuminate\Database\Eloquent\Builder
5859
*/
5960
public function testEloquentBuilderOutput()
6061
{
61-
$reflectionClass = new \ReflectionClass(Builder::class);
62+
$reflectionClass = new \ReflectionClass(EloquentBuilder::class);
6263
$reflectionMethod = $reflectionClass->getMethod('upsert');
6364

6465
$method = new Method($reflectionMethod, 'Builder', $reflectionClass);
@@ -76,12 +77,75 @@ public function testEloquentBuilderOutput()
7677
DOC;
7778
$this->assertSame($output, $method->getDocComment(''));
7879
$this->assertSame('upsert', $method->getName());
79-
$this->assertSame('\\' . Builder::class, $method->getDeclaringClass());
80+
$this->assertSame('\\' . EloquentBuilder::class, $method->getDeclaringClass());
8081
$this->assertSame('$values, $uniqueBy, $update', $method->getParams(true));
8182
$this->assertSame(['$values', '$uniqueBy', '$update'], $method->getParams(false));
8283
$this->assertSame('$values, $uniqueBy, $update = null', $method->getParamsWithDefault(true));
8384
$this->assertSame(['$values', '$uniqueBy', '$update = null'], $method->getParamsWithDefault(false));
8485
$this->assertTrue($method->shouldReturn());
86+
$this->assertSame('int', rtrim($method->getReturnTag()->getType()));
87+
}
88+
89+
/**
90+
* Test normalized return type of Illuminate\Database\Eloquent\Builder
91+
*/
92+
public function testEloquentBuilderNormalizedReturnType()
93+
{
94+
$reflectionClass = new \ReflectionClass(EloquentBuilder::class);
95+
$reflectionMethod = $reflectionClass->getMethod('where');
96+
97+
$method = new Method($reflectionMethod, 'Builder', $reflectionClass, null, [], [], ['$this' => '\\' . EloquentBuilder::class . '<static>']);
98+
99+
$output = <<<'DOC'
100+
/**
101+
* Add a basic where clause to the query.
102+
*
103+
* @param (\Closure(static): mixed)|string|array|\Illuminate\Contracts\Database\Query\Expression $column
104+
* @param mixed $operator
105+
* @param mixed $value
106+
* @param string $boolean
107+
* @return \Illuminate\Database\Eloquent\Builder<static>
108+
* @static
109+
*/
110+
DOC;
111+
$this->assertSame($output, $method->getDocComment(''));
112+
$this->assertSame('where', $method->getName());
113+
$this->assertSame('\\' . EloquentBuilder::class, $method->getDeclaringClass());
114+
$this->assertSame(['$column', '$operator', '$value', '$boolean'], $method->getParams(false));
115+
$this->assertSame(['$column', '$operator = null', '$value = null', "\$boolean = 'and'"], $method->getParamsWithDefault(false));
116+
$this->assertTrue($method->shouldReturn());
117+
$this->assertSame('\Illuminate\Database\Eloquent\Builder<static>', rtrim($method->getReturnTag()->getType()));
118+
}
119+
120+
/**
121+
* Test normalized return type of Illuminate\Database\Query\Builder
122+
*/
123+
public function testQueryBuilderNormalizedReturnType()
124+
{
125+
$reflectionClass = new \ReflectionClass(QueryBuilder::class);
126+
$reflectionMethod = $reflectionClass->getMethod('whereNull');
127+
128+
$method = new Method($reflectionMethod, 'Builder', $reflectionClass, null, [], [], ['$this' => '\\' . EloquentBuilder::class . '<static>']);
129+
130+
$output = <<<'DOC'
131+
/**
132+
* Add a "where null" clause to the query.
133+
*
134+
* @param string|array|\Illuminate\Contracts\Database\Query\Expression $columns
135+
* @param string $boolean
136+
* @param bool $not
137+
* @return \Illuminate\Database\Eloquent\Builder<static>
138+
* @static
139+
*/
140+
DOC;
141+
142+
$this->assertSame($output, $method->getDocComment(''));
143+
$this->assertSame('whereNull', $method->getName());
144+
$this->assertSame('\\' . QueryBuilder::class, $method->getDeclaringClass());
145+
$this->assertSame(['$columns', '$boolean', '$not'], $method->getParams(false));
146+
$this->assertSame(['$columns', "\$boolean = 'and'", '$not = false'], $method->getParamsWithDefault(false));
147+
$this->assertTrue($method->shouldReturn());
148+
$this->assertSame('\Illuminate\Database\Eloquent\Builder<static>', rtrim($method->getReturnTag()->getType()));
85149
}
86150

87151
/**

0 commit comments

Comments
 (0)