-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Description
Bug Report
| Q | A |
|---|---|
| Version | 3.3.2 |
Summary
If I add a custom object type, replacing the object that has the same actual value, is treated as a change in the object leading to useless changes and useless queries on flush().
Current behavior
Useless changes added
Expected behavior
No useless changes
How to reproduce
- Add a custom type, i.e.
class Id
{
public function __construct(
public readonly string $id
) {
}
}- Register ORM Type:
class IdType extends Type
{
public const string NAME = 'app_id';
public const int FIELD_LENGTH = 50;
public function getName(): string
{
return self::NAME;
}
public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
{
return $platform->getStringTypeDeclarationSQL([
'length' => self::FIELD_LENGTH,
]);
}
public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?Id
{
if ($value instanceof Id || null === $value) {
return $value;
}
if (!\is_string($value)) {
$this->throwInvalidType($value);
}
return new Id($value);
}
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string
{
if ($value instanceof Id) {
return $value->id;
}
if (null === $value || '' === $value) {
return null;
}
if (!\is_string($value)) {
$this->throwInvalidType($value);
}
return $value;
}doctrine:
dbal:
types:
app_id: App\Entity\ORMType\AppIdType- Use it in an entity
class Foo {
#[ORM\Id]
#[ORM\Column(type: AppIdType::NAME, length: AppIdType::FIELD_LENGTH, unique: true, nullable: false)]
public Id $id;
}Now if I (or ORM) reassign the id propery value with another instance but with the same actual value, ORM will generate useless queries like UPDATE Foo set id = 'id' where id = 'id'. I'm facing it with associations where ORM detects false changes and executes useless queries. In an instance, I have a few hundred loaded objects in UOW and it detects false changes in all of them, executing hundreds of useless changes without real changes. The issue is in the \Doctrine\ORM\UnitOfWork::computeChangeSet() method where it performs the following comparison:
// skip if value haven't changed
if ($orgValue === $actualValue) {
continue;
}It's clear that different object instances will be treated as different values no matter what is the real value. In my project, I'm using this patch to add support for custom identicality comparison:
--- a/src/UnitOfWork.php
+++ b/src/UnitOfWork.php
@@ -681,6 +681,12 @@
continue;
}
+ if (is_object($orgValue) && method_exists($orgValue, '__is_identical')) {
+ if ($orgValue->__is_identical($actualValue)) {
+ continue;
+ }
+ }
+
// if regular field
if (! isset($class->associationMappings[$propName])) {
$changeSet[$propName] = [$orgValue, $actualValue];And then in my custom type class:
public function __is_identical(mixed $id): bool
{
return $this->id == $id->id;
}PS: The real class does not use a string value and has a more complex logic, but I simplified the example to use just a string.