Completed
Push — master ( f94511...1e834a )
by Tomáš
03:19 queued 12s
created

SchemaCacheProvider   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 159
Duplicated Lines 0 %

Test Coverage

Coverage 10.45%

Importance

Changes 4
Bugs 2 Features 0
Metric Value
eloc 67
c 4
b 2
f 0
dl 0
loc 159
ccs 7
cts 67
cp 0.1045
rs 10
wmc 20

8 Methods

Rating   Name   Duplication   Size   Complexity  
A save() 0 12 1
A isCached() 0 3 1
A getTypesCacheFile() 0 4 1
A getCacheKey() 0 3 1
A getSchema() 0 19 3
A __construct() 0 8 1
A getSchemaCacheFile() 0 4 1
B getTypeConfigDecorator() 0 62 11
1
<?php declare(strict_types = 1);
2
3
namespace Portiny\GraphQL\GraphQL\Schema;
4
5
use Closure;
6
use GraphQL\Language\AST\DocumentNode;
7
use GraphQL\Language\Parser;
8
use GraphQL\Type\Definition\ObjectType;
9
use GraphQL\Type\Definition\ResolveInfo;
10
use GraphQL\Type\Definition\ScalarType;
11
use GraphQL\Type\Schema;
12
use GraphQL\Utils\AST;
13
use GraphQL\Utils\BuildSchema;
14
use GraphQL\Utils\SchemaPrinter;
15
use Portiny\GraphQL\Contract\Provider\MutationFieldsProviderInterface;
16
use Portiny\GraphQL\Contract\Provider\QueryFieldsProviderInterface;
17
use Portiny\GraphQL\GraphQL\Type\Types;
18
19
final class SchemaCacheProvider
20
{
21
	/**
22
	 * @var string
23
	 */
24
	private $cacheDir;
25
26
	/**
27
	 * @var QueryFieldsProviderInterface
28
	 */
29
	private $queryFieldsProvider;
30
31
	/**
32
	 * @var MutationFieldsProviderInterface
33
	 */
34
	private $mutationFieldsProvider;
35
36
	/**
37
	 * @var Schema
38
	 */
39
	private $schema;
40
41
42 1
	public function __construct(
43
		string $cacheDir,
44
		QueryFieldsProviderInterface $queryFieldsProvider,
45
		MutationFieldsProviderInterface $mutationFieldsProvider
46
	) {
47 1
		$this->cacheDir = $cacheDir;
48 1
		$this->queryFieldsProvider = $queryFieldsProvider;
49 1
		$this->mutationFieldsProvider = $mutationFieldsProvider;
50 1
	}
51
52
53
	public function getSchema(string $cacheKey): ?Schema
54
	{
55
		if ($this->schema !== null) {
56
			return $this->schema;
57
		}
58
59
		// load types from cache
60
		$cacheContent = file_get_contents($this->getTypesCacheFile($cacheKey));
61
		if ($cacheContent === false) {
62
			return null;
63
		}
64
		Types::loadTypesFromClasses(unserialize($cacheContent));
65
66
		// load schema from cache
67
		/** @var DocumentNode $document */
68
		$document = AST::fromArray(require $this->getSchemaCacheFile($cacheKey));
69
		$this->schema = BuildSchema::build($document, $this->getTypeConfigDecorator());
70
71
		return $this->schema;
72
	}
73
74
75
	public function isCached(string $cacheKey): bool
76
	{
77
		return file_exists($this->getSchemaCacheFile($cacheKey));
78
	}
79
80
81
	public function save(string $cacheKey, Schema $schema): void
82
	{
83
		// schema cache
84
		$sdl = SchemaPrinter::doPrint($schema);
85
		$documentNode = Parser::parse($sdl);
86
		file_put_contents(
87
			$this->getSchemaCacheFile($cacheKey),
88
			"<?php\nreturn " . var_export(AST::toArray($documentNode), true) . ';'
89
		);
90
91
		// types cache
92
		file_put_contents($this->getTypesCacheFile($cacheKey), serialize(Types::getTypeClasses()));
93
	}
94
95
96 1
	public function getCacheKey(?array $allowedQueries = null, ?array $allowedMutations = null): string
97
	{
98 1
		return md5(serialize($allowedQueries) . serialize($allowedMutations));
99
	}
100
101
102
	private function getTypesCacheFile(string $cacheKey): string
103
	{
104
		@mkdir($this->cacheDir, 0777, true); //@ - may exists
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for mkdir(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

104
		/** @scrutinizer ignore-unhandled */ @mkdir($this->cacheDir, 0777, true); //@ - may exists

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
105
		return $this->cacheDir . '/types-' . $cacheKey . '.php';
106
	}
107
108
109
	private function getSchemaCacheFile(string $cacheKey): string
110
	{
111
		@mkdir($this->cacheDir, 0777, true); //@ - may exists
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for mkdir(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

111
		/** @scrutinizer ignore-unhandled */ @mkdir($this->cacheDir, 0777, true); //@ - may exists

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
112
		return $this->cacheDir . '/schema-' . $cacheKey . '.php';
113
	}
114
115
116
	private function getTypeConfigDecorator(): Closure
117
	{
118
		return function (array $typeConfig) {
119
			$typeConfig['resolveField'] = function ($value, $args, $context, ResolveInfo $info) use ($typeConfig) {
120
				$fieldName = (string) $info->fieldName;
121
122
				switch ($typeConfig['name']) {
123
					case 'Query':
124
						$queryField = $this->queryFieldsProvider->getField($fieldName);
125
						if ($queryField) {
126
							return $queryField->resolve($value, $args, $context);
127
						}
128
						break;
129
130
					case 'Mutation':
131
						$mutationField = $this->mutationFieldsProvider->getField($fieldName);
132
						if ($mutationField) {
133
							return $mutationField->resolve($value, $args, $context);
134
						}
135
						break;
136
137
					default:
138
						$type = Types::findByName($typeConfig['name']);
139
						if ($type instanceof ObjectType) {
140
							$fieldNameForResolving = $fieldName;
141
							if ($type->hasField($fieldNameForResolving)) {
142
								$typeField = $type->getField($fieldNameForResolving);
143
								$resolver = $typeField->resolveFn;
144
								if (is_callable($resolver)) {
145
									return $resolver($value, $args, $context, $info);
146
								}
147
							}
148
149
							$resolver = $type->resolveFieldFn;
150
							if (is_callable($resolver)) {
151
								return $resolver($value, $args, $context, $info);
152
							}
153
						}
154
				}
155
156
				return null;
157
			};
158
159
			$typeConfig['parseValue'] = function ($value) use ($typeConfig) {
160
				$customType = Types::findByName($typeConfig['name']);
161
				if ($customType instanceof ScalarType) {
162
					return $customType->parseValue($value);
163
				}
164
165
				return $value;
166
			};
167
168
			$typeConfig['parseLiteral'] = function ($valueNode, ?array $variables = null) use ($typeConfig) {
169
				$customType = Types::findByName($typeConfig['name']);
170
				if ($customType instanceof ScalarType) {
171
					return $customType->parseLiteral($valueNode, $variables);
172
				}
173
174
				return AST::valueFromASTUntyped($valueNode, $variables);
175
			};
176
177
			return $typeConfig;
178
		};
179
	}
180
181
}
182