Description
@Vincz I went through the documentation and gathered some notes. Here are couple of improvements that could be done:
1. Add support for type auto-guessing from property type-hints
e.g. this:
class Hero {
/**
* @GQL\Field
*/
public string $name;
/**
* @GQL\Field
*/
public ?string $secret;
}
should be equal to this:
class Hero {
/**
* @GQL\Field(type="String!")
*/
public string $name;
/**
* @GQL\Field(type="String")
*/
public ?string $secret;
}
2. Omit the attribute's key
Treat the first argument as "type" by default
e.g. this:
/**
* @GQL\Field("Float!")
*/
public $latitude;
should be equal to this:
/**
* @GQL\Field(type="Float!")
*/
public $latitude;
The name
attribute could be default instead of type
as well, we just need to think, what makes more sense.
The same should be applied for @arg.
The Symfony's @Route
uses such syntax, See: https://symfony.com/doc/current/routing.html
3. Detect the @param
and @return
annotations for auto-guessing
As you stated in the documentation, some types can be auto-guessed from the return type of the method, but if it's array
, then you need to declare the type explicitly:
/**
* @GQL\Provider
*/
class UsersProviders {
/**
* @GQL\Query(type="[User]", name="users")
*/
public function getUsers(): array {
return $this->repository->findAll();
}
}
We could omit explicit type declaration, e.g.:
/**
* @GQL\Provider
*/
class UsersProviders {
/**
* @GQL\Query(name="users")
*
* @return User[]
*/
public function getUsers(): array {
return $this->repository->findAll();
}
}
The following syntaxes should be valid:
Tag | GraphQL type |
---|---|
User[] |
[Users!]! |
User[]|null |
[Users!] |
array<User> |
[Users!]! |
array<User|null> |
[Users]! |
null|array<User|null> |
[Users] |
null|array<User> |
[Users!] |
* iterable<User> |
[Users] |
iterable
should have all the same forms as array
.
All these syntaxes are perfectly valid and supported by many IDEs. Moreover array<Type>
-syntax is sometimes required for static analyser. But most of the times only Type[]
syntax will be used.
The idea is to use that, what is already invented and well known by users. That would help them avoid learning new things.
See: https://phpstan.org/writing-php-code/phpdoc-types#general-arrays
4. Omit the get
prefix when generating the config
When you create a GraphQL schema it's considered a good practice to name your "endpoints" without the get prefix (e.g. books, authors, friends). We can encourage users to follow this practice in which we automatically omit the get prefix from methods.
Consider this example from the docs:
/**
* @GQL\Provider
*/
class UsersProviders {
/**
* @GQL\Query(type="[User]", name="users")
*/
public function getUsers() {
return $this->repository->findAll();
}
}
You defined name="users"
. I would suggest to do it by default, and if someone still wants to add get prefix he can use name="getUsers"
5. Make configuration as flat as possible.
If you implement all the points above, the configuration will become less verbose and config will be understood from both IDE and GraphQLBudnel. The annotations will also become more "flat".
Instead of this:
/**
* @GQL\Type
*/
class Hero
{
/**
* @GQL\Field(
* name="friends",
* type="[Hero]",
* args={@GQL\Arg(name="limit", type="Int")}
* )
*/
public function getFriends(int $limit) {
return array_slice($this->friends, 0, $limit);
}
}
We could have this:
/**
* @GQL\Type
*/
class Hero
{
/**
* @GQL\Field
*
* @param int $limit
*
* @return Hero[]
*/
public function getFriends(int $limit) {
return array_slice($this->friends, 0, $limit);
}
}
Another examples of "flatness"
Before:
/**
* @GQL\Enum(values={
* @GQL\EnumValue(name="TATOUINE", description="The planet of Tatouine"),
* @GQL\EnumValue(name="BESPIN", deprecationReason="Not used anymore. The planet has been destroyed !")
* })
* @GQL\Description("The list of planets!")
*/
class Planet
{
const TATOUINE = "1";
const BESPIN = "2";
const DAGOBAH = 3;
const HOTH = "4";
public $value;
}
After:
/**
* @GQL\Enum
* @GQL\Description("The list of planets!")
*/
class Planet
{
/**
* @GQL\EnumValue(description="The planet of Tatouine")
*/
const TATOUINE = 1;
/**
* @GQL\EnumValue(description="The planet of Tatouine")
*/
const BESPIN = 2;
const HOTH = 3;
const DAGOBAH = 4;
public $value;
}
Another example
before:
/**
* @GQL\Type
*/
class Hero
{
/**
* @GQL\Field(fieldBuilder={"GenericIdBuilder", {"name": "heroId"}})
*/
public $id;
/**
* @GQL\Field(type="[Hero]",
* args={
* @GQL\Arg(name="droidsOnly", type="Boolean", description="Retrieve only droids heroes"),
* @GQL\Arg(name="nameStartsWith", type="String", description="Retrieve only heroes with name starting with")
* },
* resolve="resolver('hero_friends', [args['droidsOnly'], args['nameStartsWith']])"
* )
*/
public $friends;
}
after:
/**
* @GQL\Type
*/
class Hero
{
/**
* @GQL\Field
* @GQL\FieldBuilder("GenericIdBuilder", {"name": "heroId"})
*/
public $id;
/**
* @GQL\Field
*
* @GQL\Arg(name="droidsOnly", type="Boolean", description="Retrieve only droids heroes"),
* @GQL\Arg(name="nameStartsWith", type="String", description="Retrieve only heroes with name starting with")
*
* @GQL\Resolve("resolver('hero_friends', [args['droidsOnly'], args['nameStartsWith']])")
*
* @var Hero[]
*/
public $friends;
}
That's it for now. I know some examples may look stupid or make no sence, because I never used annotations, but lets start the discussion from here.