Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dirty solution to detect changes at columnDefinition #11593

Open
CheAlex opened this issue Sep 4, 2024 · 0 comments
Open

Dirty solution to detect changes at columnDefinition #11593

CheAlex opened this issue Sep 4, 2024 · 0 comments

Comments

@CheAlex
Copy link

CheAlex commented 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']);
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant