moxuandi / yii2-graphql
为YII2 PHP框架提供的Facebook GraphQL服务器端实现
Requires
- php: >=7.2.0
- ecodev/graphql-upload: ^6.0.0
- laminas/laminas-diactoros: ^2.4.0
- webonyx/graphql-php: ^14.3
- yiisoft/yii2: ~2.0.35
Requires (Dev)
- phpunit/phpunit: 4.8.*
README
使用Facebook GraphQL 的PHP服务端实现。扩展 graphql-php 以适用于YII2。
yii-graphql特点
- 配置简化,包括简化标准GraphQL协议的定义。
- 按需/懒加载,根据类型定义的全限定名,实现按需加载与懒,不需要在系统初始时将全部类型定义加载进入。
- 支持mutation输入验证
- 提供控制器集成与授权支持
安装
使用 composer 安装
composer require moxuandi/yii2-graphql
类型
类型系统是GraphQL的核心,体现在GraphQLType中,通过解构GraphQL协议,并利用graph-php库达到细粒度的对所有元素的控制,方便根据自身需要进行类扩展。
GraphQLType的主要元素,**注意元素并不对应到属性或方法中(下同)**
Query
GraphQLQuery,GraphQLMutation继承了GraphQLField,元素结构是一致的,想做对于一些复用性的Field,可以继承它。GraphQL的每次查询都需要对应到一个GraphQLQuery对象
GraphQLField的主要元素
Mutation
与GraphQLQuery非常相似,参考说明。
简化处理
简化了Field的声明,字段可直接使用type
// 标准方式 'id' => [ 'type'=>type::id(), ], // 简化写法 'id'=>type::id()
在YII中使用
本组件采用trait的方式在Component组件中被引入,组件宿主建议的方式是Module
class Module extends \yii\base\Module { use GraphQLModuleTrait; }
Yii配置文件
'components' => [ 'graphql'=>[ 'class'=>'xxx\xxxx\module', //主graphql协议配置 'schema' => [ 'query' => [ 'user' => 'app\graphql\query\UsersQuery' ], 'mutation' => [ 'login' ], //if you use sample query except query contain interface,fragment,not need set //the key must same as your class definded 'types'=>[ 'Story'=>'yiiunit\extensions\graphql\objects\types\StoryType' ], ] ], ];
采用的是actions的方法进行集成
class xxxController extends Controller{ function actions() { return [ 'index'=>[ 'class'=>'yii\graphql\GraphQLAction' ] ]; } }
在采用动态解析的情况下,如果不想定义types时,schema的写法有讲究。可采用Type::class,避免采用Key方式,也方便直接通过IDE导航到对应的类下
'type' => GraphQL::type(UserType::class);
输入验证
针对mutation的数据提交,提供了验证支持。除了GraphQL基于的验证外,还可以使用yii的验证,目前为针对输入参数验证。直接在mutation定义中增加rules方法,与Yii Model的使用方式是一致的。
public function rules() { return [ ['password','boolean'] ]; }
授权验证
由于GraphQL查询可以采用组合方式,如一次查询合并了两个query,而这两个query具有不同的授权约束,因此在graph中需要采用自定义的验证方式。我把这多次查询称为GraphQL actions;当所有的GraphQL actions条件都满足配置时,才通过授权检查。
授权
在controller的行为方法中设置采用的授权方法,例子如下,
function behaviors() { return [ 'authenticator'=>[ 'class'=>'yii\graphql\filter\auth\CompositeAuth', 'authMethods'=>[ \yii\filters\auth\QueryParamAuth::class, ], 'except'=>['hello'] ], ]; }
如果要支持IntrospectionQueryr的授权,相应的GraphQL action为"__schema"
Demo
创建基于GraphQL协议的查询
每次查询对应一个GraphQLQuery文件
use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Type\Definition\Type; use yii\graphql\base\GraphQLQuery; use yii\graphql\GraphQL; class UserQuery extends GraphQLQuery { public function type() { return GraphQL::type(UserType::class); } public function args() { return [ 'id'=>[ 'type' => Type::nonNull(Type::id()) ], ]; } public function resolve($value, $args, $context, ResolveInfo $info) { return DataSource::findUser($args['id']); } }
根据查询协议定义类型文件
use GraphQL\Type\Definition\Type; use yii\graphql\base\GraphQLType; use yii\graphql\GraphQL; class UserType extends GraphQLType { protected $attributes = [ 'name'=>'user', 'description'=>'user is user' ]; public function fields() { $result = [ 'id' => ['type'=>Type::id()], 'email' => Types::email(), 'email2' => Types::email(), 'photo' => [ 'type' => GraphQL::type(ImageType::class), 'description' => 'User photo URL', 'args' => [ 'size' => Type::nonNull(GraphQL::type(ImageSizeEnumType::class)), ] ], 'firstName' => [ 'type' => Type::string(), ], 'lastName' => [ 'type' => Type::string(), ], 'lastStoryPosted' => GraphQL::type(StoryType::class), 'fieldWithError' => [ 'type' => Type::string(), 'resolve' => function() { throw new \Exception("This is error field"); } ] ]; return $result; } public function resolvePhotoField(User $user,$args){ return DataSource::getUserPhoto($user->id, $args['size']); } public function resolveIdField(User $user, $args) { return $user->id.'test'; } public function resolveEmail2Field(User $user, $args) { return $user->email2.'test'; } }
查询实例
'hello' => " query hello{hello} ", 'singleObject' => " query user { user(id:\"2\") { id email email2 photo(size:ICON){ id url } firstName lastName } } ", 'multiObject' => " query multiObject { user(id: \"2\") { id email photo(size:ICON){ id url } } stories(after: \"1\") { id author{ id } body } } ", 'updateObject' => " mutation updateUserPwd{ updateUserPwd(id: \"1001\", password: \"123456\") { id, username } } "
深入了解
有必要了解一些GraphQL.php的相关知识,这部分git上的文档相对还少些,需要对源码的阅读。下面列出重点
DocumentNode (语法解构)
array definitions
array OperationDefinitionNode
string kind
array NameNode
string kind
string value
Future
- ActiveRecord生成工具,用于生成查询和mutation类。
- 对于GraphQL的一些特殊语法,如参数语法,内置指令语法还未进行测试