Search code examples
phplaravelgraphqllaravel-lighthouse

Lighthouse can't find my custom Types for Union


how are you doing?

I'm struggling with unions I'd like some clarification in regards of custom resolvers for union type based on custom types. Mi current schema is this for product.graphql which is imported inside schema.graphql

type ProductServing {
    name: String
    price: Float
}

type ProductDish {
    price: Float
    calories: Int
    servings: [ProductServing]
}

type ProductDrink {
    price: Float
    servings: [ProductServing]
}

union ProductProperties = ProductDish | ProductDrink

type Product {
    id: ID!
    user: User! @belongsTo
    media: Media
    blocks: [Block]! @belongsToMany
    slug: String!
    name: String!
    description: String
    properties: ProductProperties!
    activated_at: DateTime
    created_at: DateTime!
    updated_at: DateTime!
} 

Of course this schema alone wont work because Lighthouse cannot understand the custom type. I've created two classes for Types:

// App\GraphQL\Types
class ProductDish extends ObjectType
{
    public function __construct()
    {
        $config = [
            "name" => "ProductDish",
            "fields" => [
                "__type" => Type:string(),
                "price" => Type::float(),
                "calories" => Type::int(),
            ],
        ];
        parent::__construct($config);
    }
}

class ProductDrink extends ObjectType
{
    public function __construct()
    {
        $config = [
            "name" => "ProductDrink",
            "fields" => [
                "__type" => Type:string(),
                "price" => Type::float(),
            ],
        ];
        parent::__construct($config);
    }
}

And a union class for ProductProperties which use the __invoke method

// App\GraphQL\Unions;
public function __invoke($rootValue, GraphQLContext $context, ResolveInfo $resolveInfo) : Type
{
    $type = $rootValue["__type"];
    switch ($type) {
        case "dish" :
            return $this->typeRegistry->get(ProductDish::class);
        case "drink":
            return $this->typeRegistry->get(ProductDrink::class);
    }
    return $this->typeRegistry->get(class_basename($rootValue));
}

This doesn't work, otherwise I wouldn't be here, looking at the graphql-playground I get this message

"debugMessage": "Lighthouse failed while trying to load a type: App\\GraphQL\\Types\\ProductDish\n\nMake sure the type is present in your schema definition.\n"

The thing is that I'm not sure if 1) this is the right approach 2) why lighthouse can't load the type.

Can you show me the correct way of handling this?

Bear in mind that

  • ProductDish and ProductDrink are not Eloquent models but simple "wrapper" for product json field properties on Product type, which is an eloquent model
  • Same for ProductServing which is a key:value pair
  • Everything works if I remove the union. I mean if Product type has properties assigned either to ProductDish type or ProductDrink everything works smoothly but I need the union

Solution

  • I was on the right track but I needed to fix some things

    First, inside ProductProperties file the registry expect a simple string not a namespace so I had to type ->get("ProductDish") instead of ->get(ProductDish::class) then I removed the __type field inside my types, and graphql schemas because I can use a mutator inside Product model and determine which type is based on some parameters, something like this

    public function getPropertisAttribute($properties) {
        
       $properties = json_decode($properties, true);
    
       $properties["__type"] = // .. some logic to get the right type
    
       return $properties;
    }
    

    Once I've a type I can use that back into my ProductProperties union class