Skip to content

Commit

Permalink
fix: factory trashed method (#1876)
Browse files Browse the repository at this point in the history
  • Loading branch information
calebdw committed Apr 16, 2024
1 parent 101f1a4 commit 92376b5
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 31 deletions.
39 changes: 30 additions & 9 deletions src/Methods/ModelFactoryMethodsClassReflectionExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
namespace Larastan\Larastan\Methods;

use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Str;
use PHPStan\Analyser\OutOfClassScope;
use PHPStan\Reflection\ClassMemberReflection;
Expand All @@ -14,37 +16,56 @@
use PHPStan\Reflection\MethodsClassReflectionExtension;
use PHPStan\Reflection\ParametersAcceptor;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Generic\TemplateTypeMap;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;

use function array_key_exists;

class ModelFactoryMethodsClassReflectionExtension implements MethodsClassReflectionExtension
{
public function __construct(
private ReflectionProvider $reflectionProvider,
) {
}

public function hasMethod(ClassReflection $classReflection, string $methodName): bool
{
if (! $classReflection->isSubclassOf(Factory::class)) {
return false;
}

if (! Str::startsWith($methodName, ['for', 'has'])) {
return false;
}

$relationship = Str::camel(Str::substr($methodName, 3));
$modelType = $classReflection->getActiveTemplateTypeMap()->getType('TModel');

$parent = $classReflection->getParentClass();
// Generic type is not specified
if ($modelType === null) {
if (! $classReflection->isGeneric() && $classReflection->getParentClass()?->isGeneric()) {
$modelType = $classReflection->getParentClass()->getActiveTemplateTypeMap()->getType('TModel');
}
}

if ($parent === null) {
if ($modelType === null) {
return false;
}

$modelType = $parent->getActiveTemplateTypeMap()->getType('TModel');
if ($modelType->getObjectClassReflections() !== []) {
$modelReflection = $modelType->getObjectClassReflections()[0];
} else {
$modelReflection = $this->reflectionProvider->getClass(Model::class);
}

if ($modelType === null) {
if ($methodName === 'trashed' && array_key_exists(SoftDeletes::class, $modelReflection->getTraits(true))) {
return true;
}

if (! Str::startsWith($methodName, ['for', 'has'])) {
return false;
}

$relationship = Str::camel(Str::substr($methodName, 3));

return $modelType->hasMethod($relationship)->yes();
}

Expand Down
34 changes: 12 additions & 22 deletions tests/Type/data/model-factories.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@

use function PHPStan\Testing\assertType;

function doFoo(): void
{
function dummy(?int $foo): void {
assertType('Database\Factories\UserFactory', User::factory());
assertType('Database\Factories\UserFactory', User::factory()->new());
assertType('App\User', User::factory()->createOne());
Expand Down Expand Up @@ -50,27 +49,7 @@ function doFoo(): void
assertType('App\User', User::factory()->hasPosts(3, ['active' => 0])->createOne());
assertType('Database\Factories\UserFactory', User::factory()->forParent());
assertType('Database\Factories\UserFactory', User::factory()->forParent(['meta' => ['foo']]));
}

function foo(?int $foo): void
{
assertType('App\User|Illuminate\Database\Eloquent\Collection<int, App\User>', User::factory()->count($foo)->create());
assertType('App\User|Illuminate\Database\Eloquent\Collection<int, App\User>', User::factory()->count($foo)->createQuietly());
assertType('App\User|Illuminate\Database\Eloquent\Collection<int, App\User>', User::factory()->count($foo)->make());

assertType('App\User|Illuminate\Database\Eloquent\Collection<int, App\User>', User::factory($foo)->create());
assertType('App\User|Illuminate\Database\Eloquent\Collection<int, App\User>', User::factory($foo)->createQuietly());
assertType('App\User|Illuminate\Database\Eloquent\Collection<int, App\User>', User::factory($foo)->make());
}

function doBar()
{
assertType('Database\Factories\UserFactory', User::factory());
assertType('Database\Factories\UserFactory', User::factory()->new());
assertType('App\User', User::factory()->createOne());
assertType('App\User', User::factory()->createOneQuietly());
assertType('Illuminate\Database\Eloquent\Collection<int, App\User>', User::factory()->createMany([]));
assertType('App\User', User::factory()->makeOne());
assertType('Database\Factories\UserFactory', User::factory()->unverified());
assertType('Database\Factories\UserFactory', User::factory()->afterMaking(fn (User $user) => $user));
assertType('Database\Factories\UserFactory', User::factory()->afterCreating(fn (User $user) => $user));
Expand All @@ -84,4 +63,15 @@ function doBar()
assertType('App\Post', Post::factory()->makeOne());
assertType('Database\Factories\PostFactory', Post::factory()->afterMaking(fn (Post $post) => $post));
assertType('Database\Factories\PostFactory', Post::factory()->afterCreating(fn (Post $post) => $post));

assertType('App\User|Illuminate\Database\Eloquent\Collection<int, App\User>', User::factory()->count($foo)->create());
assertType('App\User|Illuminate\Database\Eloquent\Collection<int, App\User>', User::factory()->count($foo)->createQuietly());
assertType('App\User|Illuminate\Database\Eloquent\Collection<int, App\User>', User::factory()->count($foo)->make());

assertType('App\User|Illuminate\Database\Eloquent\Collection<int, App\User>', User::factory($foo)->create());
assertType('App\User|Illuminate\Database\Eloquent\Collection<int, App\User>', User::factory($foo)->createQuietly());
assertType('App\User|Illuminate\Database\Eloquent\Collection<int, App\User>', User::factory($foo)->make());

assertType('Database\Factories\UserFactory', User::factory()->trashed());
assertType('*ERROR*', Post::factory()->trashed());
}

0 comments on commit 92376b5

Please sign in to comment.