From c3ccbbaf04190b158d75499a0cb9ceaaed42d127 Mon Sep 17 00:00:00 2001 From: Liam Hammett Date: Mon, 18 Aug 2025 17:43:36 +0100 Subject: [PATCH 1/9] Add a use annotation to HasFactory trait usages --- ...ddUseAnnotationToHasFactoryTraitRector.php | 163 ++++++++++++++++++ ...eAnnotationToHasFactoryTraitRectorTest.php | 31 ++++ .../Fixture/fixture.php.inc | 30 ++++ .../Fixture/models_namespace.php.inc | 30 ++++ .../Fixture/skip_existing_annotation.php.inc | 14 ++ .../Fixture/skip_non_model.php.inc | 12 ++ .../config/configured_rule.php | 10 ++ 7 files changed, 290 insertions(+) create mode 100644 src/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector.php create mode 100644 tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/AddUseAnnotationToHasFactoryTraitRectorTest.php create mode 100644 tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/fixture.php.inc create mode 100644 tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/models_namespace.php.inc create mode 100644 tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/skip_existing_annotation.php.inc create mode 100644 tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/skip_non_model.php.inc create mode 100644 tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/config/configured_rule.php diff --git a/src/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector.php b/src/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector.php new file mode 100644 index 000000000..e17cbfbca --- /dev/null +++ b/src/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector.php @@ -0,0 +1,163 @@ + */ + use HasFactory; +} +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('Illuminate\Database\Eloquent\Model'))) { + return null; + } + + $hasChanged = false; + + foreach ($node->stmts as $stmt) { + if (! $stmt instanceof TraitUse) { + continue; + } + + if (! $this->hasHasFactoryTrait($stmt)) { + continue; + } + + if ($this->addUsePhpDocTag($stmt, $node)) { + $hasChanged = true; + } + } + + if ($hasChanged) { + return $node; + } + + return null; + } + + private function hasHasFactoryTrait(TraitUse $traitUse): bool + { + foreach ($traitUse->traits as $trait) { + $traitName = $this->getName($trait); + if ($traitName === self::HAS_FACTORY_TRAIT || $traitName === 'HasFactory') { + return true; + } + } + + return false; + } + + private function addUsePhpDocTag(TraitUse $traitUse, Class_ $class): bool + { + $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($traitUse); + + if ($phpDocInfo->hasByName(self::USE_TAG_NAME)) { + return false; + } + + $factoryClassName = $this->resolveFactoryClassName($class); + if ($factoryClassName === null) { + return false; + } + + $useAnnotationValue = 'HasFactory<' . $factoryClassName . '>'; + + $phpDocTagNode = new PhpDocTagNode( + self::USE_TAG_NAME, + new GenericTagValueNode($useAnnotationValue) + ); + + $phpDocInfo->addPhpDocTagNode($phpDocTagNode); + + $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($traitUse); + + return true; + } + + private function resolveFactoryClassName(Class_ $class): ?string + { + $className = $this->getName($class); + if ($className === null) { + return null; + } + + $classBaseName = $this->nodeNameResolver->getShortName($className); + + $factoryName = $classBaseName . 'Factory'; + + $currentNamespace = $class->namespacedName?->toString() ?? $className; + + if (str_contains($currentNamespace, '\\Models\\')) { + return '\\Database\\Factories\\' . $factoryName; + } + + if (str_contains($currentNamespace, 'App\\')) { + return '\\Database\\Factories\\' . $factoryName; + } + + return '\\Database\\Factories\\' . $factoryName; + } +} diff --git a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/AddUseAnnotationToHasFactoryTraitRectorTest.php b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/AddUseAnnotationToHasFactoryTraitRectorTest.php new file mode 100644 index 000000000..a199b7110 --- /dev/null +++ b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/AddUseAnnotationToHasFactoryTraitRectorTest.php @@ -0,0 +1,31 @@ +doTestFile($filePath); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/fixture.php.inc b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/fixture.php.inc new file mode 100644 index 000000000..7df8964b9 --- /dev/null +++ b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/fixture.php.inc @@ -0,0 +1,30 @@ + +----- + + */ + use HasFactory; +} + +?> diff --git a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/models_namespace.php.inc b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/models_namespace.php.inc new file mode 100644 index 000000000..979e7289c --- /dev/null +++ b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/models_namespace.php.inc @@ -0,0 +1,30 @@ + +----- + + */ + use HasFactory; +} + +?> diff --git a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/skip_existing_annotation.php.inc b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/skip_existing_annotation.php.inc new file mode 100644 index 000000000..a573e8dd4 --- /dev/null +++ b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/skip_existing_annotation.php.inc @@ -0,0 +1,14 @@ + */ + use HasFactory; +} + +?> diff --git a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/skip_non_model.php.inc b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/skip_non_model.php.inc new file mode 100644 index 000000000..7bba133a5 --- /dev/null +++ b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/skip_non_model.php.inc @@ -0,0 +1,12 @@ + diff --git a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/config/configured_rule.php b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/config/configured_rule.php new file mode 100644 index 000000000..374ec12aa --- /dev/null +++ b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/config/configured_rule.php @@ -0,0 +1,10 @@ +rule(AddUseAnnotationToHasFactoryTraitRector::class); +}; From b3d67523b796a4e69961d838e4d468c4e5e47751 Mon Sep 17 00:00:00 2001 From: Liam Hammett Date: Mon, 18 Aug 2025 18:13:48 +0100 Subject: [PATCH 2/9] Update to check the factory actually exists before applying the annotation --- ...ddUseAnnotationToHasFactoryTraitRector.php | 49 ++++++++++++++++--- ...eAnnotationToHasFactoryTraitRectorTest.php | 9 ++++ .../Factories/ProductFactory.php | 13 +++++ .../Factories/Tenant/UserFactory.php | 13 +++++ .../Factories/UserFactory.php | 13 +++++ .../Fixture/deep_namespace.php.inc | 30 ++++++++++++ .../Fixture/skip_factory_doesnt_exist.php.inc | 13 +++++ 7 files changed, 133 insertions(+), 7 deletions(-) create mode 100644 tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Factories/ProductFactory.php create mode 100644 tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Factories/Tenant/UserFactory.php create mode 100644 tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Factories/UserFactory.php create mode 100644 tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/deep_namespace.php.inc create mode 100644 tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/skip_factory_doesnt_exist.php.inc diff --git a/src/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector.php b/src/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector.php index e17cbfbca..1482eed51 100644 --- a/src/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector.php +++ b/src/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector.php @@ -9,6 +9,7 @@ use PhpParser\Node\Stmt\TraitUse; use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\ObjectType; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\Comments\NodeDocBlock\DocBlockUpdater; @@ -28,6 +29,7 @@ final class AddUseAnnotationToHasFactoryTraitRector extends AbstractRector public function __construct( private readonly DocBlockUpdater $docBlockUpdater, private readonly PhpDocInfoFactory $phpDocInfoFactory, + private readonly ReflectionProvider $reflectionProvider, ) {} public function getRuleDefinition(): RuleDefinition @@ -144,20 +146,53 @@ private function resolveFactoryClassName(Class_ $class): ?string return null; } - $classBaseName = $this->nodeNameResolver->getShortName($className); + $modelName = $this->nodeNameResolver->getShortName($className); - $factoryName = $classBaseName . 'Factory'; + $factoryName = $modelName . 'Factory'; $currentNamespace = $class->namespacedName?->toString() ?? $className; - if (str_contains($currentNamespace, '\\Models\\')) { - return '\\Database\\Factories\\' . $factoryName; + $factoryClassNames = $this->getPotentialFactoryClassNames($currentNamespace, $factoryName); + + foreach ($factoryClassNames as $factoryClassName) { + if ($this->reflectionProvider->hasClass($factoryClassName)) { + return $factoryClassName; + } } - if (str_contains($currentNamespace, 'App\\')) { - return '\\Database\\Factories\\' . $factoryName; + return null; + } + + /** + * @return string[] + */ + private function getPotentialFactoryClassNames(string $modelNamespace, string $factoryName): array + { + $factoryClassNames = []; + + if (str_contains($modelNamespace, '\\Models\\')) { + $afterModels = substr($modelNamespace, strpos($modelNamespace, '\\Models\\') + 8); + + if (str_contains($afterModels, '\\')) { + $namespaceParts = explode('\\', $afterModels); + array_pop($namespaceParts); + $deepNamespace = implode('\\', $namespaceParts); + + $factoryClassNames[] = '\\Database\\Factories\\' . $deepNamespace . '\\' . $factoryName; + } + } elseif (str_contains($modelNamespace, 'App\\')) { + $afterApp = substr($modelNamespace, strpos($modelNamespace, 'App\\') + 4); + + if (str_contains($afterApp, '\\')) { + $namespaceParts = explode('\\', $afterApp); + array_pop($namespaceParts); + $deepNamespace = implode('\\', $namespaceParts); + + $factoryClassNames[] = '\\Database\\Factories\\' . $deepNamespace . '\\' . $factoryName; + } } + $factoryClassNames[] = '\\Database\\Factories\\' . $factoryName; - return '\\Database\\Factories\\' . $factoryName; + return array_unique($factoryClassNames); } } diff --git a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/AddUseAnnotationToHasFactoryTraitRectorTest.php b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/AddUseAnnotationToHasFactoryTraitRectorTest.php index a199b7110..a3eee3ae7 100644 --- a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/AddUseAnnotationToHasFactoryTraitRectorTest.php +++ b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/AddUseAnnotationToHasFactoryTraitRectorTest.php @@ -10,6 +10,15 @@ final class AddUseAnnotationToHasFactoryTraitRectorTest extends AbstractRectorTestCase { + protected function setUp(): void + { + parent::setUp(); + + require_once __DIR__ . '/Factories/ProductFactory.php'; + require_once __DIR__ . '/Factories/UserFactory.php'; + require_once __DIR__ . '/Factories/Tenant/UserFactory.php'; + } + public static function provideData(): Iterator { return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); diff --git a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Factories/ProductFactory.php b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Factories/ProductFactory.php new file mode 100644 index 000000000..a987dbe20 --- /dev/null +++ b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Factories/ProductFactory.php @@ -0,0 +1,13 @@ + +----- + + */ + use HasFactory; +} + +?> diff --git a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/skip_factory_doesnt_exist.php.inc b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/skip_factory_doesnt_exist.php.inc new file mode 100644 index 000000000..6b03e8918 --- /dev/null +++ b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/skip_factory_doesnt_exist.php.inc @@ -0,0 +1,13 @@ + From 83f512df6e0d78a76a120a97c8e865226a77b3cc Mon Sep 17 00:00:00 2001 From: Liam Hammett Date: Sat, 4 Oct 2025 19:05:28 +0200 Subject: [PATCH 3/9] Refactor to UsesTagValueNode --- ...ddUseAnnotationToHasFactoryTraitRector.php | 27 ++++++++++++------- .../Fixture/deep_namespace.php.inc | 2 +- .../Fixture/fixture.php.inc | 2 +- .../Fixture/models_namespace.php.inc | 2 +- .../Fixture/skip_existing_annotation.php.inc | 2 +- 5 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector.php b/src/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector.php index 1482eed51..5d2f153bf 100644 --- a/src/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector.php +++ b/src/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector.php @@ -5,17 +5,20 @@ namespace RectorLaravel\Rector\Class_; use PhpParser\Node; +use PHPStan\Type\ObjectType; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\TraitUse; -use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode; -use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; +use RectorLaravel\AbstractRector; use PHPStan\Reflection\ReflectionProvider; -use PHPStan\Type\ObjectType; -use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\Comments\NodeDocBlock\DocBlockUpdater; -use RectorLaravel\AbstractRector; -use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; +use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; +use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\UsesTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; +use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; +use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; +use Rector\BetterPhpDocParser\ValueObject\Type\FullyQualifiedIdentifierTypeNode; /** * @see \RectorLaravel\Tests\Rector\Class_\AddUseAnnotationToHasFactoryTraitRector\AddUseAnnotationToHasFactoryTraitRectorTest @@ -52,7 +55,7 @@ class User extends Model class User extends Model { - /** @use HasFactory<\Database\Factories\UserFactory> */ + /** @use \Illuminate\Database\Eloquent\Factories\HasFactory<\Database\Factories\UserFactory> */ use HasFactory; } CODE_SAMPLE @@ -125,11 +128,15 @@ private function addUsePhpDocTag(TraitUse $traitUse, Class_ $class): bool return false; } - $useAnnotationValue = 'HasFactory<' . $factoryClassName . '>'; - $phpDocTagNode = new PhpDocTagNode( self::USE_TAG_NAME, - new GenericTagValueNode($useAnnotationValue) + new UsesTagValueNode( + new GenericTypeNode( + new FullyQualifiedIdentifierTypeNode(self::HAS_FACTORY_TRAIT), + [new FullyQualifiedIdentifierTypeNode($factoryClassName)] + ), + '' + ) ); $phpDocInfo->addPhpDocTagNode($phpDocTagNode); diff --git a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/deep_namespace.php.inc b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/deep_namespace.php.inc index c8b3c007f..9a3734c5e 100644 --- a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/deep_namespace.php.inc +++ b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/deep_namespace.php.inc @@ -22,7 +22,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; class User extends Model { /** - * @use HasFactory<\Database\Factories\Tenant\UserFactory> + * @use \Illuminate\Database\Eloquent\Factories\HasFactory<\Database\Factories\Tenant\UserFactory> */ use HasFactory; } diff --git a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/fixture.php.inc b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/fixture.php.inc index 7df8964b9..eb10fae13 100644 --- a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/fixture.php.inc +++ b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/fixture.php.inc @@ -22,7 +22,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; class User extends Model { /** - * @use HasFactory<\Database\Factories\UserFactory> + * @use \Illuminate\Database\Eloquent\Factories\HasFactory<\Database\Factories\UserFactory> */ use HasFactory; } diff --git a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/models_namespace.php.inc b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/models_namespace.php.inc index 979e7289c..f9caeb057 100644 --- a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/models_namespace.php.inc +++ b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/models_namespace.php.inc @@ -22,7 +22,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; class Product extends Model { /** - * @use HasFactory<\Database\Factories\ProductFactory> + * @use \Illuminate\Database\Eloquent\Factories\HasFactory<\Database\Factories\ProductFactory> */ use HasFactory; } diff --git a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/skip_existing_annotation.php.inc b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/skip_existing_annotation.php.inc index a573e8dd4..9ef160e88 100644 --- a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/skip_existing_annotation.php.inc +++ b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/skip_existing_annotation.php.inc @@ -7,7 +7,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; class User extends Model { - /** @use HasFactory<\Database\Factories\UserFactory> */ + /** @use \Illuminate\Database\Eloquent\Factories\HasFactory<\Database\Factories\UserFactory> */ use HasFactory; } From 87548768aeae65e92138b005d60e0ef064fd005c Mon Sep 17 00:00:00 2001 From: Liam Hammett Date: Sat, 4 Oct 2025 19:05:48 +0200 Subject: [PATCH 4/9] Remove redundant trait name check --- src/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector.php b/src/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector.php index 5d2f153bf..7eb7c5edc 100644 --- a/src/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector.php +++ b/src/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector.php @@ -107,7 +107,7 @@ private function hasHasFactoryTrait(TraitUse $traitUse): bool { foreach ($traitUse->traits as $trait) { $traitName = $this->getName($trait); - if ($traitName === self::HAS_FACTORY_TRAIT || $traitName === 'HasFactory') { + if ($traitName === self::HAS_FACTORY_TRAIT) { return true; } } From 883bfde2b6fb82a0698de67243e6878b09e80d74 Mon Sep 17 00:00:00 2001 From: Liam Hammett Date: Sat, 4 Oct 2025 19:19:46 +0200 Subject: [PATCH 5/9] Update files to correct directory/namespace --- .../AddUseAnnotationToHasFactoryTraitRectorTest.php | 9 --------- .../Fixture/deep_namespace.php.inc | 2 +- .../Fixture/fixture.php.inc | 2 +- .../Fixture/models_namespace.php.inc | 2 +- .../Fixture/skip_existing_annotation.php.inc | 2 +- .../{Factories => Source}/ProductFactory.php | 2 +- .../{Factories => Source/Tenant}/UserFactory.php | 2 +- .../{Factories/Tenant => Source}/UserFactory.php | 2 +- .../config/configured_rule_without_configuration.php | 12 ++++++++++++ 9 files changed, 19 insertions(+), 16 deletions(-) rename tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/{Factories => Source}/ProductFactory.php (64%) rename tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/{Factories => Source/Tenant}/UserFactory.php (62%) rename tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/{Factories/Tenant => Source}/UserFactory.php (63%) create mode 100644 tests/Rector/FuncCall/ConfigToTypedConfigMethodCallRector/config/configured_rule_without_configuration.php diff --git a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/AddUseAnnotationToHasFactoryTraitRectorTest.php b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/AddUseAnnotationToHasFactoryTraitRectorTest.php index a3eee3ae7..a199b7110 100644 --- a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/AddUseAnnotationToHasFactoryTraitRectorTest.php +++ b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/AddUseAnnotationToHasFactoryTraitRectorTest.php @@ -10,15 +10,6 @@ final class AddUseAnnotationToHasFactoryTraitRectorTest extends AbstractRectorTestCase { - protected function setUp(): void - { - parent::setUp(); - - require_once __DIR__ . '/Factories/ProductFactory.php'; - require_once __DIR__ . '/Factories/UserFactory.php'; - require_once __DIR__ . '/Factories/Tenant/UserFactory.php'; - } - public static function provideData(): Iterator { return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); diff --git a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/deep_namespace.php.inc b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/deep_namespace.php.inc index 9a3734c5e..955b14aa0 100644 --- a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/deep_namespace.php.inc +++ b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/deep_namespace.php.inc @@ -22,7 +22,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; class User extends Model { /** - * @use \Illuminate\Database\Eloquent\Factories\HasFactory<\Database\Factories\Tenant\UserFactory> + * @use \Illuminate\Database\Eloquent\Factories\HasFactory<\RectorLaravel\Tests\Rector\Class_\AddUseAnnotationToHasFactoryTraitRector\Source\Tenant\UserFactory> */ use HasFactory; } diff --git a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/fixture.php.inc b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/fixture.php.inc index eb10fae13..4fbdbce58 100644 --- a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/fixture.php.inc +++ b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/fixture.php.inc @@ -22,7 +22,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; class User extends Model { /** - * @use \Illuminate\Database\Eloquent\Factories\HasFactory<\Database\Factories\UserFactory> + * @use \Illuminate\Database\Eloquent\Factories\HasFactory<\RectorLaravel\Tests\Rector\Class_\AddUseAnnotationToHasFactoryTraitRector\Source\UserFactory> */ use HasFactory; } diff --git a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/models_namespace.php.inc b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/models_namespace.php.inc index f9caeb057..bc7b459c1 100644 --- a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/models_namespace.php.inc +++ b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/models_namespace.php.inc @@ -22,7 +22,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; class Product extends Model { /** - * @use \Illuminate\Database\Eloquent\Factories\HasFactory<\Database\Factories\ProductFactory> + * @use \Illuminate\Database\Eloquent\Factories\HasFactory<\RectorLaravel\Tests\Rector\Class_\AddUseAnnotationToHasFactoryTraitRector\Source\ProductFactory> */ use HasFactory; } diff --git a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/skip_existing_annotation.php.inc b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/skip_existing_annotation.php.inc index 9ef160e88..14f152d6b 100644 --- a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/skip_existing_annotation.php.inc +++ b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/skip_existing_annotation.php.inc @@ -7,7 +7,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; class User extends Model { - /** @use \Illuminate\Database\Eloquent\Factories\HasFactory<\Database\Factories\UserFactory> */ + /** @use \Illuminate\Database\Eloquent\Factories\HasFactory<\RectorLaravel\Tests\Rector\Class_\AddUseAnnotationToHasFactoryTraitRector\Source\UserFactory> */ use HasFactory; } diff --git a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Factories/ProductFactory.php b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Source/ProductFactory.php similarity index 64% rename from tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Factories/ProductFactory.php rename to tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Source/ProductFactory.php index a987dbe20..4683f57a1 100644 --- a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Factories/ProductFactory.php +++ b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Source/ProductFactory.php @@ -1,6 +1,6 @@ import(__DIR__ . '/../../../../../../config/config.php'); + + $rectorConfig->rule(ConfigToTypedConfigMethodCallRector::class); +}; From 5e7dc1b762ac8a8cabcba1330b8af71f285d5502 Mon Sep 17 00:00:00 2001 From: Liam Hammett Date: Sat, 4 Oct 2025 19:28:51 +0200 Subject: [PATCH 6/9] Make rule configurable --- ...ddUseAnnotationToHasFactoryTraitRector.php | 70 +++++++++++++------ .../config/configured_rule.php | 6 +- 2 files changed, 55 insertions(+), 21 deletions(-) diff --git a/src/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector.php b/src/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector.php index 7eb7c5edc..0bd622d21 100644 --- a/src/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector.php +++ b/src/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector.php @@ -11,24 +11,33 @@ use RectorLaravel\AbstractRector; use PHPStan\Reflection\ReflectionProvider; use Rector\Comments\NodeDocBlock\DocBlockUpdater; +use Rector\Contract\Rector\ConfigurableRectorInterface; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; use PHPStan\PhpDocParser\Ast\PhpDoc\UsesTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; -use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; +use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample; use Rector\BetterPhpDocParser\ValueObject\Type\FullyQualifiedIdentifierTypeNode; +use Webmozart\Assert\Assert; /** * @see \RectorLaravel\Tests\Rector\Class_\AddUseAnnotationToHasFactoryTraitRector\AddUseAnnotationToHasFactoryTraitRectorTest */ -final class AddUseAnnotationToHasFactoryTraitRector extends AbstractRector +final class AddUseAnnotationToHasFactoryTraitRector extends AbstractRector implements ConfigurableRectorInterface { + public const string FACTORY_NAMESPACES = 'factoryNamespaces'; + private const string USE_TAG_NAME = '@use'; private const string HAS_FACTORY_TRAIT = 'Illuminate\Database\Eloquent\Factories\HasFactory'; + /** + * @var string[] + */ + private array $factoryNamespaces = ['Database\\Factories']; + public function __construct( private readonly DocBlockUpdater $docBlockUpdater, private readonly PhpDocInfoFactory $phpDocInfoFactory, @@ -39,7 +48,7 @@ public function getRuleDefinition(): RuleDefinition { return new RuleDefinition( 'Adds @use annotation to HasFactory trait usage to provide better IDE support.', - [new CodeSample( + [new ConfiguredCodeSample( <<<'CODE_SAMPLE' use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -58,11 +67,26 @@ class User extends Model /** @use \Illuminate\Database\Eloquent\Factories\HasFactory<\Database\Factories\UserFactory> */ use HasFactory; } -CODE_SAMPLE +CODE_SAMPLE, + [self::FACTORY_NAMESPACES => ['Database\\Factories']] )] ); } + public function configure(array $configuration): void + { + if ($configuration === []) { + $this->factoryNamespaces = ['Database\\Factories']; + + return; + } + + Assert::keyExists($configuration, self::FACTORY_NAMESPACES); + Assert::isArray($configuration[self::FACTORY_NAMESPACES]); + Assert::allString($configuration[self::FACTORY_NAMESPACES]); + $this->factoryNamespaces = $configuration[self::FACTORY_NAMESPACES]; + } + /** * @return array> */ @@ -177,28 +201,34 @@ private function getPotentialFactoryClassNames(string $modelNamespace, string $f { $factoryClassNames = []; - if (str_contains($modelNamespace, '\\Models\\')) { - $afterModels = substr($modelNamespace, strpos($modelNamespace, '\\Models\\') + 8); + foreach ($this->factoryNamespaces as $factoryNamespace) { + // Remove leading backslash if present + $factoryNamespace = ltrim($factoryNamespace, '\\'); - if (str_contains($afterModels, '\\')) { - $namespaceParts = explode('\\', $afterModels); - array_pop($namespaceParts); - $deepNamespace = implode('\\', $namespaceParts); + if (str_contains($modelNamespace, '\\Models\\')) { + $afterModels = substr($modelNamespace, strpos($modelNamespace, '\\Models\\') + 8); - $factoryClassNames[] = '\\Database\\Factories\\' . $deepNamespace . '\\' . $factoryName; - } - } elseif (str_contains($modelNamespace, 'App\\')) { - $afterApp = substr($modelNamespace, strpos($modelNamespace, 'App\\') + 4); + if (str_contains($afterModels, '\\')) { + $namespaceParts = explode('\\', $afterModels); + array_pop($namespaceParts); + $deepNamespace = implode('\\', $namespaceParts); + + $factoryClassNames[] = '\\' . $factoryNamespace . '\\' . $deepNamespace . '\\' . $factoryName; + } + } elseif (str_contains($modelNamespace, 'App\\')) { + $afterApp = substr($modelNamespace, strpos($modelNamespace, 'App\\') + 4); - if (str_contains($afterApp, '\\')) { - $namespaceParts = explode('\\', $afterApp); - array_pop($namespaceParts); - $deepNamespace = implode('\\', $namespaceParts); + if (str_contains($afterApp, '\\')) { + $namespaceParts = explode('\\', $afterApp); + array_pop($namespaceParts); + $deepNamespace = implode('\\', $namespaceParts); - $factoryClassNames[] = '\\Database\\Factories\\' . $deepNamespace . '\\' . $factoryName; + $factoryClassNames[] = '\\' . $factoryNamespace . '\\' . $deepNamespace . '\\' . $factoryName; + } } + + $factoryClassNames[] = '\\' . $factoryNamespace . '\\' . $factoryName; } - $factoryClassNames[] = '\\Database\\Factories\\' . $factoryName; return array_unique($factoryClassNames); } diff --git a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/config/configured_rule.php b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/config/configured_rule.php index 374ec12aa..b1f3197f4 100644 --- a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/config/configured_rule.php +++ b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/config/configured_rule.php @@ -6,5 +6,9 @@ use RectorLaravel\Rector\Class_\AddUseAnnotationToHasFactoryTraitRector; return static function (RectorConfig $rectorConfig): void { - $rectorConfig->rule(AddUseAnnotationToHasFactoryTraitRector::class); + $rectorConfig->ruleWithConfiguration(AddUseAnnotationToHasFactoryTraitRector::class, [ + AddUseAnnotationToHasFactoryTraitRector::FACTORY_NAMESPACES => [ + 'RectorLaravel\\Tests\\Rector\\Class_\\AddUseAnnotationToHasFactoryTraitRector\\Source', + ], + ]); }; From 59ca54afd7c32b913b1ab12c53959f6eb507ce4f Mon Sep 17 00:00:00 2001 From: Liam Hammett Date: Sat, 4 Oct 2025 19:29:03 +0200 Subject: [PATCH 7/9] Run `composer docs` to update documentation --- docs/rector_rules_overview.md | 61 ++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/docs/rector_rules_overview.md b/docs/rector_rules_overview.md index bef70236d..c5f6373aa 100644 --- a/docs/rector_rules_overview.md +++ b/docs/rector_rules_overview.md @@ -1,4 +1,4 @@ -# 84 Rules Overview +# 86 Rules Overview ## AbortIfRector @@ -208,6 +208,27 @@ Add `parent::register();` call to `register()` class method in child of `Illumin
+## AddUseAnnotationToHasFactoryTraitRector + +Adds `@use` annotation to HasFactory trait usage to provide better IDE support. + +:wrench: **configure it!** + +- class: [`RectorLaravel\Rector\Class_\AddUseAnnotationToHasFactoryTraitRector`](../src/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector.php) + +```diff + use Illuminate\Database\Eloquent\Model; + use Illuminate\Database\Eloquent\Factories\HasFactory; + + class User extends Model + { ++ /** @use \Illuminate\Database\Eloquent\Factories\HasFactory<\Database\Factories\UserFactory> */ + use HasFactory; + } +``` + +
+ ## AnonymousMigrationsRector Convert migrations to anonymous classes. @@ -229,13 +250,19 @@ Convert migrations to anonymous classes. ## AppEnvironmentComparisonToParameterRector -Replace `$app->environment() === 'local'` with `$app->environment('local')` +Replace app environment comparison with parameter or method call - class: [`RectorLaravel\Rector\Expr\AppEnvironmentComparisonToParameterRector`](../src/Rector/Expr/AppEnvironmentComparisonToParameterRector.php) ```diff --$app->environment() === 'production'; -+$app->environment('production'); +-$app->environment() === 'local'; +-$app->environment() !== 'production'; +-$app->environment() === 'testing'; +-in_array($app->environment(), ['local', 'testing']); ++$app->isLocal(); ++! $app->isProduction(); ++$app->environment('testing'); ++$app->environment(['local', 'testing']); ```
@@ -892,6 +919,32 @@ Changes middlewares from rule definitions from string to array notation.
+## MakeModelAttributesAndScopesProtectedRector + +Makes Model attributes and scopes protected + +- class: [`RectorLaravel\Rector\ClassMethod\MakeModelAttributesAndScopesProtectedRector`](../src/Rector/ClassMethod/MakeModelAttributesAndScopesProtectedRector.php) + +```diff + class User extends Model + { +- public function foo(): Attribute ++ protected function foo(): Attribute + { + return Attribute::get(fn () => $this->bar); + } + + #[Scope] +- public function active(Builder $query): Builder ++ protected function active(Builder $query): Builder + { + return $query->where('active', true); + } + } +``` + +
+ ## MigrateToSimplifiedAttributeRector Migrate to the new Model attributes syntax From 9c89d28cfe6a75c7d13a60add99a82026a5cdc15 Mon Sep 17 00:00:00 2001 From: Liam Hammett Date: Sat, 4 Oct 2025 19:45:52 +0200 Subject: [PATCH 8/9] If factory property exists, use that --- ...ddUseAnnotationToHasFactoryTraitRector.php | 40 +++++++++++++++++++ .../Fixture/with_factory_property.php.inc | 34 ++++++++++++++++ .../with_factory_property_string.php.inc | 34 ++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/with_factory_property.php.inc create mode 100644 tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/with_factory_property_string.php.inc diff --git a/src/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector.php b/src/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector.php index 0bd622d21..be8738d5a 100644 --- a/src/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector.php +++ b/src/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector.php @@ -172,6 +172,11 @@ private function addUsePhpDocTag(TraitUse $traitUse, Class_ $class): bool private function resolveFactoryClassName(Class_ $class): ?string { + $factoryFromProperty = $this->getFactoryFromProperty($class); + if ($factoryFromProperty !== null) { + return $factoryFromProperty; + } + $className = $this->getName($class); if ($className === null) { return null; @@ -194,6 +199,41 @@ private function resolveFactoryClassName(Class_ $class): ?string return null; } + private function getFactoryFromProperty(Class_ $class): ?string + { + foreach ($class->stmts as $stmt) { + if (! $stmt instanceof \PhpParser\Node\Stmt\Property) { + continue; + } + + if (! $this->isName($stmt, 'factory')) { + continue; + } + + if ($stmt->props[0]->default === null) { + continue; + } + + $defaultValue = $stmt->props[0]->default; + + if ($defaultValue instanceof \PhpParser\Node\Expr\ClassConstFetch) { + $factoryClassName = $this->getName($defaultValue->class); + if ($factoryClassName !== null && $this->reflectionProvider->hasClass($factoryClassName)) { + return $factoryClassName; + } + } + + if ($defaultValue instanceof \PhpParser\Node\Scalar\String_) { + $factoryClassName = $defaultValue->value; + if ($this->reflectionProvider->hasClass($factoryClassName)) { + return $factoryClassName; + } + } + } + + return null; + } + /** * @return string[] */ diff --git a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/with_factory_property.php.inc b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/with_factory_property.php.inc new file mode 100644 index 000000000..3e92f5fea --- /dev/null +++ b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/with_factory_property.php.inc @@ -0,0 +1,34 @@ + +----- + + */ + use HasFactory; + + protected static string $factory = \RectorLaravel\Tests\Rector\Class_\AddUseAnnotationToHasFactoryTraitRector\Source\ProductFactory::class; +} + +?> diff --git a/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/with_factory_property_string.php.inc b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/with_factory_property_string.php.inc new file mode 100644 index 000000000..b155ffa1b --- /dev/null +++ b/tests/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector/Fixture/with_factory_property_string.php.inc @@ -0,0 +1,34 @@ + +----- + + */ + use HasFactory; + + protected static string $factory = 'RectorLaravel\Tests\Rector\Class_\AddUseAnnotationToHasFactoryTraitRector\Source\ProductFactory'; +} + +?> From b21be645bfde8094f3629ae0f88e2b1df53cba2e Mon Sep 17 00:00:00 2001 From: Liam Hammett Date: Mon, 6 Oct 2025 12:53:45 +0200 Subject: [PATCH 9/9] Code style fixes --- ...ddUseAnnotationToHasFactoryTraitRector.php | 38 ++++++++++++------- .../configured_rule_without_configuration.php | 12 ------ 2 files changed, 24 insertions(+), 26 deletions(-) delete mode 100644 tests/Rector/FuncCall/ConfigToTypedConfigMethodCallRector/config/configured_rule_without_configuration.php diff --git a/src/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector.php b/src/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector.php index be8738d5a..a453b1277 100644 --- a/src/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector.php +++ b/src/Rector/Class_/AddUseAnnotationToHasFactoryTraitRector.php @@ -5,21 +5,23 @@ namespace RectorLaravel\Rector\Class_; use PhpParser\Node; -use PHPStan\Type\ObjectType; +use PhpParser\Node\Expr\ClassConstFetch; +use PhpParser\Node\Scalar\String_; use PhpParser\Node\Stmt\Class_; +use PhpParser\Node\Stmt\Property; use PhpParser\Node\Stmt\TraitUse; -use RectorLaravel\AbstractRector; -use PHPStan\Reflection\ReflectionProvider; -use Rector\Comments\NodeDocBlock\DocBlockUpdater; -use Rector\Contract\Rector\ConfigurableRectorInterface; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; -use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; use PHPStan\PhpDocParser\Ast\PhpDoc\UsesTagValueNode; -use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode; -use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; +use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; +use PHPStan\Reflection\ReflectionProvider; +use PHPStan\Type\ObjectType; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; -use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample; use Rector\BetterPhpDocParser\ValueObject\Type\FullyQualifiedIdentifierTypeNode; +use Rector\Comments\NodeDocBlock\DocBlockUpdater; +use Rector\Contract\Rector\ConfigurableRectorInterface; +use RectorLaravel\AbstractRector; +use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample; +use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; use Webmozart\Assert\Assert; /** @@ -202,7 +204,7 @@ private function resolveFactoryClassName(Class_ $class): ?string private function getFactoryFromProperty(Class_ $class): ?string { foreach ($class->stmts as $stmt) { - if (! $stmt instanceof \PhpParser\Node\Stmt\Property) { + if (! $stmt instanceof Property) { continue; } @@ -216,14 +218,14 @@ private function getFactoryFromProperty(Class_ $class): ?string $defaultValue = $stmt->props[0]->default; - if ($defaultValue instanceof \PhpParser\Node\Expr\ClassConstFetch) { + if ($defaultValue instanceof ClassConstFetch) { $factoryClassName = $this->getName($defaultValue->class); if ($factoryClassName !== null && $this->reflectionProvider->hasClass($factoryClassName)) { return $factoryClassName; } } - if ($defaultValue instanceof \PhpParser\Node\Scalar\String_) { + if ($defaultValue instanceof String_) { $factoryClassName = $defaultValue->value; if ($this->reflectionProvider->hasClass($factoryClassName)) { return $factoryClassName; @@ -246,7 +248,11 @@ private function getPotentialFactoryClassNames(string $modelNamespace, string $f $factoryNamespace = ltrim($factoryNamespace, '\\'); if (str_contains($modelNamespace, '\\Models\\')) { - $afterModels = substr($modelNamespace, strpos($modelNamespace, '\\Models\\') + 8); + $modelsPosition = strpos($modelNamespace, '\\Models\\'); + if ($modelsPosition === false) { + continue; + } + $afterModels = substr($modelNamespace, $modelsPosition + 8); if (str_contains($afterModels, '\\')) { $namespaceParts = explode('\\', $afterModels); @@ -256,7 +262,11 @@ private function getPotentialFactoryClassNames(string $modelNamespace, string $f $factoryClassNames[] = '\\' . $factoryNamespace . '\\' . $deepNamespace . '\\' . $factoryName; } } elseif (str_contains($modelNamespace, 'App\\')) { - $afterApp = substr($modelNamespace, strpos($modelNamespace, 'App\\') + 4); + $appPosition = strpos($modelNamespace, 'App\\'); + if ($appPosition === false) { + continue; + } + $afterApp = substr($modelNamespace, $appPosition + 4); if (str_contains($afterApp, '\\')) { $namespaceParts = explode('\\', $afterApp); diff --git a/tests/Rector/FuncCall/ConfigToTypedConfigMethodCallRector/config/configured_rule_without_configuration.php b/tests/Rector/FuncCall/ConfigToTypedConfigMethodCallRector/config/configured_rule_without_configuration.php deleted file mode 100644 index 606f6ad38..000000000 --- a/tests/Rector/FuncCall/ConfigToTypedConfigMethodCallRector/config/configured_rule_without_configuration.php +++ /dev/null @@ -1,12 +0,0 @@ -import(__DIR__ . '/../../../../../../config/config.php'); - - $rectorConfig->rule(ConfigToTypedConfigMethodCallRector::class); -};