diff --git a/lib/Doctrine/ORM/Internal/Hydration/HydrationException.php b/lib/Doctrine/ORM/Internal/Hydration/HydrationException.php index d807c0eca28..d4bc984d8cf 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/HydrationException.php +++ b/lib/Doctrine/ORM/Internal/Hydration/HydrationException.php @@ -83,9 +83,8 @@ public static function missingDiscriminatorMetaMappingColumn($entityName, $discr } /** - * @param string $discrValue - * @param string[] $discrValues - * @psalm-param list $discrValues + * @param string $discrValue + * @param list $discrValues * * @return HydrationException */ diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index c33c437f72b..81eaa99bb60 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -554,9 +554,9 @@ class ClassMetadataInfo implements ClassMetadata * * @see discriminatorColumn * - * @var array + * @var array * - * @psalm-var array + * @psalm-var array */ public $discriminatorMap = []; @@ -3254,7 +3254,7 @@ final public function getDiscriminatorColumn(): array * Sets the discriminator values used by this class. * Used for JOINED and SINGLE_TABLE inheritance mapping strategies. * - * @psalm-param array $map + * @param array $map * * @return void */ @@ -3268,9 +3268,8 @@ public function setDiscriminatorMap(array $map) /** * Adds one entry of the discriminator map with a new class and corresponding name. * - * @param string $name - * @param string $className - * @psalm-param class-string $className + * @param int|string $name + * @param string $className * * @return void * @@ -3744,7 +3743,6 @@ public function getAssociationsByTargetClass($targetClass) /** * @param string|null $className - * @psalm-param string|class-string|null $className * * @return string|null null if the input value is null * @psalm-return class-string|null diff --git a/lib/Doctrine/ORM/Mapping/DiscriminatorMap.php b/lib/Doctrine/ORM/Mapping/DiscriminatorMap.php index 5e822daa613..ad36032b298 100644 --- a/lib/Doctrine/ORM/Mapping/DiscriminatorMap.php +++ b/lib/Doctrine/ORM/Mapping/DiscriminatorMap.php @@ -15,15 +15,11 @@ #[Attribute(Attribute::TARGET_CLASS)] final class DiscriminatorMap implements Annotation { - /** - * @var array - * @psalm-var array - */ + /** @var array */ public $value; /** - * @param array $value - * @psalm-param array $value + * @param array $value */ public function __construct(array $value) { diff --git a/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php b/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php index bfa07b532d0..95c74d1a877 100644 --- a/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php +++ b/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php @@ -82,7 +82,7 @@ public function exportClassMetadata(ClassMetadataInfo $metadata) foreach ($metadata->discriminatorMap as $value => $className) { $discriminatorMappingXml = $discriminatorMapXml->addChild('discriminator-mapping'); - $discriminatorMappingXml->addAttribute('value', $value); + $discriminatorMappingXml->addAttribute('value', (string) $value); $discriminatorMappingXml->addAttribute('class', $className); } } diff --git a/lib/Doctrine/ORM/Utility/IdentifierFlattener.php b/lib/Doctrine/ORM/Utility/IdentifierFlattener.php index 837f2dc1d57..8df8eb9ca2f 100644 --- a/lib/Doctrine/ORM/Utility/IdentifierFlattener.php +++ b/lib/Doctrine/ORM/Utility/IdentifierFlattener.php @@ -11,7 +11,7 @@ use function assert; use function implode; -use function is_object; +use function is_a; /** * The IdentifierFlattener utility now houses some of the identifier manipulation logic from unit of work, so that it @@ -55,7 +55,7 @@ public function flattenIdentifier(ClassMetadata $class, array $id): array $flatId = []; foreach ($class->identifier as $field) { - if (isset($class->associationMappings[$field]) && isset($id[$field]) && is_object($id[$field])) { + if (isset($class->associationMappings[$field]) && isset($id[$field]) && is_a($id[$field], $class->associationMappings[$field]['targetEntity'])) { $targetClassMetadata = $this->metadataFactory->getMetadataFor( $class->associationMappings[$field]['targetEntity'] ); diff --git a/psalm-baseline.xml b/psalm-baseline.xml index f0e56991cc8..ad0bb9a1798 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -495,8 +495,7 @@ - - $class + $repositoryClassName @@ -871,8 +870,7 @@ - - $map + (string) $xmlRoot['repository-class'] isset($xmlRoot['repository-class']) ? (string) $xmlRoot['repository-class'] : null diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/GH9335Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH9335Test.php new file mode 100644 index 00000000000..cd1729902f7 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH9335Test.php @@ -0,0 +1,219 @@ +setUpEntitySchema([GH9335Book::class, GH9335Author::class]); + } + + /** + * Verifies that entities with foreign keys with custom id object types don't throw an exception + * + * The test passes when refresh() does not throw an exception + */ + public function testFlattenIdentifierWithObjectId(): void + { + $author = new GH9335Author('Douglas Adams'); + $book = new GH9335Book(new GH9335IntObject(42), 'The Hitchhiker\'s Guide to the Galaxy', $author); + + $this->_em->persist($author); + $this->_em->persist($book); + $this->_em->flush(); + + $this->_em->refresh($book); + + self::assertInstanceOf(GH9335IntObject::class, $book->getId()); + } +} + + +class GH9335IntObjectType extends Type +{ + public function getSQLDeclaration(array $column, AbstractPlatform $platform): string + { + return $platform->getIntegerTypeDeclarationSQL($column); + } + + public function getName(): string + { + return self::class; + } + + public function convertToDatabaseValue($value, AbstractPlatform $platform): int + { + return $value->wrappedInt; + } + + public function convertToPHPValue($value, AbstractPlatform $platform): GH9335IntObject + { + return new GH9335IntObject((int) $value); + } + + public function getBindingType(): int + { + return ParameterType::INTEGER; + } + + public function requiresSQLCommentHint(AbstractPlatform $platform): bool + { + return true; + } +} + +class GH9335IntObject +{ + /** @var int */ + public $wrappedInt; + + public function __construct(int $wrappedInt) + { + $this->wrappedInt = $wrappedInt; + } + + public function __toString(): string + { + return (string) $this->wrappedInt; + } +} + +/** + * @Entity + */ +class GH9335Book +{ + /** + * @var GH9335IntObject + * @Id + * @Column(type=GH9335IntObjectType::class, unique=true) + */ + private $id; + + /** + * @Column(type="string") + * @var string + */ + private $title; + + + /** + * @OneToOne(targetEntity="GH9335Author", mappedBy="book", cascade={"persist", "remove"}) + * @var GH9335Author + */ + private $author; + + public function __construct(GH9335IntObject $id, string $title, ?GH9335Author $author = null) + { + $this->setId($id); + $this->setTitle($title); + $this->setAuthor($author); + } + + public function getId(): ?GH9335IntObject + { + return $this->id; + } + + public function setId($id): void + { + $this->id = $id; + } + + public function getTitle(): ?string + { + return $this->title; + } + + public function setTitle($title): void + { + $this->title = $title; + } + + public function getAuthor(): ?GH9335Author + { + return $this->author; + } + + public function setAuthor(?GH9335Author $author): self + { + $this->author = $author; + + // set the owning side of the relation + if ($author) { + $author->setBook($this); + } + + return $this; + } +} + +/** + * @Entity + */ +class GH9335Author +{ + /** + * @var GH9335Book + * @Id + * @OneToOne(targetEntity="GH9335Book", inversedBy="author") + * @JoinColumn(name="book") + */ + private $book; + + /** + * @Column(type="string", nullable="true" ) + * @var string + */ + private $name; + + public function __construct(?string $name) + { + $this->setName($name); + } + + public function getBook(): ?GH9335Book + { + return $this->book; + } + + public function setBook(GH9335Book $book): self + { + $this->book = $book; + + return $this; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): void + { + $this->name = $name; + } +} diff --git a/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php b/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php index 1a691504431..a7d8617ae41 100644 --- a/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php +++ b/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php @@ -16,8 +16,6 @@ use Doctrine\Tests\Mocks\NullSqlWalker; use Doctrine\Tests\OrmTestCase; -use const PHP_EOL; - class LanguageRecognitionTest extends OrmTestCase { /** @var EntityManagerInterface */ @@ -28,34 +26,16 @@ protected function setUp(): void $this->entityManager = $this->getTestEntityManager(); } - public function assertValidDQL($dql, $debug = false): void + public function assertValidDQL(string $dql): void { - try { - $parserResult = $this->parseDql($dql); - $this->addToAssertionCount(1); - } catch (QueryException $e) { - if ($debug) { - echo $e->getTraceAsString() . PHP_EOL; - } - - self::fail($e->getMessage()); - } + $this->parseDql($dql); + $this->addToAssertionCount(1); } - public function assertInvalidDQL($dql, $debug = false): void + public function assertInvalidDQL(string $dql): void { - try { - $parserResult = $this->parseDql($dql); - - self::fail('No syntax errors were detected, when syntax errors were expected'); - } catch (QueryException $e) { - if ($debug) { - echo $e->getMessage() . PHP_EOL; - echo $e->getTraceAsString() . PHP_EOL; - } - - $this->addToAssertionCount(1); - } + $this->expectException(QueryException::class); + $this->parseDql($dql); } /** @@ -370,7 +350,7 @@ public function testSelectLiteralInSubselect(): void */ public function testConstantValueInSelect(): void { - $this->assertValidDQL("SELECT u.name, 'foo' AS bar FROM Doctrine\Tests\Models\CMS\CmsUser u", true); + $this->assertValidDQL("SELECT u.name, 'foo' AS bar FROM Doctrine\Tests\Models\CMS\CmsUser u"); } public function testDuplicateAliasInSubselectPart(): void @@ -594,7 +574,7 @@ public function testCustomFunctionsReturningStringInStringPrimary(): void { $this->entityManager->getConfiguration()->addCustomStringFunction('CC', Query\AST\Functions\ConcatFunction::class); - $this->assertValidDQL("SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE CC('%', u.name) LIKE '%foo%'", true); + $this->assertValidDQL("SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE CC('%', u.name) LIKE '%foo%'"); } /**