webonyx / graphql-php

PHP implementation of the GraphQL specification based on the reference implementation in JavaScript
https://webonyx.github.io/graphql-php
MIT License
4.64k stars 566 forks source link

Nested types - how access to parent values #487

Closed krajcikondra closed 5 years ago

krajcikondra commented 5 years ago

Hi,

I have query

rent {
   car {
      id, vin
     equipments {
       name
}
}

}

RentType.php


class RentType extends ObjectType {

        public function getConfig(): array {
            return [
                'name' => 'Rent',
                'fields' => function() {
                    $fields['car'] = [
                        'type' => $this->carType,
                        'resolve' => function(array $rentData) {
                            $car = $this->carRepository->find($carData['id_car']);
                            return $car->toArray();
                        }
                    ];
                }
            ];
        }

CarType.php

class CarType extends ObjectType {

    public function getConfig(): array {
        return [
            'name' => 'Car',
            'fields' => function() {
                $fields['equipments'] = [
                    'type' => Type::listOf($this->carEquipmentType),
                    'resolve' => function(array $carData) {
                        $car = $this->carRepository->find($rentData['id_car']);
                        $equipmentValues = [];
                        foreach ($car->getEquipmentValueCollection() as $equipmentValue) {
                            $equipmentValues[] = $equipmentValue->toArray();
                        }
                        return $equipmentValues;
                    }
                ];
            }
        ];
    }

}

and CarEquipmentType.php

class CarEquipmentType extends ObjectType {

    public function getConfig(): array {
        return [
            'name' => 'CarEquipment',
            'fields' => function($rentData) {

                // here I need access to id_car value. $rentData are root values and I can get id_car from there
                    $carId = $rentData['id_car'];

                // but this way seems me stupid because but it trust that in root of query will be ever RentType. This is not universal way. It seems to me better access to directly to parent because it will be ever CarType and get id of car

                $parentValues = .....; // I dont know how
                $carValues = $parentValues;
                $carId = $carValues['id'];
                                $car = $this->carRepository->find($carId);
                                $car->getEquipments() .....
                                ......
                                return $equipmentValues;
            }
        ];
    }
}

It is some way how access to values of parent in resolve method?

vladar commented 5 years ago

Child is controlled by the direct parent.

So two good options:

Not so good option:

Why is it a bad option?

krajcikondra commented 5 years ago

Can

Child is controlled by the direct parent.

So two good options:

  • Make equipments part of the Rent, not Car (as for me, this is a proper relation in your case)
  • Or pass rent data along with a car data down in your resolvers. So when you resolve a car - pass rent data with it (since your children depend on it anyway)

Not so good option:

  • Use context (3rd argument of the resolver). Set current rent in context in the rent resolver and then read it from context in the equipment resolver.

Why is it a bad option?

  • It makes it much harder to reason about the code. By looking at equipment resolver - it is not clear where does it come from
  • It also makes your type context-dependant which makes it less testable.
  • Plus it may cause issues with recursive nesting

Can you show me code how pass data along with carData or rentData to resolver please? It sounds good but I dont know how pass data to resolver

vladar commented 5 years ago
class RentType extends ObjectType {

        public function getConfig(): array {
            return [
                'name' => 'Rent',
                'fields' => function() {
                    $fields['car'] = [
                        'type' => $this->carType,
                        'resolve' => function(array $rentData) {
                            $car = $this->carRepository->find($carData['id_car']);
                            return ['car' => $car->toArray(), 'rent' => $rentData];
                        }
                    ];
                }
            ];
        }
}

class CarType extends ObjectType {

    public function getConfig(): array {
        return [
            'name' => 'Car',
            'fields' => function() {
                $fields['equipments'] = [
                    'type' => Type::listOf($this->carEquipmentType),
                    'resolve' => function(array $data) {
                        $carData = $data['car'];
                        $rentData = $data['rent'];
                        // ...
                    }
                ];
            }
        ];
    }
}
Fivedark commented 3 years ago

Hi,

we're facing a similar problem in our project but using Symfony and Overblog GraphQL-Bundle which is based on the Webonyx graphl-php lib. We also use Overblogs dataloader-php to solve the n+1 problem.

Our resolvers mostly return directly our doctrine entities (objects), so the suggested solution to return an associative array to pass data down the nested resolvers is no solution for us. Also extending our entities with all possible parents feels dirty.

But I have another idea: The ResolveInfo already contains a path property which contains the names of all parents up to the root. What if the ResolveInfo also contains a parent array with all the parent objects?