-
Notifications
You must be signed in to change notification settings - Fork 101
Native enum support #409
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
Native enum support #409
Conversation
This looks good @dsavina. We need to get the tests passing. Firstly, we'll want to skip the Regarding |
I'm aware of the need to filter tests based on PHP version, I'll do it ASAP. In addition, I will also need to isolate enum files in their own namespace to prevent loading invalid PHP code when working with PHP < 8.1.
I'm rather talking about the GraphQL standard, which only allows Perhaps an example will clarify, say we have an enum LogLevel: int
{
case Trace = 0;
case Debug = 1;
case Info = 2;
case Warn = 3;
case Error = 4;
case Fatal = 5;
} a model class Log
{
#[Field]
public LogLevel $level;
#[Field]
public DateTime $date;
#[Field]
public string $message;
} and a query #[Query]
/** @return Log[] */
public function logs(LogLevel $level = LogLevel::Info): iterable
{
foreach ($this->logs as $log) {
if ($log->level->value >= $level->value) {
yield $log;
}
}
} We then have two choices:
query {
_type(name: "LogLevel") {
enumValues {
name
}
}
} which server would respond with: {
"data": {
"__type": {
"enumValues": [
{
"name": "Trace"
},
{
"name": "Debug"
},
{
"name": "Info"
},
...
]
}
}
} Requesting query {
logs(level: Warn) {
level
message
}
} which server would respond with: {
"data": {
"logs": [
{
"level": "Error",
"message": "Something went wrong"
},
{
"level": "Fatal",
"message": "Something went really wrong"
}
]
}
}
query {
logs(level: 3) {
level
message
}
} which server would now respond with: {
"data": {
"logs": [
{
"level": 4,
"message": "Something went wrong"
},
{
"level": 5,
"message": "Something went really wrong"
}
]
}
} We can also query: query {
logs(level: 6) {
level
message
}
} which would throw an error, as I'm not comfortable with this second option, where the introspection never specified which values are allowed, since parameter |
It looks like Apollo Server allows for you to override which one is used through a resolver: From the spec:
In the case of an I can see a lot of cases where, both, the name and value are important. Is it possible to allow both of these fields while still being compliant with the spec? I haven't used enums in GraphQL too much. I do recall the current implementation with In my opinion, we need to find out if it's possible and/or desirable to output both the name and the value, or we need to |
There is no such thing as a Enum value in GraphQL, afaik most languages don't expose the Enum value. |
@Lappihuan I do not disagree with you and am not suggesting that PHP has any relevance to this conversation. However, if there is a value that's available and it does not break the spec to include it as a field, I'm not sure I see the issue with providing it. There are many cases where having a value can be extremely valuable, in conjunction with the name. If it breaks the spec and won't work, that's that - let's discount the idea, and move forward with an annotation option. |
@dsavina would be great to get this wrapped up. We should go ahead with what you have now. In order for us to support the option to output either the constant name or the assigned value, we'd really need to add a new attribute for Enum types, or possibly add a new property to the As for outputting the name and the value, I don't even know if you could query the "value" of an Enum through most clients. Technically you could make it available in the |
If I understand the issue correctly, why can't we only allow string backed enums and refuse to accept other php enums ? |
i'm still wondering why this is even a question. i am still confused why the value of a enum would be of any intrest for an api consumer. the current implementation would work like this: PHP:
GQL:
No mather what the backed value is:
GQL:
|
We should just follow the webonyx implementation. https://webonyx.github.io/graphql-php/type-definitions/enums/ The current I'm all for getting this right now though, now, and not trying to create a 1:1 implementation of the Current class MyCLabsEnumType extends EnumType
{
public function __construct(string $enumClassName, string $typeName)
{
$consts = $enumClassName::toArray();
$constInstances = [];
foreach ($consts as $key => $value) {
$constInstances[$key] = ['value' => $enumClassName::$key()];
}
parent::__construct([
'name' => $typeName,
'values' => $constInstances,
]);
}
... As you can see, the key is being assigned to both, the key and value. However, the webonyx implementation supports keys and values, according to the native |
Exposing the value over GQL is just not in the GQL Spec... |
@Lappihuan can you be more concrete here in terms of what you're suggesting different? What is wrong with implementing Enums according to the webonyx type mapping implementation? |
Sorry I couldn't get back on this subject earlier. Β
This is not exact: the enum name is indeed used as the key in the Webonyx representation of the enum case. However, the value obtained with expression Therefore, the question remains regarding the exposed names. I think the best approach would be to leave the choice to developers, by supporting an option |
@dsavina I'm all for offering the option here. That was my initial recommendation. For the sake of simplicity, I think we could go with a direct mapping according to the We have the I think we should unify these attributes by deprecating the |
Hi @oojacoboo, I'm sorry, I'm very busy these days, I won't be able to come back on this for the next two weeks. I should find the time to do it somewhere around February 10, would that work? |
@dsavina that should be fine. We'll be anxiously awaiting :) |
7b362d4
to
87e3883
Compare
Hi there, Just a quick update: I found some time to change the implementation in order to use I couldn't update the tests in order to bypass all this whenever running under 8.1, I will do it first thing on Monday. |
95d6076
to
0181ed9
Compare
Hi, I did all I could, I'm not 100% satisfied but I guess it can be improved afterwards:
|
|
||
// phpcs:disable SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.MissingVariable | ||
/** @var class-string<UnitEnum> $enumClass */ | ||
// phpcs:enable SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.MissingVariable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we getting much value out of this? I'd just remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PHPStan (before I disabled it) required a class-string<UnitEnum>
, as signed in EnumType
constructor, and wasn't able to determine $enumClass
complies at this point (after the enum_exist
check).
On the other side, CodeSniffer doesn't like precising the type subsequently to a variable declaration (which I think is a shame, but I guess I understand the rule), therefore the phpcs:disable
directive.
I could simply soften the typecheck in EnumType::__constructor
, from class-string<UnitEnum>
to class-string
, but I must say I'm not so fond of this option.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, we should be doing better validation in the constructor for this string, instead of relying on static type analysis. What kind of feedback is a developer going to here here if an incorrect class-string is provided? It's nice to provide these additional static checks for IDE completion and lib development testing, but provides little runtime, or even development, assurances.
Is this error not referring to the fact that it's a param
and not a var
in terms of phpcs parsing? Why aren't we applying this in the docblock of the current function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The validation lies precisely in the enum_exists()
check above. The ideal would have been for PHPStan to exploit this check in order to infer a class-string<UnitEnum>
, just like is_int()
would lead to inferring an integer value, I'm merely giving it a little push here.
The input argument of the method doesn't strictly have to be a valid enum class name, it simply won't be mapped as an EnumType
if not; null
will be returned, and the mapper will fallback to the next in line (once again, behaviour copied from MyCLabsTypeMapper
). Typing the argument as class-string<UnitEnum>
means the check must be done outside of the method, both in EnumTypeMapper::map(Type $type)
and EnumTypeMapper::mapNameToType(string $typeName)
, but even then PHPStan would need this little push as it's unable to infer class-string<UnitEnum>
from enum_exists()
.
src/Mappers/Root/EnumTypeMapper.php
Outdated
} | ||
|
||
/** @var array<string, class-string<UnitEnum>> */ | ||
private $nameToClassMapping; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's put all properties at the top of the class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Absolutely, my bad (copied from src/Mappers/Root/MyCLabsEnumTypeMapper.php)
@dsavina thanks again for the work on this. I added a few comments on the code, but overall I think this is good. I agree that it'd be ideal to use a Did you follow the implementation for the |
I'm not sure I understand the last question, but my guess is that using I suspect the |
So, regarding my last comment, it's mostly around future deprecation and API planning. If we support |
As I said earlier:
To give you more context, my first implementation using - GraphQL\Error\InvariantViolation : Schema must contain unique named types but contains multiple types named "Color" (see http://webonyx.github.io/graphql-php/type-system/#type-registry). Debugging showed that, indeed, in |
@dsavina I don't think @moufmouf monitors this lib anymore. I haven't seen any responses from him in quite some time. Regarding that error, that's a fairly common one. My guess is that a type is being created according to the implementation you had, but also it's assumed, as you state, that it's a standard type and creating one for that as well. It shouldn't be too difficult to add a condition, check if it's an enum, and push the mapping accordingly. Do you still have the implementation for this? If you can commit that, I'd be happy to have a look - see if I can get it working. |
1aa7ffd
to
dfec80d
Compare
@dsavina I'm not sure what changes you've made on this branch to support the |
β¦feature/native-enum-support
@dsavina I've merged and pushed everything onto this PR. As you can see, there is a failing test for an enum. It looks like it's not using the values of the enum properly. If you could please resolve this issue and review everything else, we can go ahead and get this merged in ASAP - target a new release. Questions:
|
Codecov Report
@@ Coverage Diff @@
## master #409 +/- ##
============================================
- Coverage 98.15% 96.25% -1.90%
- Complexity 1590 1625 +35
============================================
Files 144 146 +2
Lines 4002 4085 +83
============================================
+ Hits 3928 3932 +4
- Misses 74 153 +79
Continue to review full report at Codecov.
|
@dsavina there was a bug with the
|
I've updated the documentation a bit and it's now merged into master. Thanks for kicking things off with this @dsavina and everyone else for your contributions. |
Hi @oojacoboo, sorry for not being available during the past week. This is great news, thank you for the last corrections! |
For the record, I've been able to test this on a rather large API implementation and everything looks good. It's running native Enum and MyCLabs\Enum side-by-side at the moment. |
Hey! Thanks for the great changes! Are there any plans to make a new release with these changes? @oojacoboo @dsavina |
Please see here: #469 (comment) |
This aims to provide support for native enums, introduced with PHP 8.1.
Backed enums using
string
values will base their names on their case values. Non-backed enums, andint
-backed enums, will instead use their case names.I'm actually wondering about this last rule: should perhaps the
int
-backed enums be compatible with typeInt
instead ofString
? But then, introspection won't be able to hint allowed values, will it?Any suggestions?