doctrine / dbal

Doctrine Database Abstraction Layer
https://www.doctrine-project.org/projects/dbal.html
MIT License
9.45k stars 1.33k forks source link

restore comment hint (useful for enumtype declaration) #6444

Open xkzl opened 3 months ago

xkzl commented 3 months ago

Bug Report

Q A
Version 4.0.0

Summary

I add/register my custom EnumType at early stage to doctrine and then I am used to add proper comment hint for EnumType to allow for conversion from StringType to correct EnumType using DC2Type at EnumSubscriber::postGenerateSchema event stage; Doctrine was using CommentHint and it was useful. This has been removed from dbal in the commit 4134b86cb986f6fc68fe9ba7c8a7416debfa22bd for some reasons.

I cannot find any alternative, but rolling back a minimal code reintroducing comment hint to DC2Type detection for Doctrine to Type conversion. Not doing so creates a diff in the DB schema resulting in a systematic update of the database scheme every time I launch a doctrine:schema:update command, due to fictive difference in the type of the columns.

<?php

namespace App\DatabaseSubscriber;

use App\Database\Type\EnumType;
use Doctrine\DBAL\Schema\Column;
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;

class EnumSubscriber
{
    public function postGenerateSchema(GenerateSchemaEventArgs $eventArgs)
    {
        $columns = [];

        foreach ($eventArgs->getSchema()->getTables() as $table) {
            foreach ($table->getColumns() as $column) {
                if ($column->getType() instanceof EnumType) {
                    $columns[] = $column;
                }
            }
        }

        /** @var Column $column */
        foreach ($columns as $column) {

            $enum = $column->getType();
            $column->setComment(trim(sprintf('%s (%s)', $column->getComment(), implode(',', $enum::getPermittedValues()))));
        }
    }
}

Current behaviour

Current behavior, makes db schema update infinitely refreshing.

How to reproduce

Just create a EnumType following; and use postGenerateSchema subscriber.

Expected behaviour

I would expect to restore in AbstractSchemaManager.php:

    public function extractDoctrineTypeFromComment($comment, $currentType)

Then restore on every platform,

        $type = $this->platform->getDoctrineTypeMapping($dbType);
        $type                   = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type);

Unless this was suppressed on purpose, it was very useful feature to incorporate EnumType. If an alternative is possible, please advice.

xkzl commented 3 months ago

I have restore the minimal code to make use of comment hint to detect EnumType based on (DC2Type:[xxx]) information as it was implemented before 4134b86cb986f6fc68fe9ba7c8a7416debfa22bd. It allows to fix this issue implementing EnumType using EventListener::postGenerationSchema().

Please consider it, this was very useful feature and removed without much information to fix it.

berkut1 commented 3 months ago

But can't you solve your problem by creating custom types? https://www.doctrine-project.org/projects/doctrine-dbal/en/4.0/reference/types.html#custom-mapping-types

I use EnumType extensively in my project through custom types, and when testing with dbal4, I had no problems, and there shouldn't be any, because for Doctrine, it will still be int or string (depending on which EnumType you use). The only thing you need to do when creating a type is to inherit from the parent Type and implement convertToDatabaseValue(), convertToPHPValue(), getSQLDeclaration(), and getBindingType().

xkzl commented 3 months ago

Adding the type to doctrine is not a problem, this is standard in the documentation.

The problem lies in the conversion back from doctrine to type. Previous behavior was to look for (DC2Type:XXX) in the comment hint. At the moment, the field gets back into 'StringType' instead of the correct 'EnumType', so on request the db schema gets indefinitely upgraded as the diff is triggering update.

berkut1 commented 3 months ago

Sorry if I misunderstood you, but the Doctrine looks at the type in the same way as it did with DC2Type, that is, you need to create and register the type you need (custom type).

UPD: If you are getting StringType, it means that you did not create your custom type or did not update it after switching to DBAL4.

xkzl commented 3 months ago

Could you please provide an example?

I already highlighted the suppressed lines that are problematic. I don't think there are strong motivations to explain why comment hints have been removed. Looking at the commit there is actually not much explanation of that part and why comment hint has been removed. It seems to be a mixed modification wrapped up in another commit.

I cannot get this EnumType working, but I am sure there must be a way if you say so. I would be happy to give it a try. I added the EnumType to Doctrine configuration, I can use EnumType but this issue is taking place when going from Doctrine to Type. If you can suggest a doctrine event to listen to I would be happy to use it and to have this configured in time, but without this, the only solution I have is to use comment hint.

Comment hint is a quite reasonable feature and using hints in various context is a common practice including for queries. I don't see why we should remove a useful feature if it gives some flexibility to the implementation.

xkzl commented 3 months ago

https://github.com/doctrine/dbal/issues/6257

xkzl commented 3 months ago

All checks passed but codecov. (is that some key i should be providing ?) Any maintainer please advice 🙏

greg0ire commented 3 months ago

Regarding CodeCov, you have nothing to do, I reported it here: https://github.com/codecov/codecov-action/issues/1487

greg0ire commented 3 months ago

Regarding your issue, I think it might have been fixed with last week's 3.8.5 and 4.0.3 release. Have you tried upgrading, and disabling type comments?

berkut1 commented 3 months ago

@greg0ire As I understand he uses DBAL4, not DBAL3.

@xkzl There is my custom type for int Enums.

enum Status: int
{
    case ACTIVE = 1;
    case SUSPENDED = 2;
    case ARCHIVED = 3;

    #[Pure]
    public function label(): string
    {
        return self::getLabel($this);
    }

    private function getLabel(self $value): string
    {
        return match ($value) {
            self::ACTIVE => 'STATUS_ACTIVE',
            self::SUSPENDED => 'STATUS_SUSPENDED',
            self::ARCHIVED => 'STATUS_ARCHIVED',
        };
    }

    public static function lists(): array
    {
        $cases = self::cases();
        $statuses = [];
        foreach ($cases as $status) {
            $statuses[$status->value] = $status->label();
        }
        return $statuses;
    }
}
final class StatusType extends Type
{
    final public const string NAME = 'user_user_status';

    #[\Override]
    public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): int
    {
        return $value instanceof Status ? $value->value : $value;
    }

    #[\Override]
    public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?Status
    {
        return !empty($value) ? Status::from($value) : null;
    }

    #[\Override]
    public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
    {
        return $platform->getSmallIntTypeDeclarationSQL($column);
    }

    #[\Override]
    public function getBindingType(): int
    {
        return ParameterType::INTEGER;
    }

    public function getName(): string //I left this method for Symfony phpstorm plugin
    {
        return self::NAME;
    }
}

It works perfectly in DBAL4/ORM3, but I have to extend Type instead of SmallInt how it was in DBAL3, because they moved to strict types.

xkzl commented 3 months ago

@berkut1 just to be on the same baseline here is the EnumType definition as proposed by Doctrine documentation https://www.doctrine-project.org/projects/doctrine-orm/en/3.2/cookbook/mysql-enums.html#solution-2-defining-a-type

I am interested to the registration type part and which doctrine event should be used, not really in your custom EnumType definition. How/where do you registered? I think this is the main point.

@greg0ire I just checked and in my latest checks I am using dbal 4.0.3, with orm 3.2.0. Could you advise how to disable type comment ?

Additionally, here is the latest reference to type comment I can find in the documentation and it is from dbal 3.8.5 https://www.doctrine-project.org/projects/doctrine-dbal/en/3.8/explanation/dc2type-comments.html Having a page for 4.0 might be nice such that it would provide smooth example how to transition if not required anymore.

berkut1 commented 3 months ago

How/where do you registered? I think this is the main point.

I register it through Symfony https://symfony.com/doc/current/doctrine/dbal.html#registering-custom-mapping-types

Having a page for 4.0 might be nice such that it would provide smooth example how to transition if not required anymore.

There is no need transition, it just should work if you correctly created your custom types, especially getSQLDeclaration

greg0ire commented 3 months ago

Types are always disabled in 4.0.x

xkzl commented 3 months ago

How/where do you registered? I think this is the main point.

I register it through Symfony

https://symfony.com/doc/current/doctrine/dbal.html#registering-custom-mapping-types

Ok so you are not directly using the registration command mentioned in https://www.doctrine-project.org/projects/doctrine-dbal/en/4.0/reference/types.html#custom-mapping-types . Then, I think this is a different situation and it is done via doctrine configuration in your case. I think I better understand the differences between the two situations. My EnumType is part of a bundle. I don't think I can do it your way and I need à to listen to an event. I registered the type at the boot of my bundle using the initial command provided in the custom mapping type (basically Type::addType)

So according to my investigations, this issue is still here for 4.0.3 @greg0ire. So at this time, the only solution I can see is to use comment hints unless I missed something. Would you consider merging this PR @greg0ire, if it doesn't hurt current revisions/progress? If needed, I can additionally provide a test to make sure comment hints can get extracted so it will not be forgotten anymore.

greg0ire commented 3 months ago

Would you consider merging this PR

DC2 comments are supposed to be completely gone in 4.0.x so… no?

xkzl commented 3 months ago

What would be the alternative if we cannot add EnumType to bundles by registering manually through Type:addType on time for correct construction of columns...

greg0ire commented 3 months ago

Use the enumType column attribute?

xkzl commented 3 months ago

Thanks for trying.

greg0ire commented 3 months ago

No worries. Also, it seems you can even avoid dealing with enumType if you use a recent enough version of PHP and the ORM:

As of version 2.11 Doctrine can also automatically map typed properties using a PHP 8.1 enum to set the right type and enumType.

xkzl commented 1 month ago

I have yet to find a solution to the issue. Is there any plan to fix or address it? So far, the only fix I've found is the provided file.

Additionally, I'd like to highlight the use case of SetType handling SET() in databases, on top of EnumType.

berkut1 commented 1 month ago

@xkzl can you check this theory? https://github.com/doctrine/migrations/issues/1441#issuecomment-2227444186 Of course if you use ORM.

xkzl commented 1 month ago

yes I can do that, maybe in few days only.