Completed
Push — master ( ceb02a...91c3aa )
by Tomáš
05:33
created

SchemaCacheProvider::save()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 6
c 2
b 0
f 0
nc 1
nop 2
dl 0
loc 12
cc 1
rs 10
ccs 0
cts 7
cp 0
crap 2
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\Schema;
11
use GraphQL\Utils\AST;
12
use GraphQL\Utils\BuildSchema;
13
use GraphQL\Utils\SchemaPrinter;
14
use Portiny\GraphQL\Contract\Provider\MutationFieldsProviderInterface;
15
use Portiny\GraphQL\Contract\Provider\QueryFieldsProviderInterface;
16
use Portiny\GraphQL\GraphQL\Type\Types;
17
18
final class SchemaCacheProvider
19
{
20
	/**
21
	 * @var string
22
	 */
23
	private $cacheDir;
24
25
	/**
26
	 * @var QueryFieldsProviderInterface
27
	 */
28
	private $queryFieldsProvider;
29
30
	/**
31
	 * @var MutationFieldsProviderInterface
32
	 */
33
	private $mutationFieldsProvider;
34
35
	/**
36
	 * @var Schema
37
	 */
38
	private $schema;
39
40
41 1
	public function __construct(
42
		string $cacheDir,
43
		QueryFieldsProviderInterface $queryFieldsProvider,
44
		MutationFieldsProviderInterface $mutationFieldsProvider
45
	) {
46 1
		$this->cacheDir = $cacheDir;
47 1
		$this->queryFieldsProvider = $queryFieldsProvider;
48 1
		$this->mutationFieldsProvider = $mutationFieldsProvider;
49 1
	}
50
51
52
	public function getSchema(string $cacheKey): ?Schema
53
	{
54
		if ($this->schema !== null) {
55
			return $this->schema;
56
		}
57
58
		// load types from cache
59
		$cacheContent = file_get_contents($this->getTypesCacheFile($cacheKey));
60
		if ($cacheContent === false) {
61
			return null;
62
		}
63
		Types::loadTypesFromClasses(unserialize($cacheContent));
64
65
		// load schema from cache
66
		/** @var DocumentNode $document */
67
		$document = AST::fromArray(require $this->getSchemaCacheFile($cacheKey));
68
		$this->schema = BuildSchema::build($document, $this->getTypeConfigDecorator());
69
70
		return $this->schema;
71
	}
72
73
74
	public function isCached(string $cacheKey): bool
75
	{
76
		return file_exists($this->getSchemaCacheFile($cacheKey));
77
	}
78
79
80
	public function save(string $cacheKey, Schema $schema): void
81
	{
82
		// schema cache
83
		$sdl = SchemaPrinter::doPrint($schema);
84
		$documentNode = Parser::parse($sdl);
85
		file_put_contents(
86
			$this->getSchemaCacheFile($cacheKey),
87
			"<?php\nreturn " . var_export(AST::toArray($documentNode), true) . ';'
88
		);
89
90
		// types cache
91
		file_put_contents($this->getTypesCacheFile($cacheKey), serialize(Types::getTypeClasses()));
92
	}
93
94
95 1
	public function getCacheKey(?array $allowedQueries = null, ?array $allowedMutations = null): string
96
	{
97 1
		return md5(serialize($allowedQueries) . serialize($allowedMutations));
98
	}
99
100
101
	private function getTypesCacheFile(string $cacheKey): string
102
	{
103
		@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

103
		/** @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...
104
		return $this->cacheDir . '/types-' . $cacheKey . '.php';
105
	}
106
107
108
	private function getSchemaCacheFile(string $cacheKey): string
109
	{
110
		@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

110
		/** @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...
111
		return $this->cacheDir . '/schema-' . $cacheKey . '.php';
112
	}
113
114
115
	private function getTypeConfigDecorator(): Closure
116
	{
117
		return function (array $typeConfig) {
118
			$typeConfig['resolveField'] = function ($value, $args, $context, ResolveInfo $info) use ($typeConfig) {
119
				$fieldName = (string) $info->fieldName;
120
121
				switch ($typeConfig['name']) {
122
					case 'Query':
123
						$queryField = $this->queryFieldsProvider->getField($fieldName);
124
						if ($queryField) {
125
							return $queryField->resolve($value, $args, $context);
126
						}
127
						break;
128
129
					case 'Mutation':
130
						$mutationField = $this->mutationFieldsProvider->getField($fieldName);
131
						if ($mutationField) {
132
							return $mutationField->resolve($value, $args, $context);
133
						}
134
						break;
135
136
					default:
137
						$type = Types::findByName($typeConfig['name']);
138
						if ($type instanceof ObjectType) {
139
							$fieldNameForResolving = $fieldName;
140
							if ($type->hasField($fieldNameForResolving)) {
141
								$typeField = $type->getField($fieldNameForResolving);
142
								$resolver = $typeField->resolveFn;
143
								if (is_callable($resolver)) {
144
									return $resolver($value, $args, $context, $info);
145
								}
146
							}
147
148
							$resolver = $type->resolveFieldFn;
149
							if (is_callable($resolver)) {
150
								return $resolver($value, $args, $context, $info);
151
							}
152
153
							return null;
154
						}
155
				}
156
157
				return null;
158
			};
159
160
			return $typeConfig;
161
		};
162
	}
163
164
}
165