Skip to content

Commit fdc5808

Browse files
authored
Lazy loading of types (#557)
* lazy load types * fix introspection resolve * tests, linting, stanning * fix annotation * remove annotation, debug stuff * cleanup * fix annotations * start on stanning * rename to Schema::resolveType * remove some more useless resolve calls * remove another unneeded resolve call * remove unused * workaround for Scrutinizer * ws * make assertType 0 cost * use getType in assertValid * add lazy load to blog sample * fix scrutinizer * use InvariantViolation * resolve abstract type * remove whoopsie * remove annotation * lint * linting and stanning * fix completeAbstractValue * fix tests * lint * cast * fix stan issues * use trait * remove assert stuff * lock sniffer * stanning * remove unused * remove unused * remove unused * remove assert * remove cast * seal class * add typehint * rename, change visibility * add typehint * stanning * expand type * expand type * remove unused * reorganize * simplify * use isset * touch * revert * disable specific rule * cast type * broaden type * annotate return type * remove redundant code * skip method call * move execution of lazy type to typeloader * remove pointless test
1 parent a747575 commit fdc5808

21 files changed

+553
-185
lines changed

examples/01-blog/Blog/Type/Scalar/EmailType.php

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,14 @@
66
use GraphQL\Type\Definition\CustomScalarType;
77
use GraphQL\Utils\Utils;
88

9-
class EmailType
9+
class EmailType extends CustomScalarType
1010
{
11-
public static function create()
11+
public function __construct(array $config = [])
1212
{
13-
return new CustomScalarType([
14-
'name' => 'Email',
15-
'serialize' => [__CLASS__, 'serialize'],
16-
'parseValue' => [__CLASS__, 'parseValue'],
17-
'parseLiteral' => [__CLASS__, 'parseLiteral'],
13+
parent::__construct([
14+
'serialize' => [__CLASS__, 's_serialize'],
15+
'parseValue' => [__CLASS__, 's_parseValue'],
16+
'parseLiteral' => [__CLASS__, 's_parseLiteral'],
1817
]);
1918
}
2019

@@ -24,7 +23,7 @@ public static function create()
2423
* @param string $value
2524
* @return string
2625
*/
27-
public static function serialize($value)
26+
public static function s_serialize($value)
2827
{
2928
// Assuming internal representation of email is always correct:
3029
return $value;
@@ -40,7 +39,7 @@ public static function serialize($value)
4039
* @param mixed $value
4140
* @return mixed
4241
*/
43-
public static function parseValue($value)
42+
public static function s_parseValue($value)
4443
{
4544
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
4645
throw new \UnexpectedValueException("Cannot represent value as email: " . Utils::printSafe($value));
@@ -55,7 +54,7 @@ public static function parseValue($value)
5554
* @return string
5655
* @throws Error
5756
*/
58-
public static function parseLiteral($valueNode)
57+
public static function s_parseLiteral($valueNode)
5958
{
6059
// Note: throwing GraphQL\Error\Error vs \UnexpectedValueException to benefit from GraphQL
6160
// error location in query:

examples/01-blog/Blog/Types.php

Lines changed: 46 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
<?php
22
namespace GraphQL\Examples\Blog;
33

4+
use Exception;
45
use GraphQL\Examples\Blog\Type\CommentType;
56
use GraphQL\Examples\Blog\Type\Enum\ContentFormatEnum;
67
use GraphQL\Examples\Blog\Type\Enum\ImageSizeEnumType;
78
use GraphQL\Examples\Blog\Type\Field\HtmlField;
89
use GraphQL\Examples\Blog\Type\SearchResultType;
910
use GraphQL\Examples\Blog\Type\NodeType;
10-
use GraphQL\Examples\Blog\Type\QueryType;
1111
use GraphQL\Examples\Blog\Type\Scalar\EmailType;
1212
use GraphQL\Examples\Blog\Type\StoryType;
1313
use GraphQL\Examples\Blog\Type\Scalar\UrlType;
@@ -25,113 +25,66 @@
2525
*/
2626
class Types
2727
{
28-
// Object types:
29-
private static $user;
30-
private static $story;
31-
private static $comment;
32-
private static $image;
33-
private static $query;
34-
35-
/**
36-
* @return UserType
37-
*/
38-
public static function user()
39-
{
40-
return self::$user ?: (self::$user = new UserType());
41-
}
42-
43-
/**
44-
* @return StoryType
45-
*/
46-
public static function story()
47-
{
48-
return self::$story ?: (self::$story = new StoryType());
49-
}
50-
51-
/**
52-
* @return CommentType
53-
*/
54-
public static function comment()
55-
{
56-
return self::$comment ?: (self::$comment = new CommentType());
57-
}
58-
59-
/**
60-
* @return ImageType
61-
*/
62-
public static function image()
63-
{
64-
return self::$image ?: (self::$image = new ImageType());
65-
}
66-
67-
/**
68-
* @return QueryType
69-
*/
70-
public static function query()
71-
{
72-
return self::$query ?: (self::$query = new QueryType());
73-
}
28+
private static $types = [];
29+
const LAZY_LOAD_GRAPHQL_TYPES = true;
7430

31+
public static function user() : callable { return static::get(UserType::class); }
32+
public static function story() : callable { return static::get(StoryType::class); }
33+
public static function comment() : callable { return static::get(CommentType::class); }
34+
public static function image() : callable { return static::get(ImageType::class); }
35+
public static function node() : callable { return static::get(NodeType::class); }
36+
public static function mention() : callable { return static::get(SearchResultType::class); }
37+
public static function imageSizeEnum() : callable { return static::get(ImageSizeEnumType::class); }
38+
public static function contentFormatEnum() : callable { return static::get(ContentFormatEnum::class); }
39+
public static function email() : callable { return static::get(EmailType::class); }
40+
public static function url() : callable { return static::get(UrlType::class); }
7541

76-
// Interface types
77-
private static $node;
78-
79-
/**
80-
* @return NodeType
81-
*/
82-
public static function node()
42+
public static function get($classname)
8343
{
84-
return self::$node ?: (self::$node = new NodeType());
44+
return static::LAZY_LOAD_GRAPHQL_TYPES ? function() use ($classname) {
45+
return static::byClassName($classname);
46+
} : static::byClassName($classname);
8547
}
8648

49+
protected static function byClassName($classname) {
50+
$parts = explode("\\", $classname);
51+
$cacheName = strtolower(preg_replace('~Type$~', '', $parts[count($parts) - 1]));
52+
$type = null;
8753

88-
// Unions types:
89-
private static $mention;
90-
91-
/**
92-
* @return SearchResultType
93-
*/
94-
public static function mention()
95-
{
96-
return self::$mention ?: (self::$mention = new SearchResultType());
97-
}
54+
if (!isset(self::$types[$cacheName])) {
55+
if (class_exists($classname)) {
56+
$type = new $classname();
57+
}
9858

59+
self::$types[$cacheName] = $type;
60+
}
9961

100-
// Enum types
101-
private static $imageSizeEnum;
102-
private static $contentFormatEnum;
62+
$type = self::$types[$cacheName];
10363

104-
/**
105-
* @return ImageSizeEnumType
106-
*/
107-
public static function imageSizeEnum()
108-
{
109-
return self::$imageSizeEnum ?: (self::$imageSizeEnum = new ImageSizeEnumType());
64+
if (!$type) {
65+
throw new Exception("Unknown graphql type: " . $classname);
66+
}
67+
return $type;
11068
}
11169

112-
/**
113-
* @return ContentFormatEnum
114-
*/
115-
public static function contentFormatEnum()
70+
public static function byTypeName($shortName, $removeType=true)
11671
{
117-
return self::$contentFormatEnum ?: (self::$contentFormatEnum = new ContentFormatEnum());
118-
}
72+
$cacheName = strtolower($shortName);
73+
$type = null;
11974

120-
// Custom Scalar types:
121-
private static $urlType;
122-
private static $emailType;
75+
if (isset(self::$types[$cacheName])) {
76+
return self::$types[$cacheName];
77+
}
12378

124-
public static function email()
125-
{
126-
return self::$emailType ?: (self::$emailType = EmailType::create());
127-
}
79+
$method = lcfirst($shortName);
80+
if(method_exists(get_called_class(), $method)) {
81+
$type = self::{$method}();
82+
}
12883

129-
/**
130-
* @return UrlType
131-
*/
132-
public static function url()
133-
{
134-
return self::$urlType ?: (self::$urlType = new UrlType());
84+
if(!$type) {
85+
throw new Exception("Unknown graphql type: " . $shortName);
86+
}
87+
return $type;
13588
}
13689

13790
/**

examples/01-blog/graphql.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// php -S localhost:8080 ./graphql.php
44
require_once __DIR__ . '/../../vendor/autoload.php';
55

6+
use GraphQL\Examples\Blog\Type\QueryType;
67
use \GraphQL\Examples\Blog\Types;
78
use \GraphQL\Examples\Blog\AppContext;
89
use \GraphQL\Examples\Blog\Data\DataSource;
@@ -48,7 +49,10 @@
4849

4950
// GraphQL schema to be passed to query executor:
5051
$schema = new Schema([
51-
'query' => Types::query()
52+
'query' => new QueryType(),
53+
'typeLoader' => function($name) {
54+
return Types::byTypeName($name, true);
55+
}
5256
]);
5357

5458
$result = GraphQL::executeQuery(

src/Executor/ReferenceExecutor.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
use function array_values;
4646
use function get_class;
4747
use function is_array;
48+
use function is_callable;
4849
use function is_string;
4950
use function sprintf;
5051

@@ -528,7 +529,6 @@ private function resolveField(ObjectType $parentType, $rootValue, $fieldNodes, $
528529
// The resolve function's optional 3rd argument is a context value that
529530
// is provided to every resolve function within an execution. It is commonly
530531
// used to represent an authenticated user, or request-specific caches.
531-
$context = $exeContext->contextValue;
532532
// The resolve function's optional 4th argument is a collection of
533533
// information about the current execution state.
534534
$info = new ResolveInfo(
@@ -938,10 +938,15 @@ private function completeLeafValue(LeafType $returnType, &$result)
938938
*/
939939
private function completeAbstractValue(AbstractType $returnType, $fieldNodes, ResolveInfo $info, $path, &$result)
940940
{
941-
$exeContext = $this->exeContext;
942-
$runtimeType = $returnType->resolveType($result, $exeContext->contextValue, $info);
943-
if ($runtimeType === null) {
941+
$exeContext = $this->exeContext;
942+
$typeCandidate = $returnType->resolveType($result, $exeContext->contextValue, $info);
943+
944+
if ($typeCandidate === null) {
944945
$runtimeType = self::defaultTypeResolver($result, $exeContext->contextValue, $info, $returnType);
946+
} elseif (is_callable($typeCandidate)) {
947+
$runtimeType = Schema::resolveType($typeCandidate);
948+
} else {
949+
$runtimeType = $typeCandidate;
945950
}
946951
$promise = $this->getPromise($runtimeType);
947952
if ($promise !== null) {

src/Type/Definition/FieldArgument.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use GraphQL\Error\InvariantViolation;
88
use GraphQL\Language\AST\InputValueDefinitionNode;
9+
use GraphQL\Type\Schema;
910
use GraphQL\Utils\Utils;
1011
use function array_key_exists;
1112
use function is_array;
@@ -77,12 +78,9 @@ public static function createMap(array $config) : array
7778
return $map;
7879
}
7980

80-
/**
81-
* @return InputType&Type
82-
*/
8381
public function getType() : Type
8482
{
85-
return $this->type;
83+
return Schema::resolveType($this->type);
8684
}
8785

8886
public function defaultValueExists() : bool

src/Type/Definition/FieldDefinition.php

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use GraphQL\Error\Error;
88
use GraphQL\Error\InvariantViolation;
99
use GraphQL\Language\AST\FieldDefinitionNode;
10+
use GraphQL\Type\Schema;
1011
use GraphQL\Utils\Utils;
1112
use function is_array;
1213
use function is_callable;
@@ -58,7 +59,7 @@ class FieldDefinition
5859
*/
5960
public $config;
6061

61-
/** @var OutputType&Type */
62+
/** @var callable|(OutputType&Type) */
6263
public $type;
6364

6465
/** @var callable|string */
@@ -174,12 +175,9 @@ public function getArg($name)
174175
return null;
175176
}
176177

177-
/**
178-
* @return OutputType&Type
179-
*/
180178
public function getType() : Type
181179
{
182-
return $this->type;
180+
return Schema::resolveType($this->type);
183181
}
184182

185183
/**
@@ -217,7 +215,7 @@ public function assertValid(Type $parentType)
217215
)
218216
);
219217

220-
$type = $this->type;
218+
$type = $this->getType();
221219
if ($type instanceof WrappingType) {
222220
$type = $type->getWrappedType(true);
223221
}

src/Type/Definition/InputObjectField.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use GraphQL\Error\Error;
88
use GraphQL\Error\InvariantViolation;
99
use GraphQL\Language\AST\InputValueDefinitionNode;
10+
use GraphQL\Type\Schema;
1011
use GraphQL\Utils\Utils;
1112
use function array_key_exists;
1213
use function sprintf;
@@ -55,7 +56,12 @@ public function __construct(array $opts)
5556
*/
5657
public function getType() : Type
5758
{
58-
return $this->type;
59+
/**
60+
* TODO: Replace this cast with native assert
61+
*
62+
* @var Type&InputType
63+
*/
64+
return Schema::resolveType($this->type);
5965
}
6066

6167
public function defaultValueExists() : bool

0 commit comments

Comments
 (0)