Magento 2 Documentation  2.3
Documentation for Magento 2 CMS v2.3 (December 2018)
GraphQlReader.php
Go to the documentation of this file.
1 <?php
6 declare(strict_types=1);
7 
9 
13 
18 {
19  const GRAPHQL_PLACEHOLDER_FIELD_NAME = 'placeholder_graphql_field';
20 
21  const GRAPHQL_SCHEMA_FILE = 'schema.graphqls';
22 
28  private $fileResolver;
29 
33  private $typeReader;
34 
38  private $fileName;
39 
43  private $defaultScope;
44 
51  public function __construct(
52  FileResolverInterface $fileResolver,
53  TypeReaderComposite $typeReader,
54  $fileName = self::GRAPHQL_SCHEMA_FILE,
55  $defaultScope = 'global'
56  ) {
57  $this->fileResolver = $fileResolver;
58  $this->typeReader = $typeReader;
59  $this->defaultScope = $defaultScope;
60  $this->fileName = $fileName;
61  }
62 
66  public function read($scope = null) : array
67  {
68  $results = [];
69  $scope = $scope ?: $this->defaultScope;
70  $schemaFiles = $this->fileResolver->get($this->fileName, $scope);
71  if (!count($schemaFiles)) {
72  return $results;
73  }
74 
78  $knownTypes = [];
79  foreach ($schemaFiles as $partialSchemaContent) {
80  $partialSchemaTypes = $this->parseTypes($partialSchemaContent);
81 
82  // Keep declarations from current partial schema, add missing declarations from all previously read schemas
83  $knownTypes = $partialSchemaTypes + $knownTypes;
84  $schemaContent = implode("\n", $knownTypes);
85 
86  $partialResults = $this->readPartialTypes($schemaContent);
87 
88  $results = array_replace_recursive($results, $partialResults);
89  }
90 
91  $results = $this->copyInterfaceFieldsToConcreteTypes($results);
92  return $results;
93  }
94 
101  private function readPartialTypes(string $graphQlSchemaContent) : array
102  {
103  $partialResults = [];
104 
105  $graphQlSchemaContent = $this->addPlaceHolderInSchema($graphQlSchemaContent);
106 
107  $schema = \GraphQL\Utils\BuildSchema::build($graphQlSchemaContent);
108 
109  foreach ($schema->getTypeMap() as $typeName => $typeMeta) {
110  // Only process custom types and skip built-in object types
111  if ((strpos($typeName, '__') !== 0 && (!$typeMeta instanceof \GraphQL\Type\Definition\ScalarType))) {
112  $type = $this->typeReader->read($typeMeta);
113  if (!empty($type)) {
114  $partialResults[$typeName] = $type;
115  } else {
116  throw new \LogicException("'{$typeName}' cannot be processed.");
117  }
118  }
119  }
120 
121  $partialResults = $this->removePlaceholderFromResults($partialResults);
122 
123  return $partialResults;
124  }
125 
132  private function parseTypes(string $graphQlSchemaContent) : array
133  {
134  $typeKindsPattern = '(type|interface|union|enum|input)';
135  $typeNamePattern = '([_A-Za-z][_0-9A-Za-z]+)';
136  $typeDefinitionPattern = '([^\{]*)(\{[^\}]*\})';
137  $spacePattern = '[\s\t\n\r]+';
138 
139  preg_match_all(
140  "/{$typeKindsPattern}{$spacePattern}{$typeNamePattern}{$spacePattern}{$typeDefinitionPattern}/i",
141  $graphQlSchemaContent,
142  $matches
143  );
144 
145  $parsedTypes = [];
146 
147  if (!empty($matches)) {
148  foreach ($matches[0] as $matchKey => $matchValue) {
149  $matches[0][$matchKey] = $this->convertInterfacesToAnnotations($matchValue);
150  }
151 
156  $parsedTypes = array_combine($matches[2], $matches[0]);
157  }
158  return $parsedTypes;
159  }
160 
167  private function copyInterfaceFieldsToConcreteTypes(array $source): array
168  {
169  foreach ($source as $interface) {
170  if ($interface['type'] == 'graphql_interface') {
171  foreach ($source as $typeName => $type) {
172  if (isset($type['implements'])
173  && isset($type['implements'][$interface['name']])
174  && isset($type['implements'][$interface['name']]['copyFields'])
175  && $type['implements'][$interface['name']]['copyFields'] === true
176  ) {
177  $source[$typeName]['fields'] = isset($type['fields'])
178  ? array_replace($interface['fields'], $type['fields']) : $interface['fields'];
179  }
180  }
181  }
182  }
183 
184  return $source;
185  }
186 
193  private function convertInterfacesToAnnotations(string $graphQlSchemaContent): string
194  {
195  $implementsKindsPattern = 'implements';
196  $typeNamePattern = '([_A-Za-z][_0-9A-Za-z]+)';
197  $spacePattern = '([\s\t\n\r]+)';
198  $spacePatternNotMandatory = '[\s\t\n\r]*';
199  preg_match_all(
200  "/{$spacePattern}{$implementsKindsPattern}{$spacePattern}{$typeNamePattern}"
201  . "(,{$spacePatternNotMandatory}$typeNamePattern)*/im",
202  $graphQlSchemaContent,
203  $allMatchesForImplements
204  );
205 
206  if (!empty($allMatchesForImplements)) {
207  foreach (array_unique($allMatchesForImplements[0]) as $implementsString) {
208  $implementsStatementString = preg_replace(
209  "/{$spacePattern}{$implementsKindsPattern}{$spacePattern}/m",
210  '',
211  $implementsString
212  );
213  preg_match_all(
214  "/{$typeNamePattern}+/im",
215  $implementsStatementString,
216  $implementationsMatches
217  );
218 
219  if (!empty($implementationsMatches)) {
220  $annotationString = ' @implements(interfaces: [';
221  foreach ($implementationsMatches[0] as $interfaceName) {
222  $annotationString.= "\"{$interfaceName}\", ";
223  }
224  $annotationString = rtrim($annotationString, ', ');
225  $annotationString .= ']) ';
226  $graphQlSchemaContent = str_replace($implementsString, $annotationString, $graphQlSchemaContent);
227  }
228  }
229  }
230 
231  return $graphQlSchemaContent;
232  }
233 
242  private function addPlaceHolderInSchema(string $graphQlSchemaContent) :string
243  {
244  $placeholderField = self::GRAPHQL_PLACEHOLDER_FIELD_NAME;
245  $typesKindsPattern = '(type|interface|input)';
246  $enumKindsPattern = '(enum)';
247  $typeNamePattern = '([_A-Za-z][_0-9A-Za-z]+)';
248  $typeDefinitionPattern = '([^\{]*)(\{[\s\t\n\r^\}]*\})';
249  $spacePattern = '([\s\t\n\r]+)';
250 
251  //add placeholder in empty types
252  $graphQlSchemaContent = preg_replace(
253  "/{$typesKindsPattern}{$spacePattern}{$typeNamePattern}{$spacePattern}{$typeDefinitionPattern}/im",
254  "\$1\$2\$3\$4\$5{\n{$placeholderField}: String\n}",
255  $graphQlSchemaContent
256  );
257 
258  //add placeholder in empty enums
259  $graphQlSchemaContent = preg_replace(
260  "/{$enumKindsPattern}{$spacePattern}{$typeNamePattern}{$spacePattern}{$typeDefinitionPattern}/im",
261  "\$1\$2\$3\$4\$5{\n{$placeholderField}\n}",
262  $graphQlSchemaContent
263  );
264  return $graphQlSchemaContent;
265  }
266 
273  private function removePlaceholderFromResults(array $partialResults) : array
274  {
275  $placeholderField = self::GRAPHQL_PLACEHOLDER_FIELD_NAME;
276  //remove parsed placeholders
277  foreach ($partialResults as $typeKeyName => $partialResultTypeArray) {
278  if (isset($partialResultTypeArray['fields'][$placeholderField])) {
279  //unset placeholder for fields
280  unset($partialResults[$typeKeyName]['fields'][$placeholderField]);
281  } elseif (isset($partialResultTypeArray['items'][$placeholderField])) {
282  //unset placeholder for enums
283  unset($partialResults[$typeKeyName]['items'][$placeholderField]);
284  }
285  }
286  return $partialResults;
287  }
288 }
$results
Definition: popup.phtml:13
__construct(FileResolverInterface $fileResolver, TypeReaderComposite $typeReader, $fileName=self::GRAPHQL_SCHEMA_FILE, $defaultScope='global')
elseif(isset( $params[ 'redirect_parent']))
Definition: iframe.phtml:17
$source
Definition: source.php:23
$type
Definition: item.phtml:13