Skip to content

Commit e47ecb8

Browse files
committed
Merge branch 'dynamic_relation_closures' into 2.x
2 parents 412862a + bd76cbc commit e47ecb8

16 files changed

+602
-218
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ This fork is intended to provide the community with immediate access to these en
2727
This fork includes the following changes and enhancements:
2828

2929
- [feat: update relation generics (support Laravel >= 11.15)](https://github.com/larastan/larastan/pull/1990)
30+
- [feat: support dynamic relation closures](https://github.com/larastan/larastan/pull/2048)
3031
- [feat: support newFactory method when resolving factory](https://github.com/larastan/larastan/pull/1922)
3132
- [feat: add support for config array shapes](https://github.com/larastan/larastan/pull/2004)
3233
- [feat: support multiple database connections](https://github.com/larastan/larastan/pull/1879)

extension.neon

+13
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,19 @@ services:
562562
tags:
563563
- phpstan.broker.dynamicFunctionReturnTypeExtension
564564

565+
-
566+
class: Larastan\Larastan\Parameters\EloquentBuilderRelationParameterExtension
567+
tags:
568+
- phpstan.methodParameterClosureTypeExtension
569+
570+
-
571+
class: Larastan\Larastan\Parameters\ModelRelationParameterExtension
572+
tags:
573+
- phpstan.staticMethodParameterClosureTypeExtension
574+
575+
-
576+
class: Larastan\Larastan\Parameters\RelationClosureHelper
577+
565578
-
566579
class: Larastan\Larastan\ReturnTypes\AppMakeHelper
567580

src/Methods/BuilderHelper.php

+34
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,18 @@
1818
use PHPStan\ShouldNotHappenException;
1919
use PHPStan\TrinaryLogic;
2020
use PHPStan\Type\Generic\GenericObjectType;
21+
use PHPStan\Type\ObjectType;
2122
use PHPStan\Type\Type;
23+
use PHPStan\Type\TypeCombinator;
24+
use PHPStan\Type\TypeWithClassName;
2225
use PHPStan\Type\VerbosityLevel;
2326

2427
use function array_key_exists;
2528
use function array_shift;
29+
use function collect;
2630
use function count;
2731
use function in_array;
32+
use function is_string;
2833
use function preg_split;
2934
use function substr;
3035
use function ucfirst;
@@ -233,4 +238,33 @@ public function determineBuilderName(string $modelClassName): string
233238

234239
return $returnType->describe(VerbosityLevel::value());
235240
}
241+
242+
/**
243+
* @param array<int, string|TypeWithClassName>|string|TypeWithClassName $models
244+
*
245+
* @return ($models is array<int, string|TypeWithClassName> ? Type : ObjectType)
246+
*/
247+
public function getBuilderTypeForModels(array|string|TypeWithClassName $models): Type
248+
{
249+
return collect()
250+
->wrap($models)
251+
->unique()
252+
->mapWithKeys(static function ($model) {
253+
if (is_string($model)) {
254+
return [$model => new ObjectType($model)];
255+
}
256+
257+
return [$model->getClassName() => $model];
258+
})
259+
->mapToGroups(fn ($type, $class) => [$this->determineBuilderName($class) => $type])
260+
->map(function ($models, $builder) {
261+
$builderReflection = $this->reflectionProvider->getClass($builder);
262+
263+
return $builderReflection->isGeneric()
264+
? new GenericObjectType($builder, [TypeCombinator::union(...$models)])
265+
: new ObjectType($builder);
266+
})
267+
->values()
268+
->pipe(static fn ($types) => TypeCombinator::union(...$types));
269+
}
236270
}

src/Methods/ModelForwardsCallsExtension.php

+6-8
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,8 @@
1717
use PHPStan\Reflection\ParametersAcceptor;
1818
use PHPStan\Reflection\ParametersAcceptorSelector;
1919
use PHPStan\Reflection\Php\DummyParameter;
20-
use PHPStan\Reflection\ReflectionProvider;
2120
use PHPStan\ShouldNotHappenException;
2221
use PHPStan\TrinaryLogic;
23-
use PHPStan\Type\Generic\GenericObjectType;
2422
use PHPStan\Type\ObjectType;
2523
use PHPStan\Type\StaticType;
2624
use PHPStan\Type\ThisType;
@@ -39,7 +37,6 @@ final class ModelForwardsCallsExtension implements MethodsClassReflectionExtensi
3937

4038
public function __construct(
4139
private BuilderHelper $builderHelper,
42-
private ReflectionProvider $reflectionProvider,
4340
private EloquentBuilderForwardsCallsExtension $eloquentBuilderForwardsCallsExtension,
4441
) {
4542
}
@@ -84,11 +81,12 @@ private function findMethod(ClassReflection $classReflection, string $methodName
8481
return $this->counterMethodReflection($classReflection, $methodName);
8582
}
8683

87-
$builderName = $this->builderHelper->determineBuilderName($classReflection->getName());
88-
$builderReflection = $this->reflectionProvider->getClass($builderName)->withTypes([new ObjectType($classReflection->getName())]);
89-
$builderType = $builderReflection->isGeneric()
90-
? new GenericObjectType($builderName, [new ObjectType($classReflection->getName())])
91-
: new ObjectType($builderName);
84+
$builderType = $this->builderHelper->getBuilderTypeForModels($classReflection->getName());
85+
$builderReflection = $builderType->getClassReflection();
86+
87+
if ($builderReflection === null) {
88+
return null;
89+
}
9290

9391
if ($builderReflection->hasNativeMethod($methodName)) {
9492
$reflection = $builderReflection->getNativeMethod($methodName);

src/Methods/RelationForwardsCallsExtension.php

+5-2
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,12 @@ private function findMethod(ClassReflection $classReflection, string $methodName
7878
$modelReflection = $this->reflectionProvider->getClass(Model::class);
7979
}
8080

81-
$builderName = $this->builderHelper->determineBuilderName($modelReflection->getName());
81+
$builderReflection = $this->builderHelper->getBuilderTypeForModels($modelReflection->getName())
82+
->getClassReflection();
8283

83-
$builderReflection = $this->reflectionProvider->getClass($builderName)->withTypes([$relatedModel]);
84+
if ($builderReflection === null) {
85+
return null;
86+
}
8487

8588
if ($builderReflection->hasNativeMethod($methodName)) {
8689
$reflection = $builderReflection->getNativeMethod($methodName);
+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Larastan\Larastan\Parameters;
6+
7+
use PHPStan\Reflection\ParameterReflection;
8+
use PHPStan\Reflection\PassedByReference;
9+
use PHPStan\Type\Type;
10+
11+
final class ClosureQueryParameter implements ParameterReflection
12+
{
13+
public function __construct(
14+
private string $name,
15+
private Type $type,
16+
) {
17+
}
18+
19+
public function getName(): string
20+
{
21+
return $this->name;
22+
}
23+
24+
public function isOptional(): bool
25+
{
26+
return false;
27+
}
28+
29+
public function getType(): Type
30+
{
31+
return $this->type;
32+
}
33+
34+
public function passedByReference(): PassedByReference
35+
{
36+
return PassedByReference::createNo();
37+
}
38+
39+
public function isVariadic(): bool
40+
{
41+
return false;
42+
}
43+
44+
public function getDefaultValue(): Type|null
45+
{
46+
return null;
47+
}
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Larastan\Larastan\Parameters;
6+
7+
use PhpParser\Node\Expr\MethodCall;
8+
use PHPStan\Analyser\Scope;
9+
use PHPStan\Reflection\MethodReflection;
10+
use PHPStan\Reflection\ParameterReflection;
11+
use PHPStan\Type\MethodParameterClosureTypeExtension;
12+
use PHPStan\Type\Type;
13+
14+
final class EloquentBuilderRelationParameterExtension implements MethodParameterClosureTypeExtension
15+
{
16+
public function __construct(private RelationClosureHelper $relationClosureHelper)
17+
{
18+
}
19+
20+
public function isMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool
21+
{
22+
return $this->relationClosureHelper->isMethodSupported($methodReflection, $parameter);
23+
}
24+
25+
public function getTypeFromMethodCall(
26+
MethodReflection $methodReflection,
27+
MethodCall $methodCall,
28+
ParameterReflection $parameter,
29+
Scope $scope,
30+
): Type|null {
31+
return $this->relationClosureHelper->getTypeFromMethodCall($methodReflection, $methodCall, $parameter, $scope);
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Larastan\Larastan\Parameters;
6+
7+
use PhpParser\Node\Expr\StaticCall;
8+
use PHPStan\Analyser\Scope;
9+
use PHPStan\Reflection\MethodReflection;
10+
use PHPStan\Reflection\ParameterReflection;
11+
use PHPStan\Type\StaticMethodParameterClosureTypeExtension;
12+
use PHPStan\Type\Type;
13+
14+
final class ModelRelationParameterExtension implements StaticMethodParameterClosureTypeExtension
15+
{
16+
public function __construct(private RelationClosureHelper $relationClosureHelper)
17+
{
18+
}
19+
20+
public function isStaticMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool
21+
{
22+
return $this->relationClosureHelper->isMethodSupported($methodReflection, $parameter);
23+
}
24+
25+
public function getTypeFromStaticMethodCall(
26+
MethodReflection $methodReflection,
27+
StaticCall $methodCall,
28+
ParameterReflection $parameter,
29+
Scope $scope,
30+
): Type|null {
31+
return $this->relationClosureHelper->getTypeFromMethodCall($methodReflection, $methodCall, $parameter, $scope);
32+
}
33+
}

0 commit comments

Comments
 (0)