├── .gitignore
├── Readme.md
├── composer.json
├── docs
├── advanced.md
└── upgrade.md
├── src
├── Console
│ ├── GeneratorCommand.php
│ ├── MutationMakeCommand.php
│ ├── PublishCommand.php
│ ├── QueryMakeCommand.php
│ ├── TypeMakeCommand.php
│ └── stubs
│ │ ├── mutation.stub
│ │ ├── query.stub
│ │ └── type.stub
├── Error
│ └── ValidationError.php
├── Events
│ ├── SchemaAdded.php
│ └── TypeAdded.php
├── Exception
│ ├── SchemaNotFound.php
│ └── TypeNotFound.php
├── GraphQL.php
├── GraphQLController.php
├── ServiceProvider.php
├── Support
│ ├── EnumType.php
│ ├── Facades
│ │ └── GraphQL.php
│ ├── Field.php
│ ├── InputObjectType.php
│ ├── Instance.php
│ ├── InterfaceType.php
│ ├── Mutation.php
│ ├── Query.php
│ ├── ScalarType.php
│ ├── Traits
│ │ └── ShouldValidate.php
│ └── Type.php
├── View
│ └── GraphiQLComposer.php
├── config
│ └── require
│ │ └── graphql.php
└── resources
│ └── views
│ └── graphiql.php
└── tests
├── ConfigTest.php
├── EndpointTest.php
├── FieldTest.php
├── GraphQLQueryTest.php
├── GraphQLTest.php
├── GraphiQLTest.php
├── InterfaceTypeTest.php
├── MutationTest.php
├── Objects
├── CustomExampleType.php
├── ErrorFormatter.php
├── ExampleField.php
├── ExampleInterfaceType.php
├── ExampleType.php
├── ExampleValidationField.php
├── ExamplesContextQuery.php
├── ExamplesQuery.php
├── ExamplesRootQuery.php
├── UpdateExampleMutation.php
├── data.php
└── queries.php
├── QueryTest.php
├── TestCase.php
├── TypeTest.php
├── fixture
├── app
│ └── .gitignore
└── composer.json
└── logs
└── .gitignore
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | *.log
3 | composer.lock
4 | /*_backup/
5 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # Yii2 GraphQL
2 |
3 | Данное расширение позволяет реализовать GraphQL API в Yii2. Расширение основано на [расширении GraphQL webonyx](https://github.com/webonyx/graphql-php). Более подробная информация о GraphQL - [GraphQL Introduction](http://facebook.github.io/react/blog/2015/05/01/graphql-introduction.html).
4 |
5 |
6 | ## Установка
7 |
8 | #### Зависимости:
9 |
10 | * [Базовое расширение GraphQL PHP](https://github.com/webonyx/graphql-php)
11 | * [Filsh/yii2-oauth2-server](https://github.com/Filsh/yii2-oauth2-server)
12 |
13 |
14 | **1-** Установка пакета через Composer. Добавьте следующий код в файл `composer.json`.
15 | ```json
16 | {
17 | "require": {
18 | "maxxxam/graphql": "~0.2"
19 | }
20 | }
21 | ```
22 |
23 | **2-** Запустите установку пакетов Composer
24 |
25 | ```bash
26 | $ composer install
27 | ```
28 |
29 | или обновите вместе с существующими пакетами
30 |
31 | ```bash
32 | $ composer update
33 | ```
34 |
35 | ### Yii2
36 |
37 | **1-** Добавьте компонент в `config/main.php` file
38 | ```php
39 | 'graphql' => [
40 | 'graphql' => require(__DIR__ . DIRECTORY_SEPARATOR . 'require' . DIRECTORY_SEPARATOR . 'graphql.php')
41 | ],
42 | ```
43 |
44 | **2-** Переместите config `./src/config/require` в `common/config/require`. Отредактируйте конфигурацию согласно вашему окружению.
45 | Объекты Types, Queries, Mutations могут быть загружены вручную (заданием в конфигурации), либо автоматически из заданных директорий.
46 | Пример конфигурации `graphql.php` приведен ниже:
47 | ```php
48 |
49 | $appNamespace = 'app\modules\graphql';
50 | $graphqlDir = dirname(dirname(__DIR__)) . '/modules/graphql';
51 |
52 | return [
53 | 'class' => 'GraphQLYii\GraphQL',
54 | 'namespace' => $appNamespace,
55 | 'graphqlDir' => $graphqlDir,
56 | 'types' => [
57 | 'UserType' => $appNamespace . '\types\UserType',
58 | 'AccessType' => $appNamespace . '\types\AccessType',
59 | 'ImageType' => $appNamespace . '\types\ImageType',
60 | 'EventType' => $appNamespace . '\types\EventType',
61 | 'TrophyType' => $appNamespace . '\types\TrophyType',
62 | 'TeamType' => $appNamespace . '\types\TeamType',
63 | 'AlbumType' => $appNamespace . '\types\AlbumType',
64 | 'FriendType' => $appNamespace . '\types\FriendType',
65 | 'SportType' => $appNamespace . '\types\SportType',
66 | ],
67 | 'queries' => [
68 | 'UsersQuery' => $appNamespace . '\query\user\UsersQuery',
69 | 'UserQuery' => $appNamespace . '\query\user\UserQuery'
70 | ],
71 | 'mutations' => [
72 | 'AddFriendMutation' => $appNamespace . '\mutation\user\AddFriendMutation',
73 | 'RemoveFriendMutation' => $appNamespace . '\mutation\user\RemoveFriendMutation',
74 | 'SignupMutation' => $appNamespace . '\mutation\user\SignupMutation',
75 | ],
76 | 'typesPath' => '/types',
77 | 'queriesPath' => '/queries',
78 | 'mutationsPath' => '/mutations',
79 | 'subscriptionPath' => '/subscription',
80 | ];
81 | ```
82 |
83 | **3-** Создайте новый action для обработки пользовательского запроса
84 |
85 | ```php
86 | public function actionIndex(){
87 | Yii::$app->response->format = Response::FORMAT_JSON;
88 |
89 | $data = Yii::$app->request->post();
90 |
91 | $query = isset($data['query']) ? str_replace(["\r","\n"], "", $data['query']) : null;
92 | $params = isset($data['variables']) ? str_replace(["\r","\n"], "", $data['variables']) : null;
93 |
94 | /** @var GraphQL $GraphQL */
95 | $GraphQL = \Yii::$app->get('graphql');
96 |
97 | $result = $GraphQL->query($query, $params);
98 |
99 | if (!empty($result['errors'])){
100 | Yii::$app->response->setStatusCode(400);
101 | }
102 |
103 | Yii::$app->response->headers->add('Content-Length', strlen(json_encode($result)));
104 | Yii::$app->response->headers->add('Content-Type', 'application/json');
105 |
106 | $stream = fopen('php://memory','wb');
107 | fwrite($stream, json_encode($result));
108 | rewind($stream);
109 |
110 | Yii::$app->response->stream = $stream;
111 | Yii::$app->response->send();
112 |
113 | return true;
114 |
115 | }
116 | ```
117 |
118 | **4-** В качестве авторизации используется oAuth2 сервер [Filsh/yii2-oauth2-server](https://github.com/Filsh/yii2-oauth2-server)
119 |
120 | **5-** Добавьте поведение в Controller
121 |
122 | ```php
123 | public function behaviors()
124 | {
125 | Yii::$app->controller->enableCsrfValidation = false;
126 | return ArrayHelper::merge(parent::behaviors(), [
127 | 'authenticator' => [
128 | 'class' => CompositeAuth::className(),
129 | 'authMethods' => [
130 | ['class' => HttpBearerAuth::className()],
131 | ['class' => QueryParamAuth::className(), 'tokenParam' => 'accessToken'],
132 | ]
133 | ],
134 | 'exceptionFilter' => [
135 | 'class' => ErrorToExceptionFilter::className()
136 | ],
137 | ]);
138 | }
139 | ```
140 |
141 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "maxxxam/graphql-yii",
3 | "type": "library",
4 | "description": "GraphQL, yii2 lib",
5 | "keywords": ["GraphQL","yii2"],
6 | "homepage": "",
7 | "license": "MIT",
8 | "authors": [
9 | {
10 | "name": "Maxim Belkanov",
11 | "email": "maxxxam-me@yandex.ru"
12 | }
13 | ],
14 | "require": {
15 | "php": ">=5.5.9",
16 | "illuminate/support": "5.1.*|5.2.*|5.3.*|5.4.*",
17 | "webonyx/graphql-php": "0.9.*"
18 | },
19 | "require-dev": {
20 |
21 | },
22 | "autoload": {
23 | "psr-4": {
24 | "GraphQLYii\\": "src/"
25 | }
26 | },
27 | "autoload-dev": {
28 | "classmap": [
29 | "tests/"
30 | ]
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/docs/advanced.md:
--------------------------------------------------------------------------------
1 | # Advanced Usage
2 |
3 | - [Query variables](#query-variables)
4 | - [Custom field](#custom-field)
5 | - [Eager loading relationships](#eager-loading-relationships)
6 |
7 | ### Query Variables
8 |
9 | GraphQL offer you the possibility to use variables in your query so you don't need to "hardcode" value. This is done like that:
10 |
11 | ```
12 | query FetchUserByID($id: String) {
13 | user(id: $id) {
14 | id
15 | email
16 | }
17 | }
18 | ```
19 |
20 | When you query the GraphQL endpoint, you can pass a `params` parameter.
21 |
22 | ```
23 | http://homestead.app/graphql?query=query+FetchUserByID($id:String){user(id:$id){id,email}}¶ms={"id":"1"}
24 | ```
25 |
26 | ### Custom field
27 |
28 | You can also define a field as a class if you want to reuse it in multiple types.
29 |
30 | ```php
31 |
32 | namespace App\GraphQL\Fields;
33 |
34 | use GraphQL\Type\Definition\Type;
35 | use Folklore\GraphQL\Support\Field;
36 |
37 | class PictureField extends Field {
38 |
39 | protected $attributes = [
40 | 'description' => 'A picture'
41 | ];
42 |
43 | public function type(){
44 | return Type::string();
45 | }
46 |
47 | public function args()
48 | {
49 | return [
50 | 'width' => [
51 | 'type' => Type::int(),
52 | 'description' => 'The width of the picture'
53 | ],
54 | 'height' => [
55 | 'type' => Type::int(),
56 | 'description' => 'The height of the picture'
57 | ]
58 | ];
59 | }
60 |
61 | protected function resolve($root, $args)
62 | {
63 | $width = isset($args['width']) ? $args['width']:100;
64 | $height = isset($args['height']) ? $args['height']:100;
65 | return 'http://placehold.it/'.$width.'x'.$height;
66 | }
67 |
68 | }
69 |
70 | ```
71 |
72 | You can then use it in your type declaration
73 |
74 | ```php
75 |
76 | namespace App\GraphQL\Type;
77 |
78 | use GraphQL\Type\Definition\Type;
79 | use Folklore\GraphQL\Support\Type as GraphQLType;
80 |
81 | use App\GraphQL\Fields\PictureField;
82 |
83 | class UserType extends GraphQLType {
84 |
85 | protected $attributes = [
86 | 'name' => 'User',
87 | 'description' => 'A user'
88 | ];
89 |
90 | public function fields()
91 | {
92 | return [
93 | 'id' => [
94 | 'type' => Type::nonNull(Type::string()),
95 | 'description' => 'The id of the user'
96 | ],
97 | 'email' => [
98 | 'type' => Type::string(),
99 | 'description' => 'The email of user'
100 | ],
101 | //Instead of passing an array, you pass a class path to your custom field
102 | 'picture' => PictureField::class
103 | ];
104 | }
105 |
106 | }
107 |
108 | ```
109 |
110 | ### Eager loading relationships
111 |
112 | The third argument passed to a query's resolve method is an instance of `GraphQL\Type\Definition\ResolveInfo` which you can use to retrieve keys from the request. The following is an example of using this information to eager load related Eloquent models.
113 |
114 | Your Query would look like
115 |
116 | ```php
117 | namespace App\GraphQL\Query;
118 |
119 | use GraphQL;
120 | use GraphQL\Type\Definition\Type;
121 | use GraphQL\Type\Definition\ResolveInfo;
122 | use Folklore\GraphQL\Support\Query;
123 |
124 | use App\User;
125 |
126 | class UsersQuery extends Query
127 | {
128 | protected $attributes = [
129 | 'name' => 'Users query'
130 | ];
131 |
132 | public function type()
133 | {
134 | return Type::listOf(GraphQL::type('user'));
135 | }
136 |
137 | public function args()
138 | {
139 | return [
140 | 'id' => ['name' => 'id', 'type' => Type::string()],
141 | 'email' => ['name' => 'email', 'type' => Type::string()]
142 | ];
143 | }
144 |
145 | public function resolve($root, $args, $context, ResolveInfo $info)
146 | {
147 | $fields = $info->getFieldSelection($depth = 3);
148 |
149 | $users = User::query();
150 |
151 | foreach ($fields as $field => $keys) {
152 | if ($field === 'profile') {
153 | $users->with('profile');
154 | }
155 |
156 | if ($field === 'posts') {
157 | $users->with('posts');
158 | }
159 | }
160 |
161 | return $users->get();
162 | }
163 | }
164 | ```
165 |
166 | Your Type for User would look like
167 |
168 | ```php
169 | 'User',
184 | 'description' => 'A user',
185 | ];
186 |
187 | /**
188 | * @return array
189 | */
190 | public function fields()
191 | {
192 | return [
193 | 'uuid' => [
194 | 'type' => Type::nonNull(Type::string()),
195 | 'description' => 'The uuid of the user'
196 | ],
197 | 'email' => [
198 | 'type' => Type::nonNull(Type::string()),
199 | 'description' => 'The email of user'
200 | ],
201 | 'profile' => [
202 | 'type' => GraphQL::type('Profile'),
203 | 'description' => 'The user profile',
204 | ],
205 | 'posts' => [
206 | 'type' => Type::listOf(GraphQL::type('Post')),
207 | 'description' => 'The user posts',
208 | ]
209 | ];
210 | }
211 | }
212 |
213 | ```
214 |
215 | At this point we have a profile and a post type as expected for any model
216 |
217 | ```php
218 | class ProfileType extends GraphQLType
219 | {
220 | protected $attributes = [
221 | 'name' => 'Profile',
222 | 'description' => 'A user profile',
223 | ];
224 |
225 | public function fields()
226 | {
227 | return [
228 | 'name' => [
229 | 'type' => Type::string(),
230 | 'description' => 'The name of user'
231 | ]
232 | ];
233 | }
234 | }
235 | ```
236 |
237 | ```php
238 | class PostType extends GraphQLType
239 | {
240 | protected $attributes = [
241 | 'name' => 'Post',
242 | 'description' => 'A post',
243 | ];
244 |
245 | public function fields()
246 | {
247 | return [
248 | 'title' => [
249 | 'type' => Type::nonNull(Type::string()),
250 | 'description' => 'The title of the post'
251 | ],
252 | 'body' => [
253 | 'type' => Type::string(),
254 | 'description' => 'The body the post'
255 | ]
256 | ];
257 | }
258 | }
259 | ```
260 |
261 |
262 | Lastly your query would look like, if using Homestead
263 |
264 | For example, if you use homestead:
265 |
266 | ```
267 | http://homestead.app/graphql?query=query+FetchUsers{users{uuid, email, team{name}}}
268 | ```
269 |
--------------------------------------------------------------------------------
/docs/upgrade.md:
--------------------------------------------------------------------------------
1 | ## Upgrade to 1.0
2 |
3 | ### Multiple schemas
4 | The main difference between versions prior 1.0 and 1.0 is the use of multiple schemas. You will need to update your config to have the following structure:
5 |
6 | ```php
7 |
8 | 'schema' => 'default',
9 |
10 | 'schemas' => [
11 | 'default' => [
12 | 'query' => [
13 | // Your queries
14 | ],
15 | 'mutation' => [
16 | // Your mutations
17 | ]
18 | ]
19 | ]
20 |
21 | ```
22 |
23 | ### Routes
24 | If you want to use routes that can accept schema name, you need to change `routes` to the following:
25 |
26 | ```php
27 |
28 | 'routes' => '{graphql_schema?}',
29 |
30 | // or if you use different routes for query and mutation
31 |
32 | 'routes' => [
33 | 'query' => 'query/{graphql_schema?}',
34 | 'mutation' => 'mutation/{graphql_schema?}'
35 | ],
36 |
37 | ```
38 |
39 | ### Facade methods
40 | The method `GraphQL::addQuery` and `GraphQL::addMutation` has been removed since it doesn't make sense with multiple schemas. You can use the new `GraphQL::addSchema` method to add new schemas.
41 |
42 | ### Context
43 | Since graphql-php v0.7 the arguments passed to the `resolve` method has changed. There is a third argument called `context`.
44 |
45 | ```php
46 | public function resolve($root, $args, $context, ResolveInfo $info)
47 | {
48 |
49 | }
50 | ```
51 |
--------------------------------------------------------------------------------
/src/Console/GeneratorCommand.php:
--------------------------------------------------------------------------------
1 | replaceType($stub, $name);
63 | }
64 |
65 | /**
66 | * Replace the namespace for the given stub.
67 | *
68 | * @param string $stub
69 | * @param string $name
70 | * @return $this
71 | */
72 | protected function replaceType($stub, $name)
73 | {
74 | preg_match('/([^\\\]+)$/', $name, $matches);
75 | $stub = str_replace(
76 | 'DummyMutation',
77 | $matches[1],
78 | $stub
79 | );
80 |
81 | return $stub;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Console/PublishCommand.php:
--------------------------------------------------------------------------------
1 | destination)
33 | *
34 | * @var array
35 | */
36 | protected $fileMap = [];
37 |
38 | public function __construct(Filesystem $files)
39 | {
40 | parent::__construct();
41 | $this->files = $files;
42 |
43 | $fromPath = __DIR__ . '/../../..';
44 | $this->fileMap = [
45 | $fromPath.'/config/config.php' => app()->basePath('config/graphql.php'),
46 | $fromPath.'/resources/views/graphiql.php' => app()->basePath('resources/views/vendor/graphql/graphiql.php')
47 | ];
48 | }
49 |
50 | /**
51 | * Execute the console command.
52 | *
53 | * @return mixed
54 | */
55 | public function handle()
56 | {
57 | foreach ($this->fileMap as $from => $to) {
58 | if ($this->files->exists($to) && !$this->option('force')) {
59 | continue;
60 | }
61 | $this->createParentDirectory(dirname($to));
62 | $this->files->copy($from, $to);
63 | $this->status($from, $to, 'File');
64 | }
65 | }
66 |
67 | /**
68 | * Create the directory to house the published files if needed.
69 | *
70 | * @param string $directory
71 | * @return void
72 | */
73 | protected function createParentDirectory($directory)
74 | {
75 | if (!$this->files->isDirectory($directory)) {
76 | $this->files->makeDirectory($directory, 0755, true);
77 | }
78 | }
79 |
80 | /**
81 | * Write a status message to the console.
82 | *
83 | * @param string $from
84 | * @param string $to
85 | * @return void
86 | */
87 | protected function status($from, $to)
88 | {
89 | $from = str_replace(base_path(), '', realpath($from));
90 | $to = str_replace(base_path(), '', realpath($to));
91 | $this->line("Copied File [{$from}] To [{$to}]");
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/src/Console/QueryMakeCommand.php:
--------------------------------------------------------------------------------
1 | replaceType($stub, $name);
63 | }
64 |
65 | /**
66 | * Replace the namespace for the given stub.
67 | *
68 | * @param string $stub
69 | * @param string $name
70 | * @return $this
71 | */
72 | protected function replaceType($stub, $name)
73 | {
74 | preg_match('/([^\\\]+)$/', $name, $matches);
75 | $stub = str_replace(
76 | 'DummyQuery',
77 | $matches[1],
78 | $stub
79 | );
80 |
81 | return $stub;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Console/TypeMakeCommand.php:
--------------------------------------------------------------------------------
1 | replaceType($stub, $name);
63 | }
64 |
65 | /**
66 | * Replace the namespace for the given stub.
67 | *
68 | * @param string $stub
69 | * @param string $name
70 | * @return $this
71 | */
72 | protected function replaceType($stub, $name)
73 | {
74 | preg_match('/([^\\\]+)$/', $name, $matches);
75 | $stub = str_replace(
76 | 'DummyType',
77 | $matches[1],
78 | $stub
79 | );
80 |
81 | return $stub;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Console/stubs/mutation.stub:
--------------------------------------------------------------------------------
1 | 'DummyMutation',
14 | 'description' => 'A mutation'
15 | ];
16 |
17 | public function type()
18 | {
19 | return Type::listOf(Type::string());
20 | }
21 |
22 | public function args()
23 | {
24 | return [
25 |
26 | ];
27 | }
28 |
29 | public function resolve($root, $args, $context, ResolveInfo $info)
30 | {
31 | return [];
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Console/stubs/query.stub:
--------------------------------------------------------------------------------
1 | 'DummyQuery',
14 | 'description' => 'A query'
15 | ];
16 |
17 | public function type()
18 | {
19 | return Type::listOf(Type::string());
20 | }
21 |
22 | public function args()
23 | {
24 | return [
25 |
26 | ];
27 | }
28 |
29 | public function resolve($root, $args, $context, ResolveInfo $info)
30 | {
31 | return [];
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Console/stubs/type.stub:
--------------------------------------------------------------------------------
1 | 'DummyType',
13 | 'description' => 'A type'
14 | ];
15 |
16 | public function fields()
17 | {
18 | return [
19 |
20 | ];
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Error/ValidationError.php:
--------------------------------------------------------------------------------
1 | validator = $validator;
13 |
14 | return $this;
15 | }
16 |
17 | public function getValidator()
18 | {
19 | return $this->validator;
20 | }
21 |
22 | public function getValidatorMessages()
23 | {
24 | return $this->validator ? $this->validator->messages():[];
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Events/SchemaAdded.php:
--------------------------------------------------------------------------------
1 | schema = $schema;
11 | $this->name = $name;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Events/TypeAdded.php:
--------------------------------------------------------------------------------
1 | type = $type;
11 | $this->name = $name;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Exception/SchemaNotFound.php:
--------------------------------------------------------------------------------
1 | clearTypeInstances();
52 |
53 | if (!empty($this->schema)){
54 | try{
55 | $schema = new $this->schema;
56 | $this->schema = $schema::build();
57 | }catch(\Exception $error){
58 | throw new SchemaNotFound('Type ' . $this->schema . ' not found.');
59 | }
60 | }
61 |
62 | /** Collect objects from config and files directory */
63 | $this->types = array_unique( array_merge($this->types, $this->getListFiles($this->graphqlDir, $this->typesPath)) );
64 | $this->queries = array_unique( array_merge($this->queries, $this->getListFiles($this->graphqlDir, $this->queriesPath)) );
65 | $this->mutations = array_unique( array_merge($this->mutations, $this->getListFiles($this->graphqlDir, $this->mutationsPath)) );
66 |
67 | /** Collect all types */
68 | $types = [];
69 | foreach ($this->types as $name => $type) {
70 | $newType = $this->type($name);
71 | if ($newType) {
72 | $types[] = $newType;
73 | }
74 | }
75 |
76 | $this->queryInstance = $this->objectType($this->queries, [
77 | 'name' => 'Query'
78 | ]);
79 |
80 | $this->mutationInstance = $this->objectType($this->mutations, [
81 | 'name' => 'Mutation'
82 | ]);
83 |
84 | return new Schema([
85 | 'query' => $this->queryInstance,
86 | 'mutation' => $this->mutationInstance,
87 | 'types' => $types
88 | ]);
89 | }
90 |
91 | /**
92 | * Получение объектов из директории
93 | * @param $basepath
94 | * @param string $subPath
95 | * @return array
96 | */
97 | private function getListFiles($basepath, $subPath = ''){
98 | $files = [];
99 | $subNamespace = str_replace('/', '\\', $subPath);
100 | $path = $basepath . $subPath;
101 | if ($path !== ''){
102 | if (file_exists($path)) {
103 | $fp = opendir($path);
104 | while ($cvFile = readdir($fp)) {
105 | $fileName = $path . '/' . $cvFile;
106 | if (is_file($fileName)) {
107 | if (preg_match('/(.*)\.php/', $cvFile, $matches)) {
108 | $files[$matches[1]] = $this->namespace . $subNamespace . '\\' . $matches[1];
109 | }
110 | } elseif (!in_array($cvFile, ['.', '..'], true) && is_dir($fileName)) {
111 | $files = array_merge($files, $this->getListFiles($basepath, $subPath . '/' . $cvFile));
112 | }
113 | }
114 | closedir($fp);
115 | }
116 | }
117 | return $files;
118 | }
119 |
120 | public function type($name, $fresh = false)
121 | {
122 | if (!isset($this->types[$name])) {
123 | throw new TypeNotFound('Type '. $name.' not found.');
124 | }
125 |
126 | if (!$fresh && isset($this->typesInstances[$name])) {
127 | return $this->typesInstances[$name];
128 | }
129 |
130 | $class = $this->types[$name];
131 |
132 | $type = $this->objectType($class, [
133 | 'name' => $name
134 | ]);
135 |
136 | if ($type) {
137 | $this->typesInstances[$name] = $type;
138 | }
139 |
140 | return $type;
141 | }
142 |
143 | /**
144 | * Uses if Query is field of anyone types
145 | * @param $name
146 | * @param bool $fresh
147 | * @return array
148 | */
149 | public function queryAsField($name)
150 | {
151 | if (!empty($this->queryInstance)) {
152 | $queryInstance = $this->queryInstance->getField($name);
153 | return [
154 | 'type' => $queryInstance->getType(),
155 | 'args' => $queryInstance->config['args'],
156 | 'resolve' => $queryInstance->resolveFn
157 | ];
158 | }
159 | }
160 |
161 | public function objectType($type, $opts = [])
162 | {
163 | // If it's already an ObjectType, just update properties and return it.
164 | // If it's an array, assume it's an array of fields and build ObjectType
165 | // from it. Otherwise, build it from a string or an instance.
166 | $objectType = null;
167 | if ($type instanceof ObjectType) {
168 | $objectType = $type;
169 | foreach ($opts as $key => $value) {
170 | if (property_exists($objectType, $key)) {
171 | $objectType->{$key} = $value;
172 | }
173 | if (isset($objectType->config[$key])) {
174 | $objectType->config[$key] = $value;
175 | }
176 | }
177 | } elseif (is_array($type)) {
178 | $objectType = $this->buildObjectTypeFromFields($type, $opts);
179 | } else {
180 | $objectType = $this->buildObjectTypeFromClass($type, $opts);
181 | }
182 |
183 | return $objectType;
184 | }
185 |
186 | public function query($query, $params = [], $opts = [])
187 | {
188 | $result = $this->queryAndReturnResult($query, $params, $opts);
189 |
190 | if (!empty($result->errors)) {
191 | return [
192 | 'data' => $result->data,
193 | 'errors' => array_map([$this, 'formatError'], $result->errors)
194 | ];
195 | }
196 |
197 | return [
198 | 'data' => $result->data
199 | ];
200 |
201 | }
202 |
203 | public function queryAndReturnResult($query, $params = [], $opts = [])
204 | {
205 | $root = array_get($opts, 'root', null);
206 | $context = array_get($opts, 'context', null);
207 | $schemaName = array_get($opts, 'schema', null);
208 | $operationName = array_get($opts, 'operationName', null);
209 |
210 | $schema = $this->schema($schemaName);
211 |
212 | $result = GraphQLBase::executeAndReturnResult($schema, $query, $root, $context, $params, $operationName);
213 |
214 | return $result;
215 | }
216 |
217 | public function addTypes($types)
218 | {
219 | foreach ($types as $name => $type) {
220 | $this->addType($type, is_numeric($name) ? null:$name);
221 | }
222 | }
223 |
224 | public function addType($class, $name = null)
225 | {
226 | $name = $this->getTypeName($class, $name);
227 | $this->types[$name] = $class;
228 | }
229 |
230 | public function addSchema($name, $schema)
231 | {
232 | $this->schemas[$name] = $schema;
233 | }
234 |
235 | public function clearType($name)
236 | {
237 | if (isset($this->types[$name])) {
238 | unset($this->types[$name]);
239 | }
240 | }
241 |
242 | public function clearSchema($name)
243 | {
244 | if (isset($this->schemas[$name])) {
245 | unset($this->schemas[$name]);
246 | }
247 | }
248 |
249 | public function clearTypes()
250 | {
251 | $this->types = [];
252 | }
253 |
254 | public function clearSchemas()
255 | {
256 | $this->schemas = [];
257 | }
258 |
259 | public function getTypes()
260 | {
261 | return $this->types;
262 | }
263 |
264 | public function getSchemas()
265 | {
266 | return $this->schemas;
267 | }
268 |
269 | protected function clearTypeInstances()
270 | {
271 | $this->typesInstances = [];
272 | }
273 |
274 | protected function buildObjectTypeFromClass($type, $opts = [])
275 | {
276 | if (!is_object($type)) {
277 | try {
278 | $type = new $type($opts);
279 | } catch (\Error $e){
280 | return false;
281 | }
282 | }
283 |
284 | foreach ($opts as $key => $value) {
285 | $type->{$key} = $value;
286 | }
287 |
288 | return $type->toType();
289 | }
290 |
291 | protected function buildObjectTypeFromFields($fields, $opts = [])
292 | {
293 | $typeFields = [];
294 | foreach ($fields as $name => $field) {
295 | if (is_string($field)) {
296 | try {
297 | $field = new $field;
298 | } catch (\Error $e){
299 | continue;
300 | }
301 | $name = is_numeric($name) ? $field->name:$name;
302 | $field->name = $name;
303 | $field = $field->toArray();
304 | } else {
305 | $name = is_numeric($name) ? $field['name']:$name;
306 | $field['name'] = $name;
307 | }
308 | $typeFields[$name] = $field;
309 | }
310 |
311 | return new ObjectType(array_merge([
312 | 'fields' => $typeFields
313 | ], $opts));
314 | }
315 |
316 | protected function getTypeName($class, $name = null)
317 | {
318 | if ($name) {
319 | return $name;
320 | }
321 |
322 | $type = is_object($class) ? $class : new $class;
323 | return $type->name;
324 | }
325 |
326 | public static function formatError($e)
327 | {
328 | $error = [
329 | 'message' => $e->getMessage()
330 | ];
331 |
332 | $locations = $e->getLocations();
333 | if (!empty($locations)) {
334 | $error['locations'] = array_map(function ($loc) {
335 | return $loc->toArray();
336 | }, $locations);
337 | }
338 |
339 | $previous = $e->getPrevious();
340 | if (!empty($previous->statusCode)) {
341 | $error['statusCode'] = $previous->statusCode;
342 | }
343 |
344 | $previous = $e->getPrevious();
345 | if (!empty($previous)) {
346 | $error['file'] = $previous->getFile();
347 | $error['line'] = $previous->getLine();
348 | }
349 |
350 | $stackTrace = $e->getTrace();
351 | if (!empty($stackTrace)) {
352 | foreach ($stackTrace as $key => $item) {
353 | $newItem = [];
354 | $newItem['file'] = isset($item['file']) ? $item['file'] : '';
355 | $newItem['line'] = isset($item['line']) ? $item['line'] : '';
356 | $newItem['function'] = isset($item['function']) ? $item['function'] : '';
357 | $error['trace'][] = $newItem;
358 | }
359 | }
360 |
361 | if ($previous && $previous instanceof ValidationError) {
362 | $error['validation'] = $previous->getValidatorMessages();
363 | }
364 |
365 | return $error;
366 | }
367 | }
368 |
--------------------------------------------------------------------------------
/src/GraphQLController.php:
--------------------------------------------------------------------------------
1 | response->format = Response::FORMAT_JSON;
25 |
26 | $data = Yii::$app->request->post();
27 |
28 | $query = isset($data['query']) ? str_replace(["\r","\n"], "", $data['query']) : null;
29 | $params = isset($data['variables']) ? str_replace(["\r","\n"], "", $data['variables']) : null;
30 |
31 | /** @var GraphQL $GraphQL */
32 | $GraphQL = \Yii::$app->get('graphql');
33 |
34 | $result = $GraphQL->query($query, $params);
35 |
36 | if (!empty($result['errors'])){
37 | Yii::$app->response->setStatusCode(400);
38 | }
39 |
40 | Yii::$app->response->headers->add('Content-Length', strlen(json_encode($result)));
41 | Yii::$app->response->headers->add('Content-Type', 'application/json');
42 |
43 | $stream = fopen('php://memory','wb');
44 | fwrite($stream, json_encode($result));
45 | rewind($stream);
46 |
47 | Yii::$app->response->stream = $stream;
48 | Yii::$app->response->send();
49 |
50 | return true;
51 |
52 | }
53 |
54 | public function behaviors()
55 | {
56 | Yii::$app->controller->enableCsrfValidation = false;
57 | return ArrayHelper::merge(parent::behaviors(), [
58 | 'authenticator' => [
59 | 'class' => CompositeAuth::className(),
60 | 'authMethods' => [
61 | ['class' => HttpBearerAuth::className()],
62 | ['class' => QueryParamAuth::className(), 'tokenParam' => 'accessToken'],
63 | ]
64 | ],
65 | 'exceptionFilter' => [
66 | 'class' => ErrorToExceptionFilter::className()
67 | ],
68 | ]);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/ServiceProvider.php:
--------------------------------------------------------------------------------
1 | app['router'];
16 | }
17 |
18 | /**
19 | * Bootstrap any application services.
20 | *
21 | * @return void
22 | */
23 | public function boot()
24 | {
25 | $this->bootEvents();
26 |
27 | $this->bootPublishes();
28 |
29 | $this->bootTypes();
30 |
31 | $this->bootSchemas();
32 |
33 | $this->bootRouter();
34 |
35 | $this->bootViews();
36 |
37 | $this->bootSecurity();
38 | }
39 |
40 | /**
41 | * Bootstrap router
42 | *
43 | * @return void
44 | */
45 | protected function bootRouter()
46 | {
47 | if (config('graphql.routes')) {
48 | $router = $this->getRouter();
49 | include __DIR__.'/routes.php';
50 | }
51 | }
52 |
53 | /**
54 | * Bootstrap events
55 | *
56 | * @return void
57 | */
58 | protected function bootEvents()
59 | {
60 | //Update the schema route pattern when schema is added
61 | $this->app['events']->listen(\Folklore\GraphQL\Events\SchemaAdded::class, function () {
62 | $schemaNames = array_keys($this->app['graphql']->getSchemas());
63 | $this->getRouter()->pattern('graphql_schema', '('.implode('|', $schemaNames).')');
64 | });
65 | }
66 |
67 | /**
68 | * Bootstrap publishes
69 | *
70 | * @return void
71 | */
72 | protected function bootPublishes()
73 | {
74 | $configPath = __DIR__.'/../../config';
75 | $viewsPath = __DIR__.'/../../resources/views';
76 |
77 | $this->mergeConfigFrom($configPath.'/config.php', 'graphql');
78 |
79 | $this->loadViewsFrom($viewsPath, 'graphql');
80 |
81 | $this->publishes([
82 | $configPath.'/config.php' => config_path('graphql.php'),
83 | ], 'config');
84 |
85 | $this->publishes([
86 | $viewsPath => base_path('resources/views/vendor/graphql'),
87 | ], 'views');
88 | }
89 |
90 | /**
91 | * Add types from config
92 | *
93 | * @return void
94 | */
95 | protected function bootTypes()
96 | {
97 | $configTypes = config('graphql.types');
98 | foreach ($configTypes as $name => $type) {
99 | if (is_numeric($name)) {
100 | $this->app['graphql']->addType($type);
101 | } else {
102 | $this->app['graphql']->addType($type, $name);
103 | }
104 | }
105 | }
106 |
107 | /**
108 | * Add schemas from config
109 | *
110 | * @return void
111 | */
112 | protected function bootSchemas()
113 | {
114 | $configSchemas = config('graphql.schemas');
115 | foreach ($configSchemas as $name => $schema) {
116 | $this->app['graphql']->addSchema($name, $schema);
117 | }
118 | }
119 |
120 | /**
121 | * Bootstrap Views
122 | *
123 | * @return void
124 | */
125 | protected function bootViews()
126 | {
127 | $graphiQL = config('graphql.graphiql', true);
128 | if ($graphiQL) {
129 | $view = config('graphql.graphiql.view', 'graphql::graphiql');
130 | app('view')->composer($view, \Folklore\GraphQL\View\GraphiQLComposer::class);
131 | }
132 | }
133 |
134 | /**
135 | * Configure security from config
136 | * @return void
137 | */
138 | protected function bootSecurity()
139 | {
140 | $maxQueryComplexity = config('graphql.security.query_max_complexity');
141 | if ($maxQueryComplexity !== null) {
142 | $queryComplexity = DocumentValidator::getRule('QueryComplexity');
143 | $queryComplexity->setMaxQueryComplexity($maxQueryComplexity);
144 | }
145 |
146 | $maxQueryDepth = config('graphql.security.query_max_depth');
147 | if ($maxQueryDepth !== null) {
148 | $queryDepth = DocumentValidator::getRule('QueryDepth');
149 | $queryDepth->setMaxQueryDepth($maxQueryDepth);
150 | }
151 | }
152 |
153 | /**
154 | * Register any application services.
155 | *
156 | * @return void
157 | */
158 | public function register()
159 | {
160 | $this->registerGraphQL();
161 |
162 | $this->registerConsole();
163 | }
164 |
165 | /**
166 | * Register GraphQL facade
167 | *
168 | * @return void
169 | */
170 | public function registerGraphQL()
171 | {
172 | $this->app->singleton('graphql', function ($app) {
173 | return new GraphQL($app);
174 | });
175 | }
176 |
177 | /**
178 | * Register console commands
179 | *
180 | * @return void
181 | */
182 | public function registerConsole()
183 | {
184 | $this->commands(\Folklore\GraphQL\Console\TypeMakeCommand::class);
185 | $this->commands(\Folklore\GraphQL\Console\QueryMakeCommand::class);
186 | $this->commands(\Folklore\GraphQL\Console\MutationMakeCommand::class);
187 | }
188 |
189 |
190 | /**
191 | * Get the services provided by the provider.
192 | *
193 | * @return array
194 | */
195 | public function provides()
196 | {
197 | return ['graphql'];
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/src/Support/EnumType.php:
--------------------------------------------------------------------------------
1 | getAttributes(), [
31 | 'values' => $this->values()
32 | ]);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Support/Facades/GraphQL.php:
--------------------------------------------------------------------------------
1 | appInstance = Instance::getAppInstance();
16 | parent::__construct($attributes);
17 | }
18 |
19 | public function attributes()
20 | {
21 | return [];
22 | }
23 |
24 | public function type()
25 | {
26 | return null;
27 | }
28 |
29 | public function args()
30 | {
31 | return [];
32 | }
33 |
34 | protected function getResolver()
35 | {
36 | if (!method_exists($this, 'resolve')) {
37 | return null;
38 | }
39 |
40 | $resolver = array($this, 'resolve');
41 | return function () use ($resolver) {
42 | $args = func_get_args();
43 | return call_user_func_array($resolver, $args);
44 | };
45 | }
46 |
47 | /**
48 | * Get the attributes from the container.
49 | *
50 | * @return array
51 | */
52 | public function getAttributes()
53 | {
54 | $attributes = $this->attributes();
55 | $args = $this->args();
56 |
57 | $attributes = array_merge($this->attributes, [
58 | 'args' => $args
59 | ], $attributes);
60 |
61 | $type = $this->type();
62 | if (isset($type)) {
63 | $attributes['type'] = $type;
64 | }
65 |
66 | $resolver = $this->getResolver();
67 | if (isset($resolver)) {
68 | $attributes['resolve'] = $resolver;
69 | }
70 |
71 | return $attributes;
72 | }
73 |
74 | /**
75 | * Convert the Fluent instance to an array.
76 | *
77 | * @return array
78 | */
79 | public function toArray()
80 | {
81 | return $this->getAttributes();
82 | }
83 |
84 | /**
85 | * Dynamically retrieve the value of an attribute.
86 | *
87 | * @param string $key
88 | * @return mixed
89 | */
90 | public function __get($key)
91 | {
92 | $attributes = $this->getAttributes();
93 | return isset($attributes[$key]) ? $attributes[$key]:null;
94 | }
95 |
96 | /**
97 | * Dynamically check if an attribute is set.
98 | *
99 | * @param string $key
100 | * @return void
101 | */
102 | public function __isset($key)
103 | {
104 | $attributes = $this->getAttributes();
105 | return isset($attributes[$key]);
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/Support/InputObjectType.php:
--------------------------------------------------------------------------------
1 | appInstance = Instance::getAppInstance();
21 | parent::__construct($attributes);
22 | }
23 |
24 | /**
25 | * @return mixed
26 | */
27 | public static function type(){
28 | $array = explode('\\', static::class);
29 | $className = $array[count($array) - 1];
30 | return Instance::getAppInstance()->type($className);
31 | }
32 |
33 | public function attributes()
34 | {
35 | return [];
36 | }
37 |
38 | public function fields()
39 | {
40 | return [];
41 | }
42 |
43 | public function interfaces()
44 | {
45 | return [];
46 | }
47 |
48 | protected function getFieldResolver($name, $field)
49 | {
50 | $resolveMethod = 'resolve'.studly_case($name).'Field';
51 | if (isset($field['resolve'])) {
52 | return $field['resolve'];
53 | } elseif (method_exists($this, $resolveMethod)) {
54 | $resolver = array($this, $resolveMethod);
55 | return function () use ($resolver) {
56 | $args = func_get_args();
57 | return call_user_func_array($resolver, $args);
58 | };
59 | }
60 |
61 | return null;
62 | }
63 |
64 | public function getFields()
65 | {
66 | $fields = $this->fields();
67 | $allFields = [];
68 | foreach ($fields as $name => $field) {
69 | if (is_string($field)) {
70 | $field = app($field);
71 | $field->name = $name;
72 | $allFields[$name] = $field->toArray();
73 | } else {
74 | $resolver = $this->getFieldResolver($name, $field);
75 | if ($resolver) {
76 | $field['resolve'] = $resolver;
77 | }
78 | $allFields[$name] = $field;
79 | }
80 | }
81 |
82 | return $allFields;
83 | }
84 |
85 | /**
86 | * Get the attributes from the container.
87 | *
88 | * @return array
89 | */
90 | public function getAttributes()
91 | {
92 | $attributes = $this->attributes();
93 | $interfaces = $this->interfaces();
94 |
95 | $attributes = array_merge($this->attributes, [
96 | 'fields' => function () {
97 | return $this->getFields();
98 | }
99 | ], $attributes);
100 |
101 | if (count($interfaces)) {
102 | $attributes['interfaces'] = $interfaces;
103 | }
104 |
105 | return $attributes;
106 | }
107 |
108 | /**
109 | * Convert the Fluent instance to an array.
110 | *
111 | * @return array
112 | */
113 | public function toArray()
114 | {
115 | return $this->getAttributes();
116 | }
117 |
118 | public function toType()
119 | {
120 | return new GQObject($this->toArray());
121 | }
122 |
123 |
124 |
125 | }
126 |
--------------------------------------------------------------------------------
/src/Support/Instance.php:
--------------------------------------------------------------------------------
1 | getTypeResolver();
33 | if (isset($resolver)) {
34 | $attributes['resolveType'] = $resolver;
35 | }
36 |
37 | return $attributes;
38 | }
39 |
40 | public function toType()
41 | {
42 | return new BaseInterfaceType($this->toArray());
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Support/Mutation.php:
--------------------------------------------------------------------------------
1 | queryAsField($className);
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/Support/ScalarType.php:
--------------------------------------------------------------------------------
1 | getAttributes(), [
49 | 'serialize' => [$this, 'serialize'],
50 | 'parseValue' => [$this, 'parseValue'],
51 | 'parseLiteral' => [$this, 'parseLiteral'],
52 | ]);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Support/Traits/ShouldValidate.php:
--------------------------------------------------------------------------------
1 | args() as $name => $arg) {
21 | if (isset($arg['rules'])) {
22 | if (is_callable($arg['rules'])) {
23 | $argsRules[$name] = call_user_func_array($arg['rules'], $arguments);
24 | } else {
25 | $argsRules[$name] = $arg['rules'];
26 | }
27 | }
28 | }
29 |
30 | return array_merge($rules, $argsRules);
31 | }
32 |
33 | protected function getValidator($args, $rules)
34 | {
35 | return app('validator')->make($args, $rules);
36 | }
37 |
38 | protected function getResolver()
39 | {
40 | $resolver = parent::getResolver();
41 | if (!$resolver) {
42 | return null;
43 | }
44 |
45 | return function () use ($resolver) {
46 | $arguments = func_get_args();
47 |
48 | $rules = call_user_func_array([$this, 'getRules'], $arguments);
49 | if (sizeof($rules)) {
50 | $args = array_get($arguments, 1, []);
51 | $validator = $this->getValidator($args, $rules);
52 | if ($validator->fails()) {
53 | throw with(new ValidationError('validation'))->setValidator($validator);
54 | }
55 | }
56 |
57 | return call_user_func_array($resolver, $arguments);
58 | };
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Support/Type.php:
--------------------------------------------------------------------------------
1 | appInstance = Instance::getAppInstance();
27 | parent::__construct($attributes);
28 | }
29 |
30 | public function attributes()
31 | {
32 | return [];
33 | }
34 |
35 | public function fields()
36 | {
37 | return [];
38 | }
39 |
40 | public function interfaces()
41 | {
42 | return [];
43 | }
44 |
45 | /**
46 | * @return mixed
47 | */
48 | public static function type(){
49 | $array = explode('\\', static::class);
50 | $className = $array[count($array) - 1];
51 | return Instance::getAppInstance()->type($className);
52 | }
53 |
54 | protected function getFieldResolver($name, $field)
55 | {
56 | $resolveMethod = 'resolve'.studly_case($name).'Field';
57 | if (isset($field['resolve'])) {
58 | return $field['resolve'];
59 | }
60 |
61 | if (method_exists($this, $resolveMethod)) {
62 | $resolver = array($this, $resolveMethod);
63 | return function () use ($resolver) {
64 | $args = func_get_args();
65 | return call_user_func_array($resolver, $args);
66 | };
67 | }
68 |
69 | return null;
70 | }
71 |
72 | public function getFields()
73 | {
74 | $fields = $this->fields();
75 | $allFields = [];
76 | foreach ($fields as $name => $field) {
77 | if (is_string($field)) {
78 | $field = app($field);
79 | $field->name = $name;
80 | $allFields[$name] = $field->toArray();
81 | } else {
82 | $resolver = $this->getFieldResolver($name, $field);
83 | if ($resolver) {
84 | $field['resolve'] = $resolver;
85 | }
86 | $allFields[$name] = $field;
87 | }
88 | }
89 |
90 | return $allFields;
91 | }
92 |
93 | /**
94 | * Get the attributes from the container.
95 | *
96 | * @return array
97 | */
98 | public function getAttributes()
99 | {
100 | $attributes = $this->attributes();
101 | $interfaces = $this->interfaces();
102 |
103 | $attributes = array_merge($this->attributes, [
104 | 'fields' => function () {
105 | return $this->getFields();
106 | }
107 | ], $attributes);
108 |
109 | if (count($interfaces)) {
110 | $attributes['interfaces'] = $interfaces;
111 | }
112 |
113 | return $attributes;
114 | }
115 |
116 | /**
117 | * Convert the Fluent instance to an array.
118 | *
119 | * @return array
120 | */
121 | public function toArray()
122 | {
123 | return $this->getAttributes();
124 | }
125 |
126 | public function toType()
127 | {
128 | if ($this->inputObject) {
129 | return new InputObjectType($this->toArray());
130 | }
131 |
132 | if ($this->scalarType) {
133 | return new CustomScalarType($this->toArray());
134 | }
135 |
136 | if ($this->enumType) {
137 | return new EnumType($this->toArray());
138 | }
139 |
140 | return new ObjectType($this->toArray());
141 | }
142 |
143 | /**
144 | * Dynamically retrieve the value of an attribute.
145 | *
146 | * @param string $key
147 | * @return mixed
148 | */
149 | public function __get($key)
150 | {
151 | $attributes = $this->getAttributes();
152 | return isset($attributes[$key]) ? $attributes[$key]:null;
153 | }
154 |
155 | /**
156 | * Dynamically check if an attribute is set.
157 | *
158 | * @param string $key
159 | * @return void
160 | */
161 | public function __isset($key)
162 | {
163 | $attributes = $this->getAttributes();
164 | return isset($attributes[$key]);
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/src/View/GraphiQLComposer.php:
--------------------------------------------------------------------------------
1 | graphqlPath = route('graphql.query');
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/config/require/graphql.php:
--------------------------------------------------------------------------------
1 | 'GraphQLYii\GraphQL',
8 | 'namespace' => $appNamespace,
9 | 'graphqlDir' => $graphqlDir,
10 | /* 'types' => [
11 | 'UserType' => $appNamespace . '\types\UserType',
12 | 'AccessType' => $appNamespace . '\types\AccessType',
13 | 'ImageType' => $appNamespace . '\types\ImageType',
14 | 'EventType' => $appNamespace . '\types\EventType',
15 | 'TrophyType' => $appNamespace . '\types\TrophyType',
16 | 'TeamType' => $appNamespace . '\types\TeamType',
17 | 'AlbumType' => $appNamespace . '\types\AlbumType',
18 | 'FriendType' => $appNamespace . '\types\FriendType',
19 | 'SportType' => $appNamespace . '\types\SportType',
20 | ],
21 | 'queries' => [
22 | 'UsersQuery' => $appNamespace . '\query\user\UsersQuery',
23 | 'UserQuery' => $appNamespace . '\query\user\UserQuery'
24 | ],
25 | 'mutations' => [
26 | 'AddFriendMutation' => $appNamespace . '\mutation\user\AddFriendMutation',
27 | 'RemoveFriendMutation' => $appNamespace . '\mutation\user\RemoveFriendMutation',
28 | 'SignupMutation' => $appNamespace . '\mutation\user\SignupMutation',
29 | ], */
30 | 'typesPath' => '/types',
31 | 'queriesPath' => '/queries',
32 | 'mutationsPath' => '/mutations',
33 | 'subscriptionPath' => '/subscription',
34 | ];
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/resources/views/graphiql.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Loading...
23 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/tests/ConfigTest.php:
--------------------------------------------------------------------------------
1 | set('graphql', [
12 |
13 | 'prefix' => 'graphql_test',
14 |
15 | 'routes' => [
16 | 'query' => 'query/{graphql_schema?}',
17 | 'mutation' => 'mutation/{graphql_schema?}'
18 | ],
19 |
20 | 'variables_input_name' => 'variables',
21 |
22 | 'schema' => 'custom',
23 |
24 | 'schemas' => [
25 | 'default' => [
26 | 'query' => [
27 | 'examples' => ExamplesQuery::class,
28 | 'examplesContext' => ExamplesContextQuery::class,
29 | 'examplesRoot' => ExamplesRootQuery::class
30 | ],
31 | 'mutation' => [
32 | 'updateExample' => UpdateExampleMutation::class
33 | ]
34 | ],
35 | 'custom' => [
36 | 'query' => [
37 | 'examplesCustom' => ExamplesQuery::class
38 | ],
39 | 'mutation' => [
40 | 'updateExampleCustom' => UpdateExampleMutation::class
41 | ]
42 | ]
43 | ],
44 |
45 | 'types' => [
46 | 'Example' => ExampleType::class,
47 | CustomExampleType::class
48 | ],
49 |
50 | 'security' => [
51 | 'query_max_complexity' => 1000,
52 | 'query_max_depth' => 10,
53 | ],
54 |
55 | ]);
56 | }
57 |
58 | public function testRouteQuery()
59 | {
60 | $response = $this->call('GET', '/graphql_test/query', [
61 | 'query' => $this->queries['examplesCustom']
62 | ]);
63 |
64 | $this->assertEquals($response->getStatusCode(), 200);
65 |
66 | $content = $response->getOriginalContent();
67 | $this->assertArrayHasKey('data', $content);
68 | }
69 |
70 | public function testRouteMutation()
71 | {
72 | $response = $this->call('POST', '/graphql_test/mutation', [
73 | 'query' => $this->queries['updateExampleCustom']
74 | ]);
75 |
76 | $this->assertEquals($response->getStatusCode(), 200);
77 |
78 | $content = $response->getOriginalContent();
79 | $this->assertArrayHasKey('data', $content);
80 | }
81 |
82 | public function testTypes()
83 | {
84 | $types = GraphQL::getTypes();
85 | $this->assertArrayHasKey('Example', $types);
86 | $this->assertArrayHasKey('CustomExample', $types);
87 | }
88 |
89 | public function testSchema()
90 | {
91 | $schema = GraphQL::schema();
92 | $schemaCustom = GraphQL::schema('custom');
93 |
94 | $this->assertEquals($schema, $schemaCustom);
95 | }
96 |
97 | public function testSchemas()
98 | {
99 | $schemas = GraphQL::getSchemas();
100 |
101 | $this->assertArrayHasKey('default', $schemas);
102 | $this->assertArrayHasKey('custom', $schemas);
103 | }
104 |
105 | public function testVariablesInputName()
106 | {
107 | $response = $this->call('GET', '/graphql_test/query/default', [
108 | 'query' => $this->queries['examplesWithParams'],
109 | 'variables' => [
110 | 'index' => 0
111 | ]
112 | ]);
113 |
114 | $this->assertEquals($response->getStatusCode(), 200);
115 |
116 | $content = $response->getOriginalContent();
117 | $this->assertArrayHasKey('data', $content);
118 | $this->assertEquals($content['data'], [
119 | 'examples' => [
120 | $this->data[0]
121 | ]
122 | ]);
123 | }
124 |
125 | public function testSecurity()
126 | {
127 | $queryComplexity = DocumentValidator::getRule('QueryComplexity');
128 | $this->assertEquals(1000, $queryComplexity->getMaxQueryComplexity());
129 |
130 | $queryDepth = DocumentValidator::getRule('QueryDepth');
131 | $this->assertEquals(10, $queryDepth->getMaxQueryDepth());
132 | }
133 |
134 | public function testErrorFormatter()
135 | {
136 | $error = $this->getMockBuilder(ErrorFormatter::class)
137 | ->setMethods(['formatError'])
138 | ->getMock();
139 |
140 | $error->expects($this->once())
141 | ->method('formatError');
142 |
143 | config([
144 | 'graphql.error_formatter' => [$error, 'formatError']
145 | ]);
146 |
147 | $result = GraphQL::query($this->queries['examplesWithError']);
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/tests/EndpointTest.php:
--------------------------------------------------------------------------------
1 | call('GET', '/graphql', [
16 | 'query' => $this->queries['examples']
17 | ]);
18 |
19 | $this->assertEquals($response->getStatusCode(), 200);
20 |
21 | $content = $response->getOriginalContent();
22 | $this->assertArrayHasKey('data', $content);
23 | $this->assertEquals($content['data'], [
24 | 'examples' => $this->data
25 | ]);
26 | }
27 |
28 | /**
29 | * Test get with custom schema
30 | *
31 | * @test
32 | */
33 | public function testGetCustom()
34 | {
35 | $response = $this->call('GET', '/graphql/custom', [
36 | 'query' => $this->queries['examplesCustom']
37 | ]);
38 |
39 | $content = $response->getOriginalContent();
40 | $this->assertArrayHasKey('data', $content);
41 | $this->assertEquals($content['data'], [
42 | 'examplesCustom' => $this->data
43 | ]);
44 | }
45 |
46 | /**
47 | * Test get with params
48 | *
49 | * @test
50 | */
51 | public function testGetWithParams()
52 | {
53 | $response = $this->call('GET', '/graphql', [
54 | 'query' => $this->queries['examplesWithParams'],
55 | 'params' => [
56 | 'index' => 0
57 | ]
58 | ]);
59 |
60 | $this->assertEquals($response->getStatusCode(), 200);
61 |
62 | $content = $response->getOriginalContent();
63 | $this->assertArrayHasKey('data', $content);
64 | $this->assertEquals($content['data'], [
65 | 'examples' => [
66 | $this->data[0]
67 | ]
68 | ]);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/tests/FieldTest.php:
--------------------------------------------------------------------------------
1 | getFieldClass();
22 | $field = new $class();
23 | $attributes = $field->getAttributes();
24 |
25 | $this->assertArrayHasKey('name', $attributes);
26 | $this->assertArrayHasKey('type', $attributes);
27 | $this->assertArrayHasKey('args', $attributes);
28 | $this->assertArrayHasKey('resolve', $attributes);
29 | $this->assertInternalType('array', $attributes['args']);
30 | $this->assertInstanceOf(Closure::class, $attributes['resolve']);
31 | $this->assertInstanceOf(get_class($field->type()), $attributes['type']);
32 | }
33 |
34 | /**
35 | * Test resolve closure
36 | *
37 | * @test
38 | */
39 | public function testResolve()
40 | {
41 | $class = $this->getFieldClass();
42 | $field = $this->getMockBuilder($class)
43 | ->setMethods(['resolve'])
44 | ->getMock();
45 |
46 | $field->expects($this->once())
47 | ->method('resolve');
48 |
49 | $attributes = $field->getAttributes();
50 | $attributes['resolve'](null, [], [], null);
51 | }
52 |
53 | /**
54 | * Test to array
55 | *
56 | * @test
57 | */
58 | public function testToArray()
59 | {
60 | $class = $this->getFieldClass();
61 | $field = new $class();
62 | $array = $field->toArray();
63 |
64 | $this->assertInternalType('array', $array);
65 |
66 | $attributes = $field->getAttributes();
67 | $this->assertEquals($attributes, $array);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/tests/GraphQLQueryTest.php:
--------------------------------------------------------------------------------
1 | queries['examples']);
19 |
20 | $this->assertObjectHasAttribute('data', $result);
21 |
22 | $this->assertEquals($result->data, [
23 | 'examples' => $this->data
24 | ]);
25 | }
26 |
27 | /**
28 | * Test query methods
29 | *
30 | * @test
31 | */
32 | public function testQuery()
33 | {
34 | $resultArray = GraphQL::query($this->queries['examples']);
35 | $result = GraphQL::queryAndReturnResult($this->queries['examples']);
36 |
37 | $this->assertInternalType('array', $resultArray);
38 | $this->assertArrayHasKey('data', $resultArray);
39 | $this->assertEquals($resultArray['data'], $result->data);
40 | }
41 |
42 | /**
43 | * Test query with params
44 | *
45 | * @test
46 | */
47 | public function testQueryAndReturnResultWithParams()
48 | {
49 | $result = GraphQL::queryAndReturnResult($this->queries['examplesWithParams'], [
50 | 'index' => 0
51 | ]);
52 |
53 | $this->assertObjectHasAttribute('data', $result);
54 | $this->assertCount(0, $result->errors);
55 | $this->assertEquals($result->data, [
56 | 'examples' => [
57 | $this->data[0]
58 | ]
59 | ]);
60 | }
61 |
62 | /**
63 | * Test query with initial root
64 | *
65 | * @test
66 | */
67 | public function testQueryAndReturnResultWithRoot()
68 | {
69 | $result = GraphQL::queryAndReturnResult($this->queries['examplesWithRoot'], null, [
70 | 'root' => [
71 | 'test' => 'root'
72 | ]
73 | ]);
74 |
75 | $this->assertObjectHasAttribute('data', $result);
76 | $this->assertCount(0, $result->errors);
77 | $this->assertEquals($result->data, [
78 | 'examplesRoot' => [
79 | 'test' => 'root'
80 | ]
81 | ]);
82 | }
83 |
84 | /**
85 | * Test query with context
86 | *
87 | * @test
88 | */
89 | public function testQueryAndReturnResultWithContext()
90 | {
91 | $result = GraphQL::queryAndReturnResult($this->queries['examplesWithContext'], null, [
92 | 'context' => [
93 | 'test' => 'context'
94 | ]
95 | ]);
96 | $this->assertObjectHasAttribute('data', $result);
97 | $this->assertCount(0, $result->errors);
98 | $this->assertEquals($result->data, [
99 | 'examplesContext' => [
100 | 'test' => 'context'
101 | ]
102 | ]);
103 | }
104 |
105 | /**
106 | * Test query with schema
107 | *
108 | * @test
109 | */
110 | public function testQueryAndReturnResultWithSchema()
111 | {
112 | $result = GraphQL::queryAndReturnResult($this->queries['examplesCustom'], null, [
113 | 'schema' => [
114 | 'query' => [
115 | 'examplesCustom' => ExamplesQuery::class
116 | ]
117 | ]
118 | ]);
119 |
120 | $this->assertObjectHasAttribute('data', $result);
121 | $this->assertCount(0, $result->errors);
122 | $this->assertEquals($result->data, [
123 | 'examplesCustom' => $this->data
124 | ]);
125 | }
126 |
127 | /**
128 | * Test query with error
129 | *
130 | * @test
131 | */
132 | public function testQueryWithError()
133 | {
134 | $result = GraphQL::query($this->queries['examplesWithError']);
135 |
136 | $this->assertArrayHasKey('data', $result);
137 | $this->assertArrayHasKey('errors', $result);
138 | $this->assertNull($result['data']);
139 | $this->assertCount(1, $result['errors']);
140 | $this->assertArrayHasKey('message', $result['errors'][0]);
141 | $this->assertArrayHasKey('locations', $result['errors'][0]);
142 | }
143 |
144 | /**
145 | * Test query with validation error
146 | *
147 | * @test
148 | */
149 | public function testQueryWithValidationError()
150 | {
151 | $result = GraphQL::query($this->queries['examplesWithValidation']);
152 |
153 | $this->assertArrayHasKey('data', $result);
154 | $this->assertArrayHasKey('errors', $result);
155 | $this->assertArrayHasKey('validation', $result['errors'][0]);
156 | $this->assertTrue($result['errors'][0]['validation']->has('index'));
157 | }
158 |
159 | /**
160 | * Test query with validation without error
161 | *
162 | * @test
163 | */
164 | public function testQueryWithValidation()
165 | {
166 | $result = GraphQL::query($this->queries['examplesWithValidation'], [
167 | 'index' => 0
168 | ]);
169 |
170 | $this->assertArrayHasKey('data', $result);
171 | $this->assertArrayNotHasKey('errors', $result);
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/tests/GraphQLTest.php:
--------------------------------------------------------------------------------
1 | assertGraphQLSchema($schema);
23 | $this->assertGraphQLSchemaHasQuery($schema, 'examples');
24 | $this->assertGraphQLSchemaHasMutation($schema, 'updateExample');
25 | $this->assertArrayHasKey('Example', $schema->getTypeMap());
26 | }
27 |
28 | /**
29 | * Test schema with object
30 | *
31 | * @test
32 | */
33 | public function testSchemaWithSchemaObject()
34 | {
35 | $schemaObject = new Schema([
36 | 'query' => new ObjectType([
37 | 'name' => 'Query'
38 | ]),
39 | 'mutation' => new ObjectType([
40 | 'name' => 'Mutation'
41 | ]),
42 | 'types' => []
43 | ]);
44 | $schema = GraphQL::schema($schemaObject);
45 |
46 | $this->assertGraphQLSchema($schema);
47 | $this->assertEquals($schemaObject, $schema);
48 | }
49 |
50 | /**
51 | * Test schema with name
52 | *
53 | * @test
54 | */
55 | public function testSchemaWithName()
56 | {
57 | $schema = GraphQL::schema('custom');
58 |
59 | $this->assertGraphQLSchema($schema);
60 | $this->assertGraphQLSchemaHasQuery($schema, 'examplesCustom');
61 | $this->assertGraphQLSchemaHasMutation($schema, 'updateExampleCustom');
62 | $this->assertArrayHasKey('Example', $schema->getTypeMap());
63 | }
64 |
65 | /**
66 | * Test schema custom
67 | *
68 | * @test
69 | */
70 | public function testSchemaWithArray()
71 | {
72 | $schema = GraphQL::schema([
73 | 'query' => [
74 | 'examplesCustom' => ExamplesQuery::class
75 | ],
76 | 'mutation' => [
77 | 'updateExampleCustom' => UpdateExampleMutation::class
78 | ],
79 | 'types' => [
80 | CustomExampleType::class
81 | ]
82 | ]);
83 |
84 | $this->assertGraphQLSchema($schema);
85 | $this->assertGraphQLSchemaHasQuery($schema, 'examplesCustom');
86 | $this->assertGraphQLSchemaHasMutation($schema, 'updateExampleCustom');
87 | $this->assertArrayHasKey('CustomExample', $schema->getTypeMap());
88 | }
89 |
90 | /**
91 | * Test schema with wrong name
92 | *
93 | * @test
94 | * @expectedException \Folklore\GraphQL\Exception\SchemaNotFound
95 | */
96 | public function testSchemaWithWrongName()
97 | {
98 | $schema = GraphQL::schema('wrong');
99 | }
100 |
101 | /**
102 | * Test type
103 | *
104 | * @test
105 | */
106 | public function testType()
107 | {
108 | $type = GraphQL::type('Example');
109 | $this->assertInstanceOf(\GraphQL\Type\Definition\ObjectType::class, $type);
110 |
111 | $typeOther = GraphQL::type('Example');
112 | $this->assertTrue($type === $typeOther);
113 |
114 | $typeOther = GraphQL::type('Example', true);
115 | $this->assertFalse($type === $typeOther);
116 | }
117 |
118 | /**
119 | * Test wrong type
120 | *
121 | * @test
122 | * @expectedException \Folklore\GraphQL\Exception\TypeNotFound
123 | */
124 | public function testWrongType()
125 | {
126 | $typeWrong = GraphQL::type('ExampleWrong');
127 | }
128 |
129 | /**
130 | * Test objectType
131 | *
132 | * @test
133 | */
134 | public function testObjectType()
135 | {
136 | $objectType = new ObjectType([
137 | 'name' => 'ObjectType'
138 | ]);
139 | $type = GraphQL::objectType($objectType, [
140 | 'name' => 'ExampleType'
141 | ]);
142 |
143 | $this->assertInstanceOf(\GraphQL\Type\Definition\ObjectType::class, $type);
144 | $this->assertEquals($objectType, $type);
145 | $this->assertEquals($type->name, 'ExampleType');
146 | }
147 |
148 | public function testObjectTypeFromFields()
149 | {
150 | $type = GraphQL::objectType([
151 | 'test' => [
152 | 'type' => Type::string(),
153 | 'description' => 'A test field'
154 | ]
155 | ], [
156 | 'name' => 'ExampleType'
157 | ]);
158 |
159 | $this->assertInstanceOf(\GraphQL\Type\Definition\ObjectType::class, $type);
160 | $this->assertEquals($type->name, 'ExampleType');
161 | $fields = $type->getFields();
162 | $this->assertArrayHasKey('test', $fields);
163 | }
164 |
165 | public function testObjectTypeClass()
166 | {
167 | $type = GraphQL::objectType(ExampleType::class, [
168 | 'name' => 'ExampleType'
169 | ]);
170 |
171 | $this->assertInstanceOf(\GraphQL\Type\Definition\ObjectType::class, $type);
172 | $this->assertEquals($type->name, 'ExampleType');
173 | $fields = $type->getFields();
174 | $this->assertArrayHasKey('test', $fields);
175 | }
176 |
177 | public function testFormatError()
178 | {
179 | $result = GraphQL::queryAndReturnResult($this->queries['examplesWithError']);
180 | $error = GraphQL::formatError($result->errors[0]);
181 |
182 | $this->assertInternalType('array', $error);
183 | $this->assertArrayHasKey('message', $error);
184 | $this->assertArrayHasKey('locations', $error);
185 | $this->assertEquals($error, [
186 | 'message' => 'Cannot query field "examplesQueryNotFound" on type "Query".',
187 | 'locations' => [
188 | [
189 | 'line' => 3,
190 | 'column' => 13
191 | ]
192 | ]
193 | ]);
194 | }
195 |
196 | public function testFormatValidationError()
197 | {
198 | $validator = Validator::make([], [
199 | 'test' => 'required'
200 | ]);
201 | $validator->fails();
202 | $validationError = with(new ValidationError('validation'))->setValidator($validator);
203 | $error = new Error('error', null, $validationError);
204 | $error = GraphQL::formatError($error);
205 |
206 | $this->assertInternalType('array', $error);
207 | $this->assertArrayHasKey('validation', $error);
208 | $this->assertTrue($error['validation']->has('test'));
209 | }
210 |
211 | /**
212 | * Test add type
213 | *
214 | * @test
215 | */
216 | public function testAddType()
217 | {
218 | $this->expectsEvents(TypeAdded::class);
219 |
220 | GraphQL::addType(CustomExampleType::class);
221 |
222 | $types = GraphQL::getTypes();
223 | $this->assertArrayHasKey('CustomExample', $types);
224 |
225 | $type = app($types['CustomExample']);
226 | $this->assertInstanceOf(CustomExampleType::class, $type);
227 |
228 | $type = GraphQL::type('CustomExample');
229 | $this->assertInstanceOf(\GraphQL\Type\Definition\ObjectType::class, $type);
230 | }
231 |
232 | /**
233 | * Test add type with a name
234 | *
235 | * @test
236 | */
237 | public function testAddTypeWithName()
238 | {
239 | GraphQL::addType(ExampleType::class, 'CustomExample');
240 |
241 | $types = GraphQL::getTypes();
242 | $this->assertArrayHasKey('CustomExample', $types);
243 |
244 | $type = app($types['CustomExample']);
245 | $this->assertInstanceOf(ExampleType::class, $type);
246 |
247 | $type = GraphQL::type('CustomExample');
248 | $this->assertInstanceOf(\GraphQL\Type\Definition\ObjectType::class, $type);
249 | }
250 |
251 | /**
252 | * Test get types
253 | *
254 | * @test
255 | */
256 | public function testGetTypes()
257 | {
258 | $types = GraphQL::getTypes();
259 | $this->assertArrayHasKey('Example', $types);
260 |
261 | $type = app($types['Example']);
262 | $this->assertInstanceOf(\Folklore\GraphQL\Support\Type::class, $type);
263 | }
264 |
265 | /**
266 | * Test add schema
267 | *
268 | * @test
269 | */
270 | public function testAddSchema()
271 | {
272 | $this->expectsEvents(SchemaAdded::class);
273 |
274 | GraphQL::addSchema('custom_add', [
275 | 'query' => [
276 | 'examplesCustom' => ExamplesQuery::class
277 | ],
278 | 'mutation' => [
279 | 'updateExampleCustom' => UpdateExampleMutation::class
280 | ],
281 | 'types' => [
282 | CustomExampleType::class
283 | ]
284 | ]);
285 |
286 | $schemas = GraphQL::getSchemas();
287 | $this->assertArrayHasKey('custom_add', $schemas);
288 | }
289 |
290 | /**
291 | * Test get schemas
292 | *
293 | * @test
294 | */
295 | public function testGetSchemas()
296 | {
297 | $schemas = GraphQL::getSchemas();
298 | $this->assertArrayHasKey('default', $schemas);
299 | $this->assertArrayHasKey('custom', $schemas);
300 | $this->assertInternalType('array', $schemas['default']);
301 | }
302 | }
303 |
--------------------------------------------------------------------------------
/tests/GraphiQLTest.php:
--------------------------------------------------------------------------------
1 | assertTrue(app('view')->exists('graphql::graphiql'));
15 | }
16 |
17 | /**
18 | * Test endpoint
19 | *
20 | * @test
21 | */
22 | public function testEndpoint()
23 | {
24 | $queryPath = route('graphql.query');
25 |
26 | $response = $this->call('GET', route('graphql.graphiql'));
27 | $this->assertEquals(200, $response->status());
28 | $this->assertEquals($queryPath, $response->original->graphqlPath);
29 | $content = $response->getContent();
30 | $this->assertContains($queryPath, $content);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/InterfaceTypeTest.php:
--------------------------------------------------------------------------------
1 | getAttributes();
18 |
19 | $this->assertArrayHasKey('resolveType', $attributes);
20 | $this->assertInstanceOf(Closure::class, $attributes['resolveType']);
21 | }
22 |
23 | /**
24 | * Test get attributes resolve type
25 | *
26 | * @test
27 | */
28 | public function testGetAttributesResolveType()
29 | {
30 | $type = $this->getMockBuilder(ExampleInterfaceType::class)
31 | ->setMethods(['resolveType'])
32 | ->getMock();
33 |
34 | $type->expects($this->once())
35 | ->method('resolveType');
36 |
37 | $attributes = $type->getAttributes();
38 | $attributes['resolveType'](null);
39 | }
40 |
41 | /**
42 | * Test to type
43 | *
44 | * @test
45 | */
46 | public function testToType()
47 | {
48 | $type = new ExampleInterfaceType();
49 | $interfaceType = $type->toType();
50 |
51 | $this->assertInstanceOf(InterfaceType::class, $interfaceType);
52 |
53 | $this->assertEquals($interfaceType->name, $type->name);
54 |
55 | $fields = $interfaceType->getFields();
56 | $this->assertArrayHasKey('test', $fields);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/tests/MutationTest.php:
--------------------------------------------------------------------------------
1 | set('graphql.types', [
20 | 'Example' => ExampleType::class
21 | ]);
22 | }
23 |
24 | /**
25 | * Test get rules
26 | *
27 | * @test
28 | */
29 | public function testGetRules()
30 | {
31 | $class = $this->getFieldClass();
32 | $field = new $class();
33 | $rules = $field->getRules();
34 |
35 | $this->assertInternalType('array', $rules);
36 | $this->assertArrayHasKey('test', $rules);
37 | $this->assertArrayHasKey('test_with_rules', $rules);
38 | $this->assertArrayHasKey('test_with_rules_closure', $rules);
39 | $this->assertEquals($rules['test'], ['required']);
40 | $this->assertEquals($rules['test_with_rules'], ['required']);
41 | $this->assertEquals($rules['test_with_rules_closure'], ['required']);
42 | }
43 |
44 | /**
45 | * Test resolve
46 | *
47 | * @test
48 | */
49 | public function testResolve()
50 | {
51 | $class = $this->getFieldClass();
52 | $field = $this->getMockBuilder($class)
53 | ->setMethods(['resolve'])
54 | ->getMock();
55 |
56 | $field->expects($this->once())
57 | ->method('resolve');
58 |
59 | $attributes = $field->getAttributes();
60 | $attributes['resolve'](null, [
61 | 'test' => 'test',
62 | 'test_with_rules' => 'test',
63 | 'test_with_rules_closure' => 'test'
64 | ], [], null);
65 | }
66 |
67 | /**
68 | * Test resolve throw validation error
69 | *
70 | * @test
71 | * @expectedException \Folklore\GraphQL\Error\ValidationError
72 | */
73 | public function testResolveThrowValidationError()
74 | {
75 | $class = $this->getFieldClass();
76 | $field = new $class();
77 |
78 | $attributes = $field->getAttributes();
79 | $attributes['resolve'](null, [], [], null);
80 | }
81 |
82 | /**
83 | * Test validation error
84 | *
85 | * @test
86 | */
87 | public function testValidationError()
88 | {
89 | $class = $this->getFieldClass();
90 | $field = new $class();
91 |
92 | $attributes = $field->getAttributes();
93 |
94 | try {
95 | $attributes['resolve'](null, [], [], null);
96 | } catch (\Folklore\GraphQL\Error\ValidationError $e) {
97 | $validator = $e->getValidator();
98 |
99 | $this->assertInstanceOf(Validator::class, $validator);
100 |
101 | $messages = $e->getValidatorMessages();
102 | $this->assertTrue($messages->has('test'));
103 | $this->assertTrue($messages->has('test_with_rules'));
104 | $this->assertTrue($messages->has('test_with_rules_closure'));
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/tests/Objects/CustomExampleType.php:
--------------------------------------------------------------------------------
1 | 'CustomExample',
11 | 'description' => 'An example'
12 | ];
13 |
14 | public function fields()
15 | {
16 | return [
17 | 'test' => [
18 | 'type' => Type::string(),
19 | 'description' => 'A test field'
20 | ]
21 | ];
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/tests/Objects/ErrorFormatter.php:
--------------------------------------------------------------------------------
1 | $e->getMessage()
12 | ];
13 |
14 | $locations = $e->getLocations();
15 | if (!empty($locations)) {
16 | $error['locations'] = array_map(function ($loc) {
17 | return $loc->toArray();
18 | }, $locations);
19 | }
20 |
21 | $previous = $e->getPrevious();
22 | if ($previous && $previous instanceof ValidationError) {
23 | $error['validation'] = $previous->getValidatorMessages();
24 | }
25 |
26 | return $error;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/Objects/ExampleField.php:
--------------------------------------------------------------------------------
1 | 'example'
10 | ];
11 |
12 | public function type()
13 | {
14 | return Type::listOf(Type::string());
15 | }
16 |
17 | public function args()
18 | {
19 | return [
20 | 'index' => [
21 | 'name' => 'index',
22 | 'type' => Type::int()
23 | ]
24 | ];
25 | }
26 |
27 | public function resolve($root, $args)
28 | {
29 | return ['test'];
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/Objects/ExampleInterfaceType.php:
--------------------------------------------------------------------------------
1 | 'ExampleInterface',
11 | 'description' => 'An example interface'
12 | ];
13 |
14 | public function resolveType($root)
15 | {
16 | return Type::string();
17 | }
18 |
19 | public function fields()
20 | {
21 | return [
22 | 'test' => [
23 | 'type' => Type::string(),
24 | 'description' => 'A test field'
25 | ]
26 | ];
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/Objects/ExampleType.php:
--------------------------------------------------------------------------------
1 | 'Example',
11 | 'description' => 'An example'
12 | ];
13 |
14 | public function fields()
15 | {
16 | return [
17 | 'test' => [
18 | 'type' => Type::string(),
19 | 'description' => 'A test field'
20 | ],
21 | 'test_validation' => ExampleValidationField::class
22 | ];
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tests/Objects/ExampleValidationField.php:
--------------------------------------------------------------------------------
1 | 'example_validation'
13 | ];
14 |
15 | public function type()
16 | {
17 | return Type::listOf(Type::string());
18 | }
19 |
20 | public function args()
21 | {
22 | return [
23 | 'index' => [
24 | 'name' => 'index',
25 | 'type' => Type::int(),
26 | 'rules' => ['required']
27 | ]
28 | ];
29 | }
30 |
31 | public function resolve($root, $args)
32 | {
33 | return ['test'];
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/tests/Objects/ExamplesContextQuery.php:
--------------------------------------------------------------------------------
1 | 'Examples context query'
11 | ];
12 |
13 | public function type()
14 | {
15 | return GraphQL::type('Example');
16 | }
17 |
18 | public function resolve($root, $args, $context)
19 | {
20 | return $context;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/Objects/ExamplesQuery.php:
--------------------------------------------------------------------------------
1 | 'examples'
11 | ];
12 |
13 | public function type()
14 | {
15 | return Type::listOf(GraphQL::type('Example'));
16 | }
17 |
18 | public function args()
19 | {
20 | return [
21 | 'index' => ['name' => 'index', 'type' => Type::int()]
22 | ];
23 | }
24 |
25 | public function resolve($root, $args)
26 | {
27 | $data = include(__DIR__.'/data.php');
28 |
29 | if (isset($args['index'])) {
30 | return [
31 | $data[$args['index']]
32 | ];
33 | }
34 |
35 | return $data;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/tests/Objects/ExamplesRootQuery.php:
--------------------------------------------------------------------------------
1 | 'Examples root query'
11 | ];
12 |
13 | public function type()
14 | {
15 | return GraphQL::type('Example');
16 | }
17 |
18 | public function resolve($root, $args, $context)
19 | {
20 | return $root;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/Objects/UpdateExampleMutation.php:
--------------------------------------------------------------------------------
1 | 'updateExample'
11 | ];
12 |
13 | public function type()
14 | {
15 | return GraphQL::type('Example');
16 | }
17 |
18 | public function rules()
19 | {
20 | return [
21 | 'test' => ['required']
22 | ];
23 | }
24 |
25 | public function args()
26 | {
27 | return [
28 | 'test' => [
29 | 'name' => 'test',
30 | 'type' => Type::string()
31 | ],
32 |
33 | 'test_with_rules' => [
34 | 'name' => 'test',
35 | 'type' => Type::string(),
36 | 'rules' => ['required']
37 | ],
38 |
39 | 'test_with_rules_closure' => [
40 | 'name' => 'test',
41 | 'type' => Type::string(),
42 | 'rules' => function () {
43 | return ['required'];
44 | }
45 | ]
46 | ];
47 | }
48 |
49 | public function resolve($root, $args)
50 | {
51 | return [
52 | 'test' => array_get($args, 'test')
53 | ];
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/tests/Objects/data.php:
--------------------------------------------------------------------------------
1 | 'Example 1'
6 | ],
7 | [
8 | 'test' => 'Example 2'
9 | ],
10 | [
11 | 'test' => 'Example 3'
12 | ]
13 | ];
14 |
--------------------------------------------------------------------------------
/tests/Objects/queries.php:
--------------------------------------------------------------------------------
1 | "
7 | query QueryExamples {
8 | examples {
9 | test
10 | }
11 | }
12 | ",
13 |
14 | 'examplesCustom' => "
15 | query QueryExamplesCustom {
16 | examplesCustom {
17 | test
18 | }
19 | }
20 | ",
21 |
22 | 'examplesWithParams' => "
23 | query QueryExamplesParams(\$index: Int) {
24 | examples(index: \$index) {
25 | test
26 | }
27 | }
28 | ",
29 |
30 | 'examplesWithContext' => "
31 | query QueryExamplesContext {
32 | examplesContext {
33 | test
34 | }
35 | }
36 | ",
37 |
38 | 'examplesWithRoot' => "
39 | query QueryExamplesRoot {
40 | examplesRoot {
41 | test
42 | }
43 | }
44 | ",
45 |
46 | 'examplesWithError' => "
47 | query QueryExamplesWithError {
48 | examplesQueryNotFound {
49 | test
50 | }
51 | }
52 | ",
53 |
54 | 'examplesWithValidation' => "
55 | query QueryExamplesWithValidation(\$index: Int) {
56 | examples {
57 | test_validation(index: \$index)
58 | }
59 | }
60 | ",
61 |
62 | 'updateExampleCustom' => "
63 | mutation UpdateExampleCustom(\$test: String) {
64 | updateExampleCustom(test: \$test) {
65 | test
66 | }
67 | }
68 | "
69 |
70 | ];
71 |
--------------------------------------------------------------------------------
/tests/QueryTest.php:
--------------------------------------------------------------------------------
1 | set('graphql.types', [
19 | 'Example' => ExampleType::class
20 | ]);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | queries = include(__DIR__.'/Objects/queries.php');
18 | $this->data = include(__DIR__.'/Objects/data.php');
19 | }
20 |
21 | protected function getEnvironmentSetUp($app)
22 | {
23 | $app['config']->set('graphql.schemas.default', [
24 | 'query' => [
25 | 'examples' => ExamplesQuery::class,
26 | 'examplesContext' => ExamplesContextQuery::class,
27 | 'examplesRoot' => ExamplesRootQuery::class
28 | ],
29 | 'mutation' => [
30 | 'updateExample' => UpdateExampleMutation::class
31 | ]
32 | ]);
33 |
34 | $app['config']->set('graphql.schemas.custom', [
35 | 'query' => [
36 | 'examplesCustom' => ExamplesQuery::class
37 | ],
38 | 'mutation' => [
39 | 'updateExampleCustom' => UpdateExampleMutation::class
40 | ]
41 | ]);
42 |
43 | $app['config']->set('graphql.types', [
44 | 'Example' => ExampleType::class
45 | ]);
46 | }
47 |
48 | protected function assertGraphQLSchema($schema)
49 | {
50 | $this->assertInstanceOf('GraphQL\Schema', $schema);
51 | }
52 |
53 | protected function assertGraphQLSchemaHasQuery($schema, $key)
54 | {
55 | //Query
56 | $query = $schema->getQueryType();
57 | $queryFields = $query->getFields();
58 | $this->assertArrayHasKey($key, $queryFields);
59 |
60 | $queryField = $queryFields[$key];
61 | $queryListType = $queryField->getType();
62 | $queryType = $queryListType->getWrappedType();
63 | $this->assertInstanceOf('GraphQL\Type\Definition\FieldDefinition', $queryField);
64 | $this->assertInstanceOf('GraphQL\Type\Definition\ListOfType', $queryListType);
65 | $this->assertInstanceOf('GraphQL\Type\Definition\ObjectType', $queryType);
66 | }
67 |
68 | protected function assertGraphQLSchemaHasMutation($schema, $key)
69 | {
70 | //Mutation
71 | $mutation = $schema->getMutationType();
72 | $mutationFields = $mutation->getFields();
73 | $this->assertArrayHasKey($key, $mutationFields);
74 |
75 | $mutationField = $mutationFields[$key];
76 | $mutationType = $mutationField->getType();
77 | $this->assertInstanceOf('GraphQL\Type\Definition\FieldDefinition', $mutationField);
78 | $this->assertInstanceOf('GraphQL\Type\Definition\ObjectType', $mutationType);
79 | }
80 |
81 | protected function getPackageProviders($app)
82 | {
83 | return [
84 | \Folklore\GraphQL\ServiceProvider::class
85 | ];
86 | }
87 |
88 | protected function getPackageAliases($app)
89 | {
90 | return [
91 | 'GraphQL' => \Folklore\GraphQL\Support\Facades\GraphQL::class
92 | ];
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/tests/TypeTest.php:
--------------------------------------------------------------------------------
1 | getFields();
18 |
19 | $this->assertArrayHasKey('test', $fields);
20 | $this->assertEquals($fields['test'], [
21 | 'type' => Type::string(),
22 | 'description' => 'A test field'
23 | ]);
24 | }
25 |
26 | /**
27 | * Test get attributes
28 | *
29 | * @test
30 | */
31 | public function testGetAttributes()
32 | {
33 | $type = new ExampleType();
34 | $attributes = $type->getAttributes();
35 |
36 | $this->assertArrayHasKey('name', $attributes);
37 | $this->assertArrayHasKey('fields', $attributes);
38 | $this->assertInstanceOf(Closure::class, $attributes['fields']);
39 | $this->assertInternalType('array', $attributes['fields']());
40 | }
41 |
42 | /**
43 | * Test get attributes fields closure
44 | *
45 | * @test
46 | */
47 | public function testGetAttributesFields()
48 | {
49 | $type = $this->getMockBuilder(ExampleType::class)
50 | ->setMethods(['getFields'])
51 | ->getMock();
52 |
53 | $type->expects($this->once())
54 | ->method('getFields');
55 |
56 | $attributes = $type->getAttributes();
57 | $attributes['fields']();
58 | }
59 |
60 | /**
61 | * Test to array
62 | *
63 | * @test
64 | */
65 | public function testToArray()
66 | {
67 | $type = new ExampleType();
68 | $array = $type->toArray();
69 |
70 | $this->assertInternalType('array', $array);
71 |
72 | $attributes = $type->getAttributes();
73 | $this->assertEquals($attributes, $array);
74 | }
75 |
76 | /**
77 | * Test to type
78 | *
79 | * @test
80 | */
81 | public function testToType()
82 | {
83 | $type = new ExampleType();
84 | $objectType = $type->toType();
85 |
86 | $this->assertInstanceOf(ObjectType::class, $objectType);
87 |
88 | $this->assertEquals($objectType->name, $type->name);
89 |
90 | $fields = $objectType->getFields();
91 | $this->assertArrayHasKey('test', $fields);
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/tests/fixture/app/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/tests/fixture/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "laravel/laravel",
3 | "description": "The Laravel Framework.",
4 | "keywords": ["framework", "laravel"],
5 | "license": "MIT",
6 | "type": "project",
7 | "require": {
8 | "laravel/framework": "~5.0"
9 | },
10 | "require-dev": {
11 | "phpunit/phpunit": "~4.0"
12 | },
13 | "autoload": {
14 | "classmap": [
15 | "database",
16 | "tests/TestCase.php"
17 | ],
18 | "psr-4": {
19 | "App\\": "app/"
20 | }
21 | },
22 | "minimum-stability": "dev"
23 | }
24 |
--------------------------------------------------------------------------------
/tests/logs/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------