SOLID
Gato GraphQL follows the SOLID approach for the software architecture, providing different entities to tackle different responsibilities, as to make the code maintainable, extensible and understandable.
This is how the user entity is already provided by the plugin. The User
type is provided through this code:
class UserTypeResolver extends AbstractTypeResolver
{
public function getTypeName(): string
{
return 'User';
}
public function getSchemaTypeDescription(): ?string
{
return $this->translationAPI->__('Representation of a user', "users");
}
public function getID(object $user)
{
return $this->usersAPI->getUserId($user);
}
public function getTypeDataLoaderClass(): string
{
return UserTypeDataLoader::class;
}
}
The type resolver does not directly load the objects from the database, but instead delegates this task to a TypeDataLoader
object (in the example above, from class UserTypeDataLoader
).
Adding fields username
, email
and url
to type User
is done via a FieldResolver
object with this code:
class UserFieldResolver extends AbstractDBDataFieldResolver
{
public static function getClassesToAttachTo(): array
{
return [
UserTypeResolver::class,
];
}
public static function getFieldNamesToResolve(): array
{
return [
'username',
'email',
'url',
];
}
public function getSchemaFieldDescription(
TypeResolverInterface $typeResolver,
string $fieldName
): ?string {
$descriptions = [
'username' => $this->translationAPI->__("User's username handle", "users"),
'email' => $this->translationAPI->__("User's email", "users"),
'url' => $this->translationAPI->__("URL of the user's profile in the website", "users"),
];
return $descriptions[$fieldName];
}
public function getSchemaFieldType(
TypeResolverInterface $typeResolver,
string $fieldName
): ?string {
$types = [
'username' => SchemaDefinition::TYPE_STRING,
'email' => SchemaDefinition::TYPE_EMAIL,
'url' => SchemaDefinition::TYPE_URL,
];
return $types[$fieldName];
}
public function resolveValue(
TypeResolverInterface $typeResolver,
object $user,
string $fieldName,
array $fieldArgs = []
) {
switch ($fieldName) {
case 'username':
return $this->usersAPI->getUserLogin($user);
case 'email':
return $this->usersAPI->getUserEmail($user);
case 'url':
return $this->usersAPI->getUserURL($user);
}
return null;
}
}
As it can be observed, the definition of a field for the GraphQL schema, and its resolution, has been split into a multitude of functions:
getSchemaFieldDescription
getSchemaFieldType
resolveValue
Other functions include:
getSchemaFieldArgs
: to declare the field arguments (including their name, description, type, and if they are mandatory or not)isSchemaFieldResponseNonNullable
: to indicate if a field is non-nullablegetImplementedInterfaceClasses
: to define the resolvers for interfaces implemented by the fieldsresolveFieldTypeResolverClass
: to define the type resolver when the field is a connectionresolveFieldMutationResolverClass
: to define the resolver when the field executes mutations
This code is more legible than if all functionality is satisfied through a single function, or through a configuration array, thus making it easier to implement and maintain the resolvers.