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

add BackedEnum support #2412

Closed
wants to merge 7 commits into from
Closed

Conversation

IonBazan
Copy link
Member

@IonBazan IonBazan commented Feb 20, 2022

Q A
Type feature
BC Break no
Fixed issues #2404

Summary

Provides native support for BackedEnums. This could be later integrated with #2411 to allow even better DX.
Already integrated in this PR 🚀

Additional changes:

@IonBazan IonBazan added this to the 2.4.0 milestone Feb 20, 2022
@IonBazan IonBazan self-assigned this Feb 20, 2022
@IonBazan IonBazan force-pushed the feature/enum-support branch 11 times, most recently from 598dd69 to 76d0553 Compare February 21, 2022 07:31
@IonBazan IonBazan marked this pull request as ready for review February 21, 2022 07:56
@IonBazan
Copy link
Member Author

IonBazan commented Feb 21, 2022

This is now ready for review. Please note that the "required" Coding Standards run will not be executed because I changed the PHP version there so the required name no longer match repository settings.

Also, it will produce conflicts with #2411 - depending on which PR is merged first, the other one must be updated to bring typed property guessing for enums.

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.

Great you took care of porting adding the feature! Beside my remarks above please also add a relevant documentation.

Food for thought: in the past it was requested that we pass $mapping to types. While I'm still reluctant to do so it'd make the implementation easier as we'd just add new enum type that would read target enum from the mapping. Thoughts on this?

.github/workflows/coding-standards.yml Outdated Show resolved Hide resolved
*/
public function __construct(
?string $name = null,
?string $type = 'string',
Copy link
Member

Choose a reason for hiding this comment

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

We need to remember to change the default value here as #2411 did

@@ -275,6 +275,10 @@ public function loadMetadataForClass($className, \Doctrine\Persistence\Mapping\C
$mapping['notSaved'] = ((string) $attributes['not-saved'] === 'true');
}

if (isset($mapping['enum-type'])) {
Copy link
Member

@malarzm malarzm Feb 21, 2022

Choose a reason for hiding this comment

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

This looks fishy given the check above and below are checking (and assigning from) the $attributes array.

public static function nonEnumTypeMapped(string $className, string $fieldName, string $enumType): self
{
return new self(sprintf(
'Attempting to map non-enum type %s as enum in document class %s::$%s',
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
'Attempting to map non-enum type %s as enum in document class %s::$%s',
'Attempting to map a non-enum type %s as an enum: %s::%s',

This should be the most consistent with other exceptions we have


public static function enumsRequirePhp81(string $className, string $fieldName): self
{
return new self(sprintf('Enum types require PHP 8.1 in %s::$%s', $className, $fieldName));
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
return new self(sprintf('Enum types require PHP 8.1 in %s::$%s', $className, $fieldName));
return new self(sprintf('Enum types require PHP 8.1 in %s::%s', $className, $fieldName));

We don't use $ in other exceptions

ValueError $previous
): self {
return new self(sprintf(
'Trying to hydrate enum property "%s::$%s". Problem: Case "%s" is not listed in enum "%s"',
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
'Trying to hydrate enum property "%s::$%s". Problem: Case "%s" is not listed in enum "%s"',
'Trying to hydrate an enum property %s::%s, but "%s" is not listed as one of "%s" cases',

* @param class-string $className
* @param class-string<BackedEnum> $enumType
*/
public static function invalidEnumValue(
Copy link
Member

Choose a reason for hiding this comment

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

use function is_int;
use function is_string;

class ReflectionEnumProperty extends ReflectionProperty
Copy link
Member

Choose a reason for hiding this comment

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

It's a pity this class did not went into a common repository. If you're not willing to pursue this then it should be final and marked as @internal

Copy link
Member

Choose a reason for hiding this comment

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

@beberlei do you have any insights why ReflectionEnumProperty isn't a part of https://github.com/doctrine/persistence/tree/2.3.x/lib/Doctrine/Persistence/Reflection?

Copy link
Member

Choose a reason for hiding this comment

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

While waiting for doctrine/common merge and release we could add this class (final and internal). Once doctrine/common is there we'll change implementation :)

Copy link
Member Author

Choose a reason for hiding this comment

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

@malarzm What about the exception thrown? The doctrine/persistence one throws native ValueError, while here we are converting it to a HydratorException which is not available in doctrine/persistence. I suggest simplifying this implementation to throw native error instead too for easier migration.

Copy link
Member

@malarzm malarzm Mar 8, 2022

Choose a reason for hiding this comment

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

The HydratorInterface doesn't make any promises on what exceptions can be thrown, but generally it throws HydratorException when something is off with data (https://github.com/doctrine/mongodb-odm/blob/2.3.x/lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php#L333 and more, look for throw). If possible and relatively simple I'd wrap native ValueError with our HydratorException. If it's not simple then let's the native error be thrown, we can't save users from everything :)

<document name="Documents81\Card">
<id />
<field name="suit" type="string" enum-type="Documents81\Suit" />
<field name="nullableSuit" type="string" enum-type="Documents81\Suit" nullable="true" />
Copy link
Member

Choose a reason for hiding this comment

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

When we go down type-inferring rule we'll need to think how to correctly choose string vs int for the type conversion.

Copy link
Member Author

Choose a reason for hiding this comment

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

We can check if the enum class implements StringBackedEnum or IntBackedEnum to detect the underlying type.

@malarzm
Copy link
Member

malarzm commented Mar 8, 2022

#2411 was merged, this needs to be rebased (it's conflicting anyway) and adapted :)

@IonBazan
Copy link
Member Author

IonBazan commented Mar 8, 2022

@malarzm rebased and adjusted accordingly. I will keep it as separate commits for cleaner diffs but I'll squash them once approved.

@IonBazan
Copy link
Member Author

IonBazan commented Mar 22, 2022

Since doctrine/persistence v2.4 is released, we can now use it. I decided to drop the custom ReflectionProperty and keep throwing the ValueError if something goes wrong (to be consistent with normal fields).

@@ -2556,6 +2575,15 @@ private function validateAndCompleteTypedFieldMapping(array $mapping): array
return $mapping;
}

if (PHP_VERSION_ID >= 80100 && ! $type->isBuiltin() && enum_exists($type->getName(), true)) {
Copy link
Member

Choose a reason for hiding this comment

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

$reflection = new ReflectionEnum($type->getName());
$type = $reflection->getBackingType();

assert($type instanceof ReflectionNamedType);
Copy link
Member

Choose a reason for hiding this comment

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

null case is not handled (assert is just for PHPStan). Using a non-backed enum should warrant an exception

@VincentLanglet
Copy link
Contributor

VincentLanglet commented Apr 29, 2022

What's need to finish this PR ? Do you need help @IonBazan ?
Seems to be a blocking point for the 2.4 version with doctrine/persistence v3 support (cf #2428 (comment))

@malarzm
Copy link
Member

malarzm commented Apr 29, 2022

FWIW I'll be wrapping this up over the weekend.

@IonBazan
Copy link
Member Author

IonBazan commented Apr 30, 2022

Thanks @malarzm for taking over. Sorry, I didn't have enough time to make it prod-ready.

PS. Let me know if you want me to squash anything before you add your changes.

@malarzm malarzm mentioned this pull request May 2, 2022
@malarzm
Copy link
Member

malarzm commented May 3, 2022

Merged in #2440

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants