graphql_core_schema-1.0.x-dev/src/ViewsSchemaBuilder.php
src/ViewsSchemaBuilder.php
<?php
namespace Drupal\graphql_core_schema;
use Drupal\views\ViewEntityInterface;
use Drupal\views\ViewExecutable;
use GraphQL\Type\Definition\CustomScalarType;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Utils\SchemaPrinter;
/**
* The ViewsSchemaBuilder class.
*/
class ViewsSchemaBuilder {
/**
* The generated types.
*
* @var \GraphQL\Type\Definition\TypeWithFields[]
*/
protected $types;
/**
* The ViewExecutable base fields.
*
* @var array
*/
protected $baseFields;
/**
* The Entity scalar.
*
* Only used as a placeholder, will not be part of the generated schema.
*
* @var \GraphQL\Type\Definition\CustomScalarType
*/
protected $entityScalar;
/**
* The enabled views and displays.
*
* @var string[]
*/
protected $enabledViewDisplays;
/**
* Constructs a new ViewsSchemaBuilder.
*
* @param array $enabled
* List of enabled views.
*/
public function __construct(array $enabled) {
$this->enabledViewDisplays = $enabled;
$this->entityScalar = new CustomScalarType(['name' => 'Entity']);
$this->types = [];
$this->types['ViewContextualFilter'] = new InputObjectType([
'name' => 'ViewContextualFilter',
'fields' => [
'key' => Type::nonNull(Type::string()),
'value' => Type::string(),
],
]);
$this->types['ViewQueryParam'] = new InputObjectType([
'name' => 'ViewQueryParam',
'fields' => [
'key' => Type::nonNull(Type::string()),
'value' => Type::string(),
],
]);
$this->types['ViewExecutableResult'] = new ObjectType([
'name' => 'ViewExecutableResult',
'fields' => [
'rows' => fn () => Type::listOf($this->entityScalar),
'total_rows' => Type::nonNull(Type::int()),
],
]);
$this->types['ViewValue'] = new CustomScalarType([
'name' => 'ViewValue',
]);
$this->types['ViewFilterGroupItem'] = new ObjectType([
'name' => 'ViewFilterGroupItem',
'fields' => [
'title' => Type::string(),
'operator' => Type::string(),
'value' => Type::string(),
],
]);
$this->types['ViewFilterGroupInfo'] = new ObjectType([
'name' => 'ViewFilterGroupInfo',
'fields' => [
'label' => Type::string(),
'description' => Type::string(),
'identifier' => Type::string(),
'optional' => Type::boolean(),
'widget' => Type::string(),
'multiple' => Type::boolean(),
'remember' => Type::boolean(),
'defaultGroup' => Type::string(),
'groupItems' => fn () => Type::listOf($this->types['ViewFilterGroupItem']),
],
]);
$this->types['ViewFilter'] = new ObjectType([
'name' => 'ViewFilter',
'fields' => [
'pluginId' => Type::string(),
'baseId' => Type::string(),
'adminLabel' => Type::string(),
'adminLabelShort' => Type::string(),
'adminSummary' => Type::string(),
'field' => Type::string(),
'realField' => Type::string(),
'table' => Type::string(),
'value' => fn () => $this->types['ViewValue'],
'options' => fn () => $this->types['ViewValue'],
'groupInfo' => fn () => $this->types['ViewFilterGroupInfo'],
'operator' => Type::string(),
'noOperator' => Type::boolean(),
'alwaysRequired' => Type::boolean(),
'isExposed' => Type::boolean(),
'isAGroup' => Type::boolean(),
],
]);
$this->types['ViewSort'] = new ObjectType([
'name' => 'ViewSort',
'fields' => [
'pluginId' => Type::string(),
'baseId' => Type::string(),
'field' => Type::string(),
'realField' => Type::string(),
],
]);
$this->types['ViewPager'] = new ObjectType([
'name' => 'ViewPager',
'fields' => [
'perPage' => Type::int(),
'totalItems' => Type::int(),
],
]);
$this->baseFields = [
'filters' => fn () => Type::listOf($this->types['ViewFilter']),
'sorts' => fn () => Type::listOf($this->types['ViewSort']),
'pager' => fn () => $this->types['ViewPager'],
'execute' => [
'type' => fn () => $this->types['ViewExecutableResult'],
'args' => [
'page' => Type::int(),
'limit' => Type::int(),
'sortBy' => Type::string(),
'sortOrder' => Type::string(),
'contextualFilters' => fn () => Type::listOf($this->types['ViewContextualFilter']),
'queryParams' => fn() => Type::listOf($this->types['ViewQueryParam']),
],
],
];
$this->types['ViewExecutable'] = new InterfaceType([
'name' => 'ViewExecutable',
'fields' => $this->baseFields,
]);
}
/**
* Get the GraphQL type for a view.
*
* @param \Drupal\views\ViewExecutable $executable
* The view executable.
*
* @return string
* The GraphQL type name for the view.
*/
public static function getGraphqlTypeName(ViewExecutable $executable) {
return EntitySchemaHelper::toPascalCase([
'view',
$executable->storage->id(),
$executable->current_display,
]);
}
/**
* Generate the type for the view.
*
* @param \Drupal\views\ViewEntityInterface $view
* The view entity.
*/
public function generateViewType(ViewEntityInterface $view) {
$displays = $view->get('display');
foreach ($displays as $displayId => $options) {
$key = $view->id() . ':' . $displayId;
$executable = $view->getExecutable();
$executable->setDisplay($displayId);
// Only expose enabled view displays.
if (!in_array($key, $this->enabledViewDisplays)) {
continue;
}
$graphqlTypeName = self::getGraphqlTypeName($executable);
$display = $executable->getDisplay();
$filters = array_reduce(array_filter($display->getOption('filters') ?: [], function ($filter) {
return array_key_exists('exposed', $filter) && $filter['exposed'];
}), function ($carry, $current) {
return $carry + [
$current['expose']['identifier'] => $current,
];
}, []);
$filterFields = [];
foreach ($filters as $filterName => $filter) {
if ($this->isGenericInputFilter($filter)) {
continue;
}
$fieldName = EntitySchemaHelper::toCamelCase($filterName);
$filterFields[$fieldName] = $filter['expose']['multiple']
? Type::listOf(Type::string())
: Type::string();
}
// Build field arguments.
$args = [
'page' => Type::int(),
'limit' => Type::int(),
];
if (!empty($filterFields)) {
$id = implode('_', [
$view->id(),
$displayId,
'view',
'filters',
'input',
]);
$argTypeName = EntitySchemaHelper::toPascalCase($id);
if (!isset($this->types[$argTypeName])) {
$this->types[$argTypeName] = new InputObjectType([
'name' => $argTypeName,
'fields' => $filterFields,
]);
}
$args['filters'] = fn() => $this->types[$argTypeName];
}
// Check if sorting is enabled.
if (!empty($display->getOption('sorts'))) {
$args['sortBy'] = Type::string();
$args['sortOrder'] = Type::string();
}
$fields = $this->baseFields;
$fields['execute']['args'] = array_merge($fields['execute']['args'], $args);
$this->types[$graphqlTypeName] = new ObjectType([
'name' => $graphqlTypeName,
'interfaces' => [fn () => $this->types['ViewExecutable']],
'fields' => $fields,
]);
}
}
/**
* Checks if a filter definition is a generic input filter.
*
* @param mixed $filter
* The filter.
* $filter['value'] = [];
* $filter['value'] = [
* "text",
* "test"
* ];
* $filter['value'] = [
* 'distance' => 10,
* 'distance2' => 30,
* ...
* ];.
*
* @return bool
* Returns true if it is a generic filter.
*/
public function isGenericInputFilter($filter) {
if (!is_array($filter['value']) || count($filter['value']) == 0) {
return FALSE;
}
$firstKey = array_keys($filter['value'])[0];
return is_string($firstKey);
}
/**
* Creates a definition for a generic input filter.
*
* @param mixed $filter
* The filter.
* $filter['value'] = [];
* $filter['value'] = [
* "text",
* "test"
* ];
* $filter['value'] = [
* 'distance' => 10,
* 'distance2' => 30,
* ...
* ];.
*
* @return array
* The filter definition.
*/
public function createGenericInputFilterDefinition($filter) {
$filterId = $filter['expose']['identifier'];
$id = implode('_', [
$filter['expose']['multiple'] ? $filterId : $filterId . '_multi',
'view',
'filter',
'input',
]);
$fields = [];
foreach ($filter['value'] as $fieldKey => $fieldDefaultValue) {
$fields[$fieldKey] = [
'type' => Type::string(),
];
}
$genericInputFilter = [
'id' => $id,
'name' => EntitySchemaHelper::toPascalCase([$id]),
'fields' => $fields,
];
$this->derivatives[$id] = $genericInputFilter;
return [
'type' => $filter['expose']['multiple'] ? $genericInputFilter['name'] : $genericInputFilter['name'],
];
}
/**
* Get the generated schema.
*
* @return string
* The generated schema.
*/
public function getGeneratedSchema() {
$result = '';
foreach ($this->types as $type) {
$result .= SchemaPrinter::printType($type) . "\n";
}
return $result;
}
/**
* Returns information about view arguments (contextual filters).
*
* @param array $viewArguments
* The "arguments" option of a view display.
*
* @return array
* The argument list.
*/
protected function getArgumentsFields(array $viewArguments) {
$fields = [];
foreach ($viewArguments as $argumentId => $argument) {
$fields[$argumentId] = fn() => Type::string();
}
return $fields;
}
}
