diff --git a/config/sets/laravel90.php b/config/sets/laravel90.php
index ba558cee..a90e3bfb 100644
--- a/config/sets/laravel90.php
+++ b/config/sets/laravel90.php
@@ -10,6 +10,7 @@
use Rector\Renaming\ValueObject\MethodCallRename;
use Rector\Visibility\Rector\ClassMethod\ChangeMethodVisibilityRector;
use Rector\Visibility\ValueObject\ChangeMethodVisibility;
+use RectorLaravel\Rector\Class_\AddExtendsAnnotationToModelFactoriesRector;
use RectorLaravel\Rector\PropertyFetch\ReplaceFakerInstanceWithHelperRector;
# see https://laravel.com/docs/9.x/upgrade
@@ -70,6 +71,9 @@
// https://github.com/laravel/framework/commit/7746337149a7ffd6b4a862d9bd54593cf3520708
$rectorConfig->rule(ReplaceFakerInstanceWithHelperRector::class);
+ // https://github.com/laravel/framework/pull/39169
+ $rectorConfig->rule(AddExtendsAnnotationToModelFactoriesRector::class);
+
$rectorConfig
->ruleWithConfiguration(RenameMethodRector::class, [
// https://github.com/laravel/framework/commit/9b4f011fb95c70444812f61d46c8e21fb5b66dd9
diff --git a/docs/rector_rules_overview.md b/docs/rector_rules_overview.md
index e2374ad3..bd84a5e0 100644
--- a/docs/rector_rules_overview.md
+++ b/docs/rector_rules_overview.md
@@ -1,4 +1,4 @@
-# 37 Rules Overview
+# 39 Rules Overview
## AddArgumentDefaultValueRector
@@ -40,6 +40,26 @@ return static function (RectorConfig $rectorConfig): void {
+## AddExtendsAnnotationToModelFactoriesRector
+
+Adds the `@extends` annotation to Factories.
+
+- class: [`RectorLaravel\Rector\Class_\AddExtendsAnnotationToModelFactoriesRector`](../src/Rector/Class_/AddExtendsAnnotationToModelFactoriesRector.php)
+
+```diff
+ use Illuminate\Database\Eloquent\Factories\Factory;
+
++/**
++ * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
++ */
+ class UserFactory extends Factory
+ {
+ protected $model = \App\Models\User::class;
+ }
+```
+
+
+
## AddGenericReturnTypeToRelationsRector
Add generic return type to relations in child of `Illuminate\Database\Eloquent\Model`
@@ -795,6 +815,23 @@ It will removes the dump data just like dd or dump functions from the code.`
+## RemoveModelPropertyFromFactoriesRector
+
+Removes the `$model` property from Factories.
+
+- class: [`RectorLaravel\Rector\Class_\RemoveModelPropertyFromFactoriesRector`](../src/Rector/Class_/RemoveModelPropertyFromFactoriesRector.php)
+
+```diff
+ use Illuminate\Database\Eloquent\Factories\Factory;
+
+ class UserFactory extends Factory
+ {
+- protected $model = \App\Models\User::class;
+ }
+```
+
+
+
## ReplaceFakerInstanceWithHelperRector
Replace `$this->faker` with the `fake()` helper function in Factories
diff --git a/src/Rector/Class_/AddExtendsAnnotationToModelFactoriesRector.php b/src/Rector/Class_/AddExtendsAnnotationToModelFactoriesRector.php
new file mode 100644
index 00000000..682b10ef
--- /dev/null
+++ b/src/Rector/Class_/AddExtendsAnnotationToModelFactoriesRector.php
@@ -0,0 +1,137 @@
+
+ */
+class UserFactory extends Factory
+{
+ protected $model = \App\Models\User::class;
+}
+CODE_SAMPLE
+ ),
+ ]);
+ }
+
+ /**
+ * @return array>
+ */
+ public function getNodeTypes(): array
+ {
+ return [Class_::class];
+ }
+
+ /**
+ * @param Class_ $node
+ */
+ public function refactor(Node $node): ?Node
+ {
+ if (! $this->isObjectType($node, new ObjectType(self::FACTORY_CLASS_NAME))) {
+ return null;
+ }
+
+ foreach ($node->stmts as $stmt) {
+ if (! $stmt instanceof Property) {
+ continue;
+ }
+
+ if (! $this->isName($stmt, 'model')) {
+ continue;
+ }
+
+ $this->addExtendsPhpDocTag($node, $stmt);
+
+ break;
+ }
+
+ return $node;
+ }
+
+ public function addExtendsPhpDocTag(Node $node, Property $property): void
+ {
+ if ($property->props === []) {
+ return;
+ }
+
+ $modelName = $this->getModelName($property->props[0]->default);
+
+ if ($modelName === null) {
+ return;
+ }
+
+ $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);
+
+ if ($phpDocInfo->hasByName(self::EXTENDS_TAG_NAME)) {
+ return;
+ }
+
+ $phpDocTagNode = new PhpDocTagNode(self::EXTENDS_TAG_NAME, new ExtendsTagValueNode(
+ new GenericTypeNode(
+ new FullyQualifiedIdentifierTypeNode(self::FACTORY_CLASS_NAME),
+ [new FullyQualifiedIdentifierTypeNode($modelName)]
+ ),
+ ''
+ ));
+
+ $phpDocInfo->addPhpDocTagNode($phpDocTagNode);
+ }
+
+ private function getModelName(?Expr $defaultProp): ?string
+ {
+ if ($defaultProp instanceof ClassConstFetch) {
+ return $this->getName($defaultProp->class);
+ }
+
+ if ($defaultProp instanceof String_) {
+ return $defaultProp->value;
+ }
+
+ return null;
+ }
+}
diff --git a/src/Rector/Class_/RemoveModelPropertyFromFactoriesRector.php b/src/Rector/Class_/RemoveModelPropertyFromFactoriesRector.php
new file mode 100644
index 00000000..bc890446
--- /dev/null
+++ b/src/Rector/Class_/RemoveModelPropertyFromFactoriesRector.php
@@ -0,0 +1,80 @@
+>
+ */
+ public function getNodeTypes(): array
+ {
+ return [Class_::class];
+ }
+
+ /**
+ * @param Class_ $node
+ */
+ public function refactor(Node $node): ?Node
+ {
+ if (! $this->isObjectType($node, new ObjectType('Illuminate\Database\Eloquent\Factories\Factory'))) {
+ return null;
+ }
+
+ foreach ($node->stmts as $index => $stmt) {
+ if (! $stmt instanceof Property) {
+ continue;
+ }
+
+ if (! $this->isName($stmt, 'model')) {
+ continue;
+ }
+
+ unset($node->stmts[$index]);
+
+ break;
+ }
+
+ return $node;
+ }
+}
diff --git a/tests/Rector/Class_/AddExtendsAnnotationToModelFactoriesRector/AddExtendsAnnotationToModelFactoriesRectorTest.php b/tests/Rector/Class_/AddExtendsAnnotationToModelFactoriesRector/AddExtendsAnnotationToModelFactoriesRectorTest.php
new file mode 100644
index 00000000..03be9229
--- /dev/null
+++ b/tests/Rector/Class_/AddExtendsAnnotationToModelFactoriesRector/AddExtendsAnnotationToModelFactoriesRectorTest.php
@@ -0,0 +1,28 @@
+doTestFile($filePath);
+ }
+
+ public static function provideData(): Iterator
+ {
+ return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
+ }
+
+ public function provideConfigFilePath(): string
+ {
+ return __DIR__ . '/config/configured_rule.php';
+ }
+}
diff --git a/tests/Rector/Class_/AddExtendsAnnotationToModelFactoriesRector/Fixture/fixture.php.inc b/tests/Rector/Class_/AddExtendsAnnotationToModelFactoriesRector/Fixture/fixture.php.inc
new file mode 100644
index 00000000..129bd4d2
--- /dev/null
+++ b/tests/Rector/Class_/AddExtendsAnnotationToModelFactoriesRector/Fixture/fixture.php.inc
@@ -0,0 +1,28 @@
+
+-----
+
+ */
+class UserFactory extends Factory
+{
+ protected $model = \App\Models\User::class;
+}
+
+?>
diff --git a/tests/Rector/Class_/AddExtendsAnnotationToModelFactoriesRector/Fixture/skip_existing_extends_tag.php.inc b/tests/Rector/Class_/AddExtendsAnnotationToModelFactoriesRector/Fixture/skip_existing_extends_tag.php.inc
new file mode 100644
index 00000000..230c16ba
--- /dev/null
+++ b/tests/Rector/Class_/AddExtendsAnnotationToModelFactoriesRector/Fixture/skip_existing_extends_tag.php.inc
@@ -0,0 +1,15 @@
+
+ */
+class UserFactory extends Factory
+{
+ protected $model = \App\Models\User::class;
+}
+
+?>
diff --git a/tests/Rector/Class_/AddExtendsAnnotationToModelFactoriesRector/Fixture/using_class_string_as_model_type.php.inc b/tests/Rector/Class_/AddExtendsAnnotationToModelFactoriesRector/Fixture/using_class_string_as_model_type.php.inc
new file mode 100644
index 00000000..996e194c
--- /dev/null
+++ b/tests/Rector/Class_/AddExtendsAnnotationToModelFactoriesRector/Fixture/using_class_string_as_model_type.php.inc
@@ -0,0 +1,28 @@
+
+-----
+
+ */
+class UserFactory extends Factory
+{
+ protected $model = 'App\Models\User';
+}
+
+?>
diff --git a/tests/Rector/Class_/AddExtendsAnnotationToModelFactoriesRector/config/configured_rule.php b/tests/Rector/Class_/AddExtendsAnnotationToModelFactoriesRector/config/configured_rule.php
new file mode 100644
index 00000000..5208b5c2
--- /dev/null
+++ b/tests/Rector/Class_/AddExtendsAnnotationToModelFactoriesRector/config/configured_rule.php
@@ -0,0 +1,13 @@
+import(__DIR__ . '/../../../../../config/config.php');
+
+ $rectorConfig->rule(AddExtendsAnnotationToModelFactoriesRector::class);
+};
diff --git a/tests/Rector/Class_/RemoveModelPropertyFromFactoriesRector/Fixture/fixture.php.inc b/tests/Rector/Class_/RemoveModelPropertyFromFactoriesRector/Fixture/fixture.php.inc
new file mode 100644
index 00000000..8c0f8d5b
--- /dev/null
+++ b/tests/Rector/Class_/RemoveModelPropertyFromFactoriesRector/Fixture/fixture.php.inc
@@ -0,0 +1,24 @@
+
+-----
+
diff --git a/tests/Rector/Class_/RemoveModelPropertyFromFactoriesRector/RemoveModelPropertyFromFactoriesRectorTest.php b/tests/Rector/Class_/RemoveModelPropertyFromFactoriesRector/RemoveModelPropertyFromFactoriesRectorTest.php
new file mode 100644
index 00000000..8600366c
--- /dev/null
+++ b/tests/Rector/Class_/RemoveModelPropertyFromFactoriesRector/RemoveModelPropertyFromFactoriesRectorTest.php
@@ -0,0 +1,28 @@
+doTestFile($filePath);
+ }
+
+ public static function provideData(): Iterator
+ {
+ return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
+ }
+
+ public function provideConfigFilePath(): string
+ {
+ return __DIR__ . '/config/configured_rule.php';
+ }
+}
diff --git a/tests/Rector/Class_/RemoveModelPropertyFromFactoriesRector/config/configured_rule.php b/tests/Rector/Class_/RemoveModelPropertyFromFactoriesRector/config/configured_rule.php
new file mode 100644
index 00000000..d60363e2
--- /dev/null
+++ b/tests/Rector/Class_/RemoveModelPropertyFromFactoriesRector/config/configured_rule.php
@@ -0,0 +1,13 @@
+import(__DIR__ . '/../../../../../config/config.php');
+
+ $rectorConfig->rule(RemoveModelPropertyFromFactoriesRector::class);
+};