Skip to content

Type-safe solution for "undefined" input values #726

@Eliasyoussef47

Description

@Eliasyoussef47

I'm looking for a type-safe way to represent "undefined" (similar to TypeScript) values in input types. I define "undefined" values as the lack of provided value (by the client) for a specific class field (PHP calls these properties) in an input type (class).

I've read some issues on this matter already, such as #210. But none of the proposed solutions is good enough.

Desired behavior

I want GraphQLite to resolve/map/set the value of an input class field (or constructor parameter) to a custom type made by me when no value was provided for that field by the client.

Type-safe

With "type-safe" I mean a solution that the type system of PHP communicates to and guarantees the developer of:

  1. The type of value that is saved in a class field. That is, the PHP type annotation communicates that a field might not have been provided.
  2. The value that will be returned when the getter is called. That is, no other type than the declared types is returned.
  3. That no obscure or unintended behavior will happen. That is, no TypeError should be thrown because a developer simply called a getter.

No hackiness

I don't want to use isset() or other hacky things to first check if a field is initialized.

Nullability

The type of the field in the input class can still be nullable and undefined-able (not a word but you understand what I mean).
In TypeScript, this would be expressed as string|null|undefined.
For completeness' sake, it would be nice to have null|undefined as a possibility (even though it sounds weird). EDIT: Never mind. This library can't map a field with only null as the type, so this shouldn't be possible either.

"Undefined" type

After some research, I have determined that it would be best to use a PHP enum as a custom type to represent undefined values (I can elaborate on this conclusion if needed).

enum Undefined
{
    case UNDEFINED;
}

Example of the desired outcome

An example of an input type:

#[Input]
readonly class MayHaveUndefinedValueObject
{
    #[Field]
    private string $name;

    #[Field]
    private string|null|Undefined $description;

    #[Field]
    private bool|Undefined $important;

    /**
     * @param string $name
     * @param string|null|Undefined $description
     * @param bool|Undefined $important
     */
    public function __construct(
        string $name,
        string|null|Undefined $description,
        bool|Undefined $important
    ) {
        $this->name = $name;
        $this->description = $description;
        $this->important = $important;
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function getDescription(): string|null|Undefined
    {
        return $this->description;
    }

    public function getImportant(): bool|Undefined
    {
        return $this->important;
    }
}

As you see, it is also desired that this works with private fields that only have getters and no setters. It would also be nice to have the ability to use readonly classes.

Notice how the return type of the getter clearly communicates that the field's value might not have been set by the client. In my project, currently, calling the getter might throw TypeError which the developer might not expect with no clear knowledge of how this class is being used in different places.

I would like a way for GraphQLite to automatically set the value of constructor parameters, that accept Undefined, to Undefined::UNDEFINED. From what I know, currently, GraphQLite will set the value to null if the parameter accepts it and no value is provided.

Already looked into

  1. Root type mappers.
    a. I tried to implement this but couldn't figure out how to map from something that doesn't exist (because no value is provided). This was a couple of months ago and I don't have the code anymore.
  2. (Class) Type mappers.
    a. Read the documentation but I don't think this can work.
  3. Parameter middleware.
    a. Haven't tried it and I'm not sure that this could work.

🥅 What I wish to achieve with this issue

I would like to get answers to these questions:

  1. Is it currently possible to make GraphQLite set the value of a parameter or field (that has no provided value) to a custom type? And how?
  2. If question 1 is not possible, could you at least provide me with hints as to which parts of the GraphQLite code base need to be changed to make this possible? And will the maintainers be open for a pull request for this feature?
  3. Are there any workarounds or existing solutions that you might recommend for my goals?

Extra requirements

I don't want to use #[Input(name: 'MayHaveUndefinedValueObjectUpdateInput', update: true)] because:

  1. It is not type-safe.
  2. The getters don't communicate that the field might be undefined.
  3. The getters might unexpectedly throw a TypeError.
  4. I don't want to use hacky things like isset().

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions