Dirty solution to detect changes at columnDefinition #7414

Open
opened 2026-01-22 15:51:25 +01:00 by admin · 0 comments
Owner

Originally created by @CheAlex on GitHub (Sep 4, 2024).

Feature Request

Q A
New Feature yes
RFC yes

Summary

Doctrine doesn't detect changes at columnDefinition: https://www.doctrine-project.org/projects/doctrine-orm/en/3.2/reference/attributes-reference.html#column

Dirty solution

Doctrine doesn't detect changes at columnDefinition, but Doctrine detects mapping's changes if comment changed. The solution is to add virtual comment with value based on hash of columnDefinition:

columnDefinition changed -> hash(columnDefinition) changed -> comment changed -> Doctrine detect mapping's changes

Tested example

<?php

declare(strict_types=1);

namespace RootNs\Entity\Attributes;

#[\Attribute(\Attribute::TARGET_PROPERTY)]
final class ColumnDefinitionHashComment
{
}
<?php

declare(strict_types=1);

namespace RootNs\EventListener;

use Doctrine\ORM\Mapping as ORM;
use RootNs\Entity\Attributes\ColumnDefinitionHashComment;

#[ORM\Entity]
#[ORM\Table(name: 'users')]
class User
{
    #[ORM\Id]
    #[ORM\Column(name: 'id', type: 'uuid', unique: true, nullable: false)]
    private UuidInterface $id;

    #[ORM\Column(type: 'json_document', options: [
        'jsonb' => true,
    ])]
    private UserData $data;

    #[ColumnDefinitionHashComment]
    #[ORM\Column(
        type: 'integer',
        nullable: false,
        insertable: false,
        updatable: false,
        columnDefinition: 'INT GENERATED ALWAYS AS (JSON_VALUE(data, "$.id")) NOT NULL',
    )]
    private readonly int $relationId;
}
<?php

declare(strict_types=1);

namespace RootNs\EventListener;

use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
use Doctrine\ORM\Mapping\FieldMapping;
use RootNs\Entity\Attributes\ColumnDefinitionHashComment;
use ReflectionProperty;

class ColumnDefinitionHashCommentSubscriber
{
    public function loadClassMetadata(LoadClassMetadataEventArgs $event): void
    {
        $classMetadata = $event->getClassMetadata();

        foreach ($classMetadata->fieldMappings as $fieldName => $fieldMapping) {
            $reflectionProperty = $classMetadata->reflClass->getProperty($fieldName);
            assert(null !== $reflectionProperty);
            $attributes = $reflectionProperty->getAttributes(ColumnDefinitionHashComment::class);

            if ([] === $attributes) {
                continue;
            }

            $this->addColumnDefinitionCommentHash($classMetadata->getName(), $reflectionProperty, $fieldMapping);
        }
    }

    private function addColumnDefinitionCommentHash(
        string $className,
        ReflectionProperty $reflectionProperty,
        FieldMapping $fieldMapping
    ): void {
        if (null === $fieldMapping->columnDefinition) {
            throw new \LogicException(sprintf(
                'At entity field %s:%s columnDefinition must be defined',
                $className,
                $reflectionProperty->getName()
            ));
        }

        if (false !== strripos($fieldMapping->columnDefinition, 'comment')) {
            throw new \LogicException(sprintf(
                'At entity field %s:%s comment must not be at columnDefinition',
                $className,
                $reflectionProperty->getName()
            ));
        }

        // xxh3 - is the fastest supported algorithm in php: https://php.watch/articles/php-hash-benchmark
        $fieldMapping->options['comment'] = sprintf(
            '(columnDefinitionHash:%s)',
            hash('xxh3', $fieldMapping->columnDefinition)
        );
        $fieldMapping->columnDefinition .= sprintf(' COMMENT "%s"', $fieldMapping->options['comment']);
    }
}
Originally created by @CheAlex on GitHub (Sep 4, 2024). ### Feature Request <!-- Fill in the relevant information below to help triage your issue. --> | Q | A |------------ | ------ | New Feature | yes | RFC | yes #### Summary Doctrine doesn't detect changes at `columnDefinition`: https://www.doctrine-project.org/projects/doctrine-orm/en/3.2/reference/attributes-reference.html#column <!-- Provide a summary of the feature you would like to see implemented. --> #### Dirty solution Doctrine doesn't detect changes at `columnDefinition`, but Doctrine detects mapping's changes if comment changed. The solution is to add virtual comment with value based on hash of `columnDefinition`: `columnDefinition` changed -> `hash(columnDefinition)` changed -> comment changed -> Doctrine detect mapping's changes #### Tested example ``` <?php declare(strict_types=1); namespace RootNs\Entity\Attributes; #[\Attribute(\Attribute::TARGET_PROPERTY)] final class ColumnDefinitionHashComment { } ``` ``` <?php declare(strict_types=1); namespace RootNs\EventListener; use Doctrine\ORM\Mapping as ORM; use RootNs\Entity\Attributes\ColumnDefinitionHashComment; #[ORM\Entity] #[ORM\Table(name: 'users')] class User { #[ORM\Id] #[ORM\Column(name: 'id', type: 'uuid', unique: true, nullable: false)] private UuidInterface $id; #[ORM\Column(type: 'json_document', options: [ 'jsonb' => true, ])] private UserData $data; #[ColumnDefinitionHashComment] #[ORM\Column( type: 'integer', nullable: false, insertable: false, updatable: false, columnDefinition: 'INT GENERATED ALWAYS AS (JSON_VALUE(data, "$.id")) NOT NULL', )] private readonly int $relationId; } ``` ``` <?php declare(strict_types=1); namespace RootNs\EventListener; use Doctrine\ORM\Event\LoadClassMetadataEventArgs; use Doctrine\ORM\Mapping\FieldMapping; use RootNs\Entity\Attributes\ColumnDefinitionHashComment; use ReflectionProperty; class ColumnDefinitionHashCommentSubscriber { public function loadClassMetadata(LoadClassMetadataEventArgs $event): void { $classMetadata = $event->getClassMetadata(); foreach ($classMetadata->fieldMappings as $fieldName => $fieldMapping) { $reflectionProperty = $classMetadata->reflClass->getProperty($fieldName); assert(null !== $reflectionProperty); $attributes = $reflectionProperty->getAttributes(ColumnDefinitionHashComment::class); if ([] === $attributes) { continue; } $this->addColumnDefinitionCommentHash($classMetadata->getName(), $reflectionProperty, $fieldMapping); } } private function addColumnDefinitionCommentHash( string $className, ReflectionProperty $reflectionProperty, FieldMapping $fieldMapping ): void { if (null === $fieldMapping->columnDefinition) { throw new \LogicException(sprintf( 'At entity field %s:%s columnDefinition must be defined', $className, $reflectionProperty->getName() )); } if (false !== strripos($fieldMapping->columnDefinition, 'comment')) { throw new \LogicException(sprintf( 'At entity field %s:%s comment must not be at columnDefinition', $className, $reflectionProperty->getName() )); } // xxh3 - is the fastest supported algorithm in php: https://php.watch/articles/php-hash-benchmark $fieldMapping->options['comment'] = sprintf( '(columnDefinitionHash:%s)', hash('xxh3', $fieldMapping->columnDefinition) ); $fieldMapping->columnDefinition .= sprintf(' COMMENT "%s"', $fieldMapping->options['comment']); } } ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#7414