1. Go to this page and download the library: Download rebing/graphql-laravel library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
rebing / graphql-laravel example snippets
declare(strict_types = 1);
namespace App\GraphQL\Types;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Type as GraphQLType;
class BookType extends GraphQLType
{
protected $attributes = [
'name' => 'Book',
'description' => 'A book',
];
public function fields(): array
{
return [
'id' => [
'type' => Type::nonNull(Type::int()),
'description' => 'The id of the book',
],
'title' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The title of the book',
],
'author' => [
'type' => Type::string(),
'description' => 'The name of the author',
],
];
}
}
declare(strict_types = 1);
namespace App\GraphQL\Queries;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Query;
class BooksQuery extends Query
{
protected $attributes = [
'name' => 'books',
];
public function type(): Type
{
return Type::nonNull(Type::listOf(Type::nonNull(GraphQL::type('Book'))));
}
public function args(): array
{
return [
'title' => [
'type' => Type::string(),
'description' => 'Filter by title',
],
];
}
public function resolve($root, array $args): array
{
$books = [
['id' => 1, 'title' => 'The Great Gatsby', 'author' => 'F. Scott Fitzgerald'],
['id' => 2, 'title' => '1984', 'author' => 'George Orwell'],
['id' => 3, 'title' => 'To Kill a Mockingbird', 'author' => 'Harper Lee'],
];
if (isset($args['title'])) {
return array_values(array_filter($books, fn ($book) => str_contains($book['title'], $args['title'])));
}
return $books;
}
}
'default_schema' => 'default',
'schemas' => [
'default' => [
'query' => [
ExampleQuery::class,
],
'mutation' => [
ExampleMutation::class,
],
'types' => [
],
],
'user' => [
'query' => [
App\GraphQL\Queries\ProfileQuery::class
],
'mutation' => [
],
'types' => [
],
'middleware' => ['auth:api'],
// Which HTTP methods to support; must be given in UPPERCASE!
// Default is POST only; enable GET explicitly if needed
'method' => ['GET', 'POST'],
'execution_middleware' => [
\Rebing\GraphQL\Support\ExecutionMiddleware\UnusedVariablesMiddleware::class,
],
// Route attributes applied to the generated HTTP route for this schema
// Example: expose this schema on a dedicated subdomain
'route_attributes' => [
'domain' => 'api.example.com',
],
// Per-schema route group attributes. The `guard` entry is consumed by
// the built-in `AddAuthUserContextValueMiddleware` to populate the
// GraphQL context value (`$ctx`) from the named guard. Defaults to
// the application's default guard when unset.
'group_attributes' => [
'guard' => 'api',
],
// Override the default controller for this schema.
// Supports string ('Class@method') and array ([Class::class, 'method']) formats.
// The controller method receives the same parameters as GraphQLController@query.
// 'controller' => App\Http\Controllers\MyGraphQLController::class . '@query',
],
],
declare(strict_types = 1);
namespace App\GraphQL\Schemas;
use Rebing\GraphQL\Support\Contracts\ConfigConvertible;
class DefaultSchema implements ConfigConvertible
{
public function toConfig(): array
{
return [
'query' => [
ExampleQuery::class,
],
'mutation' => [
ExampleMutation::class,
],
'types' => [
],
];
}
}
declare(strict_types = 1);
namespace App\GraphQL\Types;
use App\Models\User;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Type as GraphQLType;
class UserType extends GraphQLType
{
protected $attributes = [
'name' => 'User',
'description' => 'A user',
// Note: only necessary if you use the SelectFields package
'model' => User::class,
];
public function fields(): array
{
return [
'id' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The id of the user',
// Use 'alias', if the database column is different from the type name.
// This is supported for discrete values as well as relations.
// - you can also use `DB::raw()` to solve more complex issues
// - or a callback returning the value (string or `DB::raw()` result)
'alias' => 'user_id',
],
'email' => [
'type' => Type::string(),
'description' => 'The email of user',
'resolve' => function($root, array $args) {
// If you want to resolve the field yourself,
// it can be done here
return strtolower($root->email);
}
],
// Uses the 'getIsMeAttribute' function on our custom User model
'isMe' => [
'type' => Type::boolean(),
'description' => 'True, if the queried user is the current user',
'selectable' => false, // Does not try to query this from the database
],
// Reference another registered GraphQL type for relations
'profile' => [
'type' => GraphQL::type('UserProfile'),
'description' => 'The user profile',
],
];
}
// You can also resolve a field by declaring a method in the class
// with the following format resolve[FIELD_NAME]Field()
protected function resolveEmailField($root, array $args)
{
return strtolower($root->email);
}
}
declare(strict_types = 1);
namespace App\GraphQL\Queries;
use App\Models\User;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Query;
class UserQuery extends Query
{
protected $attributes = [
'name' => 'user',
];
public function type(): Type
{
return Type::nonNull(GraphQL::type('User'));
}
public function args(): array
{
return [
'id' => [
'type' => Type::nonNull(Type::int()),
],
];
}
public function resolve($root, array $args, $context, ResolveInfo $resolveInfo)
{
return User::findOrFail($args['id']);
}
}
declare(strict_types = 1);
namespace App\GraphQL\Mutations;
use Closure;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Rebing\GraphQL\Support\Facades\GraphQL;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\ResolveInfo;
use Rebing\GraphQL\Support\Mutation;
class UpdateUserPasswordMutation extends Mutation
{
protected $attributes = [
'name' => 'updateUserPassword'
];
public function type(): Type
{
return Type::nonNull(GraphQL::type('User'));
}
public function args(): array
{
return [
'id' => [
'type' => Type::nonNull(Type::string()),
],
'password' => [
'type' => Type::nonNull(Type::string()),
]
];
}
public function resolve($root, array $args, $context, ResolveInfo $resolveInfo)
{
$user = User::find($args['id']);
if(!$user) {
return null;
}
$user->password = Hash::make($args['password']);
$user->save();
return $user;
}
}
declare(strict_types = 1);
namespace App\GraphQL\Mutations;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Mutation;
class LoginMutation extends Mutation
{
protected $attributes = [
'name' => 'login',
'description' => 'Log in by email and password',
];
public function type(): Type
{
return GraphQL::type('User');
}
public function args(): array
{
return [
'email' => [
'type' => Type::nonNull(Type::string()),
'rules' => ['
declare(strict_types = 1);
namespace App\GraphQL\Mutations;
use Closure;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Mutation;
class UserProfilePhotoMutation extends Mutation
{
protected $attributes = [
'name' => 'userProfilePhoto',
];
public function type(): Type
{
return GraphQL::type('User');
}
public function args(): array
{
return [
'profilePicture' => [
'type' => GraphQL::type('Upload'),
'rules' => ['
class UpdateUserEmailMutation extends Mutation
{
//...
public function args(): array
{
return [
'id' => [
'type' => Type::string(),
'rules' => ['
declare(strict_types = 1);
namespace App\GraphQL\Mutations;
use Closure;
use App\Models\User;
use Rebing\GraphQL\Support\Facades\GraphQL;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Mutation;
class UpdateUserEmailMutation extends Mutation
{
protected $attributes = [
'name' => 'updateUserEmail'
];
public function type(): Type
{
return GraphQL::type('User');
}
public function args(): array
{
return [
'id' => [
'type' => Type::string(),
],
'email' => [
'type' => Type::string(),
]
];
}
protected function rules(array $args = []): array
{
return [
'id' => ['
public function validationErrorMessages(array $args = []): array
{
return [
'name.ase enter your email address',
'email.email' => 'Please enter a valid email address',
'email.exists' => 'Sorry, this email address is already in use',
];
}
public function validationAttributes(array $args = []): array
{
return [
'email' => 'email address',
];
}
...
public function boot()
{
...
GraphQL::appendGlobalResolverMiddleware(YourMiddleware::class);
// Or with new instance
GraphQL::appendGlobalResolverMiddleware(new YourMiddleware(...));
}
declare(strict_types = 1);
namespace App\GraphQL\Middleware;
use Countable;
use GraphQL\Language\Printer;
use GraphQL\Type\Definition\ResolveInfo;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\AbstractPaginator;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Route;
use Rebing\GraphQL\Support\Middleware;
class Logstash extends Middleware
{
public function terminate($field, array $args, $context, ResolveInfo $info, $result): void
{
Log::channel('logstash')->info('', (
collect([
'query' => $info->fieldName,
'operation' => $info->operation->name->value ?? null,
'type' => $info->operation->operation,
'fields' => array_keys(Arr::dot($info->getFieldSelection($depth = PHP_INT_MAX))),
'schema' => Arr::first(Route::current()->parameters()) ?? Config::get('graphql.default_schema', 'default'),
'vars' => $this->formatVariableDefinitions($info->operation->variableDefinitions),
])
->when($result instanceof Countable, function ($metadata) use ($result) {
return $metadata->put('count', $result->count());
})
->when($result instanceof AbstractPaginator, function ($metadata) use ($result) {
return $metadata->put('per_page', $result->perPage());
})
->when($result instanceof LengthAwarePaginator, function ($metadata) use ($result) {
return $metadata->put('total', $result->total());
})
->merge($this->formatArguments($args))
->toArray()
));
}
private function formatArguments(array $args): array
{
return collect($args)
->mapWithKeys(function ($value, $key) {
return ["\${$key}" => $value];
})
->toArray();
}
private function formatVariableDefinitions(?iterable $variableDefinitions = []): array
{
return collect($variableDefinitions)
->map(function ($def) {
return Printer::doPrint($def);
})
->toArray();
}
}
declare(strict_types = 1);
namespace App\GraphQL\Queries;
use Illuminate\Support\Facades\Auth;
use GraphQL\Type\Definition\ResolveInfo;
class UsersQuery extends Query
{
public function authorize($root, array $args, $ctx, ResolveInfo $resolveInfo = null): bool
{
// true, if logged in
return ! Auth::guest();
}
// ...
}
declare(strict_types = 1);
namespace App\GraphQL\Queries;
use Illuminate\Support\Facades\Auth;
use GraphQL\Type\Definition\ResolveInfo;
class UsersQuery extends Query
{
public function authorize($root, array $args, $ctx, ResolveInfo $resolveInfo = null): bool
{
if (isset($args['id'])) {
return Auth::id() == $args['id'];
}
return true;
}
// ...
}
declare(strict_types = 1);
namespace App\GraphQL\Queries;
use Illuminate\Support\Facades\Auth;
use GraphQL\Type\Definition\ResolveInfo;
class UsersQuery extends Query
{
public function authorize($root, array $args, $ctx, ResolveInfo $resolveInfo = null): bool
{
if (isset($args['id'])) {
return Auth::id() == $args['id'];
}
return true;
}
public function getAuthorizationMessage(): string
{
return 'You are not authorized to perform this action';
}
// ...
}
declare(strict_types = 1);
namespace App\GraphQL\Concerns;
use Illuminate\Support\Facades\Auth;
trait RequiresAuthentication
{
public function authorize($root, array $args, $ctx): bool
{
return !Auth::guest();
}
}
class UsersQuery extends Query
{
use \App\GraphQL\Concerns\RequiresAuthentication;
// ...
}
use Illuminate\Support\Facades\Auth;
class UserType extends GraphQLType
{
// ...
public function fields(): array
{
return [
'id' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The id of the user',
],
'email' => [
'type' => Type::string(),
'description' => 'The email of user',
'privacy' => function (mixed $root, array $args, $ctx): bool {
// Only the authenticated user can see their own email.
// $root is the User model being resolved.
// $ctx is the query context value (see notes below).
// By default, AddAuthUserContextValueMiddleware sets
// $ctx to the authenticated user model directly.
return $root->id === Auth::id();
},
],
];
}
// ...
}
use Illuminate\Support\Facades\Auth;
use GraphQL\Type\Definition\ResolveInfo;
use Rebing\GraphQL\Support\Privacy;
class MePrivacy extends Privacy
{
public function validate(mixed $root, array $fieldArgs, mixed $queryContext = null, ?ResolveInfo $resolveInfo = null): bool
{
return $root->id === Auth::id();
}
}
use MePrivacy;
class UserType extends GraphQLType
{
// ...
public function fields(): array
{
return [
'id' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The id of the user',
],
'email' => [
'type' => Type::string(),
'description' => 'The email of user',
'privacy' => MePrivacy::class,
],
];
}
// ...
}
'ssn' => [
'type' => Type::string(),
'args' => [
'reason' => [
'type' => Type::nonNull(Type::string()),
],
],
'privacy' => function (mixed $root, array $args, $ctx): bool {
// Only allow access when a valid reason is provided.
return in_array($args['reason'] ?? '', ['legal', 'compliance']);
},
],
declare(strict_types = 1);
namespace App\GraphQL\Fields;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Field;
class PictureField extends Field
{
protected $attributes = [
'description' => 'A picture',
];
public function type(): Type
{
return Type::string();
}
public function args(): array
{
return [
'width' => [
'type' => Type::int(),
'description' => 'The width of the picture'
],
'height' => [
'type' => Type::int(),
'description' => 'The height of the picture'
]
];
}
protected function resolve($root, array $args)
{
$width = isset($args['width']) ? $args['width']:100;
$height = isset($args['height']) ? $args['height']:100;
return 'https://placehold.co/'.$width.'x'.$height;
}
}
declare(strict_types = 1);
namespace App\GraphQL\Types;
use App\GraphQL\Fields\PictureField;
use App\Models\User;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Type as GraphQLType;
class UserType extends GraphQLType
{
protected $attributes = [
'name' => 'User',
'description' => 'A user',
'model' => User::class,
];
public function fields(): array
{
return [
'id' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The id of the user'
],
'email' => [
'type' => Type::string(),
'description' => 'The email of user'
],
//Instead of passing an array, you pass a class path to your custom field
'picture' => PictureField::class
];
}
}
declare(strict_types = 1);
namespace App\GraphQL\Fields;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Field;
class FormattableDate extends Field
{
protected $attributes = [
'description' => 'A field that can output a date in all sorts of ways.',
];
public function __construct(array $settings = [])
{
$this->attributes = \array_merge($this->attributes, $settings);
}
public function type(): Type
{
return Type::string();
}
public function args(): array
{
return [
'format' => [
'type' => Type::string(),
'defaultValue' => 'Y-m-d H:i',
'description' => 'Defaults to Y-m-d H:i',
],
'relative' => [
'type' => Type::boolean(),
'defaultValue' => false,
],
];
}
protected function resolve($root, array $args): ?string
{
$date = $root->{$this->getProperty()};
if (!$date instanceof Carbon) {
return null;
}
if ($args['relative']) {
return $date->diffForHumans();
}
return $date->format($args['format']);
}
protected function getProperty(): string
{
return $this->attributes['alias'] ?? $this->attributes['name'];
}
}
declare(strict_types = 1);
namespace App\GraphQL\Types;
use App\GraphQL\Fields\FormattableDate;
use App\Models\User;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Type as GraphQLType;
class UserType extends GraphQLType
{
protected $attributes = [
'name' => 'User',
'description' => 'A user',
'model' => User::class,
];
public function fields(): array
{
return [
'id' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The id of the user'
],
'email' => [
'type' => Type::string(),
'description' => 'The email of user'
],
// You can simply supply an instance of the class
'dateOfBirth' => new FormattableDate,
// Because the constructor of `FormattableDate` accepts our the array of parameters,
// we can override them very easily.
// Imagine we want our field to be called `createdAt`, but our database column
// is called `created_at`:
'createdAt' => new FormattableDate([
'alias' => 'created_at',
])
];
}
}
declare(strict_types = 1);
namespace App\GraphQL\Loaders;
use App\Models\User;
use GraphQL\Deferred;
class UserLoader
{
/** @var list<int> */
private array $pendingIds = [];
/** @var array<int,User> */
private array $loaded = [];
/**
* Register a key to be loaded and return a deferred resolver.
*/
public function load(int $id): Deferred
{
$this->pendingIds[] = $id;
return new Deferred(function () use ($id): ?User {
$this->loadPending();
return $this->loaded[$id] ?? null;
});
}
/**
* Bulk-fetch all pending keys in a single query.
*/
private function loadPending(): void
{
$ids = array_diff(array_unique($this->pendingIds), array_keys($this->loaded));
$this->pendingIds = [];
if ($ids === []) {
return;
}
$users = User::whereIn('id', $ids)->get()->keyBy('id');
foreach ($users as $id => $user) {
$this->loaded[$id] = $user;
}
}
}
declare(strict_types = 1);
namespace App\GraphQL\Types;
use App\GraphQL\Loaders\UserLoader;
use App\Models\Post;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Type as GraphQLType;
class PostType extends GraphQLType
{
protected $attributes = [
'name' => 'Post',
'description' => 'A blog post',
];
public function fields(): array
{
return [
'id' => [
'type' => Type::nonNull(Type::int()),
],
'title' => [
'type' => Type::nonNull(Type::string()),
],
'author' => [
'type' => GraphQL::type('User'),
'description' => 'The post author, loaded via dataloader',
'resolve' => function (Post $post) {
return app(UserLoader::class)->load($post->author_id);
},
],
];
}
}
class UserType extends GraphQLType
{
// ...
public function fields(): array
{
return [
// ...
// JSON column containing all posts made by this user
'posts' => [
'type' => Type::listOf(GraphQL::type('Post')),
'description' => 'A list of posts written by the user',
// Tells SelectFields this is not an Eloquent relation
// The value defaults to true
'is_relation' => false
]
];
}
// ...
}
declare(strict_types = 1);
namespace App\GraphQL\Types;
use App\Models\User;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Type as GraphQLType;
class UserType extends GraphQLType
{
protected $attributes = [
'name' => 'User',
'description' => 'A user',
'model' => User::class,
];
public function fields(): array
{
return [
'id' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The id of the user',
],
'email' => [
'type' => Type::string(),
'description' => 'The email of user',
],
'address' => [
'type' => Type::string(),
'description' => 'The address of user',
'deprecationReason' => 'Deprecated due to address field split'
],
'address_line_1' => [
'type' => Type::string(),
'description' => 'The address line 1 of user',
],
'address_line_2' => [
'type' => Type::string(),
'description' => 'The address line 2 of user',
],
];
}
}
declare(strict_types = 1);
namespace App\Providers;
use GraphQL\Type\Definition\Type;
use Illuminate\Support\ServiceProvider;
use Rebing\GraphQL\Support\Facades\GraphQL;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
GraphQL::macro('listOf', function (string $name): Type {
return Type::listOf(GraphQL::type($name));
});
}
}
'tracing' => [
'driver' => \Rebing\GraphQL\Support\Tracing\OpenTelemetryTracingDriver::class,
'driver_options' => [
// Include the GraphQL document in spans (may contain sensitive data)
'
// config/graphql.php
// Receives each GraphQL\Error\Error; must return an array
'error_formatter' => [App\GraphQL\ErrorFormatter::class, 'format'],
// Receives all errors + the formatter; must return an array of formatted errors
'errors_handler' => [App\GraphQL\ErrorHandler::class, 'handle'],
public function type(): Type
{
return GraphQL::wrapType(
'PostType',
'PostMessageType',
\App\GraphQL\Types\WrapMessagesType::class,
);
}
public function resolve($root, array $args)
{
return [
'data' => Post::find($args['post_id']),
'messages' => new Collection([
new SimpleMessage("Congratulations, the post was found"),
new SimpleMessage("This post cannot be edited", "warning"),
]),
];
}
namespace Tests\Feature;
use Tests\TestCase;
class BooksQueryTest extends TestCase
{
public function test_can_query_books(): void
{
$response = $this->postJson('/graphql', [
'query' => '{ books { id title author } }',
]);
$response->assertOk()
->assertJsonStructure([
'data' => [
'books' => [
'*' => ['id', 'title', 'author'],
],
],
]);
}
}