#Fix: Php enum support by charjr · Pull R...
1 messages · Page 1 of 1 (latest)
I'll take a look at this soon.
Code seems ok, but if you send a trace it would be more helpful.
Do you have Dagger cloud set up, @cinder ravine?
Looking at the PR there's a few things that's important to know. Enum values need to be "names". Basically think about what's allowed as a programming variable name, not the name of an enum member. That means they should only be strings, that begin with a letter, but can have numbers and underscores, and by convention should be in SCREAMING_SNAKE_CASE. The SDK is allowed to create enum names based on the conversion of that value to the language's naming convention. So you're expected to support this:
enum Dummy: string {
case Hello = 'HELLO';
case World = 'WORLD';
But not this (comma, space, exclamation point):
enum Dummy: string {
case Hello = 'hello, ';
case World = 'world!';
The first one will produce the following GraphQL schema:
enum Dummy type {
HELLO
WORLD
}
As you can see, GraphQL enums don't have names+values, they're considered values that are valid names (as in identifier or variable names).
So the second example would produce a syntax error:
enum Dummy type {
hello,
world!
}
Same goes for other types that aren't strings, like integers (can't begin with a number). This will probably be improved in https://github.com/dagger/dagger/issues/8671.
As for scalars, here's the thing. If you have a SCALAR_KIND, that means it's a custom scalar, and it's always a string. Modules don't support custom scalars yet, they're only defined in the core API. dagger.JSON and dagger.Void are examples of that. This may be confusing since there are other kinds of scalars, although for a given type you only have one of these:
BOOLEAN_KIND: built-in scalar of type booleanINTEGER_KIND: built-in scalar of type integerSTRING_KIND: built-in scalar of type stringENUM_KIND: an enum type with a name and an enumerations of string based scalars (their values)SCALAR_KIND: a custom scalar with a name; all custom scalars are string based, but they're implemented via two functions to serialize and deserialize off of the string valueVOID_KIND: a Dagger specific custom scalar that's always an empty string
In the general sense, scalars are leaf values in a GraphQL type system.
... you're expected to support this:
enum Dummy: string { case Hello = 'HELLO'; case World = 'WORLD';But not this (comma, space, exclamation point):
enum Dummy: string { case Hello = 'hello, '; case World = 'world!';
When serialising an Enum I've accounted for this by serialising the name instead of the value.
So for both examples it serialise the names: Hello and World.
In the second example I purposefully made the values hello, and world! to assert that the values do not get serialised.
Similarly if a user must give an enum as an argument, it expects the name. So you would be expected to send in dagger call -m mod func --enum-arg=Hello
I figured this made for the best user experience for a PHP developer as:
- It doesn't impose any additional rules they aren't already used to (In PHP, enum names have to follow similar conventions; they MUST start with a letter, but may contain numbers and underscores)
- It provides a consistent interface for all types of PHP Enums:
enum Dummy
{
case North;
case East;
case South;
case West;
}
enum Dummy: string
{
case North = 'north?';
case East = 'EaSt';
case South = 'SOUTH';
case West = 'West!!!!';
}
enum Dummy: int
{
case North = 0;
case East = 1;
case South = 2;
case West = 3;
}
All 3 of the above are valid PHP Enums. But by basing it off the name they will ALL serialise to North, East, South or West
they should only be strings, that begin with a letter, but can have numbers and underscores, and by convention should be in SCREAMING_SNAKE_CASE.
Do I need to convert enum names toSCREAMING_SNAKE_CASEfor them to be recognised?
Do you have Dagger cloud set up, @cinder ravine?
I have it set up so I can see my local traces. I'm not sure how (or if I have permission) to set up the cloud for seeing the CI pipeline on PRs
No, that's a GraphQL convention, but not enforced by Dagger.
If you have a failing example currently running in CI in the PR, then I can get that trace.
The problem with that approach is that the values are ignored and not used at all in other modules. They're only accessible from the module that defines the enum.
My naive assumption here is that it doesn't seem to be registering the values at all
graphql doesn't have a concept of backed enums though
Not sure what you mean there.
so php has two types of enum, unit enums and backed enums; the second type allows for a named case to have a scalar representation
but both types of enums always have names, and names are always a string
so we can rely on that
graphql as far as i see only has an enumeration which defines a set of possible values
the name of the case is the most logical thing to use here as otherwise you'd have to not support one of the two enum types
Look, if you do this:
enum Dummy: string
{
case North = 'north?';
case East = 'EaSt';
case South = 'SOUTH';
case West = 'West!!!!';
}
This will produce the following in a Python module that uses the PHP module as a dependency:
class Dummy(enum.Enum):
NORTH = "NORTH"
EAST = "EAST"
SOUTH = "SOUTH"
WEST = "WEST"
So you have to explain that enum values in PHP only exist on the module that defines it. Other modules won't see it. The most appropriate thing for us, since GraphQL only has values in enums, to use the values in the languages, and have the SDK generate names that match the values but have their case converted to the proper naming convention, on the client code. This follows that module authors should define enums with names that match their (string based) values, and SDKs should either enforce, document or warn about this.
Having said that, @placid fractal has recently added support for custom GraphQL directives in our internal code for the GraphQL server so it's now possible to produce this schema:
enum Dummy {
HELLO @value("hello, ")
WORLD @value("world!")
}
As I pointed out in #1290721727071916076 message, the improvement for this is in https://github.com/dagger/dagger/issues/8671, where we then allow SDKs to register both a name and a value, and update codegen in SDKs to produce the right names and values for enums in their languages.
What do you want to do? Have PHP work differently than the other SDKs and explain the difference to PHP users until this change becomes available? Bring the PHP SDK inline with the others both now and then? Or wait for that to release enums in PHP?
fyi custom directives aren't yet merged
I think we should wait till that PR is merged so that we can support all enums without breaking backwards compatibility
(should be good, just need reviews)
However we still can't seem to register enum values at all
I just tried hardcoding in a case of HI on any enum it finds at all.
$enumTypeDef = dag()
->typeDef()
->withEnum(
$daggerObject->getNormalisedName(),
$daggerObject->getDescription(),
)->withEnumValue('HI', 'hello');
Even with this, the trace does not show any values being registered to the enum: https://dagger.cloud/charjr/traces/c1413fce3b5065d3047509c82164c4d7
Thank you, I'll probably rebase it off your branch so we can make sure it works 🙂
we don't actually have an in-flight fix for the work in this issue fyi https://github.com/dagger/dagger/issues/8671
Would that cause an issue with registering enum values though? I literally seem unable to registering any value to the enum at all, even hardcoding HI
The trace shows the call to WithEnum, but it does not show any WithEnumValue.
That's certainly an issue in PHP's code, we can take a look at it more closely.
As in, an issue in the generated code? I've not looked into that part of its functionality much yet... I'm not sure where I would start.
If I can help though I would love to, but I would need to be pointed in the right direction (and probably ask a lot of questions along the way 🙏 )
Sorry, went for lunch: This is the example I've been trying to run
It registers the enum; If I call it without passing the argument, it works.
If I try passing the argument however, I get an error because it doesn't recognise the value and actually has no values at all
bear in mind it's based off of my fix-php-enums branch
Helder I apologise I've found the issue now. It was a careless mistake in the branch I was using.
I'm sorry if I wasted your time.
I had two similarly named branches and hadn't noticed I was testing the wrong one.
It does work now
I will rebase it and get it ready. But we will hold off until Jed's PR is merged so we can use the current implementation.
Ok, but to be clear, the PR that's ready for review is just for adding support for custom GraphQL directives in the schema. It'll take more time to actually make the changes to enums we talked about.
I think that should be fine.
If you don't mind me asking, could you explain what issue this causes:
This will produce the following in a Python module that uses the PHP module as a dependency:
class Dummy(enum.Enum):
NORTH = "NORTH"
EAST = "EAST"
SOUTH = "SOUTH"
WEST = "WEST"
I understand they wont have access to the value of the enum, but if all they're doing is passing the value back to the PHP SDK, what issue would it cause? As long as the PHP module knows how to get the original enum from it?
I only ask in case I am missing something, so I can better understand the interaction between modules
That's only an issue if the value is actually needed outside the module that defines it. Depends on the use case, and I'd actually also like to know more about those use cases. It can also be confusing to a user of the dependency, to see different code in the defining module than on the client one (this has happened with Go users).
For example, if a module foo defines this enum:
enum Bar: string
{
case North = 'north?';
case East = 'EaSt';
case South = 'SOUTH';
case West = 'West!!!!';
}
Another PHP module using it as a dependency will generate this client binding:
enum FooBar: string
{
case North = 'North';
case East = 'East';
case South = 'South';
case West = 'West';
}