From 48956c94c6e0c3cc47ffd9008b08c2d0d9303cd3 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Fri, 7 Aug 2026 11:32:30 +0200 Subject: [PATCH] deprecate MoveCurrentDateTimeDefaultInEntityToConstructorRector, drop force_nullable option The database-level default and the PHP-level default are not interchangeable, so moving the default into the constructor can produce buggy code. Also drop the force_nullable option of TypedPropertyFromToOneRelationTypeRector, as the relation can always be unset while the entity is being built. --- config/sets/doctrine-code-quality.php | 2 - .../config/configured_rule.php | 10 -- .../Fixture/already_constructor.php.inc | 54 ------- .../Fixture/fixture.php.inc | 47 ------ .../Fixture/move_full_date.php.inc | 41 ----- .../skip_already_default_constructor.php.inc | 23 --- .../skip_no_constructor_default_null.php.inc | 29 ---- .../Fixture/skip_null_defaults.php.inc | 18 --- .../Fixture/skip_nullable_property.php.inc | 17 -- ...DefaultInEntityToConstructorRectorTest.php | 28 ---- .../config/configured_rule.php | 10 -- .../doctrine_attribute_many_to_one.php.inc | 39 ----- .../doctrine_many_to_one.php.inc | 45 ------ ...mToOneRelationTypeRectorConfiguredTest.php | 28 ---- .../config/configured_rule.php | 18 --- ...TimeDefaultInEntityToConstructorRector.php | 148 ++---------------- ...pedPropertyFromToOneRelationTypeRector.php | 60 +------ .../ConstructorAssignPropertyAnalyzer.php | 46 ------ src/NodeFactory/ValueAssignFactory.php | 48 ------ .../ConstructorManipulator.php | 33 ---- .../ToOneRelationPropertyTypeResolver.php | 80 ++-------- 21 files changed, 24 insertions(+), 800 deletions(-) delete mode 100644 rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAnnotationRector/config/configured_rule.php delete mode 100644 rules-tests/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector/Fixture/already_constructor.php.inc delete mode 100644 rules-tests/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector/Fixture/fixture.php.inc delete mode 100644 rules-tests/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector/Fixture/move_full_date.php.inc delete mode 100644 rules-tests/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector/Fixture/skip_already_default_constructor.php.inc delete mode 100644 rules-tests/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector/Fixture/skip_no_constructor_default_null.php.inc delete mode 100644 rules-tests/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector/Fixture/skip_null_defaults.php.inc delete mode 100644 rules-tests/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector/Fixture/skip_nullable_property.php.inc delete mode 100644 rules-tests/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector/MoveCurrentDateTimeDefaultInEntityToConstructorRectorTest.php delete mode 100644 rules-tests/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector/config/configured_rule.php delete mode 100644 rules-tests/CodeQuality/Rector/Property/TypedPropertyFromToOneRelationTypeRector/FixtureConfigured/doctrine_attribute_many_to_one.php.inc delete mode 100644 rules-tests/CodeQuality/Rector/Property/TypedPropertyFromToOneRelationTypeRector/FixtureConfigured/doctrine_many_to_one.php.inc delete mode 100644 rules-tests/CodeQuality/Rector/Property/TypedPropertyFromToOneRelationTypeRector/TypedPropertyFromToOneRelationTypeRectorConfiguredTest.php delete mode 100644 rules-tests/CodeQuality/Rector/Property/TypedPropertyFromToOneRelationTypeRector/config/configured_rule.php delete mode 100644 src/NodeAnalyzer/ConstructorAssignPropertyAnalyzer.php delete mode 100644 src/NodeFactory/ValueAssignFactory.php delete mode 100644 src/NodeManipulator/ConstructorManipulator.php diff --git a/config/sets/doctrine-code-quality.php b/config/sets/doctrine-code-quality.php index 7e45e20f..2416c90b 100644 --- a/config/sets/doctrine-code-quality.php +++ b/config/sets/doctrine-code-quality.php @@ -4,7 +4,6 @@ use Rector\Config\RectorConfig; use Rector\Doctrine\Bundle230\Rector\Class_\AddAnnotationToRepositoryRector; -use Rector\Doctrine\CodeQuality\Rector\Class_\MoveCurrentDateTimeDefaultInEntityToConstructorRector; use Rector\Doctrine\CodeQuality\Rector\Class_\RemoveEmptyTableAttributeRector; use Rector\Doctrine\CodeQuality\Rector\Property\CorrectDefaultTypesOnEntityPropertyRector; use Rector\Doctrine\CodeQuality\Rector\Property\TypedPropertyFromColumnTypeRector; @@ -15,7 +14,6 @@ return static function (RectorConfig $rectorConfig): void { $rectorConfig->rules([ - MoveCurrentDateTimeDefaultInEntityToConstructorRector::class, CorrectDefaultTypesOnEntityPropertyRector::class, RemoveEmptyTableAttributeRector::class, diff --git a/rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAnnotationRector/config/configured_rule.php b/rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAnnotationRector/config/configured_rule.php deleted file mode 100644 index 02079426..00000000 --- a/rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAnnotationRector/config/configured_rule.php +++ /dev/null @@ -1,10 +0,0 @@ -rule(CompleteReturnDocblockFromToManyRector::class); -}; diff --git a/rules-tests/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector/Fixture/already_constructor.php.inc b/rules-tests/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector/Fixture/already_constructor.php.inc deleted file mode 100644 index 7c99a75c..00000000 --- a/rules-tests/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector/Fixture/already_constructor.php.inc +++ /dev/null @@ -1,54 +0,0 @@ - ------ -when = new \DateTime(); - } -} - -?> diff --git a/rules-tests/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector/Fixture/fixture.php.inc b/rules-tests/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector/Fixture/fixture.php.inc deleted file mode 100644 index 2364c1e6..00000000 --- a/rules-tests/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector/Fixture/fixture.php.inc +++ /dev/null @@ -1,47 +0,0 @@ - ------ -when = new \DateTime(); - } -} - -?> diff --git a/rules-tests/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector/Fixture/move_full_date.php.inc b/rules-tests/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector/Fixture/move_full_date.php.inc deleted file mode 100644 index b5df442e..00000000 --- a/rules-tests/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector/Fixture/move_full_date.php.inc +++ /dev/null @@ -1,41 +0,0 @@ - ------ -when = new \DateTime('1900-01-01 00:00:00'); - } -} - -?> diff --git a/rules-tests/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector/Fixture/skip_already_default_constructor.php.inc b/rules-tests/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector/Fixture/skip_already_default_constructor.php.inc deleted file mode 100644 index f3514ed3..00000000 --- a/rules-tests/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector/Fixture/skip_already_default_constructor.php.inc +++ /dev/null @@ -1,23 +0,0 @@ -when = \Nette\Utils\DateTime::from('now'); - } -} diff --git a/rules-tests/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector/Fixture/skip_no_constructor_default_null.php.inc b/rules-tests/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector/Fixture/skip_no_constructor_default_null.php.inc deleted file mode 100644 index 775a7cdd..00000000 --- a/rules-tests/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector/Fixture/skip_no_constructor_default_null.php.inc +++ /dev/null @@ -1,29 +0,0 @@ -remindedAt; - } - - public function setRemindedAt(?DateTime $remindedAt): SkipNoConstructorDefaultNull - { - $this->remindedAt = $remindedAt; - - return $this; - } -} diff --git a/rules-tests/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector/Fixture/skip_null_defaults.php.inc b/rules-tests/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector/Fixture/skip_null_defaults.php.inc deleted file mode 100644 index dec555bb..00000000 --- a/rules-tests/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector/Fixture/skip_null_defaults.php.inc +++ /dev/null @@ -1,18 +0,0 @@ -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/rules-tests/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector/config/configured_rule.php b/rules-tests/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector/config/configured_rule.php deleted file mode 100644 index 86550271..00000000 --- a/rules-tests/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector/config/configured_rule.php +++ /dev/null @@ -1,10 +0,0 @@ -rule(MoveCurrentDateTimeDefaultInEntityToConstructorRector::class); -}; diff --git a/rules-tests/CodeQuality/Rector/Property/TypedPropertyFromToOneRelationTypeRector/FixtureConfigured/doctrine_attribute_many_to_one.php.inc b/rules-tests/CodeQuality/Rector/Property/TypedPropertyFromToOneRelationTypeRector/FixtureConfigured/doctrine_attribute_many_to_one.php.inc deleted file mode 100644 index bff68476..00000000 --- a/rules-tests/CodeQuality/Rector/Property/TypedPropertyFromToOneRelationTypeRector/FixtureConfigured/doctrine_attribute_many_to_one.php.inc +++ /dev/null @@ -1,39 +0,0 @@ - ------ - diff --git a/rules-tests/CodeQuality/Rector/Property/TypedPropertyFromToOneRelationTypeRector/FixtureConfigured/doctrine_many_to_one.php.inc b/rules-tests/CodeQuality/Rector/Property/TypedPropertyFromToOneRelationTypeRector/FixtureConfigured/doctrine_many_to_one.php.inc deleted file mode 100644 index f8d9b581..00000000 --- a/rules-tests/CodeQuality/Rector/Property/TypedPropertyFromToOneRelationTypeRector/FixtureConfigured/doctrine_many_to_one.php.inc +++ /dev/null @@ -1,45 +0,0 @@ - ------ - diff --git a/rules-tests/CodeQuality/Rector/Property/TypedPropertyFromToOneRelationTypeRector/TypedPropertyFromToOneRelationTypeRectorConfiguredTest.php b/rules-tests/CodeQuality/Rector/Property/TypedPropertyFromToOneRelationTypeRector/TypedPropertyFromToOneRelationTypeRectorConfiguredTest.php deleted file mode 100644 index 8cf90bf0..00000000 --- a/rules-tests/CodeQuality/Rector/Property/TypedPropertyFromToOneRelationTypeRector/TypedPropertyFromToOneRelationTypeRectorConfiguredTest.php +++ /dev/null @@ -1,28 +0,0 @@ -doTestFile($filePath); - } - - public static function provideData(): Iterator - { - return self::yieldFilesFromDirectory(__DIR__ . '/FixtureConfigured'); - } - - public function provideConfigFilePath(): string - { - return __DIR__ . '/config/configured_rule.php'; - } -} diff --git a/rules-tests/CodeQuality/Rector/Property/TypedPropertyFromToOneRelationTypeRector/config/configured_rule.php b/rules-tests/CodeQuality/Rector/Property/TypedPropertyFromToOneRelationTypeRector/config/configured_rule.php deleted file mode 100644 index 4f43cae6..00000000 --- a/rules-tests/CodeQuality/Rector/Property/TypedPropertyFromToOneRelationTypeRector/config/configured_rule.php +++ /dev/null @@ -1,18 +0,0 @@ -ruleWithConfiguration( - TypedPropertyFromToOneRelationTypeRector::class, - [ - TypedPropertyFromToOneRelationTypeRector::FORCE_NULLABLE => false, - ] - ); - - $rectorConfig->phpVersion(PhpVersionFeature::UNION_TYPES); -}; diff --git a/rules/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector.php b/rules/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector.php index a5726391..7d6ffaf9 100644 --- a/rules/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector.php +++ b/rules/CodeQuality/Rector/Class_/MoveCurrentDateTimeDefaultInEntityToConstructorRector.php @@ -5,49 +5,20 @@ namespace Rector\Doctrine\CodeQuality\Rector\Class_; use PhpParser\Node; -use PhpParser\Node\Expr; -use PhpParser\Node\Expr\Assign; use PhpParser\Node\Stmt\Class_; -use PhpParser\Node\Stmt\ClassMethod; -use PhpParser\Node\Stmt\Property; -use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprTrueNode; -use Rector\BetterPhpDocParser\PhpDoc\ArrayItemNode; -use Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode; -use Rector\BetterPhpDocParser\PhpDoc\StringNode; -use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; -use Rector\BetterPhpDocParser\ValueObject\PhpDoc\DoctrineAnnotation\CurlyListNode; -use Rector\Comments\NodeDocBlock\DocBlockUpdater; -use Rector\Doctrine\Enum\MappingClass; -use Rector\Doctrine\NodeAnalyzer\ConstructorAssignPropertyAnalyzer; -use Rector\Doctrine\NodeFactory\ValueAssignFactory; -use Rector\Doctrine\NodeManipulator\ConstructorManipulator; -use Rector\Doctrine\TypedCollections\NodeModifier\PropertyDefaultNullRemover; -use Rector\PhpParser\Node\Value\ValueResolver; +use Rector\Configuration\Deprecation\Contract\DeprecatedInterface; +use Rector\Exception\ShouldNotHappenException; use Rector\Rector\AbstractRector; -use Rector\ValueObject\MethodName; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** - * @see https://stackoverflow.com/a/7698687/1348344 - * - * @see \Rector\Doctrine\Tests\CodeQuality\Rector\Class_\MoveCurrentDateTimeDefaultInEntityToConstructorRector\MoveCurrentDateTimeDefaultInEntityToConstructorRectorTest + * @deprecated as the database level default and the PHP level default are not interchangeable. Moving the default to + * the constructor changes when the value is created and can produce buggy code. Handle these cases + * manually instead. */ -final class MoveCurrentDateTimeDefaultInEntityToConstructorRector extends AbstractRector +final class MoveCurrentDateTimeDefaultInEntityToConstructorRector extends AbstractRector implements DeprecatedInterface { - private bool $hasChanged = false; - - public function __construct( - private readonly ConstructorManipulator $constructorManipulator, - private readonly ValueAssignFactory $valueAssignFactory, - private readonly ConstructorAssignPropertyAnalyzer $constructorAssignPropertyAnalyzer, - private readonly DocBlockUpdater $docBlockUpdater, - private readonly PhpDocInfoFactory $phpDocInfoFactory, - private readonly ValueResolver $valueResolver, - private readonly PropertyDefaultNullRemover $propertyDefaultNullRemover - ) { - } - public function getRuleDefinition(): RuleDefinition { return new RuleDefinition( @@ -111,108 +82,9 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - $this->hasChanged = false; - - foreach ($node->getProperties() as $property) { - $this->refactorProperty($property, $node); - } - - if ($this->hasChanged) { - return $node; - } - - return null; - } - - private function refactorProperty(Property $property, Class_ $class): void - { - $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($property); - - $doctrineAnnotationTagValueNode = $phpDocInfo->getByAnnotationClass(MappingClass::COLUMN); - if (! $doctrineAnnotationTagValueNode instanceof DoctrineAnnotationTagValueNode) { - return; - } - - $typeArrayItemNode = $doctrineAnnotationTagValueNode->getValue('type'); - if (! $typeArrayItemNode instanceof ArrayItemNode) { - return; - } - - $typeValue = $typeArrayItemNode->value; - - if ($typeValue instanceof StringNode) { - $typeValue = $typeValue->value; - } - - if ($typeValue !== 'datetime') { - return; - } - - $constructorAssign = $this->constructorAssignPropertyAnalyzer->resolveConstructorAssign($class, $property); - - // skip nullable - $nullableArrayItemNode = $doctrineAnnotationTagValueNode->getValue('nullable'); - if ($nullableArrayItemNode instanceof ArrayItemNode && $nullableArrayItemNode->value instanceof ConstExprTrueNode) { - return; - } - - // 0. already has default - if ($constructorAssign instanceof Assign) { - return; - } - - // 1. remove default options from database level - $optionsArrayItemNode = $doctrineAnnotationTagValueNode->getValue('options'); - if ($optionsArrayItemNode instanceof ArrayItemNode) { - if (! $optionsArrayItemNode->value instanceof CurlyListNode) { - return; - } - - $optionsArrayItemNode->value->removeValue('default'); - - // if empty, remove it completely - if ($optionsArrayItemNode->value->getValues() === []) { - $doctrineAnnotationTagValueNode->removeValue('options'); - } - - $this->hasChanged = true; - } - - $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($property); - $this->refactorClassWithRemovalDefault($class, $property); - } - - private function refactorClassWithRemovalDefault(Class_ $class, Property $property): void - { - $this->refactorClass($class, $property); - - $classMethod = $class->getMethod(MethodName::CONSTRUCT); - if (! $classMethod instanceof ClassMethod && $property->type instanceof Node) { - return; - } - - // 3. remove default from property - $this->propertyDefaultNullRemover->remove($property); - - $this->hasChanged = true; - } - - private function refactorClass(Class_ $class, Property $property): void - { - /** @var string $propertyName */ - $propertyName = $this->getName($property); - $onlyProperty = $property->props[0]; - - $defaultExpr = $onlyProperty->default; - if (! $defaultExpr instanceof Expr) { - return; - } - - if ($this->valueResolver->isNull($defaultExpr)) { - return; - } - - $expression = $this->valueAssignFactory->createDefaultDateTimeWithValueAssign($propertyName, $defaultExpr); - $this->constructorManipulator->addStmtToConstructor($class, $expression); + throw new ShouldNotHappenException(sprintf( + '"%s" is deprecated and should not be used anymore. Remove it from your config files.', + self::class, + )); } } diff --git a/rules/CodeQuality/Rector/Property/TypedPropertyFromToOneRelationTypeRector.php b/rules/CodeQuality/Rector/Property/TypedPropertyFromToOneRelationTypeRector.php index 3862ff3f..651e1d7a 100644 --- a/rules/CodeQuality/Rector/Property/TypedPropertyFromToOneRelationTypeRector.php +++ b/rules/CodeQuality/Rector/Property/TypedPropertyFromToOneRelationTypeRector.php @@ -14,7 +14,6 @@ use PHPStan\Type\UnionType; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger; -use Rector\Contract\Rector\ConfigurableRectorInterface; use Rector\Doctrine\NodeManipulator\ToOneRelationPropertyTypeResolver; use Rector\Php\PhpVersionProvider; use Rector\PHPStanStaticTypeMapper\Enum\TypeKind; @@ -24,19 +23,14 @@ use Rector\ValueObject\PhpVersion; use Rector\ValueObject\PhpVersionFeature; use Rector\VersionBonding\Contract\MinPhpVersionInterface; -use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample; +use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; -use Webmozart\Assert\Assert; /** * @see \Rector\Doctrine\Tests\CodeQuality\Rector\Property\TypedPropertyFromToOneRelationTypeRector\TypedPropertyFromToOneRelationTypeRectorTest */ -final class TypedPropertyFromToOneRelationTypeRector extends AbstractRector implements MinPhpVersionInterface, ConfigurableRectorInterface +final class TypedPropertyFromToOneRelationTypeRector extends AbstractRector implements MinPhpVersionInterface { - public const string FORCE_NULLABLE = 'force_nullable'; - - private bool $forceNullable = true; - public function __construct( private readonly PropertyTypeDecorator $propertyTypeDecorator, private readonly PhpDocTypeChanger $phpDocTypeChanger, @@ -52,7 +46,7 @@ public function getRuleDefinition(): RuleDefinition return new RuleDefinition( 'Complete @var annotations or types based on @ORM\*toOne annotations or attributes', [ - new ConfiguredCodeSample( + new CodeSample( <<<'CODE_SAMPLE' use Doctrine\ORM\Mapping as ORM; @@ -78,57 +72,11 @@ class SimpleColumn private ?\App\Company\Entity\Company $company = null; } CODE_SAMPLE - , - [ - 'force_nullable' => true, - ] - ), - new ConfiguredCodeSample( - <<<'CODE_SAMPLE' -use Doctrine\ORM\Mapping as ORM; - -class SimpleColumn -{ - /** - * @ORM\OneToOne(targetEntity="App\Company\Entity\Company") - * @ORM\JoinColumn(nullable=false) - */ - private $company; -} -CODE_SAMPLE - , - <<<'CODE_SAMPLE' -use Doctrine\ORM\Mapping as ORM; - -class SimpleColumn -{ - /** - * @ORM\OneToOne(targetEntity="App\Company\Entity\Company") - * @ORM\JoinColumn(nullable=false) - */ - private \App\Company\Entity\Company $company; -} -CODE_SAMPLE - , - [ - 'force_nullable' => false, - ] ), ], ); } - /** - * @param array $configuration - */ - public function configure(array $configuration): void - { - if (isset($configuration[self::FORCE_NULLABLE])) { - Assert::boolean($configuration[self::FORCE_NULLABLE]); - $this->forceNullable = $configuration[self::FORCE_NULLABLE]; - } - } - /** * @return array> */ @@ -146,7 +94,7 @@ public function refactor(Node $node): Property|null return null; } - $propertyType = $this->toOneRelationPropertyTypeResolver->resolve($node, $this->forceNullable); + $propertyType = $this->toOneRelationPropertyTypeResolver->resolve($node); if (! $propertyType instanceof Type) { return null; } diff --git a/src/NodeAnalyzer/ConstructorAssignPropertyAnalyzer.php b/src/NodeAnalyzer/ConstructorAssignPropertyAnalyzer.php deleted file mode 100644 index 8fb0b69d..00000000 --- a/src/NodeAnalyzer/ConstructorAssignPropertyAnalyzer.php +++ /dev/null @@ -1,46 +0,0 @@ -getMethod(MethodName::CONSTRUCT); - if (! $constructClassMethod instanceof ClassMethod) { - return null; - } - - /** @var string $propertyName */ - $propertyName = $this->nodeNameResolver->getName($property); - - return $this->betterNodeFinder->findFirst((array) $constructClassMethod->stmts, function (Node $node) use ( - $propertyName - ): bool { - if (! $node instanceof Assign) { - return false; - } - - return $this->propertyFetchAnalyzer->isLocalPropertyFetchName($node->var, $propertyName); - }); - } -} diff --git a/src/NodeFactory/ValueAssignFactory.php b/src/NodeFactory/ValueAssignFactory.php deleted file mode 100644 index 9c855ed7..00000000 --- a/src/NodeFactory/ValueAssignFactory.php +++ /dev/null @@ -1,48 +0,0 @@ -createPropertyFetch($propertyName); - $newDateTime = $this->createNewDateTime(); - $this->addDateTimeArgumentIfNotDefault($defaultExpr, $newDateTime); - - $assign = new Assign($propertyFetch, $newDateTime); - - return new Expression($assign); - } - - private function createPropertyFetch(string $propertyName): PropertyFetch - { - return new PropertyFetch(new Variable('this'), $propertyName); - } - - private function createNewDateTime(): New_ - { - return new New_(new FullyQualified('DateTime')); - } - - private function addDateTimeArgumentIfNotDefault(Expr $defaultExpr, New_ $dateTimeNew): void - { - if ($defaultExpr instanceof String_ && ($defaultExpr->value === 'now' || $defaultExpr->value === 'now()')) { - return; - } - - $dateTimeNew->args[] = new Arg($defaultExpr); - } -} diff --git a/src/NodeManipulator/ConstructorManipulator.php b/src/NodeManipulator/ConstructorManipulator.php deleted file mode 100644 index e072976d..00000000 --- a/src/NodeManipulator/ConstructorManipulator.php +++ /dev/null @@ -1,33 +0,0 @@ -getMethod(MethodName::CONSTRUCT); - if ($constructClassMethod instanceof ClassMethod) { - $constructClassMethod->stmts[] = $newExpression; - } else { - $constructClassMethod = $this->nodeFactory->createPublicMethod(MethodName::CONSTRUCT); - $constructClassMethod->stmts[] = $newExpression; - $this->classInsertManipulator->addAsFirstMethod($class, $constructClassMethod); - } - } -} diff --git a/src/NodeManipulator/ToOneRelationPropertyTypeResolver.php b/src/NodeManipulator/ToOneRelationPropertyTypeResolver.php index 4753b440..56feb86e 100644 --- a/src/NodeManipulator/ToOneRelationPropertyTypeResolver.php +++ b/src/NodeManipulator/ToOneRelationPropertyTypeResolver.php @@ -6,21 +6,17 @@ use Nette\Utils\Strings; use PhpParser\Node\Expr; -use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Stmt\Property; -use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprTrueNode; use PHPStan\Type\MixedType; use PHPStan\Type\NullType; use PHPStan\Type\Type; use Rector\BetterPhpDocParser\PhpDoc\ArrayItemNode; use Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode; use Rector\BetterPhpDocParser\PhpDoc\StringNode; -use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\BetterPhpDocParser\PhpDocParser\ClassAnnotationMatcher; use Rector\Doctrine\CodeQuality\Enum\CollectionMapping; use Rector\Doctrine\CodeQuality\Enum\EntityMappingKey; -use Rector\Doctrine\Enum\MappingClass; use Rector\Doctrine\NodeAnalyzer\AttributeFinder; use Rector\Doctrine\NodeAnalyzer\TargetEntityResolver; use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory; @@ -37,14 +33,14 @@ public function __construct( ) { } - public function resolve(Property $property, bool $forceNullable): ?Type + public function resolve(Property $property): ?Type { $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($property); $doctrineAnnotationTagValueNode = $phpDocInfo->getByAnnotationClasses(CollectionMapping::TO_ONE_CLASSES); if ($doctrineAnnotationTagValueNode instanceof DoctrineAnnotationTagValueNode) { - return $this->resolveFromDocBlock($phpDocInfo, $property, $doctrineAnnotationTagValueNode, $forceNullable); + return $this->processToOneRelation($property, $doctrineAnnotationTagValueNode); } $expr = $this->attributeFinder->findAttributeByClassesArgByName( @@ -59,10 +55,7 @@ public function resolve(Property $property, bool $forceNullable): ?Type $targetEntityClass = $this->targetEntityResolver->resolveFromExpr($expr); if ($targetEntityClass !== null) { - $fullyQualifiedObjectType = new FullyQualifiedObjectType($targetEntityClass); - - $isNullable = $forceNullable || $this->isNullableJoinColumn($property); - return $this->resolveFromObjectType($fullyQualifiedObjectType, $isNullable); + return $this->resolveNullableObjectType(new FullyQualifiedObjectType($targetEntityClass)); } return null; @@ -70,9 +63,7 @@ public function resolve(Property $property, bool $forceNullable): ?Type private function processToOneRelation( Property $property, - DoctrineAnnotationTagValueNode $toOneDoctrineAnnotationTagValueNode, - ?DoctrineAnnotationTagValueNode $joinDoctrineAnnotationTagValueNode, - bool $forceNullable + DoctrineAnnotationTagValueNode $toOneDoctrineAnnotationTagValueNode ): Type { $targetEntityArrayItemNode = $toOneDoctrineAnnotationTagValueNode->getValue(EntityMappingKey::TARGET_ENTITY); if (! $targetEntityArrayItemNode instanceof ArrayItemNode) { @@ -98,66 +89,15 @@ private function processToOneRelation( $targetEntityClass, $property ); - $fullyQualifiedObjectType = new FullyQualifiedObjectType($tagFullyQualifiedName); - - $isNullable = $forceNullable || $this->isNullableType($joinDoctrineAnnotationTagValueNode); - return $this->resolveFromObjectType($fullyQualifiedObjectType, $isNullable); - } - - private function shouldAddNullType(DoctrineAnnotationTagValueNode $doctrineAnnotationTagValueNode): bool - { - $isNullableValueArrayItemNode = $doctrineAnnotationTagValueNode->getValue('nullable'); - if (! $isNullableValueArrayItemNode instanceof ArrayItemNode) { - return false; - } - - return $isNullableValueArrayItemNode->value instanceof ConstExprTrueNode; - } - - private function resolveFromDocBlock( - PhpDocInfo $phpDocInfo, - Property $property, - DoctrineAnnotationTagValueNode $doctrineAnnotationTagValueNode, - bool $forceNullable - ): Type { - $joinDoctrineAnnotationTagValueNode = $phpDocInfo->findOneByAnnotationClass(MappingClass::JOIN_COLUMN); - - return $this->processToOneRelation( - $property, - $doctrineAnnotationTagValueNode, - $joinDoctrineAnnotationTagValueNode, - $forceNullable - ); - } - - private function resolveFromObjectType(FullyQualifiedObjectType $fullyQualifiedObjectType, bool $isNullable): Type - { - $types = []; - $types[] = $fullyQualifiedObjectType; - if ($isNullable) { - $types[] = new NullType(); - } - - return $this->typeFactory->createMixedPassedOrUnionType($types); + return $this->resolveNullableObjectType(new FullyQualifiedObjectType($tagFullyQualifiedName)); } - private function isNullableType(?DoctrineAnnotationTagValueNode $joinDoctrineAnnotationTagValueNode): bool + /** + * The relation is always nullable, as the entity can be created without the relation being set yet + */ + private function resolveNullableObjectType(FullyQualifiedObjectType $fullyQualifiedObjectType): Type { - if (! $joinDoctrineAnnotationTagValueNode instanceof DoctrineAnnotationTagValueNode) { - return true; - } - - return $this->shouldAddNullType($joinDoctrineAnnotationTagValueNode); - } - - private function isNullableJoinColumn(Property $property): bool - { - $joinExpr = $this->attributeFinder->findAttributeByClassesArgByName( - $property, - [MappingClass::JOIN_COLUMN, MappingClass::COLUMN], - 'nullable' - ); - return $joinExpr instanceof ConstFetch && ! in_array('false', $joinExpr->name->getParts(), true); + return $this->typeFactory->createMixedPassedOrUnionType([$fullyQualifiedObjectType, new NullType()]); } }