Skip to content

Commit

Permalink
Merge branch 'dynamic_relation_closures' into 2.x
Browse files Browse the repository at this point in the history
  • Loading branch information
calebdw committed Sep 23, 2024
2 parents 412862a + bd76cbc commit e47ecb8
Show file tree
Hide file tree
Showing 16 changed files with 602 additions and 218 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ This fork is intended to provide the community with immediate access to these en
This fork includes the following changes and enhancements:

- [feat: update relation generics (support Laravel >= 11.15)](https://github.com/larastan/larastan/pull/1990)
- [feat: support dynamic relation closures](https://github.com/larastan/larastan/pull/2048)
- [feat: support newFactory method when resolving factory](https://github.com/larastan/larastan/pull/1922)
- [feat: add support for config array shapes](https://github.com/larastan/larastan/pull/2004)
- [feat: support multiple database connections](https://github.com/larastan/larastan/pull/1879)
Expand Down
13 changes: 13 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,19 @@ services:
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension

-
class: Larastan\Larastan\Parameters\EloquentBuilderRelationParameterExtension
tags:
- phpstan.methodParameterClosureTypeExtension

-
class: Larastan\Larastan\Parameters\ModelRelationParameterExtension
tags:
- phpstan.staticMethodParameterClosureTypeExtension

-
class: Larastan\Larastan\Parameters\RelationClosureHelper

-
class: Larastan\Larastan\ReturnTypes\AppMakeHelper

Expand Down
34 changes: 34 additions & 0 deletions src/Methods/BuilderHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,18 @@
use PHPStan\ShouldNotHappenException;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeWithClassName;
use PHPStan\Type\VerbosityLevel;

use function array_key_exists;
use function array_shift;
use function collect;
use function count;
use function in_array;
use function is_string;
use function preg_split;
use function substr;
use function ucfirst;
Expand Down Expand Up @@ -233,4 +238,33 @@ public function determineBuilderName(string $modelClassName): string

return $returnType->describe(VerbosityLevel::value());
}

/**
* @param array<int, string|TypeWithClassName>|string|TypeWithClassName $models
*
* @return ($models is array<int, string|TypeWithClassName> ? Type : ObjectType)
*/
public function getBuilderTypeForModels(array|string|TypeWithClassName $models): Type
{
return collect()
->wrap($models)
->unique()
->mapWithKeys(static function ($model) {
if (is_string($model)) {
return [$model => new ObjectType($model)];
}

return [$model->getClassName() => $model];
})
->mapToGroups(fn ($type, $class) => [$this->determineBuilderName($class) => $type])
->map(function ($models, $builder) {
$builderReflection = $this->reflectionProvider->getClass($builder);

return $builderReflection->isGeneric()
? new GenericObjectType($builder, [TypeCombinator::union(...$models)])
: new ObjectType($builder);
})
->values()
->pipe(static fn ($types) => TypeCombinator::union(...$types));
}
}
14 changes: 6 additions & 8 deletions src/Methods/ModelForwardsCallsExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,8 @@
use PHPStan\Reflection\ParametersAcceptor;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Reflection\Php\DummyParameter;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\ShouldNotHappenException;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\StaticType;
use PHPStan\Type\ThisType;
Expand All @@ -39,7 +37,6 @@ final class ModelForwardsCallsExtension implements MethodsClassReflectionExtensi

public function __construct(
private BuilderHelper $builderHelper,
private ReflectionProvider $reflectionProvider,
private EloquentBuilderForwardsCallsExtension $eloquentBuilderForwardsCallsExtension,
) {
}
Expand Down Expand Up @@ -84,11 +81,12 @@ private function findMethod(ClassReflection $classReflection, string $methodName
return $this->counterMethodReflection($classReflection, $methodName);
}

$builderName = $this->builderHelper->determineBuilderName($classReflection->getName());
$builderReflection = $this->reflectionProvider->getClass($builderName)->withTypes([new ObjectType($classReflection->getName())]);
$builderType = $builderReflection->isGeneric()
? new GenericObjectType($builderName, [new ObjectType($classReflection->getName())])
: new ObjectType($builderName);
$builderType = $this->builderHelper->getBuilderTypeForModels($classReflection->getName());
$builderReflection = $builderType->getClassReflection();

if ($builderReflection === null) {
return null;
}

if ($builderReflection->hasNativeMethod($methodName)) {
$reflection = $builderReflection->getNativeMethod($methodName);
Expand Down
7 changes: 5 additions & 2 deletions src/Methods/RelationForwardsCallsExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,12 @@ private function findMethod(ClassReflection $classReflection, string $methodName
$modelReflection = $this->reflectionProvider->getClass(Model::class);
}

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

$builderReflection = $this->reflectionProvider->getClass($builderName)->withTypes([$relatedModel]);
if ($builderReflection === null) {
return null;
}

if ($builderReflection->hasNativeMethod($methodName)) {
$reflection = $builderReflection->getNativeMethod($methodName);
Expand Down
48 changes: 48 additions & 0 deletions src/Parameters/ClosureQueryParameter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace Larastan\Larastan\Parameters;

use PHPStan\Reflection\ParameterReflection;
use PHPStan\Reflection\PassedByReference;
use PHPStan\Type\Type;

final class ClosureQueryParameter implements ParameterReflection
{
public function __construct(
private string $name,
private Type $type,
) {
}

public function getName(): string
{
return $this->name;
}

public function isOptional(): bool
{
return false;
}

public function getType(): Type
{
return $this->type;
}

public function passedByReference(): PassedByReference
{
return PassedByReference::createNo();
}

public function isVariadic(): bool
{
return false;
}

public function getDefaultValue(): Type|null
{
return null;
}
}
33 changes: 33 additions & 0 deletions src/Parameters/EloquentBuilderRelationParameterExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Larastan\Larastan\Parameters;

use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParameterReflection;
use PHPStan\Type\MethodParameterClosureTypeExtension;
use PHPStan\Type\Type;

final class EloquentBuilderRelationParameterExtension implements MethodParameterClosureTypeExtension
{
public function __construct(private RelationClosureHelper $relationClosureHelper)
{
}

public function isMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool
{
return $this->relationClosureHelper->isMethodSupported($methodReflection, $parameter);
}

public function getTypeFromMethodCall(
MethodReflection $methodReflection,
MethodCall $methodCall,
ParameterReflection $parameter,
Scope $scope,
): Type|null {
return $this->relationClosureHelper->getTypeFromMethodCall($methodReflection, $methodCall, $parameter, $scope);
}
}
33 changes: 33 additions & 0 deletions src/Parameters/ModelRelationParameterExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Larastan\Larastan\Parameters;

use PhpParser\Node\Expr\StaticCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParameterReflection;
use PHPStan\Type\StaticMethodParameterClosureTypeExtension;
use PHPStan\Type\Type;

final class ModelRelationParameterExtension implements StaticMethodParameterClosureTypeExtension
{
public function __construct(private RelationClosureHelper $relationClosureHelper)
{
}

public function isStaticMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool
{
return $this->relationClosureHelper->isMethodSupported($methodReflection, $parameter);
}

public function getTypeFromStaticMethodCall(
MethodReflection $methodReflection,
StaticCall $methodCall,
ParameterReflection $parameter,
Scope $scope,
): Type|null {
return $this->relationClosureHelper->getTypeFromMethodCall($methodReflection, $methodCall, $parameter, $scope);
}
}
Loading

0 comments on commit e47ecb8

Please sign in to comment.