SchemaCacheProvider   A
last analyzed

Complexity

Total Complexity 24

Size/Duplication

Total Lines 176
Duplicated Lines 0 %

Test Coverage

Coverage 10.29%

Importance

Changes 7
Bugs 4 Features 0
Metric Value
eloc 76
c 7
b 4
f 0
dl 0
loc 176
ccs 7
cts 68
cp 0.1029
rs 10
wmc 24

8 Methods

Rating   Name   Duplication   Size   Complexity  
C getTypeConfigDecorator() 0 75 13
A save() 0 12 1
A isCached() 0 3 1
A getTypesCacheFile() 0 6 2
A getCacheKey() 0 3 1
A getSchema() 0 19 3
A __construct() 0 8 1
A getSchemaCacheFile() 0 6 2
1
<?php declare(strict_types = 1);
2
3
namespace Portiny\GraphQL\GraphQL\Schema;
4
5
use Closure;
6
use GraphQL\Executor\Executor;
7
use GraphQL\Language\AST\DocumentNode;
8
use GraphQL\Language\Parser;
9
use GraphQL\Type\Definition\InputObjectType;
10
use GraphQL\Type\Definition\ObjectType;
11
use GraphQL\Type\Definition\ResolveInfo;
12
use GraphQL\Type\Definition\ScalarType;
13
use GraphQL\Type\Schema;
14
use GraphQL\Utils\AST;
15
use GraphQL\Utils\BuildSchema;
16
use GraphQL\Utils\SchemaPrinter;
17
use Portiny\GraphQL\Contract\Provider\MutationFieldsProviderInterface;
18
use Portiny\GraphQL\Contract\Provider\QueryFieldsProviderInterface;
19
use Portiny\GraphQL\GraphQL\Type\Types;
20
21
final class SchemaCacheProvider
22
{
23
	/**
24
	 * @var string
25
	 */
26
	private $cacheDir;
27
28
	/**
29
	 * @var QueryFieldsProviderInterface
30
	 */
31
	private $queryFieldsProvider;
32
33
	/**
34
	 * @var MutationFieldsProviderInterface
35
	 */
36
	private $mutationFieldsProvider;
37
38
	/**
39
	 * @var Schema
40
	 */
41
	private $schema;
42 1
43
44
	public function __construct(
45
		string $cacheDir,
46
		QueryFieldsProviderInterface $queryFieldsProvider,
47 1
		MutationFieldsProviderInterface $mutationFieldsProvider
48 1
	) {
49 1
		$this->cacheDir = $cacheDir;
50 1
		$this->queryFieldsProvider = $queryFieldsProvider;
51
		$this->mutationFieldsProvider = $mutationFieldsProvider;
52
	}
53
54
55
	public function getSchema(string $cacheKey): ?Schema
56
	{
57
		if ($this->schema !== null) {
58
			return $this->schema;
59
		}
60
61
		// load types from cache
62
		$cacheContent = file_get_contents($this->getTypesCacheFile($cacheKey));
63
		if ($cacheContent === false) {
64
			return null;
65
		}
66
		Types::loadTypesFromClasses(unserialize($cacheContent));
67
68
		// load schema from cache
69
		/** @var DocumentNode $document */
70
		$document = AST::fromArray(require $this->getSchemaCacheFile($cacheKey));
71
		$this->schema = BuildSchema::build($document, $this->getTypeConfigDecorator());
72
73
		return $this->schema;
74
	}
75
76
77
	public function isCached(string $cacheKey): bool
78
	{
79
		return file_exists($this->getSchemaCacheFile($cacheKey));
80
	}
81
82
83
	public function save(string $cacheKey, Schema $schema): void
84
	{
85
		// schema cache
86
		$sdl = SchemaPrinter::doPrint($schema);
87
		$documentNode = Parser::parse($sdl);
88
		file_put_contents(
89
			$this->getSchemaCacheFile($cacheKey),
90
			"<?php\nreturn " . var_export(AST::toArray($documentNode), true) . ';'
91
		);
92
93
		// types cache
94
		file_put_contents($this->getTypesCacheFile($cacheKey), serialize(Types::getTypeClasses()));
95
	}
96 1
97
98 1
	public function getCacheKey(?array $allowedQueries = null, ?array $allowedMutations = null): string
99
	{
100
		return md5(serialize($allowedQueries) . serialize($allowedMutations));
101
	}
102
103
104
	private function getTypesCacheFile(string $cacheKey): string
105
	{
106
		if (! file_exists($this->cacheDir)) {
107
			mkdir($this->cacheDir, 0777, true);
108
		}
109
		return $this->cacheDir . '/types-' . $cacheKey . '.php';
110
	}
111
112
113
	private function getSchemaCacheFile(string $cacheKey): string
114
	{
115
		if (! file_exists($this->cacheDir)) {
116
			mkdir($this->cacheDir, 0777, true);
117
		}
118
		return $this->cacheDir . '/schema-' . $cacheKey . '.php';
119
	}
120
121
122
	private function getTypeConfigDecorator(): Closure
123
	{
124
		return function (array $typeConfig) {
125
			$typeConfig['resolveField'] = function ($value, $args, $context, ResolveInfo $info) use ($typeConfig) {
126
				$fieldName = (string) $info->fieldName;
127
128
				switch ($typeConfig['name']) {
129
					case 'Query':
130
						$queryField = $this->queryFieldsProvider->getField($fieldName);
131
						if ($queryField) {
132
							return $queryField->resolve($value, $args, $context);
133
						}
134
						break;
135
136
					case 'Mutation':
137
						$mutationField = $this->mutationFieldsProvider->getField($fieldName);
138
						if ($mutationField) {
139
							return $mutationField->resolve($value, $args, $context);
140
						}
141
						break;
142
143
					default:
144
						$type = Types::findByName($typeConfig['name']);
145
						if ($type instanceof ObjectType) {
146
							$fieldNameForResolving = $fieldName;
147
							if ($type->hasField($fieldNameForResolving)) {
148
								$typeField = $type->getField($fieldNameForResolving);
149
								$resolver = $typeField->resolveFn;
150
								if (is_callable($resolver)) {
151
									return $resolver($value, $args, $context, $info);
152
								}
153
							}
154
155
							/** @var callable|null $resolver */
156
							$resolver = $type->resolveFieldFn;
157
							if (is_callable($resolver)) {
158
								return $resolver($value, $args, $context, $info);
159
							}
160
161
							$defaultFieldResolver = Executor::getDefaultFieldResolver();
162
							return $defaultFieldResolver($value, $args, $context, $info);
163
						}
164
165
						return null;
166
				}
167
			};
168
169
			$typeConfig['parseValue'] = function ($value) use ($typeConfig) {
170
				$customType = Types::findByName($typeConfig['name']);
171
				if ($customType instanceof ScalarType || $customType instanceof InputObjectType) {
172
					return $customType->parseValue($value);
173
				}
174
175
				return $value;
176
			};
177
178
			$typeConfig['serialize'] = function ($value) use ($typeConfig) {
179
				$customType = Types::findByName($typeConfig['name']);
180
				if ($customType instanceof ScalarType) {
181
					return $customType->serialize($value);
182
				}
183
184
				return $value;
185
			};
186
187
			$typeConfig['parseLiteral'] = function ($valueNode, ?array $variables = null) use ($typeConfig) {
188
				$customType = Types::findByName($typeConfig['name']);
189
				if ($customType instanceof ScalarType) {
190
					return $customType->parseLiteral($valueNode, $variables);
191
				}
192
193
				return AST::valueFromASTUntyped($valueNode, $variables);
194
			};
195
196
			return $typeConfig;
197
		};
198
	}
199
200
}
201