什么是GraphQL?
GraphQL.Restful风格的API设计在面对前端日渐复杂的交互需求中显得过于死板,缺乏足够的灵活性。所以Facebook为了满足自身业务需求的变化, 舍弃Restful风格的API设计,而自己研发的一套查询协议和实现,就是GraphQL.
它的设计理念
个人认为GraphQL是数据库查询语言sql的设计思想在API开发领域的延展.就像定义一个sql查询一样,我们在客户端定义一组查询语言即可获取到自己 想要的一定结构的数据。
GraphQL并不是一个面向图数据库的查询语言,而是一个数据抽象层,包括数据格式、数据关联、查询方式定义与实现等等一揽子的东西。 GraphQL也并不是一个具体的后端编程框架,如果将REST看做适合于简单逻辑的查询标准,那么GraphQL可以做一个独立的抽象层, 通过对于多个REST风格的简单的接口的排列组合提供更多复杂多变的查询方式。与REST相比,GraphQL定义了更严格、可扩展、可维护的数据查询方式。
基于laravel的服务端实现
这个工程已经完整的实现了GraphQL.所以我们在这里就直接使用 它来。
首先我们在一个laravel的工程里面安装这个包
composer require “folklore/graphql”
然后我们将它的ServiceProvider加入到config/app.php的配置里面
1- Add the service provider to your app/config/app.php file
Folklore\GraphQL\ServiceProvider::class,
2- Add the facade to your app/config/app.php file
'GraphQL' => Folklore\GraphQL\Support\Facades\GraphQL::class,
3- Publish the configuration file
$ php artisan vendor:publish --provider="Folklore\GraphQL\ServiceProvider"
上面的Publish命令生成了一个配置文件config/graphql.php,我们看看
<?php
return [
/*
* The prefix for routes
*/
'prefix' => 'graphql',
/*
* The routes to make GraphQL request. Either a string that will apply
* to both query and mutation or an array containing the key 'query' and/or
* 'mutation' with the according Route
*
* Example:
*
* Same route for both query and mutation
*
* 'routes' => [
* 'query' => 'query/{graphql_schema?}',
* 'mutation' => 'mutation/{graphql_schema?}',
* mutation' => 'graphiql'
* ]
*
* you can also disable routes by setting routes to null
*
* 'routes' => null,
*/
'routes' => '{graphql_schema?}',
/*
* The controller to use in GraphQL requests. Either a string that will apply
* to both query and mutation or an array containing the key 'query' and/or
* 'mutation' with the according Controller and method
*
* Example:
*
* 'controllers' => [
* 'query' => '\Folklore\GraphQL\GraphQLController@query',
* 'mutation' => '\Folklore\GraphQL\GraphQLController@mutation'
* ]
*/
'controllers' => \Folklore\GraphQL\GraphQLController::class.'@query',
/*
* The name of the input variable that contain variables when you query the
* endpoint. Most libraries use "variables", you can change it here in case you need it.
* In previous versions, the default used to be "params"
*/
'variables_input_name' => 'variables',
/*
* Any middleware for the 'graphql' route group
*/
'middleware' => [],
/*
* Any headers that will be added to the response returned by the default controller
*/
'headers' => [],
/*
* Any JSON encoding options when returning a response from the default controller
* See http://php.net/manual/function.json-encode.php for the full list of options
*/
'json_encoding_options' => 0,
/*
* Config for GraphiQL (see (https://github.com/graphql/graphiql).
* To dissable GraphiQL, set this to null
*/
'graphiql' => [
'routes' => '/graphiql/{graphql_schema?}',
'controller' => \Folklore\GraphQL\GraphQLController::class.'@graphiql',
'middleware' => [],
'view' => 'graphql::graphiql'
],
/*
* The name of the default schema used when no arguments are provided
* to GraphQL::schema() or when the route is used without the graphql_schema
* parameter
*/
'schema' => 'default',
/*
* The schemas for query and/or mutation. It expects an array to provide
* both the 'query' fields and the 'mutation' fields. You can also
* provide an GraphQL\Schema object directly.
*
* Example:
*
* 'schemas' => [
* 'default' => new Schema($config)
* ]
*
* or
*
* 'schemas' => [
* 'default' => [
* 'query' => [
* 'users' => 'App\GraphQL\Query\UsersQuery'
* ],
* 'mutation' => [
*
* ]
* ]
* ]
*/
'schemas' => [
'default' => [
'query' => [
'users' => 'App\GraphQL\Query\UsersQuery'
],
'mutation' => [
'updateUserPassword' => 'App\GraphQL\Mutation\UpdateUserPasswordMutation'
]
]
],
/*
* The types available in the application. You can access them from the
* facade like this: GraphQL::type('user')
*
* Example:
*
* 'types' => [
* 'user' => 'App\GraphQL\Type\UserType'
* ]
*
* or without specifying a key (it will use the ->name property of your type)
*
* 'types' =>
* 'App\GraphQL\Type\UserType'
* ]
*/
'types' => [
'User' => 'App\GraphQL\Type\UserType'
],
/*
* This callable will receive all the Exception objects that are caught by GraphQL.
* The method should return an array representing the error.
*
* Typically:
*
* [
* 'message' => '',
* 'locations' => []
* ]
*/
'error_formatter' => [\Folklore\GraphQL\GraphQL::class, 'formatError'],
/*
* Options to limit the query complexity and depth. See the doc
* @ https://github.com/webonyx/graphql-php#security
* for details. Disabled by default.
*/
'security' => [
'query_max_complexity' => null,
'query_max_depth' => null,
'disable_introspection' => false
]
];
当我们创建了Type,Query, Mutation后,就需要将他们写入到上面的配置文件中。
创建Type
namespace App\GraphQL\Type;
use GraphQL\Type\Definition\Type;
use Folklore\GraphQL\Support\Type as GraphQLType;
class UserType extends GraphQLType {
protected $attributes = [
'name' => 'User',
'description' => 'A user'
];
/*
* Uncomment following line to make the type input object.
* http://graphql.org/learn/schema/#input-types
*/
// protected $inputObject = true;
public function fields()
{
return [
'id' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The id of the user'
],
'email' => [
'type' => Type::string(),
'description' => 'The email of user'
]
];
}
// If you want to resolve the field yourself, you can declare a method
// with the following format resolve[FIELD_NAME]Field()
protected function resolveEmailField($root, $args)
{
return strtolower($root->email);
}
}
将创建的Type写入config/graphql.php配置文件
'types' => [
'User' => 'App\GraphQL\Type\UserType'
]
创建Query
namespace App\GraphQL\Query;
use GraphQL;
use GraphQL\Type\Definition\Type;
use Folklore\GraphQL\Support\Query;
use App\User;
class UsersQuery extends Query {
protected $attributes = [
'name' => 'users'
];
public function type()
{
return Type::listOf(GraphQL::type('User'));
}
public function args()
{
return [
'id' => ['name' => 'id', 'type' => Type::string()],
'email' => ['name' => 'email', 'type' => Type::string()]
];
}
public function resolve($root, $args)
{
if(isset($args['id']))
{
return User::where('id' , $args['id'])->get();
}
else if(isset($args['email']))
{
return User::where('email', $args['email'])->get();
}
else
{
return User::all();
}
}
}
将这个Query也放入config/graphql.php配置文件中
'schemas' => [
'default' => [
'query' => [
'users' => 'App\GraphQL\Query\UsersQuery'
],
// ...
]
]
这样我们可以开启服务器检查这个查询是否正常运行:
php artisan serve
用浏览器访问: http://localhost:8000/graphql?query=query+FetchUsers{users{id,email}}
输出返回:
{"data":{"users":[{"id":"1","email":"756127792@qq.com"},{"id":"2","email":"zoobile@gmail.com"},{"id":"3","email":"756127793@qq.com"}]}}
创建Mutation
Mutation是一种带有参数的查询,这些参数用来做数据的更新,然后会返回一个特定类型的对象。
例如我们要更新用户的密码,那么我们需要创建一个UpdateUserPasswordMutation:
namespace App\GraphQL\Mutation;
use GraphQL;
use GraphQL\Type\Definition\Type;
use Folklore\GraphQL\Support\Mutation;
use App\User;
class UpdateUserPasswordMutation extends Mutation {
protected $attributes = [
'name' => 'updateUserPassword'
];
public function type()
{
return GraphQL::type('User');
}
public function args()
{
return [
'id' => ['name' => 'id', 'type' => Type::nonNull(Type::string())],
'password' => ['name' => 'password', 'type' => Type::nonNull(Type::string())]
];
}
public function resolve($root, $args)
{
$user = User::find($args['id']);
if(!$user)
{
return null;
}
$user->password = bcrypt($args['password']);
$user->save();
return $user;
}
}
可以看到resolve方法用来更新密码并返回更新后的用户实例。
我们需要将Mutation加入到配置文件中:
'schema' => [
'default' => [
'mutation' => [
'updateUserPassword' => 'App\GraphQL\Mutation\UpdateUserPasswordMutation'
],
// ...
]
]
OK,我们可以执行密码更新的操作了:
http://localhost:8000/graphql?query=mutation+users{updateUserPassword(id: “1”, password: “newpassword”){id,email}}
返回:
{"data":{"updateUserPassword":{"id":"1","email":"756127792@qq.com"}}}