Skip to content

Commit 5881583

Browse files
jrdnrcGeniJaho
andauthored
fix(eloquent relations): add pivot class for BelongsToMany relations (#316)
* fix(eloquent relations): add pivot class for BelongsToMany relations * pass resolved method return type * update generic count to 3 * check laravel version for pivot generic * forgot a `)` * cs * tests * correct relation method in tests * add stub * ensure upgradability from < 12.3.0 * Move model to `Source` directory * cs --------- Co-authored-by: Geni Jaho <jahogeni@gmail.com>
1 parent 5514de0 commit 5881583

File tree

6 files changed

+123
-5
lines changed

6 files changed

+123
-5
lines changed

src/Rector/ClassMethod/AddGenericReturnTypeToRelationsRector.php

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ class AddGenericReturnTypeToRelationsRector extends AbstractRector
5151
private const array RELATION_WITH_INTERMEDIATE_METHODS = ['hasManyThrough', 'hasOneThrough'];
5252

5353
private bool $shouldUseNewGenerics = false;
54+
private bool $shouldUsePivotGeneric = false;
5455

5556
public function __construct(
5657
private readonly TypeComparator $typeComparator,
@@ -200,6 +201,7 @@ public function refactor(Node $node): ?Node
200201

201202
// Put here to make the check as late as possible
202203
$this->setShouldUseNewGenerics();
204+
$this->setShouldUsePivotGeneric();
203205

204206
$classForChildGeneric = $this->getClassForChildGeneric($scope, $relationMethodCall);
205207
$classForIntermediateGeneric = $this->getClassForIntermediateGeneric($relationMethodCall);
@@ -221,7 +223,12 @@ public function refactor(Node $node): ?Node
221223

222224
$genericTypeNode = new GenericTypeNode(
223225
new FullyQualifiedIdentifierTypeNode($methodReturnTypeName),
224-
$this->getGenericTypes($relatedClass, $classForChildGeneric, $classForIntermediateGeneric),
226+
$this->getGenericTypes(
227+
$methodReturnType,
228+
$relatedClass,
229+
$classForChildGeneric,
230+
$classForIntermediateGeneric
231+
),
225232
);
226233

227234
// Update or add return tag
@@ -400,8 +407,8 @@ private function areGenericTypesEqual(
400407
$phpDocHasIntermediateGeneric = count($phpDocTypes) === 3;
401408

402409
if ($classForIntermediateGeneric === null && ! $phpDocHasIntermediateGeneric) {
403-
// If there is only one generic, it means method is using the old format. We should update it.
404-
if (count($phpDocTypes) === 1) {
410+
// If there are less than three generics, it means method is using the old format. We should update it.
411+
if (count($phpDocTypes) < 3) {
405412
return false;
406413
}
407414

@@ -449,7 +456,7 @@ private function doesMethodHasName(MethodCall $methodCall, array $methodNames):
449456
/**
450457
* @return IdentifierTypeNode[]
451458
*/
452-
private function getGenericTypes(string $relatedClass, ?string $childClass, ?string $intermediateClass): array
459+
private function getGenericTypes(Node $node, string $relatedClass, ?string $childClass, ?string $intermediateClass): array
453460
{
454461
$generics = [new FullyQualifiedIdentifierTypeNode($relatedClass)];
455462

@@ -463,6 +470,13 @@ private function getGenericTypes(string $relatedClass, ?string $childClass, ?str
463470
}
464471

465472
$generics[] = new IdentifierTypeNode('$this');
473+
474+
if ($this->shouldUsePivotGeneric && $this->isObjectType(
475+
$node,
476+
new ObjectType('Illuminate\Database\Eloquent\Relations\BelongsToMany')
477+
)) {
478+
$generics[] = new FullyQualifiedIdentifierTypeNode('\Illuminate\Database\Eloquent\Relations\Pivot');
479+
}
466480
}
467481

468482
return $generics;
@@ -476,4 +490,13 @@ private function setShouldUseNewGenerics(): void
476490
$this->shouldUseNewGenerics = version_compare($reflectionClassConstant->getValue(), '11.15.0', '>=');
477491
}
478492
}
493+
494+
private function setShouldUsePivotGeneric(): void
495+
{
496+
$reflectionClassConstant = new ReflectionClassConstant($this->applicationClass, 'VERSION');
497+
498+
if (is_string($reflectionClassConstant->getValue())) {
499+
$this->shouldUsePivotGeneric = version_compare($reflectionClassConstant->getValue(), '12.3.0', '>=');
500+
}
501+
}
479502
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Illuminate\Database\Eloquent\Relations;
4+
5+
if (class_exists('Illuminate\Database\Eloquent\Relations\BelongsToMany')) {
6+
return;
7+
}
8+
9+
class BelongsToMany extends Relation {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\ClassMethod\AddGenericReturnTypeToRelationsRector\Fixture\NewGenerics;
4+
5+
use Illuminate\Database\Eloquent\Model;
6+
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
7+
use RectorLaravel\Tests\Rector\ClassMethod\AddGenericReturnTypeToRelationsRector\Source\SomeModel;
8+
9+
class User extends Model
10+
{
11+
/**
12+
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany<\RectorLaravel\Tests\Rector\ClassMethod\AddGenericReturnTypeToRelationsRector\Source\SomeModel, $this>
13+
*/
14+
public function models(): BelongsToMany
15+
{
16+
return $this->belongsToMany(SomeModel::class);
17+
}
18+
}
19+
20+
?>
21+
-----
22+
<?php
23+
24+
namespace RectorLaravel\Tests\Rector\ClassMethod\AddGenericReturnTypeToRelationsRector\Fixture\NewGenerics;
25+
26+
use Illuminate\Database\Eloquent\Model;
27+
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
28+
use RectorLaravel\Tests\Rector\ClassMethod\AddGenericReturnTypeToRelationsRector\Source\SomeModel;
29+
30+
class User extends Model
31+
{
32+
/**
33+
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany<\RectorLaravel\Tests\Rector\ClassMethod\AddGenericReturnTypeToRelationsRector\Source\SomeModel, $this, \Illuminate\Database\Eloquent\Relations\Pivot>
34+
*/
35+
public function models(): BelongsToMany
36+
{
37+
return $this->belongsToMany(SomeModel::class);
38+
}
39+
}
40+
41+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\ClassMethod\AddGenericReturnTypeToRelationsRector\Fixture\NewGenerics;
4+
5+
use Illuminate\Database\Eloquent\Model;
6+
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
7+
use RectorLaravel\Tests\Rector\ClassMethod\AddGenericReturnTypeToRelationsRector\Source\SomeModel;
8+
9+
class User extends Model
10+
{
11+
public function models(): BelongsToMany
12+
{
13+
return $this->belongsToMany(SomeModel::class);
14+
}
15+
}
16+
17+
?>
18+
-----
19+
<?php
20+
21+
namespace RectorLaravel\Tests\Rector\ClassMethod\AddGenericReturnTypeToRelationsRector\Fixture\NewGenerics;
22+
23+
use Illuminate\Database\Eloquent\Model;
24+
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
25+
use RectorLaravel\Tests\Rector\ClassMethod\AddGenericReturnTypeToRelationsRector\Source\SomeModel;
26+
27+
class User extends Model
28+
{
29+
/**
30+
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany<\RectorLaravel\Tests\Rector\ClassMethod\AddGenericReturnTypeToRelationsRector\Source\SomeModel, $this, \Illuminate\Database\Eloquent\Relations\Pivot>
31+
*/
32+
public function models(): BelongsToMany
33+
{
34+
return $this->belongsToMany(SomeModel::class);
35+
}
36+
}
37+
38+
?>

tests/Rector/ClassMethod/AddGenericReturnTypeToRelationsRector/Source/NewApplication.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66

77
class NewApplication extends Application
88
{
9-
const VERSION = '11.15.0';
9+
const VERSION = '12.3.0';
1010
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\ClassMethod\AddGenericReturnTypeToRelationsRector\Source;
4+
5+
use Illuminate\Database\Eloquent\Model;
6+
7+
class SomeModel extends Model {}

0 commit comments

Comments
 (0)