Simplified blog example

This commit is contained in:
vladar 2016-11-06 21:33:13 +07:00
parent e69667c633
commit 360bf39c9b
12 changed files with 236 additions and 218 deletions

View File

@ -9,7 +9,7 @@ use GraphQL\Utils;
* Class AppContext * Class AppContext
* Instance available in all GraphQL resolvers as 3rd argument * Instance available in all GraphQL resolvers as 3rd argument
* *
* @package GraphQL\Examples\Social * @package GraphQL\Examples\Blog
*/ */
class AppContext class AppContext
{ {
@ -27,9 +27,4 @@ class AppContext
* @var \mixed * @var \mixed
*/ */
public $request; public $request;
/**
* @var DataSource
*/
public $dataSource;
} }

View File

@ -1,6 +1,5 @@
<?php <?php
namespace GraphQL\Examples\Blog\Data; namespace GraphQL\Examples\Blog\Data;
use GraphQL\Utils;
/** /**
* Class DataSource * Class DataSource
@ -12,14 +11,17 @@ use GraphQL\Utils;
*/ */
class DataSource class DataSource
{ {
private $users = []; private static $users = [];
private $stories = []; private static $stories = [];
private $storyLikes = []; private static $storyLikes = [];
private $comments = []; private static $comments = [];
private static $storyComments = [];
private static $commentReplies = [];
private static $storyMentions = [];
public function __construct() public static function init()
{ {
$this->users = [ self::$users = [
'1' => new User([ '1' => new User([
'id' => '1', 'id' => '1',
'email' => 'john@example.com', 'email' => 'john@example.com',
@ -40,19 +42,19 @@ class DataSource
]), ]),
]; ];
$this->stories = [ self::$stories = [
'1' => new Story(['id' => '1', 'authorId' => '1', 'body' => '<h1>GraphQL is awesome!</h1>']), '1' => new Story(['id' => '1', 'authorId' => '1', 'body' => '<h1>GraphQL is awesome!</h1>']),
'2' => new Story(['id' => '2', 'authorId' => '1', 'body' => '<a>Test this</a>']), '2' => new Story(['id' => '2', 'authorId' => '1', 'body' => '<a>Test this</a>']),
'3' => new Story(['id' => '3', 'authorId' => '3', 'body' => "This\n<br>story\n<br>spans\n<br>newlines"]), '3' => new Story(['id' => '3', 'authorId' => '3', 'body' => "This\n<br>story\n<br>spans\n<br>newlines"]),
]; ];
$this->storyLikes = [ self::$storyLikes = [
'1' => ['1', '2', '3'], '1' => ['1', '2', '3'],
'2' => [], '2' => [],
'3' => ['1'] '3' => ['1']
]; ];
$this->comments = [ self::$comments = [
// thread #1: // thread #1:
'100' => new Comment(['id' => '100', 'authorId' => '3', 'storyId' => '1', 'body' => 'Likes']), '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']), '110' => new Comment(['id' =>'110', 'authorId' =>'2', 'storyId' => '1', 'body' => 'Reply <b>#1</b>', 'parentId' => '100']),
@ -73,57 +75,69 @@ class DataSource
'500' => new Comment(['id' => '500', 'authorId' => '2', 'storyId' => '2', 'body' => 'Nice!']), '500' => new Comment(['id' => '500', 'authorId' => '2', 'storyId' => '2', 'body' => 'Nice!']),
]; ];
$this->storyComments = [ self::$storyComments = [
'1' => ['100', '200', '300'], '1' => ['100', '200', '300'],
'2' => ['400', '500'] '2' => ['400', '500']
]; ];
$this->commentReplies = [ self::$commentReplies = [
'100' => ['110', '120', '130'], '100' => ['110', '120', '130'],
'110' => ['111', '112', '113', '114', '115', '116', '117'], '110' => ['111', '112', '113', '114', '115', '116', '117'],
]; ];
$this->storyMentions = [ self::$storyMentions = [
'1' => [ '1' => [
$this->users['2'] self::$users['2']
], ],
'2' => [ '2' => [
$this->stories['1'], self::$stories['1'],
$this->users['3'] self::$users['3']
] ]
]; ];
} }
public function findUser($id) public static function findUser($id)
{ {
return isset($this->users[$id]) ? $this->users[$id] : null; return isset(self::$users[$id]) ? self::$users[$id] : null;
} }
public function findStory($id) public static function findStory($id)
{ {
return isset($this->stories[$id]) ? $this->stories[$id] : null; return isset(self::$stories[$id]) ? self::$stories[$id] : null;
} }
public function findComment($id) public static function findComment($id)
{ {
return isset($this->comments[$id]) ? $this->comments[$id] : null; return isset(self::$comments[$id]) ? self::$comments[$id] : null;
} }
public function findLastStoryFor($authorId) public static function findLastStoryFor($authorId)
{ {
$storiesFound = array_filter($this->stories, function(Story $story) use ($authorId) { $storiesFound = array_filter(self::$stories, function(Story $story) use ($authorId) {
return $story->authorId == $authorId; return $story->authorId == $authorId;
}); });
return !empty($storiesFound) ? $storiesFound[count($storiesFound) - 1] : null; return !empty($storiesFound) ? $storiesFound[count($storiesFound) - 1] : null;
} }
public function isLikedBy($storyId, $userId) public static function findLikes($storyId, $limit)
{ {
$subscribers = isset($this->storyLikes[$storyId]) ? $this->storyLikes[$storyId] : []; $likes = isset(self::$storyLikes[$storyId]) ? self::$storyLikes[$storyId] : [];
$result = array_map(
function($userId) {
return self::$users[$userId];
},
$likes
);
return array_slice($result, 0, $limit);
}
public static function isLikedBy($storyId, $userId)
{
$subscribers = isset(self::$storyLikes[$storyId]) ? self::$storyLikes[$storyId] : [];
return in_array($userId, $subscribers); return in_array($userId, $subscribers);
} }
public function getUserPhoto($userId, $size) public static function getUserPhoto($userId, $size)
{ {
return new Image([ return new Image([
'id' => $userId, 'id' => $userId,
@ -134,59 +148,59 @@ class DataSource
]); ]);
} }
public function findLatestStory() public static function findLatestStory()
{ {
return array_pop($this->stories); return array_pop(self::$stories);
} }
public function findStories($limit, $afterId = null) public static function findStories($limit, $afterId = null)
{ {
$start = $afterId ? (int) array_search($afterId, array_keys($this->stories)) + 1 : 0; $start = $afterId ? (int) array_search($afterId, array_keys(self::$stories)) + 1 : 0;
return array_slice(array_values($this->stories), $start, $limit); return array_slice(array_values(self::$stories), $start, $limit);
} }
public function findComments($storyId, $limit = 5, $afterId = null) public static function findComments($storyId, $limit = 5, $afterId = null)
{ {
$storyComments = isset($this->storyComments[$storyId]) ? $this->storyComments[$storyId] : []; $storyComments = isset(self::$storyComments[$storyId]) ? self::$storyComments[$storyId] : [];
$start = isset($after) ? (int) array_search($afterId, $storyComments) + 1 : 0; $start = isset($after) ? (int) array_search($afterId, $storyComments) + 1 : 0;
$storyComments = array_slice($storyComments, $start, $limit); $storyComments = array_slice($storyComments, $start, $limit);
return array_map( return array_map(
function($commentId) { function($commentId) {
return $this->comments[$commentId]; return self::$comments[$commentId];
}, },
$storyComments $storyComments
); );
} }
public function findReplies($commentId, $limit = 5, $afterId = null) public static function findReplies($commentId, $limit = 5, $afterId = null)
{ {
$commentReplies = isset($this->commentReplies[$commentId]) ? $this->commentReplies[$commentId] : []; $commentReplies = isset(self::$commentReplies[$commentId]) ? self::$commentReplies[$commentId] : [];
$start = isset($after) ? (int) array_search($afterId, $commentReplies) + 1: 0; $start = isset($after) ? (int) array_search($afterId, $commentReplies) + 1: 0;
$commentReplies = array_slice($commentReplies, $start, $limit); $commentReplies = array_slice($commentReplies, $start, $limit);
return array_map( return array_map(
function($replyId) { function($replyId) {
return $this->comments[$replyId]; return self::$comments[$replyId];
}, },
$commentReplies $commentReplies
); );
} }
public function countComments($storyId) public static function countComments($storyId)
{ {
return isset($this->storyComments[$storyId]) ? count($this->storyComments[$storyId]) : 0; return isset(self::$storyComments[$storyId]) ? count(self::$storyComments[$storyId]) : 0;
} }
public function countReplies($commentId) public static function countReplies($commentId)
{ {
return isset($this->commentReplies[$commentId]) ? count($this->commentReplies[$commentId]) : 0; return isset(self::$commentReplies[$commentId]) ? count(self::$commentReplies[$commentId]) : 0;
} }
public function findStoryMentions($storyId) public static function findStoryMentions($storyId)
{ {
return isset($this->storyMentions[$storyId]) ? $this->storyMentions[$storyId] :[]; return isset(self::$storyMentions[$storyId]) ? self::$storyMentions[$storyId] :[];
} }
} }

View File

@ -3,36 +3,37 @@ namespace GraphQL\Examples\Blog\Type;
use GraphQL\Examples\Blog\AppContext; use GraphQL\Examples\Blog\AppContext;
use GraphQL\Examples\Blog\Data\Comment; use GraphQL\Examples\Blog\Data\Comment;
use GraphQL\Examples\Blog\TypeSystem; use GraphQL\Examples\Blog\Data\DataSource;
use GraphQL\Examples\Blog\Types;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Type\Definition\ResolveInfo;
class CommentType extends BaseType class CommentType extends BaseType
{ {
public function __construct(TypeSystem $types) public function __construct()
{ {
// Option #1: using composition over inheritance to define type, see ImageType for inheritance example // Option #1: using composition over inheritance to define type, see ImageType for inheritance example
$this->definition = new ObjectType([ $this->definition = new ObjectType([
'name' => 'Comment', 'name' => 'Comment',
'fields' => function() use ($types) { 'fields' => function() {
return [ return [
'id' => $types->id(), 'id' => Types::id(),
'author' => $types->user(), 'author' => Types::user(),
'parent' => $types->comment(), 'parent' => Types::comment(),
'isAnonymous' => $types->boolean(), 'isAnonymous' => Types::boolean(),
'replies' => [ 'replies' => [
'type' => $types->listOf($types->comment()), 'type' => Types::listOf(Types::comment()),
'args' => [ 'args' => [
'after' => $types->int(), 'after' => Types::int(),
'limit' => [ 'limit' => [
'type' => $types->int(), 'type' => Types::int(),
'defaultValue' => 5 'defaultValue' => 5
] ]
] ]
], ],
'totalReplyCount' => $types->int(), 'totalReplyCount' => Types::int(),
$types->htmlField('body') Types::htmlField('body')
]; ];
}, },
'resolveField' => function($value, $args, $context, ResolveInfo $info) { 'resolveField' => function($value, $args, $context, ResolveInfo $info) {
@ -45,30 +46,30 @@ class CommentType extends BaseType
]); ]);
} }
public function author(Comment $comment, $args, AppContext $context) public function author(Comment $comment)
{ {
if ($comment->isAnonymous) { if ($comment->isAnonymous) {
return null; return null;
} }
return $context->dataSource->findUser($comment->authorId); return DataSource::findUser($comment->authorId);
} }
public function parent(Comment $comment, $args, AppContext $context) public function parent(Comment $comment)
{ {
if ($comment->parentId) { if ($comment->parentId) {
return $context->dataSource->findComment($comment->parentId); return DataSource::findComment($comment->parentId);
} }
return null; return null;
} }
public function replies(Comment $comment, $args, AppContext $context) public function replies(Comment $comment, $args)
{ {
$args += ['after' => null]; $args += ['after' => null];
return $context->dataSource->findReplies($comment->id, $args['limit'], $args['after']); return DataSource::findReplies($comment->id, $args['limit'], $args['after']);
} }
public function totalReplyCount(Comment $comment, $args, AppContext $context) public function totalReplyCount(Comment $comment)
{ {
return $context->dataSource->countReplies($comment->id); return DataSource::countReplies($comment->id);
} }
} }

View File

@ -2,11 +2,11 @@
namespace GraphQL\Examples\Blog\Type\Field; namespace GraphQL\Examples\Blog\Type\Field;
use GraphQL\Examples\Blog\Type\Enum\ContentFormatEnum; use GraphQL\Examples\Blog\Type\Enum\ContentFormatEnum;
use GraphQL\Examples\Blog\TypeSystem; use GraphQL\Examples\Blog\Types;
class HtmlField class HtmlField
{ {
public static function build(TypeSystem $types, $name, $objectKey = null) public static function build($name, $objectKey = null)
{ {
$objectKey = $objectKey ?: $name; $objectKey = $objectKey ?: $name;
@ -15,13 +15,13 @@ class HtmlField
// (for example when it is a part of some interface) // (for example when it is a part of some interface)
return [ return [
'name' => $name, 'name' => $name,
'type' => $types->string(), 'type' => Types::string(),
'args' => [ 'args' => [
'format' => [ 'format' => [
'type' => $types->contentFormatEnum(), 'type' => Types::contentFormatEnum(),
'defaultValue' => ContentFormatEnum::FORMAT_HTML 'defaultValue' => ContentFormatEnum::FORMAT_HTML
], ],
'maxLength' => $types->int() 'maxLength' => Types::int()
], ],
'resolve' => function($object, $args) use ($objectKey) { 'resolve' => function($object, $args) use ($objectKey) {
$html = $object->{$objectKey}; $html = $object->{$objectKey};

View File

@ -3,42 +3,42 @@ namespace GraphQL\Examples\Blog\Type;
use GraphQL\Examples\Blog\AppContext; use GraphQL\Examples\Blog\AppContext;
use GraphQL\Examples\Blog\Data\Image; use GraphQL\Examples\Blog\Data\Image;
use GraphQL\Examples\Blog\TypeSystem; use GraphQL\Examples\Blog\Types;
use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
class ImageType extends ObjectType class ImageType extends ObjectType
{ {
public function __construct(TypeSystem $types) public function __construct()
{ {
// Option #2: define type using inheritance, see any other object type for compositional example // Option #2: define type using inheritance, see any other object type for compositional example
$config = [ $config = [
'name' => 'ImageType', 'name' => 'ImageType',
'fields' => [ 'fields' => [
'id' => $types->id(), 'id' => Types::id(),
'type' => new EnumType([ 'type' => new EnumType([
'name' => 'ImageTypeEnum', 'name' => 'ImageTypeEnum',
'values' => [ 'values' => [
'USERPIC' => Image::TYPE_USERPIC 'USERPIC' => Image::TYPE_USERPIC
] ]
]), ]),
'size' => $types->imageSizeEnum(), 'size' => Types::imageSizeEnum(),
'width' => $types->int(), 'width' => Types::int(),
'height' => $types->int(), 'height' => Types::int(),
'url' => [ 'url' => [
'type' => $types->url(), 'type' => Types::url(),
'resolve' => [$this, 'resolveUrl'] 'resolve' => [$this, 'resolveUrl']
], ],
// Just for the sake of example // Just for the sake of example
'fieldWithError' => [ 'fieldWithError' => [
'type' => $types->string(), 'type' => Types::string(),
'resolve' => function() { 'resolve' => function() {
throw new \Exception("Field with exception"); throw new \Exception("Field with exception");
} }
], ],
'nonNullFieldWithError' => [ 'nonNullFieldWithError' => [
'type' => $types->nonNull($types->string()), 'type' => Types::nonNull(Types::string()),
'resolve' => function() { 'resolve' => function() {
throw new \Exception("Non-null field with exception"); throw new \Exception("Non-null field with exception");
} }

View File

@ -4,33 +4,31 @@ 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\Data\Image; use GraphQL\Examples\Blog\Data\Image;
use GraphQL\Examples\Blog\TypeSystem; use GraphQL\Examples\Blog\Types;
use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\InterfaceType;
class NodeType extends BaseType class NodeType extends BaseType
{ {
public function __construct(TypeSystem $types) public function __construct()
{ {
// Option #1: using composition over inheritance to define type, see ImageType for inheritance example // Option #1: using composition over inheritance to define type, see ImageType for inheritance example
$this->definition = new InterfaceType([ $this->definition = new InterfaceType([
'name' => 'Node', 'name' => 'Node',
'fields' => [ 'fields' => [
'id' => $types->id() 'id' => Types::id()
], ],
'resolveType' => function ($object) use ($types) { 'resolveType' => [$this, 'resolveType']
return $this->resolveType($object, $types);
}
]); ]);
} }
public function resolveType($object, TypeSystem $types) public function resolveType($object, Types $types)
{ {
if ($object instanceof User) { if ($object instanceof User) {
return $types->user(); return Types::user();
} else if ($object instanceof Image) { } else if ($object instanceof Image) {
return $types->image(); return Types::image();
} else if ($object instanceof Story) { } else if ($object instanceof Story) {
return $types->story(); return Types::story();
} }
} }
} }

View File

@ -2,51 +2,52 @@
namespace GraphQL\Examples\Blog\Type; namespace GraphQL\Examples\Blog\Type;
use GraphQL\Examples\Blog\AppContext; use GraphQL\Examples\Blog\AppContext;
use GraphQL\Examples\Blog\TypeSystem; use GraphQL\Examples\Blog\Data\DataSource;
use GraphQL\Examples\Blog\Types;
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; use GraphQL\Type\Definition\Type;
class QueryType extends BaseType class QueryType extends BaseType
{ {
public function __construct(TypeSystem $types) public function __construct()
{ {
// Option #1: using composition over inheritance to define type, see ImageType for inheritance example // Option #1: using composition over inheritance to define type, see ImageType for inheritance example
$this->definition = new ObjectType([ $this->definition = 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)', 'description' => 'Returns user by id (in range of 1-5)',
'args' => [ 'args' => [
'id' => $types->nonNull($types->id()) 'id' => Types::nonNull(Types::id())
] ]
], ],
'viewer' => [ 'viewer' => [
'type' => $types->user(), 'type' => Types::user(),
'description' => 'Represents currently logged-in user (for the sake of example - simply returns user with id == 1)' 'description' => 'Represents currently logged-in user (for the sake of example - simply returns user with id == 1)'
], ],
'stories' => [ 'stories' => [
'type' => $types->listOf($types->story()), 'type' => Types::listOf(Types::story()),
'description' => 'Returns subset of stories posted for this blog', 'description' => 'Returns subset of stories posted for this blog',
'args' => [ 'args' => [
'after' => [ 'after' => [
'type' => $types->id(), 'type' => Types::id(),
'description' => 'Fetch stories listed after the story with this ID' 'description' => 'Fetch stories listed after the story with this ID'
], ],
'limit' => [ 'limit' => [
'type' => $types->int(), 'type' => Types::int(),
'description' => 'Number of stories to be returned', 'description' => 'Number of stories to be returned',
'defaultValue' => 10 'defaultValue' => 10
] ]
] ]
], ],
'lastStoryPosted' => [ 'lastStoryPosted' => [
'type' => $types->story(), 'type' => Types::story(),
'description' => 'Returns last story posted for this blog' 'description' => 'Returns last story posted for this blog'
], ],
'deprecatedField' => [ 'deprecatedField' => [
'type' => $types->string(), 'type' => Types::string(),
'deprecationReason' => 'This field is deprecated!' 'deprecationReason' => 'This field is deprecated!'
], ],
'hello' => Type::string() 'hello' => Type::string()
@ -57,25 +58,25 @@ class QueryType extends BaseType
]); ]);
} }
public function user($val, $args, AppContext $context) public function user($rootValue, $args)
{ {
return $context->dataSource->findUser($args['id']); return DataSource::findUser($args['id']);
} }
public function viewer($val, $args, AppContext $context) public function viewer($rootValue, $args, AppContext $context)
{ {
return $context->viewer; return $context->viewer;
} }
public function stories($val, $args, AppContext $context) public function stories($rootValue, $args)
{ {
$args += ['after' => null]; $args += ['after' => null];
return $context->dataSource->findStories($args['limit'], $args['after']); return DataSource::findStories($args['limit'], $args['after']);
} }
public function lastStoryPosted($val, $args, AppContext $context) public function lastStoryPosted()
{ {
return $context->dataSource->findLatestStory(); return DataSource::findLatestStory();
} }
public function hello() public function hello()

View File

@ -1,30 +1,29 @@
<?php <?php
namespace GraphQL\Examples\Blog\Type; namespace GraphQL\Examples\Blog\Type;
use GraphQL\Examples\Blog\AppContext;
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\TypeSystem; use GraphQL\Examples\Blog\Types;
use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Definition\UnionType;
class MentionType extends BaseType class SearchResultType extends BaseType
{ {
public function __construct(TypeSystem $types) public function __construct()
{ {
// Option #1: using composition over inheritance to define type, see ImageType for inheritance example // Option #1: using composition over inheritance to define type, see ImageType for inheritance example
$this->definition = new UnionType([ $this->definition = new UnionType([
'name' => 'Mention', 'name' => 'SearchResultType',
'types' => function() use ($types) { 'types' => function() {
return [ return [
$types->story(), Types::story(),
$types->user() Types::user()
]; ];
}, },
'resolveType' => function($value) use ($types) { 'resolveType' => function($value) {
if ($value instanceof Story) { if ($value instanceof Story) {
return $types->story(); return Types::story();
} else if ($value instanceof User) { } else if ($value instanceof User) {
return $types->user(); return Types::user();
} }
} }
]); ]);

View File

@ -2,9 +2,9 @@
namespace GraphQL\Examples\Blog\Type; namespace GraphQL\Examples\Blog\Type;
use GraphQL\Examples\Blog\AppContext; use GraphQL\Examples\Blog\AppContext;
use GraphQL\Examples\Blog\Data\DataSource;
use GraphQL\Examples\Blog\Data\Story; use GraphQL\Examples\Blog\Data\Story;
use GraphQL\Examples\Blog\Data\User; use GraphQL\Examples\Blog\Types;
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;
use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Type\Definition\ResolveInfo;
@ -21,31 +21,44 @@ class StoryType extends BaseType
const UNLIKE = 'UNLIKE'; const UNLIKE = 'UNLIKE';
const REPLY = 'REPLY'; const REPLY = 'REPLY';
public function __construct(TypeSystem $types) public function __construct()
{ {
// Option #1: using composition over inheritance to define type, see ImageType for inheritance example // Option #1: using composition over inheritance to define type, see ImageType for inheritance example
$this->definition = new ObjectType([ $this->definition = new ObjectType([
'name' => 'Story', 'name' => 'Story',
'fields' => function() use ($types) { 'fields' => function() {
return [ return [
'id' => $types->id(), 'id' => Types::id(),
'author' => $types->user(), 'author' => Types::user(),
'mentions' => $types->listOf($types->mention()), 'mentions' => Types::listOf(Types::mention()),
'totalCommentCount' => $types->int(), 'totalCommentCount' => Types::int(),
'comments' => [ 'comments' => [
'type' => $types->listOf($types->comment()), 'type' => Types::listOf(Types::comment()),
'args' => [ 'args' => [
'after' => [ 'after' => [
'type' => $types->id(), 'type' => Types::id(),
'description' => 'Load all comments listed after given comment ID' 'description' => 'Load all comments listed after given comment ID'
], ],
'limit' => [ 'limit' => [
'type' => $types->int(), 'type' => Types::int(),
'defaultValue' => 5 'defaultValue' => 5
] ]
] ]
], ],
'affordances' => $types->listOf(new EnumType([ 'likes' => [
'type' => Types::listOf(Types::user()),
'args' => [
'limit' => [
'type' => Types::int(),
'description' => 'Limit the number of recent likes returned',
'defaultValue' => 5
]
]
],
'likedBy' => [
'type' => Types::listOf(Types::user()),
],
'affordances' => Types::listOf(new EnumType([
'name' => 'StoryAffordancesEnum', 'name' => 'StoryAffordancesEnum',
'values' => [ 'values' => [
self::EDIT, self::EDIT,
@ -55,13 +68,13 @@ class StoryType extends BaseType
self::REPLY self::REPLY
] ]
])), ])),
'hasViewerLiked' => $types->boolean(), 'hasViewerLiked' => Types::boolean(),
$types->htmlField('body'), Types::htmlField('body'),
]; ];
}, },
'interfaces' => [ 'interfaces' => [
$types->node() Types::node()
], ],
'resolveField' => function($value, $args, $context, ResolveInfo $info) { 'resolveField' => function($value, $args, $context, ResolveInfo $info) {
if (method_exists($this, $info->fieldName)) { if (method_exists($this, $info->fieldName)) {
@ -73,15 +86,15 @@ class StoryType extends BaseType
]); ]);
} }
public function author(Story $story, $args, AppContext $context) public function author(Story $story)
{ {
return $context->dataSource->findUser($story->authorId); return DataSource::findUser($story->authorId);
} }
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 === DataSource::findUser($story->authorId);
$isLiked = $context->dataSource->isLikedBy($story->id, $context->viewer->id); $isLiked = DataSource::isLikedBy($story->id, $context->viewer->id);
if ($isViewer) { if ($isViewer) {
$affordances[] = self::EDIT; $affordances[] = self::EDIT;
@ -97,17 +110,17 @@ class StoryType extends BaseType
public function hasViewerLiked(Story $story, $args, AppContext $context) public function hasViewerLiked(Story $story, $args, AppContext $context)
{ {
return $context->dataSource->isLikedBy($story->id, $context->viewer->id); return DataSource::isLikedBy($story->id, $context->viewer->id);
} }
public function totalCommentCount(Story $story, $args, AppContext $context) public function totalCommentCount(Story $story)
{ {
return $context->dataSource->countComments($story->id); return DataSource::countComments($story->id);
} }
public function comments(Story $story, $args, AppContext $context) public function comments(Story $story, $args)
{ {
$args += ['after' => null]; $args += ['after' => null];
return $context->dataSource->findComments($story->id, $args['limit'], $args['after']); return DataSource::findComments($story->id, $args['limit'], $args['after']);
} }
} }

View File

@ -2,38 +2,40 @@
namespace GraphQL\Examples\Blog\Type; namespace GraphQL\Examples\Blog\Type;
use GraphQL\Examples\Blog\AppContext; use GraphQL\Examples\Blog\AppContext;
use GraphQL\Examples\Blog\Data\DataSource;
use GraphQL\Examples\Blog\Data\User; use GraphQL\Examples\Blog\Data\User;
use GraphQL\Examples\Blog\TypeSystem; use GraphQL\Examples\Blog\Types;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Type\Definition\ResolveInfo;
class UserType extends BaseType class UserType extends BaseType
{ {
public function __construct(TypeSystem $types) public function __construct()
{ {
// Option #1: using composition over inheritance to define type, see ImageType for inheritance example // Option #1: using composition over inheritance to define type, see ImageType for inheritance example
$this->definition = new ObjectType([ $this->definition = new ObjectType([
'name' => 'User', 'name' => 'User',
'fields' => function() use ($types) { 'description' => 'Our blog authors',
'fields' => function() {
return [ return [
'id' => $types->id(), 'id' => Types::id(),
'email' => $types->email(), 'email' => Types::email(),
'photo' => [ 'photo' => [
'type' => $types->image(), 'type' => Types::image(),
'description' => 'User photo URL', 'description' => 'User photo URL',
'args' => [ 'args' => [
'size' => $types->nonNull($types->imageSizeEnum()), 'size' => Types::nonNull(Types::imageSizeEnum()),
] ]
], ],
'firstName' => [ 'firstName' => [
'type' => $types->string(), 'type' => Types::string(),
], ],
'lastName' => [ 'lastName' => [
'type' => $types->string(), 'type' => Types::string(),
], ],
'lastStoryPosted' => $types->story(), 'lastStoryPosted' => Types::story(),
'fieldWithError' => [ '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");
} }
@ -41,7 +43,7 @@ class UserType extends BaseType
]; ];
}, },
'interfaces' => [ 'interfaces' => [
$types->node() Types::node()
], ],
'resolveField' => function($value, $args, $context, ResolveInfo $info) { 'resolveField' => function($value, $args, $context, ResolveInfo $info) {
if (method_exists($this, $info->fieldName)) { if (method_exists($this, $info->fieldName)) {
@ -53,13 +55,13 @@ class UserType extends BaseType
]); ]);
} }
public function photo(User $user, $args, AppContext $context) public function photo(User $user, $args)
{ {
return $context->dataSource->getUserPhoto($user->id, $args['size']); return DataSource::getUserPhoto($user->id, $args['size']);
} }
public function lastStoryPosted(User $user, $args, AppContext $context) public function lastStoryPosted(User $user)
{ {
return $context->dataSource->findLastStoryFor($user->id); return DataSource::findLastStoryFor($user->id);
} }
} }

View File

@ -5,7 +5,7 @@ use GraphQL\Examples\Blog\Type\CommentType;
use GraphQL\Examples\Blog\Type\Enum\ContentFormatEnum; 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\Field\HtmlField;
use GraphQL\Examples\Blog\Type\MentionType; use GraphQL\Examples\Blog\Type\SearchResultType;
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;
@ -19,7 +19,7 @@ use GraphQL\Type\Definition\Type;
use GraphQL\Type\DefinitionContainer; use GraphQL\Type\DefinitionContainer;
/** /**
* Class TypeSystem * Class Types
* *
* Acts as a registry and factory for your types. * Acts as a registry and factory for your types.
* *
@ -28,115 +28,115 @@ use GraphQL\Type\DefinitionContainer;
* *
* @package GraphQL\Examples\Blog * @package GraphQL\Examples\Blog
*/ */
class TypeSystem class Types
{ {
// Object types: // Object types:
private $user; private static $user;
private $story; private static $story;
private $comment; private static $comment;
private $image; private static $image;
private $query; private static $query;
/** /**
* @return UserType * @return UserType
*/ */
public function user() public static function user()
{ {
return $this->user ?: ($this->user = new UserType($this)); return self::$user ?: (self::$user = new UserType());
} }
/** /**
* @return StoryType * @return StoryType
*/ */
public function story() public static function story()
{ {
return $this->story ?: ($this->story = new StoryType($this)); return self::$story ?: (self::$story = new StoryType());
} }
/** /**
* @return CommentType * @return CommentType
*/ */
public function comment() public static function comment()
{ {
return $this->comment ?: ($this->comment = new CommentType($this)); return self::$comment ?: (self::$comment = new CommentType());
} }
/** /**
* @return ImageType * @return ImageType
*/ */
public function image() public static function image()
{ {
return $this->image ?: ($this->image = new ImageType($this)); return self::$image ?: (self::$image = new ImageType());
} }
/** /**
* @return QueryType * @return QueryType
*/ */
public function query() public static function query()
{ {
return $this->query ?: ($this->query = new QueryType($this)); return self::$query ?: (self::$query = new QueryType());
} }
// Interface types // Interface types
private $node; private static $node;
/** /**
* @return NodeType * @return NodeType
*/ */
public function node() public static function node()
{ {
return $this->node ?: ($this->node = new NodeType($this)); return self::$node ?: (self::$node = new NodeType());
} }
// Unions types: // Unions types:
private $mention; private static $mention;
/** /**
* @return MentionType * @return SearchResultType
*/ */
public function mention() public static function mention()
{ {
return $this->mention ?: ($this->mention = new MentionType($this)); return self::$mention ?: (self::$mention = new SearchResultType());
} }
// Enum types // Enum types
private $imageSizeEnum; private static $imageSizeEnum;
private $contentFormatEnum; private static $contentFormatEnum;
/** /**
* @return ImageSizeEnumType * @return ImageSizeEnumType
*/ */
public function imageSizeEnum() public static function imageSizeEnum()
{ {
return $this->imageSizeEnum ?: ($this->imageSizeEnum = new ImageSizeEnumType()); return self::$imageSizeEnum ?: (self::$imageSizeEnum = new ImageSizeEnumType());
} }
/** /**
* @return ContentFormatEnum * @return ContentFormatEnum
*/ */
public function contentFormatEnum() public static function contentFormatEnum()
{ {
return $this->contentFormatEnum ?: ($this->contentFormatEnum = new ContentFormatEnum()); return self::$contentFormatEnum ?: (self::$contentFormatEnum = new ContentFormatEnum());
} }
// Custom Scalar types: // Custom Scalar types:
private $urlType; private static $urlType;
private $emailType; private static $emailType;
public function email() public static function email()
{ {
return $this->emailType ?: ($this->emailType = new EmailType()); return self::$emailType ?: (self::$emailType = new EmailType());
} }
/** /**
* @return UrlType * @return UrlType
*/ */
public function url() public static function url()
{ {
return $this->urlType ?: ($this->urlType = new UrlType()); return self::$urlType ?: (self::$urlType = new UrlType());
} }
/** /**
@ -144,16 +144,16 @@ class TypeSystem
* @param null $objectKey * @param null $objectKey
* @return array * @return array
*/ */
public function htmlField($name, $objectKey = null) public static function htmlField($name, $objectKey = null)
{ {
return HtmlField::build($this, $name, $objectKey); return HtmlField::build($name, $objectKey);
} }
// Let's add internal types as well for consistent experience // Let's add internal types as well for consistent experience
public function boolean() public static function boolean()
{ {
return Type::boolean(); return Type::boolean();
} }
@ -161,7 +161,7 @@ class TypeSystem
/** /**
* @return \GraphQL\Type\Definition\FloatType * @return \GraphQL\Type\Definition\FloatType
*/ */
public function float() public static function float()
{ {
return Type::float(); return Type::float();
} }
@ -169,7 +169,7 @@ class TypeSystem
/** /**
* @return \GraphQL\Type\Definition\IDType * @return \GraphQL\Type\Definition\IDType
*/ */
public function id() public static function id()
{ {
return Type::id(); return Type::id();
} }
@ -177,7 +177,7 @@ class TypeSystem
/** /**
* @return \GraphQL\Type\Definition\IntType * @return \GraphQL\Type\Definition\IntType
*/ */
public function int() public static function int()
{ {
return Type::int(); return Type::int();
} }
@ -185,7 +185,7 @@ class TypeSystem
/** /**
* @return \GraphQL\Type\Definition\StringType * @return \GraphQL\Type\Definition\StringType
*/ */
public function string() public static function string()
{ {
return Type::string(); return Type::string();
} }
@ -194,7 +194,7 @@ class TypeSystem
* @param Type|DefinitionContainer $type * @param Type|DefinitionContainer $type
* @return ListOfType * @return ListOfType
*/ */
public function listOf($type) public static function listOf($type)
{ {
return new ListOfType($type); return new ListOfType($type);
} }
@ -203,7 +203,7 @@ class TypeSystem
* @param Type|DefinitionContainer $type * @param Type|DefinitionContainer $type
* @return NonNull * @return NonNull
*/ */
public function nonNull($type) public static function nonNull($type)
{ {
return new NonNull($type); return new NonNull($type);
} }

View File

@ -3,7 +3,7 @@
// php -S localhost:8080 ./index.php // php -S localhost:8080 ./index.php
require_once '../../vendor/autoload.php'; require_once '../../vendor/autoload.php';
use \GraphQL\Examples\Blog\TypeSystem; use \GraphQL\Examples\Blog\Types;
use \GraphQL\Examples\Blog\AppContext; use \GraphQL\Examples\Blog\AppContext;
use \GraphQL\Examples\Blog\Data\DataSource; use \GraphQL\Examples\Blog\Data\DataSource;
use \GraphQL\Schema; use \GraphQL\Schema;
@ -27,17 +27,12 @@ if (!empty($_GET['debug'])) {
} }
try { try {
// Initialize user-land registry/factory of our app types: // Initialize our fake data source
$typeSystem = new TypeSystem(); DataSource::init();
// Init stub data source
// (in real-world apps this might be Doctrine's EntityManager for instance, or just DB connection):
$dataSource = new DataSource();
// Prepare context that will be available in all field resolvers (as 3rd argument): // Prepare context that will be available in all field resolvers (as 3rd argument):
$appContext = new AppContext(); $appContext = new AppContext();
$appContext->viewer = $dataSource->findUser(1); // simulated "currently logged-in user" $appContext->viewer = DataSource::findUser('1'); // simulated "currently logged-in user"
$appContext->dataSource = $dataSource;
$appContext->rootUrl = 'http://localhost:8080'; $appContext->rootUrl = 'http://localhost:8080';
$appContext->request = $_REQUEST; $appContext->request = $_REQUEST;
@ -58,7 +53,7 @@ try {
// 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' => Types::query()
]); ]);
$result = GraphQL::execute( $result = GraphQL::execute(