Unfortunatelly, we cannot longer support this package and are looking for someone to take the ownership. Currently Only PRs with bugfixes and not breaking BC are being merged. It's very sad to acknowledge this, but we hope that someone can take it further with the community.
Please, PM @viniychuk if you are interested in taking over.
This is a pure PHP realization of the GraphQL protocol based on the working draft of the official GraphQL Specification located onhttp://facebook.github.io/graphql/.
GraphQL is a query language for APIs. It brings a new paradigm to the world of client-server communication and delivers a much more predictable behavior and smallest possible over-the-wire responses to any request. GraphQL advanced in many ways and has fundamental quality improvements:
- strongly typed communication protocol makes both client and server predictable and more stable
- encourages you to build a constantly evolving APIs and not use versions in the endpoints
- bulk requests and responses to avoiding waiting for multiple HTTP handshakes
- easily generated documentation and incredibly intuitive way to explore created API
- clients will be much less likely to require backend changes
Current package is and will be trying to be kept up to date with the latest revision of the official GraphQL Specification which is now of April 2016.
Symfony bundle is available by the link –http://github /Youshido/GraphqlBundle
If you have any questions or suggestions – let's talk onGraphQL Gitter channel
- Getting Started
- Installation
- Example – Creating Blog Schema
- Query Documents
- Type System
- Building your schema
- Useful information
You should be better off starting with some examples and "Star Wars" become a somewhat "Hello world" for the GraphQL implementations. If you're looking just for that – you can get it via this link –Star Wars example. On the other hand, we prepared a step-by-step guide for those who wants to get up to speed bit by bit.
Install GraphQL package using composer. If you're not familiar with it, you should check out theirmanual.
Runcomposer require youshido/graphql
.
Alternatively you can run the following commands:
mkdir graphql-test&&cdgraphql-test
composer init -n
composer require youshido/graphql
Now you're ready to create yourGraphQL Schema
and check if everything works fine.
Your first GraphQL app will be able to receivecurrentTime
request and response with a formatted time string.
you can find this example in the examples directory –01_sandbox.
Create anindex.php
file with the following content:
<?php
namespaceSandbox;
useYoushido\GraphQL\Execution\Processor;
useYoushido\GraphQL\Schema\Schema;
useYoushido\GraphQL\Type\Object\ObjectType;
useYoushido\GraphQL\Type\Scalar\StringType;
require_once'vendor/autoload.php';
$processor=newProcessor(newSchema([
'query'=>newObjectType([
'name'=>'RootQueryType',
'fields'=> [
'currentTime'=> [
'type'=>newStringType(),
'resolve'=>function() {
returndate('Y-m-d H:ia');
}
]
]
])
]));
$processor->processPayload('{ currentTime }');
echojson_encode($processor->getResponseData())."\n";
You can now executephp index.php
and get a response with your current time:
{
data:{currentTime:"2016-05-01 19:27pm"}
}
Just like that, you have created aGraphQL Schema
with afield
currentTime
of typeString
andresolver
for it. Don't worry if you don't know what thefield
,type
andresolver
mean here, you'll learn along the way.
If you're having any troubles – here're some troubleshooting points:
- check that you have the latest composer version (
composer self-update
) - make sure your
index.php
file has been created in the same directory that you havevendor
folder in (presumably it'sgraphql-test
folder) - last but not least, check that you have php-cli installed and running and it's version >= 5.5 (
php -v
)
Also, you can always check if script from theexamples folderwork.
For our learning example we'll architect a GraphQL Schema for a Blog. You'll probably be using our package along with your favorite framework (we have a Symfony versionhere), but for the purpose of this tutorial we're keeping it all examples as plain php code.
(Complete example of the Blog schema available by the following linkhttps://github /Youshido/GraphQL/tree/master/examples/02_blog)
Our Blog will haveUsers
who can writePosts
and leaveComments
.Also, there will be aLikePost
operation that could be performed by anyone.
Let's start withPost
.Take a look at the query that returnstitle
andsummary
of the latest Post:
GraphQL query is a simple text query structured very much similar to the json format.
latestPost {
title,
summary
}
Supposedly server should reply with a relevant json response:
{
data:{
latestPost:{
title:"This is a post title",
summary:"This is a post summary"
}
}
}
It looks very simple and straight forward, so let's go ahead and write code that can handle this request.
We'll take a quick look on different approaches you can use to define your schema. Each of them has it's own pros and cons, inline approach might seem to be easier and faster when object oriented gives you more flexibility and freedom as your project grows. You should definitely use OOP approach every time you can reuse the type you're creating.
We're going to createRootQueryType
with one fieldlatestPost
.
EveryGraphQL Field
has atype
(e.g. String, Int, Boolean) and it could be of a differentkind
(e.g. Scalar, Enum, List). You can read more about it in theofficial documentation,but for now you can think offield of a type
like aboutinstance of a class
.
You can createinline-index.php
file in your project folder and paste the following code there
inline-index.php
<?php
namespaceInlineSchema;
useYoushido\GraphQL\Execution\Processor;
useYoushido\GraphQL\Schema\Schema;
useYoushido\GraphQL\Type\Object\ObjectType;
useYoushido\GraphQL\Type\Scalar\StringType;
// including autoloader
require_once__DIR__.'/vendor/autoload.php';
// instantiating Processor and setting the schema
$processor=newProcessor(newSchema([
'query'=>newObjectType([
// root query by convention has a name RootQueryType
'name'=>'RootQueryType',
'fields'=> [
'latestPost'=> [
'type'=>newObjectType([// Post type is being created as ObjectType
'name'=>'Post',// name of our type – "Post"
'fields'=> [
'title'=>newStringType(),// defining "title" field, type - String
'summary'=>newStringType(),// defining "summary" field, type - String
],
]),
'resolve'=>function() {// resolver for latestPost field
return[// for now it returns a static array with data
"title"=>"New approach in API has been revealed",
"summary"=>"In two words - GraphQL Rocks!",
];
}
]
]
])
]));
// creating payload and running it through processor
$payload='{ latestPost { title, summary } }';
$processor->processPayload($payload);
// displaying result
echojson_encode($processor->getResponseData())."\n";
To check if everything is working – execute inline-index.php:php inline-index.php
You should see response as the json encoded objectlatestPost
inside thedata
section:
{
data:{
latestPost:{
title:"New approach in API has been revealed",
summary:"In two words - GraphQL Rocks!"
}
}
}
Try to play with the code by removing one field from the request or by changing the resolve function.
It's a common situation when you need to use the same custom type in different places, so we're going to create a separate class for thePostType
and use it in ourGraphQL Schema
.
To keep everything structured we're going to put this and all our future classes into theSchema
folder.
Create a fileSchema/PostType.php
and put the following code in there:
<?php
namespaceExamples\Blog\Schema;
useYoushido\GraphQL\Type\Object\AbstractObjectType;
useYoushido\GraphQL\Type\Scalar\StringType;
classPostTypeextendsAbstractObjectType// extending abstract Object type
{
publicfunctionbuild($config)// implementing an abstract function where you build your type
{
$config
->addField('title',newStringType()) // defining"title"field of type String
->addField('summary',newStringType());// defining "summary" field of type String
}
publicfunctiongetName()
{
return"Post";// if you don't do getName – className without "Type" will be used
}
}
Now let's create the main entry point for this example –index.php
:
<?php
namespaceExamples\Blog;
useExamples\Blog\Schema\PostType;
useYoushido\GraphQL\Execution\Processor;
useYoushido\GraphQL\Schema\Schema;
useYoushido\GraphQL\Type\Object\ObjectType;
require_once__DIR__.'/vendor/autoload.php';
require_once__DIR__.'/Schema/PostType.php';// including PostType definition
$rootQueryType=newObjectType([
'name'=>'RootQueryType',
'fields'=> [
'latestPost'=> [
'type'=>newPostType(),
'resolve'=>function($source,$args,$info)
{
return[
"title"=>"New approach in API has been revealed",
"summary"=>"In two words - GraphQL Rocks!",
];
}
]
]
]);
$processor=newProcessor(newSchema([
'query'=>$rootQueryType
]));
$payload='{ latestPost { title, summary } }';
$processor->processPayload($payload);
echojson_encode($processor->getResponseData())."\n";
Ensure everything is working properly by runningphp index.php
.You should see the same response you saw for the inline approach.
Next step would be to create a separate class for the latestPostField by extendingAbstractField
class:
Schema/LatestPostField.php
<?php
namespaceExamples\Blog\Schema;
useYoushido\GraphQL\Execution\ResolveInfo;
useYoushido\GraphQL\Field\AbstractField;
classLatestPostFieldextendsAbstractField
{
publicfunctiongetType()
{
returnnewPostType();
}
publicfunctionresolve($value,array$args,ResolveInfo$info)
{
return[
"title"=>"New approach in API has been revealed",
"summary"=>"In two words - GraphQL Rocks!",
];
}
}
And now we can update ourindex.php
:
<?php
namespaceExamples\Blog;
useExamples\Blog\Schema\LatestPostField;
useYoushido\GraphQL\Execution\Processor;
useYoushido\GraphQL\Schema\Schema;
useYoushido\GraphQL\Type\Object\ObjectType;
require_once__DIR__.'/vendor/autoload.php';
require_once__DIR__.'/Schema/PostType.php';// including PostType definition
require_once__DIR__.'/Schema/LatestPostField.php';
$rootQueryType=newObjectType([
'name'=>'RootQueryType',
'fields'=> [
newLatestPostField()
]
]);
$processor=newProcessor(newSchema([
'query'=>$rootQueryType
]));
$payload='{ latestPost { title, summary } }';
$processor->processPayload($payload);
echojson_encode($processor->getResponseData())."\n";
We would recommend to stick to object oriented approach for the several reasons (that matter the most for the GraphQL specifically):
- makes your
Types
reusable - adds an ability to refactor your schema using IDEs
- autocomplete to help you avoid typos
- much easier to navigate through your Schema when project grows
With that being said, we use inline approach a lot to explore and bootstrap ideas or to develop simple fields/resolver that are going to be used in one place only. With the inline approach you can be fast and agile in creating mock-data server to test your frontend or mobile client.
Use valid Names
We highly recommend to get familiar with theofficial GraphQL Specification Remember that valid identifier in GraphQL should follow the pattern/[_A-Za-z][_0-9A-Za-z]*/
. That means any identifier should consist of a latin letter, underscore, or a digit and cannot start with a digit. Names are case sensitive
We'll continue to work on the Blog Schema to explore all essentials details of developing GraphQL server.
In GraphQL terms – query document describe a complete request received by GraphQL service. It contains list ofOperationsandFragments.Both are fully supported by our PHP library. There are two types ofOperationsin GraphQL:
- Query– a read only request that is not supposed to do any changes on the server
- Mutation– a request that changes(mutate) data on the server followed by a data fetch
You've already seen examples ofQuery
withlatestPost
andcurrentTime
,so let's define a simple Mutation that will provide API toLikethe Post.
Here's sample request and response oflikePost
mutation:
request
mutation {
likePost(id: 5)
}
response
{
data:{likePost:2}
}
Any Operation has a response type and in this case the likePost mutation type is
Int
Note, that the response type of this mutation is a scalarInt
.
Of course in real life you'll more likely have a response of typePost
for such mutation, but we're going to implement code for a simple example above and even keep it insideindex.php
:
<?php
namespaceExamples\Blog;
useExamples\Blog\Schema\LatestPostField;
useYoushido\GraphQL\Execution\Processor;
useYoushido\GraphQL\Schema\Schema;
useYoushido\GraphQL\Type\NonNullType;
useYoushido\GraphQL\Type\Object\ObjectType;
useYoushido\GraphQL\Type\Scalar\IntType;
require_once__DIR__.'/vendor/autoload.php';
require_once__DIR__.'/Schema/PostType.php';// including PostType definition
require_once__DIR__.'/Schema/LatestPostField.php';
$rootQueryType=newObjectType([
'name'=>'RootQueryType',
'fields'=> [
newLatestPostField()
]
]);
$rootMutationType=newObjectType([
'name'=>'RootMutationType',
'fields'=> [
// defining likePost mutation field
'likePost'=> [
// we specify the output type – simple Int, since it doesn't have a structure
'type'=>newIntType(),
// we need a post ID and we set it to be required Int
'args'=> [
'id'=>newNonNullType(newIntType())
],
// simple resolve function that always returns 2
'resolve'=>function() {
return2;
},
]
]
]);
$processor=newProcessor(newSchema([
'query'=>$rootQueryType,
'mutation'=>$rootMutationType
]));
$payload='mutation { likePost(id: 5) }';
$processor->processPayload($payload);
echojson_encode($processor->getResponseData())."\n";
Runphp index.php
,you should see a valid response:
{"data":{"likePost":2}}
Now, let's make ourlikePost
mutation to return the wholePost
as a result.
First, we'll addlikesCount
field to thePostType
:
<?php
namespaceExamples\Blog\Schema;
useYoushido\GraphQL\Type\Object\AbstractObjectType;
useYoushido\GraphQL\Type\Scalar\IntType;
useYoushido\GraphQL\Type\Scalar\StringType;
classPostTypeextendsAbstractObjectType
{
publicfunctionbuild($config)
{
// you can define fields in a single addFields call instead of chaining multiple addField()
$config->addFields([
'title'=>newStringType(),
'summary'=>newStringType(),
'likesCount'=>newIntType()
]);
}
// Since our class named by a convention, we can remove getName() method
}
Secondly, modifyresolve
function inLatestPostField
:
publicfunctionresolve($value,array$args,ResolveInfo$info)
{
return[
"title"=>"New approach in API has been revealed",
"summary"=>"In two words - GraphQL Rocks!",
"likesCount"=>2
];
}
Lastly, we're going to changeMutation Type
fromIntType
toPostType
and update theresolve
function to be compliant with the the new type and update the request:
<?php
//...
$rootMutationType=newObjectType([
'name'=>'RootMutationType',
'fields'=> [
'likePost'=> [
'type'=>newPostType(),
'args'=> [
'id'=>newNonNullType(newIntType())
],
'resolve'=>function() {
return[
'title'=>'New approach in API has been revealed',
'summary'=>'In two words - GraphQL Rocks!',
'likesCount'=>2
];
},
]
]
]);
//...
$payload='mutation { likePost(id: 5) { title, likesCount } }';
//...
Executephp index.php
,you should seetitle
andlikesCount
in response. We can now try to useid: 5
that we're passing as a parameter to our mutation:
$rootMutationType=newObjectType([
'name'=>'RootMutationType',
'fields'=> [
'likePost'=> [
'type'=>newPostType(),
'args'=> [
'id'=>newNonNullType(newIntType())
],
'resolve'=>function($source,$args,$resolveInfo) {
return[
'title'=>'Title for the post #'.$args['id'],// we can be sure that $args['id'] is always set
'summary'=>'In two words - GraphQL Rocks!',
'likesCount'=>2
];
},
]
]
]);
Now you have a basic understanding of how queries and mutations are structured and ready to move on to the details of the GraphQL Type System and PHP-specific features of the GraphQL server architecture.
Typeis an atom of definition in GraphQL Schema. Every field, object, or argument has a type. GraphQL is a strongly typed language.
There aresystem types
andcustom types
defined specifically for the application, in our app we'll have custom typesPost
,User
,Comment
,etc. Your custom types are usually built on top of GraphQL system types.
List of GraphQL Scalar types:
- Int
- Float
- String
- Boolean
- Id (serialized as String perspec)
In addition, we implemented some types that might be useful and which we're considering to be scalar as well:
- Timestamp
- DateTimeTz (» RFC 2822 formatted date with TimeZone)
Date and DateTime are deprecated and will be remove. We're going to provide an easy solution how to replace them in your project
If you will ever need to define a new Scalar type, you can do that by extending from theAbstractScalarType
class.
usage of scalar types will be shown in combination with other types down here
Every entity in your business logic will probably have a class that represents it's type. That class must be either extended from theAbstractObjectType
or created as an instance ofObjectType
.
In our blog example we usedObjectType
to create an inlinePostType
and extendedAbstractObjectType
to create aPostType
class in the object oriented approach.
Let's take a closer look at the structure ofPostType
and see what parameters we can configure for each field.
<?php
namespaceExamples\Blog\Schema;
useYoushido\GraphQL\Type\Object\AbstractObjectType;
useYoushido\GraphQL\Type\Scalar\BooleanType;
useYoushido\GraphQL\Type\Scalar\IntType;
useYoushido\GraphQL\Type\Scalar\StringType;
classPostTypeextendsAbstractObjectType
{
publicfunctionbuild($config)
{
// you can define fields in a single addFields call instead of chaining multiple addField()
$config->addFields([
'title'=> [
'type'=>newStringType(),
'description'=>'This field contains a post title',
'isDeprecated'=>true,
'deprecationReason'=>'field title is now deprecated',
'args'=> [
'truncate'=>newBooleanType()
],
'resolve'=>function($source,$args) {
return(!empty($args['truncate']))?explode('',$source['title'])[0].'...':$source['title'];
}
],
'summary'=>newStringType(),
'likesCount'=>newIntType()
]);
}
}
Now you can changeindex.php
to perform requests like these:
$payload='mutation { likePost(id: 5) { title(truncate: true), likesCount } }';
As you can see we now have argumentid
for the mutation and another argumenttruncate
for the fieldtitle
insidePostTitle
.We can use it everywhere thatPostType
is being used.
GraphQL supportsInterfaces
.You can define Interface and use it as aType
of an item in theList
,or use Interface to make sure that specific objects certainly have fields you need.
EachInterfaceType
has to have at least one defined field andresolveType
function. That function will be used to determine what exactType
will be returned by GraphQL resolver.
Let's create aContentBlockInterface
that can represent a piece of content for the web page that have atitle
and asummary
(just like our post earlier).
<?php
/**
* ContentBlockInterface.php
*/
namespaceExamples\Blog\Schema;
useYoushido\GraphQL\Type\InterfaceType\AbstractInterfaceType;
useYoushido\GraphQL\Type\NonNullType;
useYoushido\GraphQL\Type\Scalar\StringType;
classContentBlockInterfaceextendsAbstractInterfaceType
{
publicfunctionbuild($config)
{
$config->addField('title',newNonNullType(newStringType()));
$config->addField('summary',newStringType());
}
publicfunctionresolveType($object) {
// since there's only one type right now this interface will always resolve PostType
returnnewPostType();
}
}
Most often you'll be using only thebuild
method to define fields and that need to be implemented.
In order to associate this Interface to thePostType
we have to override it'sgetInterfaces
method:
<?php
/**
* PostType.php
*/
namespaceExamples\Blog\Schema;
useYoushido\GraphQL\Type\Object\AbstractObjectType;
useYoushido\GraphQL\Type\Scalar\IntType;
useYoushido\GraphQL\Type\Scalar\StringType;
classPostTypeextendsAbstractObjectType
{
publicfunctionbuild($config)
{
$config->addFields([
'title'=>newStringType(),
'summary'=>newStringType(),
'likesCount'=>newIntType()
]);
}
publicfunctiongetInterfaces()
{
return[newContentBlockInterface()];
}
}
As you might have noticed there's nogetName
method in both Interface and Type classes – that's a simplified approach available when you want to have your name exactly the same as the class name without theType
at the end.
If you run the script as it is right now –php index.php
,you should get an error:
{"errors":[{"message":"Implementation of ContentBlockInterface is invalid for the field title"}]}
You've got this error because thetitle
field definition in thePostType
is different from the one described in theContentBlockInterface
.
To fix it we have to declare fields that exist in theInterface
with the same names and types.
We already havetitle
but it's a nullable field so we have to change it by adding a non-null wrapper –new NonNullType(new StringType())
.
You can check the result by executing index.php script again, you should get the usual response.
For the convenience we also created$config->applyInterface()
method that could be insidebuild()
:
<?php
/**
* PostType.php
*/
namespaceExamples\Blog\Schema;
useYoushido\GraphQL\Type\Object\AbstractObjectType;
useYoushido\GraphQL\Type\Scalar\IntType;
classPostTypeextendsAbstractObjectType
{
publicfunctionbuild($config)
{
$config->applyInterface(newContentBlockInterface());
$config->addFields([
'likesCount'=>newIntType()
]);
}
publicfunctiongetInterfaces()
{
return[newContentBlockInterface()];
}
}
GraphQL Enums are the variation on the Scalar type, which represents one of the predefined values. Enums serialize as a string: the name of the represented value but can be associated with a numeric (as an example) value.
To show you how Enums work we're going to create a new class -PostStatus
:
<?php
/**
* PostStatus.php
*/
namespaceExamples\Blog\Schema;
useYoushido\GraphQL\Type\Enum\AbstractEnumType;
classPostStatusextendsAbstractEnumType
{
publicfunctiongetValues()
{
return[
[
'value'=>0,
'name'=>'DRAFT',
],
[
'value'=>1,
'name'=>'PUBLISHED',
]
];
}
}
Now, add a status field to thePostType
:
<?php
/**
* PostType.php
*/
namespaceExamples\Blog\Schema;
useYoushido\GraphQL\Type\NonNullType;
useYoushido\GraphQL\Type\Object\AbstractObjectType;
useYoushido\GraphQL\Type\Scalar\IntType;
useYoushido\GraphQL\Type\Scalar\StringType;
classPostTypeextendsAbstractObjectType
{
publicfunctionbuild($config)
{
$config->addFields([
'title'=>newNonNullType(newStringType()),
'summary'=>newStringType(),
'likesCount'=>newIntType(),
'status'=>newPostStatus()
]);
}
publicfunctiongetInterfaces()
{
return[newContentBlockInterface()];
}
}
and update the resolve function inside latestPost field:
<?php
namespaceExamples\Blog\Schema;
useYoushido\GraphQL\Execution\ResolveInfo;
useYoushido\GraphQL\Field\AbstractField;
classLatestPostFieldextendsAbstractField
{
publicfunctiongetType()
{
returnnewPostType();
}
publicfunctionresolve($value,array$args,ResolveInfo$info)
{
return[
"title"=>"New approach in API has been revealed",
"summary"=>"In two words - GraphQL Rocks!",
"status"=>1,
"likesCount"=>2
];
}
}
Request thestatus
field in your query:
$payload='{ latestPost { title, status, likesCount } }';
You should get a result similar to the following:
{"data":{"latestPost":{"title":"New approach in API has been revealed","status":"PUBLISHED"}}}
GraphQL Unions represent an object type that could be resolved as one of a specified GraphQL Object types.
To get you an idea of what this is we're going to create a new query field that will return a list of unions (and get to theListType
after it).
You can consider Union as a combined type that is needed mostly when you want to have a list of different objects
Imaging that you have a page and you need to get all content blocks for this page. Let content block be eitherPost
orBanner
.
Create aBannerType
:
<?php
/**
* BannerType.php
*/
namespaceExamples\Blog\Schema;
useYoushido\GraphQL\Type\Object\AbstractObjectType;
useYoushido\GraphQL\Type\Scalar\StringType;
classBannerTypeextendsAbstractObjectType
{
publicfunctionbuild($config)
{
$config
->addField('title',newStringType())
->addField('imageLink',newStringType());
}
}
Now let's combine theBanner
type and thePost
type to create aContentBlockUnion
that will extend anAbstractUnionType
.
EachUnionType
needs to define a list of types it unites by implementing thegetTypes
method and theresolveType
method to resolve object that will be returned for each instance of theUnion
.
<?php
/**
* ContentBlockUnion.php
*/
namespaceExamples\Blog\Schema;
useYoushido\GraphQL\Type\Union\AbstractUnionType;
classContentBlockUnionextendsAbstractUnionType
{
publicfunctiongetTypes()
{
return[newPostType(),newBannerType()];
}
publicfunctionresolveType($object)
{
// we simple look if there's a "post" inside the object id that it's a PostType otherwise it's a BannerType
returnempty($object['id'])?null:(strpos($object['id'],'post')!==false?newPostType():newBannerType());
}
}
We're also going to create a simpleDataProvider
that will give us test data to operate with:
<?php
/**
* DataProvider.php
*/
namespaceExamples\Blog\Schema;
classDataProvider
{
publicstaticfunctiongetPost($id)
{
return[
"id"=>"post-".$id,
"title"=>"Post".$id."title",
"summary"=>"This new GraphQL library for PHP works really well",
"status"=>1,
"likesCount"=>2
];
}
publicstaticfunctiongetBanner($id)
{
return[
'id'=>"banner-".$id,
'title'=>"Banner".$id,
'imageLink'=>"banner".$id.".jpg"
];
}
}
Now, we're ready to update our Schema and includeContentBlockUnion
into it.
As we're getting our schema bigger we'd like to extract it to a separate file as well:
<?php
/**
* BlogSchema.php
*/
namespaceExamples\Blog\Schema;
useYoushido\GraphQL\Config\Schema\SchemaConfig;
useYoushido\GraphQL\Schema\AbstractSchema;
useYoushido\GraphQL\Type\ListType\ListType;
classBlogSchemaextendsAbstractSchema
{
publicfunctionbuild(SchemaConfig$config)
{
$config->getQuery()->addFields([
newLatestPostField(),
'randomBanner'=> [
'type'=>newBannerType(),
'resolve'=>function() {
returnDataProvider::getBanner(rand(1,10));
}
],
'pageContentUnion'=> [
'type'=>newListType(newContentBlockUnion()),
'resolve'=>function() {
return[DataProvider::getPost(1),DataProvider::getBanner(1)];
}
]
]);
$config->getMutation()->addFields([
newLikePostField()
]);
}
}
Having this separate schema file you should update yourindex.php
to look like this:
<?php
namespaceExamples\Blog;
useExamples\Blog\Schema\BlogSchema;
useYoushido\GraphQL\Execution\Processor;
require_once__DIR__.'/vendor/autoload.php';
require_once__DIR__.'/Schema/PostType.php';
require_once__DIR__.'/Schema/LatestPostField.php';
require_once__DIR__.'/Schema/ContentBlockInterface.php';
require_once__DIR__.'/Schema/PostStatus.php';
require_once__DIR__.'/Schema/LikePostField.php';
require_once__DIR__.'/Schema/BlogSchema.php';
require_once__DIR__.'/Schema/ContentBlockUnion.php';
require_once__DIR__.'/Schema/BannerType.php';
require_once__DIR__.'/Schema/DataProvider.php';
$processor=newProcessor(newBlogSchema());
$payload='{ pageContentUnion {... on Post { title }... on Banner { title, imageLink } } }';
$processor->processPayload($payload);
echojson_encode($processor->getResponseData())."\n";
Due to the GraphQL syntax you have to specify fields for each type of object you're getting in the union request, if you're not familiar with it read more atofficial documentation If everything was done right you should see the following response:
{"data":{"pageContentUnion":[
{"title":"Post 1 title"},
{"title":"Banner 1","imageLink":"banner1.jpg"}
]}}
Also, you might want to check out how to useGraphiQL toolto get a better visualization of what you're doing here.
As you've seen in the previous exampleListType
is used to create a list of any items that are or extend GraphQL type.
List type can be also created by usingInterfaceType
as an item which gives you flexibility in defining your schema.
Let's go ahead and addListType
field to our BlogSchema.
<?php
/**
* BlogSchema.php
*/
namespaceExamples\Blog\Schema;
useYoushido\GraphQL\Config\Schema\SchemaConfig;
useYoushido\GraphQL\Schema\AbstractSchema;
useYoushido\GraphQL\Type\ListType\ListType;
classBlogSchemaextendsAbstractSchema
{
publicfunctionbuild(SchemaConfig$config)
{
$config->getQuery()->addFields([
newLatestPostField(),
'randomBanner'=> [
'type'=>newBannerType(),
'resolve'=>function() {
returnDataProvider::getBanner(rand(1,10));
}
],
'pageContentUnion'=> [
'type'=>newListType(newContentBlockUnion()),
'resolve'=>function() {
return[DataProvider::getPost(1),DataProvider::getBanner(1)];
}
],
'pageContentInterface'=> [
'type'=>newListType(newContentBlockInterface()),
'resolve'=>function() {
return[DataProvider::getPost(2),DataProvider::getBanner(3)];
}
]
]);
$config->getMutation()->addFields([
newLikePostField()
]);
}
}
We've added apageContentInterface
field that have aListType
ofContentBlockInterface
.
Resolve function returns list which consists of onePost
and oneBanner
.
To test it we'll modify our payload to the following one:
<?php
$payload='{ pageContentInterface { title} }';
Be aware, becauseBannerType
doesn't implementContentBlockInterface
you would get an error:
{"errors":["message":"Type Banner does not implement ContentBlockInterface"}]}
To fix this we just need to addContentBlockInterface
by implementinggetInterfaces
method and adding the proper field definitions to ourBannerType
:
<?php
/**
* BannerType.php
*/
namespaceExamples\Blog\Schema;
useYoushido\GraphQL\Type\Config\TypeConfigInterface;
useYoushido\GraphQL\Type\NonNullType;
useYoushido\GraphQL\Type\Object\AbstractObjectType;
useYoushido\GraphQL\Type\Scalar\StringType;
classBannerTypeextendsAbstractObjectType
{
publicfunctionbuild($config)
{
$config
->addField('title',newNonNullType(newStringType()))
->addField('summary',newStringType())
->addField('imageLink',newStringType());
}
publicfunctiongetInterfaces()
{
return[newContentBlockInterface()];
}
}
Send the request again and you'll get a nice response with titles of the both Post and Banner:
{
"data":{
"pageContentInterface":[
{"title":"Post 2 title"},
{"title":"Banner 3"}
]
}
}
So far we've been working mostly on the requests that does not require you to send any kind of data other than a simpleInt
,but in real life you'll have a lot of requests (mutations) where you'll be sending to server all kind of forms – login, registration, create post and so on.
In order to properly handle and validate that data GraphQL type system provides anInputObjectType
class.
By default all the
Scalar
types are inputs but if you want to have a single more complicated input type you need to extend anInputObjectType
.
Let's develop aPostInputType
that could be used to create a new Post in our system.
<?php
/**
* PostInputType.php
*/
namespaceExamples\Blog\Schema;
useYoushido\GraphQL\Type\Config\InputTypeConfigInterface;
useYoushido\GraphQL\Type\NonNullType;
useYoushido\GraphQL\Type\InputObject\AbstractInputObjectType;
useYoushido\GraphQL\Type\Scalar\StringType;
classPostInputTypeextendsAbstractInputObjectType
{
publicfunctionbuild($config)
{
$config
->addField('title',newNonNullType(newStringType()))
->addField('summary',newStringType());
}
}
ThisInputType
could be used to create a new mutation (we can do it in theBlogSchema::build
for testing):
<?php
// BlogSchema->build() method
$config->getMutation()->addFields([
'likePost'=>newLikePost(),
'createPost'=> [
'type'=>newPostType(),
'args'=> [
'post'=>newPostInputType(),
'author'=>newStringType()
],
'resolve'=>function($value,array$args,ResolveInfo$info) {
// code for creating a new post goes here
// we simple use our DataProvider for now
$post=DataProvider::getPost(10);
if(!empty($args['post']['title']))$post['title'] =$args['post']['title'];
return$post;
}
]
]);
Try to execute the following mutation so you can see the result:
mutation {
createPost(author: "Alex", post: {title: "Hey, this is my new post", summary: "my post" }) {
title
}
}
result:
{"data":{"createPost":{"title":"Hey, this is my new post"}}}
The best way to see the result of your queries/mutations and to inspect the Schema is to use aGraphiQL tool
NonNullType
is really simple to use – consider it as a wrapper that can ensure that your field / argument is required and being passed to the resolve function.
We have usedNonNullType
couple of times already so we'll just show you useful methods that that could be called onNonNullType
objects:
getNullableType()
getNamedType()
These two can return you a type that was wrapped up in theNonNullType
so you can get it's fields, arguments or name.
It's always a good idea to give you a heads up about any possible errors as soon as possible, better on the development stage. For this purpose specifically we made a lot of Abstract classes that will force you to implement the right methods to reduce amount of errors or if you're lucky enough – to have no errors at all.
If you want to implement a new type consider extending the following classes:
- AbstractType
- AbstractScalarType
- AbstractObjectType
- AbstractMutationObjectType
- AbstractInputObjectType
- AbstractInterfaceType
- AbstractEnumType
- AbstractListType
- AbstractUnionType
- AbstractSchemaType
You can create a mutation by extendingAbstractObjectType
or by creating a new field ofObjectType
inside yourSchema::build
method.
It is crucial for the class to have agetType
method returning the actual OutputType of your mutation but it couldn't be implemented as abstract method, so we created a wrapper class calledAbstractMutationObjectType
.
This abstract class can help you to not forget aboutOutputType
by forcing you to implement a methodgetOutputType
that will eventually be used by internalgetType
method.
This section will be updating on a regular basis with the useful links and references that might help you to quicker become a better GraphQL developer.
To improve our testing experience even more we suggest to start using GraphiQL client, that's included in our examples. It's a JavaScript GraphQL Schema Explorer.
To use it – run theserver.sh
from theexamples/02_blog/
folder and open theexamples/GraphiQL/index.html
file in your browser.
You'll see a nice looking editor that has an autocomplete function and contains all information about your current Schema on the right side in the Docs sidebar: