Skip to content

Custom object type is always treated changed even if no actual changes #11915

@speller

Description

@speller

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

  1. Add a custom type, i.e.
class Id
{
    public function __construct(
        public readonly string $id
    ) {
    }
}
  1. 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
  1. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions