mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-28 15:56:02 +03:00
Updated blog example
This commit is contained in:
parent
2ef58a615f
commit
85d2c2cef3
25
examples/01-blog/Blog/Data/Comment.php
Normal file
25
examples/01-blog/Blog/Data/Comment.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Examples\Blog\Data;
|
||||||
|
|
||||||
|
|
||||||
|
use GraphQL\Utils;
|
||||||
|
|
||||||
|
class Comment
|
||||||
|
{
|
||||||
|
public $id;
|
||||||
|
|
||||||
|
public $authorId;
|
||||||
|
|
||||||
|
public $storyId;
|
||||||
|
|
||||||
|
public $parentId;
|
||||||
|
|
||||||
|
public $body;
|
||||||
|
|
||||||
|
public $isAnonymous;
|
||||||
|
|
||||||
|
public function __construct(array $data)
|
||||||
|
{
|
||||||
|
Utils::assign($this, $data);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Examples\Blog\Data;
|
namespace GraphQL\Examples\Blog\Data;
|
||||||
|
use GraphQL\Utils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class DataSource
|
* Class DataSource
|
||||||
@ -12,28 +13,27 @@ namespace GraphQL\Examples\Blog\Data;
|
|||||||
class DataSource
|
class DataSource
|
||||||
{
|
{
|
||||||
private $users = [];
|
private $users = [];
|
||||||
|
|
||||||
private $stories = [];
|
private $stories = [];
|
||||||
|
|
||||||
private $storyLikes = [];
|
private $storyLikes = [];
|
||||||
|
private $comments = [];
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->users = [
|
$this->users = [
|
||||||
1 => new User([
|
'1' => new User([
|
||||||
'id' => 1,
|
'id' => '1',
|
||||||
'email' => 'john@example.com',
|
'email' => 'john@example.com',
|
||||||
'firstName' => 'John',
|
'firstName' => 'John',
|
||||||
'lastName' => 'Doe'
|
'lastName' => 'Doe'
|
||||||
]),
|
]),
|
||||||
2 => new User([
|
'2' => new User([
|
||||||
'id' => 2,
|
'id' => '2',
|
||||||
'email' => 'jane@example.com',
|
'email' => 'jane@example.com',
|
||||||
'firstName' => 'Jane',
|
'firstName' => 'Jane',
|
||||||
'lastName' => 'Doe'
|
'lastName' => 'Doe'
|
||||||
]),
|
]),
|
||||||
3 => new User([
|
'3' => new User([
|
||||||
'id' => 3,
|
'id' => '3',
|
||||||
'email' => 'john@example.com',
|
'email' => 'john@example.com',
|
||||||
'firstName' => 'John',
|
'firstName' => 'John',
|
||||||
'lastName' => 'Doe'
|
'lastName' => 'Doe'
|
||||||
@ -41,15 +41,56 @@ class DataSource
|
|||||||
];
|
];
|
||||||
|
|
||||||
$this->stories = [
|
$this->stories = [
|
||||||
1 => new Story(['id' => 1, 'authorId' => 1]),
|
'1' => new Story(['id' => '1', 'authorId' => '1', 'body' => '<h1>GraphQL is awesome!</h1>']),
|
||||||
2 => new Story(['id' => 2, 'authorId' => 1]),
|
'2' => new Story(['id' => '2', 'authorId' => '1', 'body' => '<a>Test this</a>']),
|
||||||
3 => new Story(['id' => 3, 'authorId' => 3]),
|
'3' => new Story(['id' => '3', 'authorId' => '3', 'body' => "This\n<br>story\n<br>spans\n<br>newlines"]),
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->storyLikes = [
|
$this->storyLikes = [
|
||||||
1 => [1, 2, 3],
|
'1' => ['1', '2', '3'],
|
||||||
2 => [],
|
'2' => [],
|
||||||
3 => [1]
|
'3' => ['1']
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->comments = [
|
||||||
|
// thread #1:
|
||||||
|
'100' => new Comment(['id' => '100', 'authorId' => '3', 'storyId' => '1', 'body' => 'Likes']),
|
||||||
|
'110' => new Comment(['id' =>'110', 'authorId' =>'2', 'storyId' => '1', 'body' => 'Reply <b>#1</b>', 'parentId' => '100']),
|
||||||
|
'111' => new Comment(['id' => '111', 'authorId' => '1', 'storyId' => '1', 'body' => 'Reply #1-1', 'parentId' => '110']),
|
||||||
|
'112' => new Comment(['id' => '112', 'authorId' => '3', 'storyId' => '1', 'body' => 'Reply #1-2', 'parentId' => '110']),
|
||||||
|
'113' => new Comment(['id' => '113', 'authorId' => '2', 'storyId' => '1', 'body' => 'Reply #1-3', 'parentId' => '110']),
|
||||||
|
'114' => new Comment(['id' => '114', 'authorId' => '1', 'storyId' => '1', 'body' => 'Reply #1-4', 'parentId' => '110']),
|
||||||
|
'115' => new Comment(['id' => '115', 'authorId' => '3', 'storyId' => '1', 'body' => 'Reply #1-5', 'parentId' => '110']),
|
||||||
|
'116' => new Comment(['id' => '116', 'authorId' => '1', 'storyId' => '1', 'body' => 'Reply #1-6', 'parentId' => '110']),
|
||||||
|
'117' => new Comment(['id' => '117', 'authorId' => '2', 'storyId' => '1', 'body' => 'Reply #1-7', 'parentId' => '110']),
|
||||||
|
'120' => new Comment(['id' => '120', 'authorId' => '3', 'storyId' => '1', 'body' => 'Reply #2', 'parentId' => '100']),
|
||||||
|
'130' => new Comment(['id' => '130', 'authorId' => '3', 'storyId' => '1', 'body' => 'Reply #3', 'parentId' => '100']),
|
||||||
|
'200' => new Comment(['id' => '200', 'authorId' => '2', 'storyId' => '1', 'body' => 'Me2']),
|
||||||
|
'300' => new Comment(['id' => '300', 'authorId' => '3', 'storyId' => '1', 'body' => 'U2']),
|
||||||
|
|
||||||
|
# thread #2:
|
||||||
|
'400' => new Comment(['id' => '400', 'authorId' => '2', 'storyId' => '2', 'body' => 'Me too']),
|
||||||
|
'500' => new Comment(['id' => '500', 'authorId' => '2', 'storyId' => '2', 'body' => 'Nice!']),
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->storyComments = [
|
||||||
|
'1' => ['100', '200', '300'],
|
||||||
|
'2' => ['400', '500']
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->commentReplies = [
|
||||||
|
'100' => ['110', '120', '130'],
|
||||||
|
'110' => ['111', '112', '113', '114', '115', '116', '117'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->storyMentions = [
|
||||||
|
'1' => [
|
||||||
|
$this->users['2']
|
||||||
|
],
|
||||||
|
'2' => [
|
||||||
|
$this->stories['1'],
|
||||||
|
$this->users['3']
|
||||||
|
]
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,16 +112,16 @@ class DataSource
|
|||||||
return !empty($storiesFound) ? $storiesFound[count($storiesFound) - 1] : null;
|
return !empty($storiesFound) ? $storiesFound[count($storiesFound) - 1] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isLikedBy(Story $story, User $user)
|
public function isLikedBy($storyId, $userId)
|
||||||
{
|
{
|
||||||
$subscribers = isset($this->storyLikes[$story->id]) ? $this->storyLikes[$story->id] : [];
|
$subscribers = isset($this->storyLikes[$storyId]) ? $this->storyLikes[$storyId] : [];
|
||||||
return in_array($user->id, $subscribers);
|
return in_array($userId, $subscribers);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUserPhoto(User $user, $size)
|
public function getUserPhoto($userId, $size)
|
||||||
{
|
{
|
||||||
return new Image([
|
return new Image([
|
||||||
'id' => $user->id,
|
'id' => $userId,
|
||||||
'type' => Image::TYPE_USERPIC,
|
'type' => Image::TYPE_USERPIC,
|
||||||
'size' => $size,
|
'size' => $size,
|
||||||
'width' => rand(100, 200),
|
'width' => rand(100, 200),
|
||||||
@ -92,4 +133,55 @@ class DataSource
|
|||||||
{
|
{
|
||||||
return array_pop($this->stories);
|
return array_pop($this->stories);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function findStories($limit, $afterId = null)
|
||||||
|
{
|
||||||
|
$start = $afterId ? (int) array_search($afterId, array_keys($this->stories)) + 1 : 0;
|
||||||
|
return array_slice(array_values($this->stories), $start, $limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findComments($storyId, $limit = 5, $afterId = null)
|
||||||
|
{
|
||||||
|
$storyComments = isset($this->storyComments[$storyId]) ? $this->storyComments[$storyId] : [];
|
||||||
|
|
||||||
|
$start = isset($after) ? (int) array_search($afterId, $storyComments) + 1 : 0;
|
||||||
|
$storyComments = array_slice($storyComments, $start, $limit);
|
||||||
|
|
||||||
|
return array_map(
|
||||||
|
function($commentId) {
|
||||||
|
return $this->comments[$commentId];
|
||||||
|
},
|
||||||
|
$storyComments
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findReplies($commentId, $limit = 5, $afterId = null)
|
||||||
|
{
|
||||||
|
$commentReplies = isset($this->commentReplies[$commentId]) ? $this->commentReplies[$commentId] : [];
|
||||||
|
|
||||||
|
$start = isset($after) ? (int) array_search($afterId, $commentReplies) + 1: 0;
|
||||||
|
$commentReplies = array_slice($commentReplies, $start, $limit);
|
||||||
|
|
||||||
|
return array_map(
|
||||||
|
function($replyId) {
|
||||||
|
return $this->comments[$replyId];
|
||||||
|
},
|
||||||
|
$commentReplies
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function countComments($storyId)
|
||||||
|
{
|
||||||
|
return isset($this->storyComments[$storyId]) ? count($this->storyComments[$storyId]) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function countReplies($commentId)
|
||||||
|
{
|
||||||
|
return isset($this->commentReplies[$commentId]) ? count($this->commentReplies[$commentId]) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findStoryMentions($storyId)
|
||||||
|
{
|
||||||
|
return isset($this->storyMentions[$storyId]) ? $this->storyMentions[$storyId] :[];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,6 @@ class Story
|
|||||||
|
|
||||||
public $isAnonymous = false;
|
public $isAnonymous = false;
|
||||||
|
|
||||||
public $isLiked = false;
|
|
||||||
|
|
||||||
public function __construct(array $data)
|
public function __construct(array $data)
|
||||||
{
|
{
|
||||||
Utils::assign($this, $data);
|
Utils::assign($this, $data);
|
||||||
|
21
examples/01-blog/Blog/Type/BaseType.php
Normal file
21
examples/01-blog/Blog/Type/BaseType.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Examples\Blog\Type;
|
||||||
|
|
||||||
|
use GraphQL\Type\Definition\Type;
|
||||||
|
use GraphQL\Type\DefinitionContainer;
|
||||||
|
|
||||||
|
class BaseType implements DefinitionContainer
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Type
|
||||||
|
*/
|
||||||
|
protected $definition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Type
|
||||||
|
*/
|
||||||
|
public function getDefinition()
|
||||||
|
{
|
||||||
|
return $this->definition;
|
||||||
|
}
|
||||||
|
}
|
61
examples/01-blog/Blog/Type/CommentType.php
Normal file
61
examples/01-blog/Blog/Type/CommentType.php
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Examples\Blog\Type;
|
||||||
|
|
||||||
|
use GraphQL\Examples\Blog\AppContext;
|
||||||
|
use GraphQL\Examples\Blog\Data\Comment;
|
||||||
|
use GraphQL\Examples\Blog\TypeSystem;
|
||||||
|
use GraphQL\Type\Definition\ObjectType;
|
||||||
|
use GraphQL\Type\Definition\ResolveInfo;
|
||||||
|
|
||||||
|
class CommentType extends BaseType
|
||||||
|
{
|
||||||
|
public function __construct(TypeSystem $types)
|
||||||
|
{
|
||||||
|
$this->definition = new ObjectType([
|
||||||
|
'name' => 'Comment',
|
||||||
|
'fields' => function() use ($types) {
|
||||||
|
return [
|
||||||
|
'id' => $types->id(),
|
||||||
|
'author' => $types->user(),
|
||||||
|
'parent' => $types->comment(),
|
||||||
|
'replies' => [
|
||||||
|
'type' => $types->listOf($types->comment()),
|
||||||
|
'args' => [
|
||||||
|
'after' => $types->int(),
|
||||||
|
'limit' => [
|
||||||
|
'type' => $types->int(),
|
||||||
|
'defaultValue' => 5
|
||||||
|
]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'totalReplyCount' => $types->int(),
|
||||||
|
|
||||||
|
$types->htmlField('body')
|
||||||
|
];
|
||||||
|
},
|
||||||
|
'resolveField' => function($value, $args, $context, ResolveInfo $info) {
|
||||||
|
if (method_exists($this, $info->fieldName)) {
|
||||||
|
return $this->{$info->fieldName}($value, $args, $context, $info);
|
||||||
|
} else {
|
||||||
|
return $value->{$info->fieldName};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function parent(Comment $comment, $args, AppContext $context)
|
||||||
|
{
|
||||||
|
return $context->dataSource->findReplies($comment->id, $args['limit'], $args['after']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function replies(Comment $comment, $args, AppContext $context)
|
||||||
|
{
|
||||||
|
$args += ['after' => null];
|
||||||
|
return $context->dataSource->findReplies($comment->id, $args['limit'], $args['after']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function totalReplyCount(Comment $comment, $args, AppContext $context)
|
||||||
|
{
|
||||||
|
return $context->dataSource->countReplies($comment->id);
|
||||||
|
}
|
||||||
|
}
|
19
examples/01-blog/Blog/Type/Enum/ContentFormatEnum.php
Normal file
19
examples/01-blog/Blog/Type/Enum/ContentFormatEnum.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Examples\Blog\Type\Enum;
|
||||||
|
|
||||||
|
use GraphQL\Examples\Blog\Type\BaseType;
|
||||||
|
use GraphQL\Type\Definition\EnumType;
|
||||||
|
|
||||||
|
class ContentFormatEnum extends BaseType
|
||||||
|
{
|
||||||
|
const FORMAT_TEXT = 'TEXT';
|
||||||
|
const FORMAT_HTML = 'HTML';
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->definition = new EnumType([
|
||||||
|
'name' => 'ContentFormatEnum',
|
||||||
|
'values' => [self::FORMAT_TEXT, self::FORMAT_HTML]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -2,16 +2,17 @@
|
|||||||
namespace GraphQL\Examples\Blog\Type\Enum;
|
namespace GraphQL\Examples\Blog\Type\Enum;
|
||||||
|
|
||||||
use GraphQL\Examples\Blog\Data\Image;
|
use GraphQL\Examples\Blog\Data\Image;
|
||||||
|
use GraphQL\Examples\Blog\Type\BaseType;
|
||||||
use GraphQL\Type\Definition\EnumType;
|
use GraphQL\Type\Definition\EnumType;
|
||||||
|
|
||||||
class ImageSizeEnumType
|
class ImageSizeEnumType extends BaseType
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @return EnumType
|
* ImageSizeEnumType constructor.
|
||||||
*/
|
*/
|
||||||
public static function getDefinition()
|
public function __construct()
|
||||||
{
|
{
|
||||||
return new EnumType([
|
$this->definition = new EnumType([
|
||||||
'name' => 'ImageSizeEnum',
|
'name' => 'ImageSizeEnum',
|
||||||
'values' => [
|
'values' => [
|
||||||
'ICON' => Image::SIZE_ICON,
|
'ICON' => Image::SIZE_ICON,
|
||||||
|
49
examples/01-blog/Blog/Type/Field/HtmlField.php
Normal file
49
examples/01-blog/Blog/Type/Field/HtmlField.php
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Examples\Blog\Type\Field;
|
||||||
|
|
||||||
|
use GraphQL\Examples\Blog\Type\Enum\ContentFormatEnum;
|
||||||
|
use GraphQL\Examples\Blog\TypeSystem;
|
||||||
|
|
||||||
|
class HtmlField
|
||||||
|
{
|
||||||
|
public static function build(TypeSystem $types, $name, $objectKey = null)
|
||||||
|
{
|
||||||
|
$objectKey = $objectKey ?: $name;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'name' => $name,
|
||||||
|
'type' => $types->string(),
|
||||||
|
'args' => [
|
||||||
|
'format' => [
|
||||||
|
'type' => $types->contentFormatEnum(),
|
||||||
|
'defaultValue' => ContentFormatEnum::FORMAT_HTML
|
||||||
|
],
|
||||||
|
'maxLength' => $types->int()
|
||||||
|
],
|
||||||
|
'resolve' => function($object, $args) use ($objectKey) {
|
||||||
|
$html = $object->{$objectKey};
|
||||||
|
$text = strip_tags($html);
|
||||||
|
|
||||||
|
if (!empty($args['maxLength'])) {
|
||||||
|
$safeText = mb_substr($text, 0, $args['maxLength']);
|
||||||
|
} else {
|
||||||
|
$safeText = $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($args['format']) {
|
||||||
|
case ContentFormatEnum::FORMAT_HTML:
|
||||||
|
if ($safeText !== $text) {
|
||||||
|
// Text was truncated, so just show what's safe:
|
||||||
|
return nl2br($safeText);
|
||||||
|
} else {
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ContentFormatEnum::FORMAT_TEXT:
|
||||||
|
default:
|
||||||
|
return $safeText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
31
examples/01-blog/Blog/Type/FieldDefinitions.php
Normal file
31
examples/01-blog/Blog/Type/FieldDefinitions.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Examples\Blog\Type;
|
||||||
|
|
||||||
|
|
||||||
|
use GraphQL\Examples\Blog\Type\Enum\ContentFormatEnum;
|
||||||
|
use GraphQL\Examples\Blog\TypeSystem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Field
|
||||||
|
* @package GraphQL\Examples\Blog\Type
|
||||||
|
*/
|
||||||
|
class FieldDefinitions
|
||||||
|
{
|
||||||
|
private $types;
|
||||||
|
|
||||||
|
public function __construct(TypeSystem $types)
|
||||||
|
{
|
||||||
|
$this->types = $types;
|
||||||
|
}
|
||||||
|
|
||||||
|
private $htmlField;
|
||||||
|
|
||||||
|
public function htmlField($name, $objectKey = null)
|
||||||
|
{
|
||||||
|
$objectKey = $objectKey ?: $name;
|
||||||
|
|
||||||
|
return $this->htmlField ?: $this->htmlField = [
|
||||||
|
'name' => $name,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -7,13 +7,11 @@ use GraphQL\Examples\Blog\TypeSystem;
|
|||||||
use GraphQL\Type\Definition\EnumType;
|
use GraphQL\Type\Definition\EnumType;
|
||||||
use GraphQL\Type\Definition\ObjectType;
|
use GraphQL\Type\Definition\ObjectType;
|
||||||
|
|
||||||
class ImageType
|
class ImageType extends BaseType
|
||||||
{
|
{
|
||||||
public static function getDefinition(TypeSystem $types)
|
public function __construct(TypeSystem $types)
|
||||||
{
|
{
|
||||||
$handler = new self();
|
$this->definition = new ObjectType([
|
||||||
|
|
||||||
return new ObjectType([
|
|
||||||
'name' => 'ImageType',
|
'name' => 'ImageType',
|
||||||
'fields' => [
|
'fields' => [
|
||||||
'id' => $types->id(),
|
'id' => $types->id(),
|
||||||
@ -28,12 +26,20 @@ class ImageType
|
|||||||
'height' => $types->int(),
|
'height' => $types->int(),
|
||||||
'url' => [
|
'url' => [
|
||||||
'type' => $types->url(),
|
'type' => $types->url(),
|
||||||
'resolve' => [$handler, 'resolveUrl']
|
'resolve' => [$this, 'resolveUrl']
|
||||||
],
|
],
|
||||||
'error' => [
|
|
||||||
|
// Just for the sake of example
|
||||||
|
'fieldWithError' => [
|
||||||
'type' => $types->string(),
|
'type' => $types->string(),
|
||||||
'resolve' => function() {
|
'resolve' => function() {
|
||||||
throw new \Exception("This is error field");
|
throw new \Exception("Field with exception");
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'nonNullFieldWithError' => [
|
||||||
|
'type' => $types->nonNull($types->string()),
|
||||||
|
'resolve' => function() {
|
||||||
|
throw new \Exception("Non-null field with exception");
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
31
examples/01-blog/Blog/Type/MentionType.php
Normal file
31
examples/01-blog/Blog/Type/MentionType.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Examples\Blog\Type;
|
||||||
|
|
||||||
|
use GraphQL\Examples\Blog\AppContext;
|
||||||
|
use GraphQL\Examples\Blog\Data\Story;
|
||||||
|
use GraphQL\Examples\Blog\Data\User;
|
||||||
|
use GraphQL\Examples\Blog\TypeSystem;
|
||||||
|
use GraphQL\Type\Definition\UnionType;
|
||||||
|
|
||||||
|
class MentionType extends BaseType
|
||||||
|
{
|
||||||
|
public function __construct(TypeSystem $types)
|
||||||
|
{
|
||||||
|
$this->definition = new UnionType([
|
||||||
|
'name' => 'Mention',
|
||||||
|
'types' => function() use ($types) {
|
||||||
|
return [
|
||||||
|
$types->story(),
|
||||||
|
$types->user()
|
||||||
|
];
|
||||||
|
},
|
||||||
|
'resolveType' => function($value) use ($types) {
|
||||||
|
if ($value instanceof Story) {
|
||||||
|
return $types->story();
|
||||||
|
} else if ($value instanceof User) {
|
||||||
|
return $types->user();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -3,30 +3,30 @@ namespace GraphQL\Examples\Blog\Type;
|
|||||||
|
|
||||||
use GraphQL\Examples\Blog\Data\Story;
|
use GraphQL\Examples\Blog\Data\Story;
|
||||||
use GraphQL\Examples\Blog\Data\User;
|
use GraphQL\Examples\Blog\Data\User;
|
||||||
use GraphQL\Examples\Blog\Image;
|
use GraphQL\Examples\Blog\Data\Image;
|
||||||
use GraphQL\Examples\Blog\TypeSystem;
|
use GraphQL\Examples\Blog\TypeSystem;
|
||||||
use GraphQL\Type\Definition\InterfaceType;
|
use GraphQL\Type\Definition\InterfaceType;
|
||||||
|
|
||||||
class NodeType
|
class NodeType extends BaseType
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
* NodeType constructor.
|
||||||
* @param TypeSystem $types
|
* @param TypeSystem $types
|
||||||
* @return InterfaceType
|
|
||||||
*/
|
*/
|
||||||
public static function getDefinition(TypeSystem $types)
|
public function __construct(TypeSystem $types)
|
||||||
{
|
{
|
||||||
return new InterfaceType([
|
$this->definition = new InterfaceType([
|
||||||
'name' => 'Node',
|
'name' => 'Node',
|
||||||
'fields' => [
|
'fields' => [
|
||||||
'id' => $types->id()
|
'id' => $types->id()
|
||||||
],
|
],
|
||||||
'resolveType' => function ($object) use ($types) {
|
'resolveType' => function ($object) use ($types) {
|
||||||
return self::resolveType($object, $types);
|
return $this->resolveType($object, $types);
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function resolveType($object, TypeSystem $types)
|
public function resolveType($object, TypeSystem $types)
|
||||||
{
|
{
|
||||||
if ($object instanceof User) {
|
if ($object instanceof User) {
|
||||||
return $types->user();
|
return $types->user();
|
||||||
|
@ -5,34 +5,53 @@ use GraphQL\Examples\Blog\AppContext;
|
|||||||
use GraphQL\Examples\Blog\TypeSystem;
|
use GraphQL\Examples\Blog\TypeSystem;
|
||||||
use GraphQL\Type\Definition\ObjectType;
|
use GraphQL\Type\Definition\ObjectType;
|
||||||
use GraphQL\Type\Definition\ResolveInfo;
|
use GraphQL\Type\Definition\ResolveInfo;
|
||||||
|
use GraphQL\Type\Definition\Type;
|
||||||
|
|
||||||
class QueryType
|
class QueryType extends BaseType
|
||||||
{
|
{
|
||||||
public static function getDefinition(TypeSystem $types)
|
public function __construct(TypeSystem $types)
|
||||||
{
|
{
|
||||||
$handler = new self();
|
$this->definition = new ObjectType([
|
||||||
|
|
||||||
return new ObjectType([
|
|
||||||
'name' => 'Query',
|
'name' => 'Query',
|
||||||
'fields' => [
|
'fields' => [
|
||||||
'user' => [
|
'user' => [
|
||||||
'type' => $types->user(),
|
'type' => $types->user(),
|
||||||
|
'description' => 'Returns user by id (in range of 1-5)',
|
||||||
'args' => [
|
'args' => [
|
||||||
'id' => [
|
'id' => $types->nonNull($types->id())
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'viewer' => [
|
||||||
|
'type' => $types->user(),
|
||||||
|
'description' => 'Represents currently logged-in user (for the sake of example - simply returns user with id == 1)'
|
||||||
|
],
|
||||||
|
'stories' => [
|
||||||
|
'type' => $types->listOf($types->story()),
|
||||||
|
'description' => 'Returns subset of stories posted for this blog',
|
||||||
|
'args' => [
|
||||||
|
'after' => [
|
||||||
'type' => $types->id(),
|
'type' => $types->id(),
|
||||||
'defaultValue' => 1
|
'description' => 'Fetch stories listed after the story with this ID'
|
||||||
|
],
|
||||||
|
'limit' => [
|
||||||
|
'type' => $types->int(),
|
||||||
|
'description' => 'Number of stories to be returned',
|
||||||
|
'defaultValue' => 10
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
'viewer' => $types->user(),
|
'lastStoryPosted' => [
|
||||||
'lastStoryPosted' => $types->story(),
|
'type' => $types->story(),
|
||||||
'stories' => [
|
'description' => 'Returns last story posted for this blog'
|
||||||
'type' => $types->listOf($types->story()),
|
],
|
||||||
'args' => []
|
'deprecatedField' => [
|
||||||
]
|
'type' => $types->string(),
|
||||||
|
'deprecationReason' => 'This field is deprecated!'
|
||||||
|
],
|
||||||
|
'hello' => Type::string()
|
||||||
],
|
],
|
||||||
'resolveField' => function($val, $args, $context, ResolveInfo $info) use ($handler) {
|
'resolveField' => function($val, $args, $context, ResolveInfo $info) {
|
||||||
return $handler->{$info->fieldName}($val, $args, $context, $info);
|
return $this->{$info->fieldName}($val, $args, $context, $info);
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -47,8 +66,24 @@ class QueryType
|
|||||||
return $context->viewer;
|
return $context->viewer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function stories($val, $args, AppContext $context)
|
||||||
|
{
|
||||||
|
$args += ['after' => null];
|
||||||
|
return $context->dataSource->findStories($args['limit'], $args['after']);
|
||||||
|
}
|
||||||
|
|
||||||
public function lastStoryPosted($val, $args, AppContext $context)
|
public function lastStoryPosted($val, $args, AppContext $context)
|
||||||
{
|
{
|
||||||
return $context->dataSource->findLatestStory();
|
return $context->dataSource->findLatestStory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function hello()
|
||||||
|
{
|
||||||
|
return 'Your graphql-php endpoint is ready! Use GraphiQL to browse API';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deprecatedField()
|
||||||
|
{
|
||||||
|
return 'You can request deprecated field, but it is not displayed in auto-generated documentation by default.';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,21 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Examples\Blog\Type\Scalar;
|
namespace GraphQL\Examples\Blog\Type\Scalar;
|
||||||
|
|
||||||
|
use GraphQL\Examples\Blog\Type\BaseType;
|
||||||
use GraphQL\Language\AST\StringValue;
|
use GraphQL\Language\AST\StringValue;
|
||||||
use GraphQL\Type\Definition\ScalarType;
|
use GraphQL\Type\Definition\CustomScalarType;
|
||||||
use GraphQL\Utils;
|
use GraphQL\Utils;
|
||||||
|
|
||||||
class EmailType extends ScalarType
|
class EmailType extends BaseType
|
||||||
{
|
{
|
||||||
public $name = 'Email';
|
public function __construct()
|
||||||
|
|
||||||
public static function create()
|
|
||||||
{
|
{
|
||||||
return new self();
|
$this->definition = new CustomScalarType([
|
||||||
|
'name' => 'Email',
|
||||||
|
'serialize' => [$this, 'serialize'],
|
||||||
|
'parseValue' => [$this, 'parseValue'],
|
||||||
|
'parseLiteral' => [$this, 'parseLiteral'],
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -5,18 +5,15 @@ use GraphQL\Language\AST\StringValue;
|
|||||||
use GraphQL\Type\Definition\ScalarType;
|
use GraphQL\Type\Definition\ScalarType;
|
||||||
use GraphQL\Utils;
|
use GraphQL\Utils;
|
||||||
|
|
||||||
class UrlType extends ScalarType
|
/**
|
||||||
|
* Class UrlTypeDefinition
|
||||||
|
*
|
||||||
|
* @package GraphQL\Examples\Blog\Type\Scalar
|
||||||
|
*/
|
||||||
|
class UrlTypeDefinition extends ScalarType
|
||||||
{
|
{
|
||||||
public $name = 'Url';
|
public $name = 'Url';
|
||||||
|
|
||||||
/**
|
|
||||||
* @return UrlType
|
|
||||||
*/
|
|
||||||
public static function create()
|
|
||||||
{
|
|
||||||
return new self();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serializes an internal value to include in a response.
|
* Serializes an internal value to include in a response.
|
||||||
*
|
*
|
@ -13,92 +13,74 @@ use GraphQL\Type\Definition\ResolveInfo;
|
|||||||
* Class StoryType
|
* Class StoryType
|
||||||
* @package GraphQL\Examples\Social\Type
|
* @package GraphQL\Examples\Social\Type
|
||||||
*/
|
*/
|
||||||
class StoryType
|
class StoryType extends BaseType
|
||||||
{
|
{
|
||||||
const EDIT = 'EDIT';
|
const EDIT = 'EDIT';
|
||||||
const DELETE = 'DELETE';
|
const DELETE = 'DELETE';
|
||||||
const LIKE = 'LIKE';
|
const LIKE = 'LIKE';
|
||||||
const UNLIKE = 'UNLIKE';
|
const UNLIKE = 'UNLIKE';
|
||||||
|
const REPLY = 'REPLY';
|
||||||
|
|
||||||
const FORMAT_TEXT = 'TEXT';
|
public function __construct(TypeSystem $types)
|
||||||
const FORMAT_HTML = 'HTML';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param TypeSystem $types
|
|
||||||
* @return ObjectType
|
|
||||||
*/
|
|
||||||
public static function getDefinition(TypeSystem $types)
|
|
||||||
{
|
{
|
||||||
// Type instance containing resolvers for field definitions
|
$this->definition = new ObjectType([
|
||||||
$handler = new self();
|
|
||||||
|
|
||||||
// Return definition for this type:
|
|
||||||
return new ObjectType([
|
|
||||||
'name' => 'Story',
|
'name' => 'Story',
|
||||||
'fields' => function() use ($types) {
|
'fields' => function() use ($types) {
|
||||||
return [
|
return [
|
||||||
'id' => $types->id(),
|
'id' => $types->id(),
|
||||||
'author' => $types->user(),
|
'author' => $types->user(),
|
||||||
'body' => [
|
'mentions' => $types->listOf($types->mention()),
|
||||||
'type' => $types->string(),
|
'totalCommentCount' => $types->int(),
|
||||||
|
'comments' => [
|
||||||
|
'type' => $types->listOf($types->comment()),
|
||||||
'args' => [
|
'args' => [
|
||||||
'format' => new EnumType([
|
'after' => [
|
||||||
'name' => 'StoryFormatEnum',
|
'type' => $types->id(),
|
||||||
'values' => [self::FORMAT_TEXT, self::FORMAT_HTML]
|
'description' => 'Load all comments listed after given comment ID'
|
||||||
]),
|
],
|
||||||
'maxLength' => $types->int()
|
'limit' => [
|
||||||
|
'type' => $types->int(),
|
||||||
|
'defaultValue' => 5
|
||||||
|
]
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
'isLiked' => $types->boolean(),
|
|
||||||
'affordances' => $types->listOf(new EnumType([
|
'affordances' => $types->listOf(new EnumType([
|
||||||
'name' => 'StoryAffordancesEnum',
|
'name' => 'StoryAffordancesEnum',
|
||||||
'values' => [
|
'values' => [
|
||||||
self::EDIT,
|
self::EDIT,
|
||||||
self::DELETE,
|
self::DELETE,
|
||||||
self::LIKE,
|
self::LIKE,
|
||||||
self::UNLIKE
|
self::UNLIKE,
|
||||||
|
self::REPLY
|
||||||
]
|
]
|
||||||
]))
|
])),
|
||||||
|
'hasViewerLiked' => $types->boolean(),
|
||||||
|
|
||||||
|
$types->htmlField('body'),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
'interfaces' => [
|
'interfaces' => [
|
||||||
$types->node()
|
$types->node()
|
||||||
],
|
],
|
||||||
'resolveField' => function($value, $args, $context, ResolveInfo $info) use ($handler) {
|
'resolveField' => function($value, $args, $context, ResolveInfo $info) {
|
||||||
if (method_exists($handler, $info->fieldName)) {
|
if (method_exists($this, $info->fieldName)) {
|
||||||
return $handler->{$info->fieldName}($value, $args, $context, $info);
|
return $this->{$info->fieldName}($value, $args, $context, $info);
|
||||||
} else {
|
} else {
|
||||||
return $value->{$info->fieldName};
|
return $value->{$info->fieldName};
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
'containerType' => $handler
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Story $story
|
|
||||||
* @param $args
|
|
||||||
* @param AppContext $context
|
|
||||||
* @return User|null
|
|
||||||
*/
|
|
||||||
public function author(Story $story, $args, AppContext $context)
|
public function author(Story $story, $args, AppContext $context)
|
||||||
{
|
{
|
||||||
if ($story->isAnonymous) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return $context->dataSource->findUser($story->authorId);
|
return $context->dataSource->findUser($story->authorId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Story $story
|
|
||||||
* @param $args
|
|
||||||
* @param AppContext $context
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function affordances(Story $story, $args, AppContext $context)
|
public function affordances(Story $story, $args, AppContext $context)
|
||||||
{
|
{
|
||||||
$isViewer = $context->viewer === $context->dataSource->findUser($story->authorId);
|
$isViewer = $context->viewer === $context->dataSource->findUser($story->authorId);
|
||||||
$isLiked = $context->dataSource->isLikedBy($story, $context->viewer);
|
$isLiked = $context->dataSource->isLikedBy($story->id, $context->viewer->id);
|
||||||
|
|
||||||
if ($isViewer) {
|
if ($isViewer) {
|
||||||
$affordances[] = self::EDIT;
|
$affordances[] = self::EDIT;
|
||||||
@ -111,4 +93,20 @@ class StoryType
|
|||||||
}
|
}
|
||||||
return $affordances;
|
return $affordances;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function hasViewerLiked(Story $story, $args, AppContext $context)
|
||||||
|
{
|
||||||
|
return $context->dataSource->isLikedBy($story->id, $context->viewer->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function totalCommentCount(Story $story, $args, AppContext $context)
|
||||||
|
{
|
||||||
|
return $context->dataSource->countComments($story->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function comments(Story $story, $args, AppContext $context)
|
||||||
|
{
|
||||||
|
$args += ['after' => null];
|
||||||
|
return $context->dataSource->findComments($story->id, $args['limit'], $args['after']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,13 +7,11 @@ use GraphQL\Examples\Blog\TypeSystem;
|
|||||||
use GraphQL\Type\Definition\ObjectType;
|
use GraphQL\Type\Definition\ObjectType;
|
||||||
use GraphQL\Type\Definition\ResolveInfo;
|
use GraphQL\Type\Definition\ResolveInfo;
|
||||||
|
|
||||||
class UserType
|
class UserType extends BaseType
|
||||||
{
|
{
|
||||||
public static function getDefinition(TypeSystem $types)
|
public function __construct(TypeSystem $types)
|
||||||
{
|
{
|
||||||
$handler = new self();
|
$this->definition = new ObjectType([
|
||||||
|
|
||||||
return new ObjectType([
|
|
||||||
'name' => 'User',
|
'name' => 'User',
|
||||||
'fields' => function() use ($types) {
|
'fields' => function() use ($types) {
|
||||||
return [
|
return [
|
||||||
@ -33,7 +31,7 @@ class UserType
|
|||||||
'type' => $types->string(),
|
'type' => $types->string(),
|
||||||
],
|
],
|
||||||
'lastStoryPosted' => $types->story(),
|
'lastStoryPosted' => $types->story(),
|
||||||
'error' => [
|
'fieldWithError' => [
|
||||||
'type' => $types->string(),
|
'type' => $types->string(),
|
||||||
'resolve' => function() {
|
'resolve' => function() {
|
||||||
throw new \Exception("This is error field");
|
throw new \Exception("This is error field");
|
||||||
@ -44,9 +42,9 @@ class UserType
|
|||||||
'interfaces' => [
|
'interfaces' => [
|
||||||
$types->node()
|
$types->node()
|
||||||
],
|
],
|
||||||
'resolveField' => function($value, $args, $context, ResolveInfo $info) use ($handler) {
|
'resolveField' => function($value, $args, $context, ResolveInfo $info) {
|
||||||
if (method_exists($handler, $info->fieldName)) {
|
if (method_exists($this, $info->fieldName)) {
|
||||||
return $handler->{$info->fieldName}($value, $args, $context, $info);
|
return $this->{$info->fieldName}($value, $args, $context, $info);
|
||||||
} else {
|
} else {
|
||||||
return $value->{$info->fieldName};
|
return $value->{$info->fieldName};
|
||||||
}
|
}
|
||||||
@ -56,7 +54,7 @@ class UserType
|
|||||||
|
|
||||||
public function photo(User $user, $args, AppContext $context)
|
public function photo(User $user, $args, AppContext $context)
|
||||||
{
|
{
|
||||||
return $context->dataSource->getUserPhoto($user, $args['size']);
|
return $context->dataSource->getUserPhoto($user->id, $args['size']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function lastStoryPosted(User $user, $args, AppContext $context)
|
public function lastStoryPosted(User $user, $args, AppContext $context)
|
||||||
|
@ -1,24 +1,29 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Examples\Blog;
|
namespace GraphQL\Examples\Blog;
|
||||||
|
|
||||||
|
use GraphQL\Examples\Blog\Type\CommentType;
|
||||||
|
use GraphQL\Examples\Blog\Type\Enum\ContentFormatEnum;
|
||||||
use GraphQL\Examples\Blog\Type\Enum\ImageSizeEnumType;
|
use GraphQL\Examples\Blog\Type\Enum\ImageSizeEnumType;
|
||||||
|
use GraphQL\Examples\Blog\Type\Field\HtmlField;
|
||||||
|
use GraphQL\Examples\Blog\Type\FieldDefinitions;
|
||||||
|
use GraphQL\Examples\Blog\Type\MentionType;
|
||||||
use GraphQL\Examples\Blog\Type\NodeType;
|
use GraphQL\Examples\Blog\Type\NodeType;
|
||||||
use GraphQL\Examples\Blog\Type\QueryType;
|
use GraphQL\Examples\Blog\Type\QueryType;
|
||||||
use GraphQL\Examples\Blog\Type\Scalar\EmailType;
|
use GraphQL\Examples\Blog\Type\Scalar\EmailType;
|
||||||
use GraphQL\Examples\Blog\Type\StoryType;
|
use GraphQL\Examples\Blog\Type\StoryType;
|
||||||
use GraphQL\Examples\Blog\Type\Scalar\UrlType;
|
use GraphQL\Examples\Blog\Type\Scalar\UrlTypeDefinition;
|
||||||
use GraphQL\Examples\Blog\Type\UserType;
|
use GraphQL\Examples\Blog\Type\UserType;
|
||||||
use GraphQL\Examples\Blog\Type\ImageType;
|
use GraphQL\Examples\Blog\Type\ImageType;
|
||||||
use GraphQL\Type\Definition\EnumType;
|
|
||||||
use GraphQL\Type\Definition\ListOfType;
|
use GraphQL\Type\Definition\ListOfType;
|
||||||
use GraphQL\Type\Definition\NonNull;
|
use GraphQL\Type\Definition\NonNull;
|
||||||
use GraphQL\Type\Definition\ObjectType;
|
|
||||||
use GraphQL\Type\Definition\Type;
|
use GraphQL\Type\Definition\Type;
|
||||||
|
use GraphQL\Type\DefinitionContainer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class TypeSystem
|
* Class TypeSystem
|
||||||
*
|
*
|
||||||
* Acts as a registry and factory for your types.
|
* Acts as a registry and factory for your types.
|
||||||
|
*
|
||||||
* As simplistic as possible for the sake of clarity of this example.
|
* As simplistic as possible for the sake of clarity of this example.
|
||||||
* Your own may be more dynamic (or even code-generated).
|
* Your own may be more dynamic (or even code-generated).
|
||||||
*
|
*
|
||||||
@ -27,65 +32,95 @@ use GraphQL\Type\Definition\Type;
|
|||||||
class TypeSystem
|
class TypeSystem
|
||||||
{
|
{
|
||||||
// Object types:
|
// Object types:
|
||||||
private $story;
|
|
||||||
private $user;
|
private $user;
|
||||||
|
private $story;
|
||||||
|
private $comment;
|
||||||
private $image;
|
private $image;
|
||||||
private $query;
|
private $query;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return ObjectType
|
* @return UserType
|
||||||
*/
|
|
||||||
public function story()
|
|
||||||
{
|
|
||||||
return $this->story ?: ($this->story = StoryType::getDefinition($this));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return ObjectType
|
|
||||||
*/
|
*/
|
||||||
public function user()
|
public function user()
|
||||||
{
|
{
|
||||||
return $this->user ?: ($this->user = UserType::getDefinition($this));
|
return $this->user ?: ($this->user = new UserType($this));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return ObjectType
|
* @return StoryType
|
||||||
|
*/
|
||||||
|
public function story()
|
||||||
|
{
|
||||||
|
return $this->story ?: ($this->story = new StoryType($this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return CommentType
|
||||||
|
*/
|
||||||
|
public function comment()
|
||||||
|
{
|
||||||
|
return $this->comment ?: ($this->comment = new CommentType($this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ImageType
|
||||||
*/
|
*/
|
||||||
public function image()
|
public function image()
|
||||||
{
|
{
|
||||||
return $this->image ?: ($this->image = ImageType::getDefinition($this));
|
return $this->image ?: ($this->image = new ImageType($this));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return ObjectType
|
* @return QueryType
|
||||||
*/
|
*/
|
||||||
public function query()
|
public function query()
|
||||||
{
|
{
|
||||||
return $this->query ?: ($this->query = QueryType::getDefinition($this));
|
return $this->query ?: ($this->query = new QueryType($this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Interfaces
|
// Interface types
|
||||||
private $nodeDefinition;
|
private $node;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return \GraphQL\Type\Definition\InterfaceType
|
* @return NodeType
|
||||||
*/
|
*/
|
||||||
public function node()
|
public function node()
|
||||||
{
|
{
|
||||||
return $this->nodeDefinition ?: ($this->nodeDefinition = NodeType::getDefinition($this));
|
return $this->node ?: ($this->node = new NodeType($this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Enums
|
// Unions types:
|
||||||
private $imageSizeEnum;
|
private $mention;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return EnumType
|
* @return MentionType
|
||||||
|
*/
|
||||||
|
public function mention()
|
||||||
|
{
|
||||||
|
return $this->mention ?: ($this->mention = new MentionType($this));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Enum types
|
||||||
|
private $imageSizeEnum;
|
||||||
|
private $contentFormatEnum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ImageSizeEnumType
|
||||||
*/
|
*/
|
||||||
public function imageSizeEnum()
|
public function imageSizeEnum()
|
||||||
{
|
{
|
||||||
return $this->imageSizeEnum ?: ($this->imageSizeEnum = ImageSizeEnumType::getDefinition());
|
return $this->imageSizeEnum ?: ($this->imageSizeEnum = new ImageSizeEnumType());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ContentFormatEnum
|
||||||
|
*/
|
||||||
|
public function contentFormatEnum()
|
||||||
|
{
|
||||||
|
return $this->contentFormatEnum ?: ($this->contentFormatEnum = new ContentFormatEnum());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Custom Scalar types:
|
// Custom Scalar types:
|
||||||
@ -94,17 +129,28 @@ class TypeSystem
|
|||||||
|
|
||||||
public function email()
|
public function email()
|
||||||
{
|
{
|
||||||
return $this->emailType ?: ($this->emailType = EmailType::create());
|
return $this->emailType ?: ($this->emailType = new EmailType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return UrlType
|
* @return UrlTypeDefinition
|
||||||
*/
|
*/
|
||||||
public function url()
|
public function url()
|
||||||
{
|
{
|
||||||
return $this->urlType ?: ($this->urlType = UrlType::create());
|
return $this->urlType ?: ($this->urlType = new UrlTypeDefinition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $name
|
||||||
|
* @param null $objectKey
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function htmlField($name, $objectKey = null)
|
||||||
|
{
|
||||||
|
return HtmlField::build($this, $name, $objectKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Let's add internal types as well for consistent experience
|
// Let's add internal types as well for consistent experience
|
||||||
|
|
||||||
@ -146,7 +192,7 @@ class TypeSystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Type $type
|
* @param Type|DefinitionContainer $type
|
||||||
* @return ListOfType
|
* @return ListOfType
|
||||||
*/
|
*/
|
||||||
public function listOf($type)
|
public function listOf($type)
|
||||||
@ -155,7 +201,7 @@ class TypeSystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $type
|
* @param Type|DefinitionContainer $type
|
||||||
* @return NonNull
|
* @return NonNull
|
||||||
*/
|
*/
|
||||||
public function nonNull($type)
|
public function nonNull($type)
|
||||||
|
@ -1,29 +1,45 @@
|
|||||||
## Blog Example
|
## Blog Example
|
||||||
|
Simple yet full-featured example of GraphQL API. Models simple blog with Stories and Users.
|
||||||
|
|
||||||
Simple but full-featured example of GraphQL API. Models simple blog with Stories and Users.
|
### Run locally
|
||||||
|
|
||||||
Note that graphql-php doesn't dictate you how to structure your application or data layer.
|
|
||||||
You may choose the way of using the library as you prefer.
|
|
||||||
|
|
||||||
Best practices in GraphQL world still emerge, so feel free to post your proposals or own
|
|
||||||
examples as PRs.
|
|
||||||
|
|
||||||
### Running locally
|
|
||||||
```
|
```
|
||||||
php -S localhost:8080 ./index.php
|
php -S localhost:8080 ./index.php
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Test if GraphQL is running
|
||||||
|
If you open `http://localhost:8080` in browser you should see `json` response with
|
||||||
|
following message:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
hello: "Your GraphQL endpoint is ready! Install GraphiQL to browse API"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that some browsers may try to download JSON file instead of showing you the response.
|
||||||
|
In this case try to install browser plugin that adds JSON support (like JSONView or similar)
|
||||||
|
|
||||||
|
### Debugging Mode
|
||||||
|
By default GraphQL endpoint exposed at `http://localhost:8080` runs in production mode without
|
||||||
|
additional debugging tools enabled.
|
||||||
|
|
||||||
|
In order to enable debugging mode with additional validation, error handling and reporting -
|
||||||
|
use `http://localhost:8080?debug=1` as endpoint
|
||||||
|
|
||||||
### Browsing API
|
### Browsing API
|
||||||
The most convenient way to browse GraphQL API is by using [GraphiQL](https://github.com/graphql/graphiql)
|
The most convenient way to browse GraphQL API is by using [GraphiQL](https://github.com/graphql/graphiql)
|
||||||
But setting it up from scratch may be inconvenient. The great and easy alternative is to use one of
|
But setting it up from scratch may be inconvenient. An easy alternative is to use one of
|
||||||
existing Google Chrome extensions:
|
the existing Google Chrome extensions:
|
||||||
- [ChromeiQL](https://chrome.google.com/webstore/detail/chromeiql/fkkiamalmpiidkljmicmjfbieiclmeij)
|
- [ChromeiQL](https://chrome.google.com/webstore/detail/chromeiql/fkkiamalmpiidkljmicmjfbieiclmeij)
|
||||||
- [GraphiQL Feen](https://chrome.google.com/webstore/detail/graphiql-feen/mcbfdonlkfpbfdpimkjilhdneikhfklp)
|
- [GraphiQL Feen](https://chrome.google.com/webstore/detail/graphiql-feen/mcbfdonlkfpbfdpimkjilhdneikhfklp)
|
||||||
|
|
||||||
Note that these extensions may be out of date, but most of the time they Just Work(TM)
|
Set `http://localhost:8080?debug=1` as your GraphQL endpoint/server in one of these extensions
|
||||||
|
and try clicking "Docs" button (usually in the top-right corner) to browse auto-generated
|
||||||
|
documentation.
|
||||||
|
|
||||||
Set up `http://localhost:8080?debug=0` as your GraphQL endpoint/server in these extensions and
|
### Running GraphQL queries
|
||||||
execute following query:
|
Copy following query to GraphiQL and execute (by clicking play button on top bar)
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
@ -31,9 +47,21 @@ execute following query:
|
|||||||
id
|
id
|
||||||
email
|
email
|
||||||
}
|
}
|
||||||
|
user(id: "2") {
|
||||||
|
id
|
||||||
|
email
|
||||||
|
}
|
||||||
|
stories(after: "1") {
|
||||||
|
id
|
||||||
|
body
|
||||||
|
comments {
|
||||||
|
...CommentView
|
||||||
|
}
|
||||||
|
}
|
||||||
lastStoryPosted {
|
lastStoryPosted {
|
||||||
id
|
id
|
||||||
isLiked
|
hasViewerLiked
|
||||||
|
|
||||||
author {
|
author {
|
||||||
id
|
id
|
||||||
photo(size: ICON) {
|
photo(size: ICON) {
|
||||||
@ -43,19 +71,49 @@ execute following query:
|
|||||||
size
|
size
|
||||||
width
|
width
|
||||||
height
|
height
|
||||||
# Uncomment to see error reporting for failed resolvers
|
# Uncomment following line to see validation error:
|
||||||
# error
|
# nonExistingField
|
||||||
|
|
||||||
|
# Uncomment to see error reporting for fields with exceptions thrown in resolvers
|
||||||
|
# fieldWithError
|
||||||
|
# nonNullFieldWithError
|
||||||
}
|
}
|
||||||
lastStoryPosted {
|
lastStoryPosted {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
body(format: HTML, maxLength: 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment CommentView on Comment {
|
||||||
|
id
|
||||||
|
body
|
||||||
|
totalReplyCount
|
||||||
|
replies {
|
||||||
|
id
|
||||||
|
body
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Debugging
|
### Run your own query
|
||||||
By default this example runs in production mode without additional debugging tools enabled.
|
Use GraphiQL autocomplete (via CTRL+space) to easily create your own query.
|
||||||
|
|
||||||
In order to enable debugging mode with additional validation of type definition configs,
|
Note: GraphQL query requires at least one field per object type (to prevent accidental overfetching).
|
||||||
PHP errors handling and reporting - change your endpoint to `http://localhost:8080?debug=1`
|
For example following query is invalid in GraphQL:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
viewer
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Try copying this query and see what happens
|
||||||
|
|
||||||
|
### Run mutation query
|
||||||
|
TODOC
|
||||||
|
|
||||||
|
### Dig into source code
|
||||||
|
Now when you tried GraphQL API as a consumer, see how it is implemented by browsing
|
||||||
|
source code.
|
||||||
|
@ -9,6 +9,7 @@ use \GraphQL\Examples\Blog\Data\DataSource;
|
|||||||
use \GraphQL\Schema;
|
use \GraphQL\Schema;
|
||||||
use \GraphQL\GraphQL;
|
use \GraphQL\GraphQL;
|
||||||
use \GraphQL\Type\Definition\Config;
|
use \GraphQL\Type\Definition\Config;
|
||||||
|
use \GraphQL\Error\FormattedError;
|
||||||
|
|
||||||
// Disable default PHP error reporting - we have better one for debug mode (see bellow)
|
// Disable default PHP error reporting - we have better one for debug mode (see bellow)
|
||||||
ini_set('display_errors', 0);
|
ini_set('display_errors', 0);
|
||||||
@ -49,6 +50,12 @@ try {
|
|||||||
}
|
}
|
||||||
$data += ['query' => null, 'variables' => null];
|
$data += ['query' => null, 'variables' => null];
|
||||||
|
|
||||||
|
if (null === $data['query']) {
|
||||||
|
$data['query'] = '
|
||||||
|
{hello}
|
||||||
|
';
|
||||||
|
}
|
||||||
|
|
||||||
// GraphQL schema to be passed to query executor:
|
// GraphQL schema to be passed to query executor:
|
||||||
$schema = new Schema([
|
$schema = new Schema([
|
||||||
'query' => $typeSystem->query()
|
'query' => $typeSystem->query()
|
||||||
@ -73,9 +80,9 @@ try {
|
|||||||
} catch (\Exception $error) {
|
} catch (\Exception $error) {
|
||||||
$httpStatus = 500;
|
$httpStatus = 500;
|
||||||
if (!empty($_GET['debug'])) {
|
if (!empty($_GET['debug'])) {
|
||||||
$result['extensions']['exception'] = \GraphQL\Error\FormattedError::createFromException($error);
|
$result['extensions']['exception'] = FormattedError::createFromException($error);
|
||||||
} else {
|
} else {
|
||||||
$result['errors'] = \GraphQL\Error\FormattedError::create('Unexpected Error');
|
$result['errors'] = [FormattedError::create('Unexpected Error')];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user