Skip to content

Commit

Permalink
Support schema extension
Browse files Browse the repository at this point in the history
  • Loading branch information
wecc committed Nov 21, 2023
1 parent bc8f29c commit f04616c
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- Support extending schema using partial GraphQL schema files.

## [10.0.0] - 2023-03-02

Expand Down
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,33 @@ Butler GraphQL will make sure that `loadComments` is only called once.

If you don't want to use `Closure::fromCallable(...)` you can change the accessibility of `loadComments` to `public`.

## Splitting a Large Schema File Into Multiple Files

Butler GraphQL lets you easily split certain parts of your GraphQL schema file to separate files.

*schema.grapqhl*
```graphql
type Query {
users: [User!]!
}

type User {
id: ID!
username: String!
}
```

*schema-user-attributes.grapqhl*
```graphql
extend type User {
firstName: String
lastName: String
email: String!
}
```

*NOTE:* A base schema is always required and it's recommended to stay away from multiple levels of extensions.

## Customize

There's no real need to configure Butler GraphQL. It's designed with *convention over configuration* in mind and should be ready to go without any configuration.
Expand All @@ -296,6 +323,11 @@ php artisan vendor:publish
- `BUTLER_GRAPHQL_SCHEMA` – Defaults to `app_path('Http/Graphql/schema.graphql')`.
- `BUTLER_GRAPHQL_NAMESPACE` – Defaults to `'App\\Http\\Graphql\\'`.

### Change the Path and Pattern for Partial Schema Files

- `BUTLER_GRAPHQL_SCHEMA_EXTENSIONS_PATH` – Defaults to `app_path('Http/Graphql/')`.
- `BUTLER_GRAPHQL_SCHEMA_EXTENSIONS_GLOB` – Defaults to `'schema-*.graphql'`.

### Debugging

- `BUTLER_GRAPHQL_INCLUDE_DEBUG_MESSAGE` – Set to `true` to include the real error message in error responses. Defaults to `false`.
Expand Down
3 changes: 3 additions & 0 deletions config/butler.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
'namespace' => env('BUTLER_GRAPHQL_NAMESPACE', 'App\\Http\\Graphql\\'),

'schema' => env('BUTLER_GRAPHQL_SCHEMA', base_path('app/Http/Graphql/schema.graphql')),

'schema_extensions_path' => env('BUTLER_GRAPHQL_SCHEMA_EXTENSIONS_PATH', base_path('app/Http/Graphql/')),
'schema_extensions_glob' => env('BUTLER_GRAPHQL_SCHEMA_EXTENSIONS_GLOB', 'schema-*.graphql'),

],

Expand Down
23 changes: 23 additions & 0 deletions src/Concerns/HandlesGraphqlRequests.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use GraphQL\Type\Definition\WrappingType;
use GraphQL\Type\Schema;
use GraphQL\Utils\BuildSchema;
use GraphQL\Utils\SchemaExtender;
use Illuminate\Contracts\Debug\ExceptionHandler;
use Illuminate\Database\Eloquent\MissingAttributeException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
Expand Down Expand Up @@ -55,6 +56,10 @@ public function __invoke(Request $request)
try {
$schema = BuildSchema::build($this->schema(), [$this, 'decorateTypeConfig']);

foreach ($this->schemaExtensions() as $extension) {
$schema = SchemaExtender::extend($schema, Parser::parse($extension), [], [$this, 'decorateTypeConfig']);
}

$source = Parser::parse($query);

$this->beforeExecutionHook($schema, $source, $operationName, $variables);
Expand Down Expand Up @@ -156,6 +161,24 @@ public function schemaPath()
return config('butler.graphql.schema');
}

public function schemaExtensions()
{
$path = Str::finish($this->schemaExtensionsPath(), DIRECTORY_SEPARATOR);
$glob = $this->schemaExtensionsGlob();

return collect(glob("{$path}{$glob}"))->map(file_get_contents(...))->toArray();
}

public function schemaExtensionsPath()
{
return config('butler.graphql.schema_extensions_path');
}

public function schemaExtensionsGlob()
{
return config('butler.graphql.schema_extensions_glob');
}

public function decorateTypeConfig(array $config, TypeDefinitionNode $typeDefinitionNode)
{
if ($this->shouldDecorateWithResolveType($typeDefinitionNode)) {
Expand Down
27 changes: 27 additions & 0 deletions tests/HandlesGraphqlRequestsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -812,4 +812,31 @@ public function test_custom_resolver_skips_array_and_object_resolving()
$data
);
}

public function test_extending_schema()
{
$this->app->config->set('butler.graphql.schema_extensions_path', __DIR__ . '/stubs/schema-extensions');

$controller = $this->app->make(GraphqlController::class);
$data = $controller(Request::create('/', 'POST', [
'query' => 'query {
extendedQuery {
field1
field2
}
}'
]));

$this->assertSame(
[
'data' => [
'extendedQuery' => [
'field1' => 'This is field 1',
'field2' => 'This is field 2',
]
],
],
$data
);
}
}
14 changes: 14 additions & 0 deletions tests/stubs/Queries/ExtendedQuery.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Butler\Graphql\Tests\Queries;

class ExtendedQuery
{
public function __invoke()
{
return [
'field1' => 'This is field 1',
'field2' => 'This is field 2',
];
}
}
7 changes: 7 additions & 0 deletions tests/stubs/schema-extensions/schema-01-extension.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
extend type Query {
extendedQuery: ExtendedType!
}

type ExtendedType {
field1: String!
}
3 changes: 3 additions & 0 deletions tests/stubs/schema-extensions/schema-02-extension.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
extend type ExtendedType {
field2: String!
}

0 comments on commit f04616c

Please sign in to comment.