218 lines
5.4 KiB
PHP
218 lines
5.4 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
|
|
}
|
|
';
|
|
// 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);
|
|
}
|
|
}
|