Skip to content

Commit 1c8025c

Browse files
committed
Merge branch 'dynamic_relation_closures' into 3.x
2 parents f1c8d4c + 2703caa commit 1c8025c

17 files changed

+606
-229
lines changed

extension.neon

+13
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,19 @@ services:
555555
tags:
556556
- phpstan.broker.dynamicFunctionReturnTypeExtension
557557

558+
-
559+
class: Larastan\Larastan\Parameters\EloquentBuilderRelationParameterExtension
560+
tags:
561+
- phpstan.methodParameterClosureTypeExtension
562+
563+
-
564+
class: Larastan\Larastan\Parameters\ModelRelationParameterExtension
565+
tags:
566+
- phpstan.staticMethodParameterClosureTypeExtension
567+
568+
-
569+
class: Larastan\Larastan\Parameters\RelationClosureHelper
570+
558571
-
559572
class: Larastan\Larastan\ReturnTypes\AppMakeHelper
560573

src/Methods/BuilderHelper.php

+34
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,18 @@
1717
use PHPStan\ShouldNotHappenException;
1818
use PHPStan\TrinaryLogic;
1919
use PHPStan\Type\Generic\GenericObjectType;
20+
use PHPStan\Type\ObjectType;
2021
use PHPStan\Type\Type;
22+
use PHPStan\Type\TypeCombinator;
23+
use PHPStan\Type\TypeWithClassName;
2124
use PHPStan\Type\VerbosityLevel;
2225

2326
use function array_key_exists;
2427
use function array_shift;
28+
use function collect;
2529
use function count;
2630
use function in_array;
31+
use function is_string;
2732
use function preg_split;
2833
use function substr;
2934
use function ucfirst;
@@ -232,4 +237,33 @@ public function determineBuilderName(string $modelClassName): string
232237

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

src/Methods/ModelForwardsCallsExtension.php

+13-12
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,8 @@
1616
use PHPStan\Reflection\ParameterReflection;
1717
use PHPStan\Reflection\ParametersAcceptor;
1818
use PHPStan\Reflection\Php\DummyParameter;
19-
use PHPStan\Reflection\ReflectionProvider;
2019
use PHPStan\ShouldNotHappenException;
2120
use PHPStan\TrinaryLogic;
22-
use PHPStan\Type\Generic\GenericObjectType;
2321
use PHPStan\Type\ObjectType;
2422
use PHPStan\Type\StaticType;
2523
use PHPStan\Type\Type;
@@ -35,7 +33,7 @@ final class ModelForwardsCallsExtension implements MethodsClassReflectionExtensi
3533
/** @var array<string, MethodReflection> */
3634
private array $cache = [];
3735

38-
public function __construct(private BuilderHelper $builderHelper, private ReflectionProvider $reflectionProvider, private EloquentBuilderForwardsCallsExtension $eloquentBuilderForwardsCallsExtension)
36+
public function __construct(private BuilderHelper $builderHelper, private EloquentBuilderForwardsCallsExtension $eloquentBuilderForwardsCallsExtension)
3937
{
4038
}
4139

@@ -80,8 +78,7 @@ private function findMethod(ClassReflection $classReflection, string $methodName
8078
if (in_array($methodName, ['increment', 'decrement'], true)) {
8179
$methodReflection = $classReflection->getNativeMethod($methodName);
8280

83-
return new class ($classReflection, $methodName, $methodReflection) implements MethodReflection
84-
{
81+
return new class ($classReflection, $methodName, $methodReflection) implements MethodReflection {
8582
private ClassReflection $classReflection;
8683

8784
private string $methodName;
@@ -168,17 +165,21 @@ public function hasSideEffects(): TrinaryLogic
168165
};
169166
}
170167

171-
$builderReflection = $this->reflectionProvider->getClass($builderName)->withTypes([new ObjectType($classReflection->getName())]);
172-
$genericBuilderAndModelType = new GenericObjectType($builderName, [new ObjectType($classReflection->getName())]);
168+
$builderType = $this->builderHelper->getBuilderTypeForModels($classReflection->getName());
169+
$builderReflection = $builderType->getClassReflection();
170+
171+
if ($builderReflection === null) {
172+
return null;
173+
}
173174

174175
if ($builderReflection->hasNativeMethod($methodName)) {
175176
$reflection = $builderReflection->getNativeMethod($methodName);
176177

177-
$parametersAcceptor = $this->transformStaticParameters($reflection, $genericBuilderAndModelType);
178+
$parametersAcceptor = $this->transformStaticParameters($reflection, $builderType);
178179

179-
$returnType = TypeTraverser::map($parametersAcceptor->getReturnType(), static function (Type $type, callable $traverse) use ($genericBuilderAndModelType) {
180+
$returnType = TypeTraverser::map($parametersAcceptor->getReturnType(), static function (Type $type, callable $traverse) use ($builderType) {
180181
if ($type instanceof TypeWithClassName && $type->getClassName() === Builder::class) {
181-
return $genericBuilderAndModelType;
182+
return $builderType;
182183
}
183184

184185
return $traverse($type);
@@ -200,7 +201,7 @@ public function hasSideEffects(): TrinaryLogic
200201
return null;
201202
}
202203

203-
private function transformStaticParameters(MethodReflection $method, GenericObjectType $builder): ParametersAcceptor
204+
private function transformStaticParameters(MethodReflection $method, ObjectType $builder): ParametersAcceptor
204205
{
205206
$acceptor = $method->getVariants()[0];
206207

@@ -218,7 +219,7 @@ private function transformStaticParameters(MethodReflection $method, GenericObje
218219
}, $acceptor->getParameters()), $acceptor->isVariadic(), $this->transformStaticType($acceptor->getReturnType(), $builder));
219220
}
220221

221-
private function transformStaticType(Type $type, GenericObjectType $builder): Type
222+
private function transformStaticType(Type $type, ObjectType $builder): Type
222223
{
223224
return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($builder): Type {
224225
if ($type instanceof StaticType) {

src/Methods/RelationForwardsCallsExtension.php

+1-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
use PHPStan\Reflection\MissingMethodFromReflectionException;
1616
use PHPStan\Reflection\ReflectionProvider;
1717
use PHPStan\ShouldNotHappenException;
18-
use PHPStan\Type\Generic\GenericObjectType;
1918
use PHPStan\Type\ObjectType;
2019
use PHPStan\Type\ThisType;
2120

@@ -80,8 +79,7 @@ private function findMethod(ClassReflection $classReflection, string $methodName
8079
return null;
8180
}
8281

83-
$builderName = $this->builderHelper->determineBuilderName($modelReflection->getName());
84-
$builderType = new GenericObjectType($builderName, [new ObjectType($modelReflection->getName())]);
82+
$builderType = $this->builderHelper->getBuilderTypeForModels($modelReflection->getName());
8583

8684
if (! $builderType->hasMethod($methodName)->yes()) {
8785
return null;
+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)