Open Vincz opened 4 years ago
Hi @Vincz,
I am already working for couple of months on a feature called Hydrator. It will basically allow you to assotiate a model class to an input-object
type and when a request comes, the model will be populated with data. It is similar to what you created (called ArgumentsTransformer
if i am correct), but it should be more comprehensive and flexible. One of the Hydrator's features will be ability to apply converters to any field and this is, I believe, what you need right now. I'll show you an example:
Mutation:
type: object
config:
fields:
createHero:
resolve: "@=mut('create_hero', [models])"
args:
input: HeroInput!
HeroInput:
type: input-object
model: App\Model\HeroInput
config:
fields:
name:
type: "String!"
partnerId:
type: "HeroId"
Model:
class Hero
{
public string $name;
/*
* @Hydrator\Field("parentId") - this only required because fields have different names
* @Converter\Entity
*/
public Hero $parent;
}
Resolver:
public function resolve(Models $models)
{
$heroInput = $models->get('input')
// or
$heroInput = $model->input;
}
So this eventually doesn't change args, but introduces models
variable (args are left untouched), which is a container for all models (there can be many, flat or nested). Now this is a simple example, but it will also allow you to define your own custom converters or configure hydration hooks for your models (for example, when you perform an update action, you usually want to retreive the data from DB first and then replace it with request data). It will also catch convertion errors and map them as validation violations.
There will be some integrated converters, like Entity
, Service
, Instance
(e.g. to convert strings to DateTime objects) etc. and if it's not enough, you can define your services and use them as custom converters.
Hydrator will also be highly integrated with the Validator feature.
So eventually what you have to do is just inject models
variable and it will detect all input-object
types with model and will hydrate them. If you use 1 model for multiple input-object
you will be able to configure some hydration strategies thourgh annotations or with special static methods inside your models.
Later, if it's conviniet to use, we can make this Hydrator to work with your annotations.
Is this what would help you in your situation?
@murtukov Yes, this would be the kind of thing that could help, but as you said it's kind of the same as the Arguments transformer. At the moment, to be honest, the annotations and arguments transformer are quite awesome for me. I'm working on a quite big project, with a lot of entities and stuff and I really enjoy the auto-guessing stuff that comes with annotations. 95% of the time, my GraphQL Type are Doctrine entities. 95% of the time my queries come from my Doctrine repositories. For my project, I created a Query Crud Fields builder applied on my root query to generate all my crud query (find & findAll basically). I also created a Mutation Crud Fields builder to populate my root mutation so it can create one mutation by configured entity of the form:
$properties[$nameUpdate] = [
'args' => [
'id' => ['type' => \sprintf('%s%s', $idType, $isCreatable ? '' : '!')],
'input' => ['type' => \sprintf('%s!', $inputType)],
],
'description' => \sprintf('Update or create an object of type %s', $type),
'type' => $type,
'resolve' => \sprintf('@=call(service("doctrine").getRepository(service("%s").getEntity("%s")).%s, arguments({id: "%s", input: "%s"}, args))', $resolver, $type, 'update', $idType, $inputType),
];
And then, I have a generic update resolver that looks like that:
public function update($id, $input)
{
$propertyAccessor = PropertyAccess::createPropertyAccessor();
$object = new $this->className();
if ($id) {
$object = $this->find($id);
} else {
$this->persist($object);
}
$properties = \get_object_vars($input);
foreach ($properties as $property => $propertyValue) {
$value = $propertyAccessor->getValue($input, $property);
$propertyAccessor->setValue($object, $property, $value);
}
$this->flush();
return $object;
}
So, really quick, for each entity, I have:
In 95% of the case, I don't need to customise the default behaviour. The auto-guessing features saves me a lot of time.
If I implement directly the Id scalar generation and handle it in the arguments transformer and the annotation reader, we could have even more auto-guessing stuff and the day PHP will implement the type hint "array of object of class" (something like public function getAll():[Hero]
), we will be able to completely turn a PHP method or function into a GraphQL query or mutation.
And regarding the hydrator, it's already working with the Scalar entity Id. If I have a input with a scalar HeroId
(ie. Entity Scalar Id) for example, I'm already getting an instance of Hero
in my resolver, so I don't have to retrieve it myself.
At the end, the key here is the ability to link GraphQL with PHP classes. I think we are trying to solve the same problems, but that we are not going in the same direction, because you go from GraphQL (describe in Yaml) to PHP (like in your example by adding a model
key to your Input Type to link them together) while I'm going from PHP to GraphQL (kind of like Doctrine, by having PHP classes describing GraphQL stuff). I'm lazy and I like to have auto-guessed stuff (like the Autowiring of the DI) and only customise the few entities that need it.
With my approach, the problem is that I end up with features only available by using annotations, and I do agree that it is a problem but I also think there is so much things we can do with them, simply because we have a map between PHP classes and their respective Type.
Maybe we could organise some sort of meeting and discuss a bit about the current state of the project and where we would like to go.
Anyway, beside this discussion, I also wanted to thank you for always taking the time to answer constructively to issues and for all the time and efforts you put in the project this days ❤️
ping @mcg-web
I'm currently working on a pretty big corporate application. So I have many entities with cruds, forms, etc... So a lot of code is "automatic" and it's great. I know feel the same need expressed here : https://github.com/overblog/GraphQLBundle/issues/683
The use case is quite simple. When I create a form for an entity (and I have many), I describe my Input with foreign key id as Int and I have to convert them back to entities in my resolver. For example:
And in my resolver, I have to do something like :
So, I have to be specific for each different entity (one resolver by entity), preventing me from having a single generic resolve function.
If we could automatically generate scalars to represent each entity, we could save a lot of time. The Input would become:
and the resolver :
and the generated scalar would simply be a id to entity converter like https://github.com/overblog/GraphQLBundle/issues/683#issuecomment-650576014 for example.
The option could be opt in by simply adding a "generateScalar" boolean option on the
@GQL\Type
annotation. I'm not sure how we could implement this out of annotation.Let me know what you think.