GermanAirlinesVA-GraphQL/classes/SchemaSourceProvider.php
2021-07-28 19:10:35 +02:00

222 lines
5.5 KiB
PHP

<?php
namespace GermanAirlinesVa\Graphql\Classes;
use Cache;
use Nuwave\Lighthouse\Schema\Source\SchemaSourceProvider as LighthouseSchemaSourceProvider;
use GraphQL\Language\Parser;
use GraphQL\Error\Error;
use Cms\Classes\ComponentManager;
use Cms\Classes\ComponentPartial;
class SchemaSourceProvider implements LighthouseSchemaSourceProvider
{
protected $fieldGraphMapCacheKey = 'graphql-schema-field-graph-map';
/**
* @var Schema
*/
public $template;
/**
* @var string
*/
protected $rootSchemaPath;
/**
* @var array
* Collects graphs and their parsed schema
*/
protected $graphMap;
/**
* @var array
* Maps field names to graphs
*/
protected $fieldGraphMap;
/**
* SchemaSource constructor.
*
* @param string|null $template
*/
public function __construct($template = null)
{
$this->template = Schema::load(is_string($template) ? $template : 'graphql');
$this->rootSchemaPath = $this->template->getPath();
}
/**
* Set schema root path.
*
* @param string $path
*
* @return SchemaSourceProvider
*/
public function setRootPath(string $path): self
{
$this->rootSchemaPath = $path;
return $this;
}
/**
* Stitch together schema documents and return the result as a string.
*
* @return string
*/
public function getSchemaString(): string
{
// root types
$schema = "
type Query {
dummy: Boolean
}
type Mutation {
dummy: Boolean
}
type Subscription {
dummy: Boolean @subscription(class: \"GermanAirlinesVa\\\\Graphql\\\\Classes\\\\Dummy\")
}
";
// schema
$schema .= collect($this->getGraphMap())->implode('schema', '');
return $schema;
}
public function findGraph($fieldName, $findOrFail = false)
{
if (!isset($this->getFieldGraphMap()[$fieldName])) {
if ($findOrFail) {
throw new Error("Could not find graph of field '{$fieldName}'.");
}
return null;
}
$name = $this->getFieldGraphMap()[$fieldName];
if (isset($this->graphMap[$name])) {
// if available, use already instantiated graph objects in memory
return $this->graphMap[$name]['graph'];
}
return Graph::loadCached($this->template, $name);
}
public function getFieldGraphMap()
{
if (!is_null($this->fieldGraphMap)) {
return $this->fieldGraphMap;
}
// restore from cache
$cacheable = true;
if ($cacheable) {
$map = Cache::get($this->fieldGraphMapCacheKey, false);
if ($map && ($map = @unserialize(@base64_decode($map))) && is_array($map)) {
$this->fieldGraphMap = $map;
return $this->fieldGraphMap;
}
}
// rebuild mapping
$this->fieldGraphMap = [];
foreach ($this->getGraphMap() as $key => $element) {
if (empty(trim($element['schema']))) {
continue;
}
// parse the AST to collect fields mapping
$ast = Parser::parse($element['schema'], ['noLocation' => true]);
$fields = collect($ast->definitions)
->filter(function ($node) {
// we only consider the root types since their fields are unique
return $node->name->value == 'Query' || $node->name->value == 'Mutation';
})
->flatMap(function ($node) {
// collect field names
return collect($node->fields)->map(function ($field) {
return $field->name->value;
});
});
// map as [$field_name => $graph_filename]
$this->fieldGraphMap = array_merge($this->fieldGraphMap, array_fill_keys($fields->toArray(), $key));
}
// cache mapping
if ($cacheable) {
Cache::put($this->fieldGraphMapCacheKey, base64_encode(serialize($this->fieldGraphMap)), 10);
}
return $this->fieldGraphMap;
}
public function getGraphMap()
{
if (!is_null($this->graphMap)) {
return $this->graphMap;
}
$this->graphMap = [];
$component_re = '/{%\s*component\s+["|\'](.*)["|\']\s*%}/m';
$component_str = [$this, 'getComponentSchemaString'];
foreach ($this->template->listGraphs() as $graph) {
/* @var $graph \GermanAirlinesVa\Graphql\Classes\Graph */
$markup = $graph->markup;
$schema = preg_replace_callback(
$component_re,
function (array $matches) use ($graph, $component_str) {
$matched_alias = $matches[1];
foreach ($graph->settings['components'] as $component => $properties) {
// find component by alias
[$name, $alias] = strpos($component, ' ') ? explode(' ', $component) : [$component, $component];
if ($alias == $matched_alias) {
// resolve component schema
return $component_str($name, $alias);
}
}
// if not found, remove
return '';
},
$markup
);
$this->graphMap[$graph->getFileName()] = [
'graph' => $graph,
'schema' => $schema,
];
}
return $this->graphMap;
}
public function getComponentSchemaString($componentName, $alias)
{
$manager = ComponentManager::instance();
$componentObj = $manager->makeComponent($componentName);
if ($partial = ComponentPartial::load($componentObj, 'schema.graphqls')) {
$content = $partial->getContent();
$content = str_replace('__SELF__', $alias, $content);
return $content;
} else {
return "# {$componentName} does not provide a default schema definition";
}
}
public function clearCache()
{
Cache::forget($this->fieldGraphMapCacheKey);
}
}