Use Facebook GraphQL with Laravel 5.2 >=. It is based on the PHP implementation here. You can find more information about GraphQL in the GraphQL Introduction on the React blog or you can read the GraphQL specifications.
composer require studio-net/laravel-graphql @dev
If you're not using Laravel 5.5>=, don't forget to append facade and service
provider to you config/app.php
file. Next, you have to publish vendor.
php artisan vendor:publish --provider="StudioNet\GraphQL\ServiceProvider"
Each source of data must have a corresponding definition in order to retrieve fetchable and mutable fields.
# app/GraphQL/Definition/UserDefinition.php
namespace App\GraphQL\Definition;
use StudioNet\GraphQL\Definition\Type;
use StudioNet\GraphQL\Support\Definition\EloquentDefinition;
use StudioNet\GraphQL\Filter\EqualsOrContainsFilter;
use App\User;
/**
* Specify user GraphQL definition
*
* @see EloquentDefinition
*/
class UserDefinition extends EloquentDefinition {
/**
* Set a name to the definition. The name will be lowercase in order to
* retrieve it with `\GraphQL::type` or `\GraphQL::listOf` methods
*
* @return string
*/
public function getName() {
return 'User';
}
/**
* Set a description to the definition
*
* @return string
*/
public function getDescription() {
return 'Represents a User';
}
/**
* Represents the source of the data. Here, Eloquent model
*
* @return string
*/
public function getSource() {
return User::class;
}
/**
* Which fields are queryable ?
*
* @return array
*/
public function getFetchable() {
return [
'id' => Type::id(),
'name' => Type::string(),
'last_login' => Type::datetime(),
'is_admin' => Type::bool(),
'permissions' => Type::json(),
// Relationship between user and posts
'posts' => \GraphQL::listOf('post')
];
}
/**
* Which fields are filterable ? And how ?
*
* @return array
*/
public function getFilterable() {
return [
'id' => new EqualsOrContainsFilter(),
"nameLike" => function($builder, $value) {
return $builder->whereRaw('name like ?', $value),
},
];
}
/**
* Resolve field `permissions`
*
* @param User $user
* @return array
*/
public function resolvePermissionsField(User $user) {
return $user->getPermissions();
}
/**
* Which fields are mutable ?
*
* @return array
*/
public function getMutable() {
return [
'id' => Type::id(),
'name' => Type::string(),
'is_admin' => Type::bool(),
'permissions' => Type::array(),
'password' => Type::string()
];
}
}
# config/graphql.php
return [
// ...
'definitions' => [
\App\GraphQL\Definition\UserDefinition::class,
\App\GraphQL\Definition\PostDefinition::class
],
// ...
]
The definition is an essential part in the process. It defines queryable and
mutable fields. Also, it allows you to apply transformers for only some data
with the getTransformers
methods. There's 5 kind of transformers to apply on :
list
: create a query to fetch many objects (User => users
)view
: create a query to retrieve one object (User => user
)drop
: create a mutation to delete an object (User => deleteUser
)store
: create a mutation to update an object (User => user
)batch
: create a mutation to update many object at once (User => users
)restore
: create a mutation to restore an object (User => restoreUser
)
By the default, the definition abstract class handles Eloquent model transformation.
A definition is composed from types. Our custom class extend the default
GraphQL\Type\Definition\Type
class in order to implement json
and datetime
availabled types.
If you want create a query by hand, it's possible.
# app/GraphQL/Query/Viewer.php
namespace App\GraphQL\Query;
use StudioNet\GraphQL\Support\Definition\Query;
use Illuminate\Support\Facades\Auth;
class Viewer extends Query {
/**
* {@inheritDoc}
*/
public function getRelatedType() {
return \GraphQL::type('user');
}
/**
* Return logged user
*
* @return \App\User|null
*/
public function getResolver() {
return Auth::user();
}
}
# config/graphql.php
return [
'schema' => [
'definitions' => [
'default' => [
'query' => [
'viewer' => \App\GraphQL\Query\Viewer::class
]
]
]
],
'definitions' => [
\App\GraphQL\Definition\UserDefinition::class
]
];
Mutation are used to update or create data.
# app/GraphQL/Mutation/Profile.php
namespace App\GraphQL\Mutation;
use StudioNet\GraphQL\Support\Definition\Mutation;
use StudioNet\GraphQL\Definition\Type;
use App\User;
class Profile extends Mutation {
/**
* {@inheritDoc}
*
* @return ObjectType
*/
public function getRelatedType() {
return \GraphQL::type('user');
}
/**
* {@inheritDoc}
*/
public function getArguments() {
return [
'id' => ['type' => Type::nonNull(Type::id())],
'blocked' => ['type' => Type::string()]
];
};
/**
* Update user
*
* @param mixed $root
* @param array $args
*
* @return User
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function getResolver($root, array $args) {
$user = User::findOrFail($args['id']);
$user->update($args);
return $user;
}
}
# config/graphql.php
return [
'schema' => [
'definitions' => [
'default' => [
'query' => [
'viewer' => \App\GraphQL\Query\Viewer::class
],
'mutation' => [
'viewer' => \App\GraphQL\Mutation\Profile::class
]
]
]
],
'definitions' => [
\App\GraphQL\Definition\UserDefinition::class
]
];
A documentation generator is implemented with the package. By default, you can access it by navigate to /doc/graphql
. You can change this behavior within the configuration file. The built-in documentation is implemented from this repository.
query {
viewer {
name
email
posts {
title
content
}
}
}
# is equivalent to (if user id exists)
query {
user (id: 1) {
name
email
posts {
title
content
}
}
}
When declaring the getFilterable
array, you can define filters for fields.
You can either use a closure, or give class implementing FilterInterface.
The closure (or the FilterInterface::updateBuilder
method) is then called
with:
- $builder : the current laravel query builder
- $value : the filter value
- $key : the filter key
You can use the predefined EqualsOrContainsFilter
like below.
public function getFilterable() {
return [
// Simple equality check (or "in" if value is an array)
'id' => new EqualsOrContainsFilter(),
// Customized filter
"nameLike" => function($builder, $value) {
return $builder->whereRaw('name like ?', $value);
},
];
}
query {
users (take: 2, filter: {"id", "1"}) {
items {
id
name
}
}
}
This will execute a query : WHERE id = 1
query {
users (take: 2, filter: {"id", ["1,2"]}) {
items {
id
name
}
}
}
This will execute a query : WHERE id in (1,2)
query {
users (take: 2, filter: {"nameLike", "%santiago%"}) {
items {
id
name
}
}
}
This will execute a query : WHERE name like '%santiago%'
You can specify the order of the results (which calls Eloquent's orderBy
) with
the order_by
argument (which is a String[]
).
query {
users (order_by: ["name"]) { items { id, name } }
}
You can specify a direction by appending asc
(which is the default) or desc
to the order field :
query {
users (order_by: ["name_desc"]) { items { id, name } }
}
You can specify multiple order_by
:
query {
users (order_by: ["name_asc", "email_desc"]) { items { id, name } }
}
You can limit the number of results with take
(Int
) :
query {
users (order_by: ["name"], take: 5) { items { id, name } }
}
You can skip some results with skip
(Int
) :
query {
users (order_by: ["name"], take: 5, skip: 10) { items { id, name } }
}
You can get useful pagination information :
query {
users (order_by: ["name"], take: 5, skip: 10) {
pagination {
totalCount
page
numPages
hasNextPage
hasPreviousPage
}
items {
id
name
}
}
}
Where :
totalCount
is the total number of resultspage
is the current page (based ontake
which is used as the page size)numPages
is the total number of pageshasNextPage
, true if there is a next pagehasPreviousPage
, true if there is a previous page
mutation {
# Delete object
delete : deleteUser(id: 5) {
first_name
last_name
},
# Update object
update : user(id: 5, with : { first_name : "toto" }) {
id
first_name
last_name
},
# Create object
create : user(with : { first_name : "toto", last_name : "blabla" }) {
id
first_name
last_name
},
# Update or create many objects at once
batch : users(objects: [{with: {first_name: 'studio'}}, {with: {first_name: 'net'}}]) {
id
first_name
}
}
You can specify a "mutable" field which is not in the Eloquent Model, and define a custom method to it.
For a field named foo_bar
, the method has to be named inputFooBarField
, and
it has the Eloquent Model and the user input value as arguments.
Exemple (in Definition
) :
use Illuminate\Database\Eloquent\Model;
/* ... */
public function getMutable() {
return [
'id' => Type::id(),
'name' => Type::string(),
// ...
// Define a custom input field, which will uppercase the value
'name_uppercase' => Type::string(),
];
}
/* ... */
/**
* Custom input field for name_uppercase
*
* @param Model $model
* @param string $value
*/
public function inputNameUppercaseField(Model $model, $value) {
$model->name = mb_strtoupper($value);
}
The input method is executed before the model is saved.
You can return an array with a "saved" callback, which will be executed post-save (which can be useful for eloquent relational models) :
/**
* Custom input field for name_uppercase
*
* @param Model $model
* @param string $value
*/
public function inputNameUppercaseField(Model $model, $value) {
$model->name = mb_strtoupper($value);
return [
'saved' => function() use ($model, $value) {
// Executed after save
}
];
}
If you want participate to the project, thank you ! In order to work properly, you should install all dev dependencies and run the following commands before pushing in order to prevent bad PR :
$> ./vendor/bin/phpmd src text phpmd.xml
$> ./vendor/bin/phpmd tests text phpmd.xml
$> ./vendor/bin/phpstan analyse --autoload-file=_ide_helper.php --level 1 src
$> ./vendor/bin/php-cs-fixer fix