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

fix: proxy with BackedEnum integer on identifier #997

Open
wants to merge 1 commit into
base: 3.4.x
Choose a base branch
from

Conversation

Gwemox
Copy link

@Gwemox Gwemox commented Oct 14, 2022

If entity identifier return an int BackedEnum, Proxy Generator generate method with cast to int. But it's impossible to cast a BackedEnum to int.

Call getId() on proxy throw an error :
Warning: Object of class App\\Enum\\ProviderId could not be converted to int

Original entity:

    public function getId(): ProviderId
    {
        return $this->id;
    }

Generated proxy method before fix:

    /**
     * {@inheritDoc}
     */
    public function getId(): \App\Enum\ProviderId
    {
        if ($this->__isInitialized__ === false) {
            return (int)  parent::getId();
        }


        $this->__initializer__ && $this->__initializer__->__invoke($this, 'getId', []);

        return parent::getId();
    }

Generated proxy method after fix:

    /**
     * {@inheritDoc}
     */
    public function getId(): \App\Enum\ProviderId
    {
        if ($this->__isInitialized__ === false) {
            return  parent::getId();
        }


        $this->__initializer__ && $this->__initializer__->__invoke($this, 'getId', []);

        return parent::getId();
    }

* @param ReflectionNamedType|ReflectionUnionType|ReflectionIntersectionType|null $type
*
*/
public static function containsEnumType($type): bool
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would type the argument as ReturnType|null, to match the one of ReflectionMethod::getReturnType

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Union types are only allowed since PHP 8.0.

The PHP min version for this project is 7.1.


if ($type instanceof ReflectionUnionType || $type instanceof ReflectionIntersectionType) {
foreach ($type->getTypes() as $unionedType) {
if (static::containsEnumType($unionedType)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would use self:: here. there is no reason to enable late static binding.

Copy link
Author

@Gwemox Gwemox Oct 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, It's corrected

@Gwemox Gwemox force-pushed the fix/proxy_for_BackedEnum_identifier branch from 4f878f3 to 5b54ff3 Compare October 14, 2022 15:08
@Gwemox Gwemox requested a review from stof October 14, 2022 15:15
Copy link
Member

@malarzm malarzm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whichever way we go, this PR will need test to prevent regressions in the future :)

@@ -940,7 +941,12 @@ private function generateMethods(ClassMetadata $class)
if ($this->isShortIdentifierGetter($method, $class)) {
$identifier = lcfirst(substr($name, 3));
$fieldType = $class->getTypeOfField($identifier);
$cast = in_array($fieldType, ['integer', 'smallint'], true) ? '(int) ' : '';

if (ClassUtils::containsEnumType($method->getReturnType())) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid this is only half-baked solution as without method's return type we still do not know what's inside. Maybe it'd be more efficient to generate an instanceof \BackedEnum check for the return statement?

Copy link
Author

@Gwemox Gwemox Nov 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check instance in template, like this ?
from:

        if ($this->__isInitialized__ === false) {
            return (int)  parent::getId();
        }

to:

        if ($this->__isInitialized__ === false) {
            return  parent::getId() instanceof \BackedEnum ? parent::getId() : (int) parent::getId();
        }

if (ClassUtils::containsEnumType($method->getReturnType())) {
$cast = '';
} else {
$cast = in_array($fieldType, ['integer', 'smallint'], true) ? '(int) ' : '';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or better yet: idea that needs somebody with a deeper ORM knowledge than me to be validated: what if we get rid of the cast instead? The proxied entity will have the field already casted to a correct type thanks to initial id hydration. This $cast construct here is 10 years old and removing it doesn't break any test so I think it's worth challenging.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I think that's the best way.

If the method is typed, for the scalar types PHP already cast return value if needed?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@malarzm AbstractProxyFactory::getProxy is responsible for setting the id field on the proxy, its not gone through hydration, instead it might just be the value passed to EntityManager::getReference($entity, $id) without casting. So getting rid of the cast would require to move the cast to a higher level in the ORM. Same for UnitOfWork::createEntity using getProxy, associated ids don't have types and are not converted to int for example.

A test in ORM would be:

$proxy = $em->getReference(CmsUser, "1");
$proxy->getId();

Unsure if we test this at the moment.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @beberlei I'll try to have a closer look there in ORM 👍

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@malarzm any news on this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quoting myself from #998

Probably we won't end with this exact solution as it seems ORM has no tests for this behaviour either. The change looks OK ORM-wise, but I'm afraid it may hit users in an unexpected and hard to debug way.

Sadly I didn't have time to get back to this :(

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can I help ?

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

Successfully merging this pull request may close these issues.

4 participants