2015-07-15 20:05:46 +03:00
|
|
|
<?php
|
|
|
|
namespace GraphQL;
|
|
|
|
|
2017-07-10 16:17:55 +03:00
|
|
|
use GraphQL\Type\Descriptor;
|
2016-04-25 00:57:09 +03:00
|
|
|
use GraphQL\Type\Definition\AbstractType;
|
2015-07-15 20:05:46 +03:00
|
|
|
use GraphQL\Type\Definition\Directive;
|
2017-03-04 23:10:52 +03:00
|
|
|
use GraphQL\Type\Definition\InterfaceType;
|
2015-07-15 20:05:46 +03:00
|
|
|
use GraphQL\Type\Definition\ObjectType;
|
|
|
|
use GraphQL\Type\Definition\Type;
|
2017-03-04 23:10:52 +03:00
|
|
|
use GraphQL\Type\Definition\UnionType;
|
2015-07-15 20:05:46 +03:00
|
|
|
use GraphQL\Type\Introspection;
|
2017-07-10 15:50:26 +03:00
|
|
|
use GraphQL\Utils\TypeInfo;
|
|
|
|
use GraphQL\Utils\Utils;
|
2015-07-15 20:05:46 +03:00
|
|
|
|
2016-10-17 14:33:47 +03:00
|
|
|
/**
|
2016-10-18 21:34:46 +03:00
|
|
|
* Schema Definition
|
|
|
|
*
|
|
|
|
* A Schema is created by supplying the root types of each type of operation:
|
|
|
|
* query, mutation (optional) and subscription (optional). A schema definition is
|
|
|
|
* then supplied to the validator and executor.
|
|
|
|
*
|
|
|
|
* Example:
|
|
|
|
*
|
|
|
|
* $schema = new GraphQL\Schema([
|
|
|
|
* 'query' => $MyAppQueryRootType,
|
|
|
|
* 'mutation' => $MyAppMutationRootType,
|
|
|
|
* ]);
|
|
|
|
*
|
|
|
|
* Note: If an array of `directives` are provided to GraphQL\Schema, that will be
|
|
|
|
* the exact list of directives represented and allowed. If `directives` is not
|
|
|
|
* provided then a default set of the specified directives (e.g. @include and
|
|
|
|
* @skip) will be used. If you wish to provide *additional* directives to these
|
|
|
|
* specified directives, you must explicitly declare them. Example:
|
|
|
|
*
|
|
|
|
* $mySchema = new GraphQL\Schema([
|
|
|
|
* ...
|
|
|
|
* 'directives' => array_merge(GraphQL::getInternalDirectives(), [ $myCustomDirective ]),
|
|
|
|
* ])
|
|
|
|
*
|
2016-10-17 14:33:47 +03:00
|
|
|
* @package GraphQL
|
|
|
|
*/
|
2015-07-15 20:05:46 +03:00
|
|
|
class Schema
|
|
|
|
{
|
2016-04-25 00:57:09 +03:00
|
|
|
/**
|
2017-03-04 23:10:52 +03:00
|
|
|
* @var Config
|
2016-04-25 00:57:09 +03:00
|
|
|
*/
|
2016-12-10 03:49:41 +03:00
|
|
|
private $config;
|
2015-07-15 20:05:46 +03:00
|
|
|
|
2016-04-25 00:57:09 +03:00
|
|
|
/**
|
2017-03-04 23:10:52 +03:00
|
|
|
* Contains actual descriptor for this schema
|
|
|
|
*
|
|
|
|
* @var Descriptor
|
2016-04-25 00:57:09 +03:00
|
|
|
*/
|
2017-03-04 23:10:52 +03:00
|
|
|
private $descriptor;
|
2015-07-15 20:05:46 +03:00
|
|
|
|
2016-04-25 00:57:09 +03:00
|
|
|
/**
|
2017-03-04 23:10:52 +03:00
|
|
|
* Contains currently resolved schema types
|
|
|
|
*
|
|
|
|
* @var Type[]
|
2016-04-25 00:57:09 +03:00
|
|
|
*/
|
2017-03-04 23:10:52 +03:00
|
|
|
private $resolvedTypes = [];
|
2015-07-15 20:05:46 +03:00
|
|
|
|
2016-04-25 00:57:09 +03:00
|
|
|
/**
|
2017-03-04 23:10:52 +03:00
|
|
|
* True when $resolvedTypes contain all possible schema types
|
2016-12-10 03:49:41 +03:00
|
|
|
*
|
2017-03-04 23:10:52 +03:00
|
|
|
* @var bool
|
2016-04-25 00:57:09 +03:00
|
|
|
*/
|
2017-03-04 23:10:52 +03:00
|
|
|
private $fullyLoaded = false;
|
2016-04-25 00:57:09 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Schema constructor.
|
2017-03-04 23:10:52 +03:00
|
|
|
*
|
|
|
|
* @param array|Config $config
|
2016-04-25 00:57:09 +03:00
|
|
|
*/
|
|
|
|
public function __construct($config = null)
|
2015-07-15 20:05:46 +03:00
|
|
|
{
|
2016-04-25 00:57:09 +03:00
|
|
|
if (func_num_args() > 1 || $config instanceof Type) {
|
|
|
|
trigger_error(
|
|
|
|
'GraphQL\Schema constructor expects config object now instead of types passed as arguments. '.
|
|
|
|
'See https://github.com/webonyx/graphql-php/issues/36',
|
|
|
|
E_USER_DEPRECATED
|
|
|
|
);
|
2016-09-15 13:32:54 +03:00
|
|
|
list($queryType, $mutationType, $subscriptionType) = func_get_args() + [null, null, null];
|
2016-04-25 00:57:09 +03:00
|
|
|
|
2017-07-10 16:17:55 +03:00
|
|
|
$config = [
|
|
|
|
'query' => $queryType,
|
|
|
|
'mutation' => $mutationType,
|
|
|
|
'subscription' => $subscriptionType
|
|
|
|
];
|
|
|
|
}
|
|
|
|
if (is_array($config)) {
|
2017-03-04 23:10:52 +03:00
|
|
|
$config = Config::create($config);
|
2016-04-25 00:57:09 +03:00
|
|
|
}
|
2015-08-17 17:01:55 +03:00
|
|
|
|
2016-05-02 00:42:05 +03:00
|
|
|
Utils::invariant(
|
2017-03-04 23:10:52 +03:00
|
|
|
$config instanceof Config,
|
|
|
|
'Schema constructor expects instance of GraphQL\Schema\Config or an array with keys: %s; but got: %s',
|
|
|
|
implode(', ', [
|
|
|
|
'query',
|
|
|
|
'mutation',
|
|
|
|
'subscription',
|
|
|
|
'types',
|
|
|
|
'directives',
|
2017-07-10 16:17:55 +03:00
|
|
|
'typeLoader',
|
|
|
|
'descriptor'
|
2017-03-04 23:10:52 +03:00
|
|
|
]),
|
|
|
|
Utils::getVariableType($config)
|
2016-05-02 00:42:05 +03:00
|
|
|
);
|
|
|
|
|
2016-12-10 03:49:41 +03:00
|
|
|
Utils::invariant(
|
2017-03-04 23:10:52 +03:00
|
|
|
$config->query instanceof ObjectType,
|
|
|
|
"Schema query must be Object Type but got: " . Utils::getVariableType($config->query)
|
2016-12-10 03:49:41 +03:00
|
|
|
);
|
2015-08-17 17:01:55 +03:00
|
|
|
|
2016-12-10 03:49:41 +03:00
|
|
|
$this->config = $config;
|
2015-08-17 17:01:55 +03:00
|
|
|
}
|
|
|
|
|
2016-04-25 00:57:09 +03:00
|
|
|
/**
|
2017-07-10 16:17:55 +03:00
|
|
|
* Returns schema query type
|
|
|
|
*
|
2016-04-25 00:57:09 +03:00
|
|
|
* @return ObjectType
|
|
|
|
*/
|
2015-07-15 20:05:46 +03:00
|
|
|
public function getQueryType()
|
|
|
|
{
|
2017-03-04 23:10:52 +03:00
|
|
|
return $this->config->query;
|
2015-07-15 20:05:46 +03:00
|
|
|
}
|
|
|
|
|
2016-04-25 00:57:09 +03:00
|
|
|
/**
|
2017-07-10 16:17:55 +03:00
|
|
|
* Returns schema mutation type
|
|
|
|
*
|
2017-03-04 23:10:52 +03:00
|
|
|
* @return ObjectType|null
|
2016-04-25 00:57:09 +03:00
|
|
|
*/
|
2015-07-15 20:05:46 +03:00
|
|
|
public function getMutationType()
|
|
|
|
{
|
2017-03-04 23:10:52 +03:00
|
|
|
return $this->config->mutation;
|
2015-07-15 20:05:46 +03:00
|
|
|
}
|
|
|
|
|
2016-04-25 00:57:09 +03:00
|
|
|
/**
|
2017-07-10 16:17:55 +03:00
|
|
|
* Returns schema subscription
|
|
|
|
*
|
2017-03-04 23:10:52 +03:00
|
|
|
* @return ObjectType|null
|
2016-04-25 00:57:09 +03:00
|
|
|
*/
|
2015-12-21 02:29:29 +03:00
|
|
|
public function getSubscriptionType()
|
|
|
|
{
|
2017-03-04 23:10:52 +03:00
|
|
|
return $this->config->subscription;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return Config
|
|
|
|
*/
|
|
|
|
public function getConfig()
|
|
|
|
{
|
|
|
|
return $this->config;
|
2015-12-21 02:29:29 +03:00
|
|
|
}
|
|
|
|
|
2015-07-15 20:05:46 +03:00
|
|
|
/**
|
2017-07-10 16:17:55 +03:00
|
|
|
* Returns array of all types in this schema. Keys of this array represent type names, values are instances
|
|
|
|
* of corresponding type definitions
|
2016-12-10 03:49:41 +03:00
|
|
|
*
|
|
|
|
* @return Type[]
|
2015-07-15 20:05:46 +03:00
|
|
|
*/
|
2016-04-25 00:57:09 +03:00
|
|
|
public function getTypeMap()
|
2015-07-15 20:05:46 +03:00
|
|
|
{
|
2017-03-04 23:10:52 +03:00
|
|
|
if (!$this->fullyLoaded) {
|
|
|
|
if ($this->config->descriptor && $this->config->typeLoader) {
|
2017-07-10 16:17:55 +03:00
|
|
|
// Following is still faster than $this->collectAllTypes() because it won't init fields
|
2017-03-04 23:10:52 +03:00
|
|
|
$typesToResolve = array_diff_key($this->config->descriptor->typeMap, $this->resolvedTypes);
|
|
|
|
foreach ($typesToResolve as $typeName => $_) {
|
|
|
|
$this->resolvedTypes[$typeName] = $this->loadType($typeName);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$this->resolvedTypes = $this->collectAllTypes();
|
|
|
|
}
|
|
|
|
$this->fullyLoaded = true;
|
|
|
|
}
|
|
|
|
return $this->resolvedTypes;
|
2016-04-25 00:57:09 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-03-04 23:10:52 +03:00
|
|
|
* Returns type by it's name
|
|
|
|
*
|
2016-04-25 00:57:09 +03:00
|
|
|
* @param string $name
|
|
|
|
* @return Type
|
|
|
|
*/
|
|
|
|
public function getType($name)
|
|
|
|
{
|
2017-03-04 23:10:52 +03:00
|
|
|
return $this->resolveType($name);
|
2016-12-10 03:49:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-03-04 23:10:52 +03:00
|
|
|
* Returns serializable schema descriptor which can be passed later
|
|
|
|
* to Schema config to enable a set of performance optimizations
|
2016-12-10 03:49:41 +03:00
|
|
|
*
|
2017-03-04 23:10:52 +03:00
|
|
|
* @return Descriptor
|
2016-12-10 03:49:41 +03:00
|
|
|
*/
|
2017-07-10 16:17:55 +03:00
|
|
|
public function describe()
|
2016-12-10 03:49:41 +03:00
|
|
|
{
|
2017-03-04 23:10:52 +03:00
|
|
|
if ($this->descriptor) {
|
|
|
|
return $this->descriptor;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->resolvedTypes = $this->collectAllTypes();
|
|
|
|
$this->fullyLoaded = true;
|
|
|
|
|
|
|
|
$descriptor = new Descriptor();
|
|
|
|
$descriptor->version = '1.0';
|
|
|
|
$descriptor->created = time();
|
|
|
|
|
|
|
|
foreach ($this->resolvedTypes as $type) {
|
|
|
|
if ($type instanceof ObjectType) {
|
|
|
|
foreach ($type->getInterfaces() as $interface) {
|
|
|
|
$descriptor->possibleTypeMap[$interface->name][$type->name] = 1;
|
|
|
|
}
|
|
|
|
} else if ($type instanceof UnionType) {
|
|
|
|
foreach ($type->getTypes() as $innerType) {
|
|
|
|
$descriptor->possibleTypeMap[$type->name][$innerType->name] = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$descriptor->typeMap[$type->name] = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->descriptor = $descriptor;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function collectAllTypes()
|
|
|
|
{
|
2017-07-28 13:53:57 +03:00
|
|
|
$initialTypes = array_merge(
|
|
|
|
[
|
|
|
|
$this->config->query,
|
|
|
|
$this->config->mutation,
|
|
|
|
$this->config->subscription,
|
|
|
|
Introspection::_schema()
|
|
|
|
],
|
|
|
|
array_values($this->resolvedTypes)
|
|
|
|
);
|
|
|
|
|
2017-03-04 23:10:52 +03:00
|
|
|
$typeMap = [];
|
|
|
|
foreach ($initialTypes as $type) {
|
2017-07-10 15:50:26 +03:00
|
|
|
$typeMap = TypeInfo::extractTypes($type, $typeMap);
|
2017-03-04 23:10:52 +03:00
|
|
|
}
|
2017-07-28 13:53:57 +03:00
|
|
|
|
|
|
|
$types = $this->config->types;
|
|
|
|
if (is_callable($types)) {
|
|
|
|
$types = $types();
|
|
|
|
|
|
|
|
Utils::invariant(
|
|
|
|
is_array($types) || $types instanceof \Traversable,
|
|
|
|
'Schema types callable must return array or instance of Traversable but got: %s',
|
|
|
|
Utils::getVariableType($types)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!empty($types)) {
|
|
|
|
foreach ($types as $type) {
|
|
|
|
Utils::invariant(
|
|
|
|
$type instanceof Type,
|
|
|
|
'Each entry of schema types must be instance of GraphQL\Type\Definition\Type but got: %s',
|
|
|
|
Utils::getVariableType($types)
|
|
|
|
);
|
|
|
|
$typeMap = TypeInfo::extractTypes($type, $typeMap);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-04 23:10:52 +03:00
|
|
|
return $typeMap + Type::getInternalTypes();
|
2016-04-25 00:57:09 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-07-10 16:17:55 +03:00
|
|
|
* Returns all possible concrete types for given abstract type
|
|
|
|
* (implementations for interfaces and members of union type for unions)
|
|
|
|
*
|
2016-04-25 00:57:09 +03:00
|
|
|
* @param AbstractType $abstractType
|
|
|
|
* @return ObjectType[]
|
|
|
|
*/
|
|
|
|
public function getPossibleTypes(AbstractType $abstractType)
|
|
|
|
{
|
2017-03-04 23:10:52 +03:00
|
|
|
if ($abstractType instanceof UnionType) {
|
|
|
|
return $abstractType->getTypes();
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @var InterfaceType $abstractType */
|
2017-07-10 16:17:55 +03:00
|
|
|
$descriptor = $this->config->descriptor ?: $this->describe();
|
2017-03-04 23:10:52 +03:00
|
|
|
|
|
|
|
$result = [];
|
|
|
|
if (isset($descriptor->possibleTypeMap[$abstractType->name])) {
|
|
|
|
foreach ($descriptor->possibleTypeMap[$abstractType->name] as $typeName => $_) {
|
|
|
|
$result[] = $this->resolveType($typeName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
2017-07-10 16:17:55 +03:00
|
|
|
/**
|
|
|
|
* Accepts name of type or type instance and returns type instance. If type with given name is not loaded yet -
|
|
|
|
* will load it first.
|
|
|
|
*
|
|
|
|
* @param $typeOrName
|
|
|
|
* @return Type
|
|
|
|
*/
|
2017-03-04 23:10:52 +03:00
|
|
|
public function resolveType($typeOrName)
|
|
|
|
{
|
|
|
|
if ($typeOrName instanceof Type) {
|
|
|
|
if ($typeOrName->name && !isset($this->resolvedTypes[$typeOrName->name])) {
|
|
|
|
$this->resolvedTypes[$typeOrName->name] = $typeOrName;
|
|
|
|
}
|
|
|
|
return $typeOrName;
|
|
|
|
}
|
|
|
|
if (!isset($this->resolvedTypes[$typeOrName])) {
|
|
|
|
$this->resolvedTypes[$typeOrName] = $this->loadType($typeOrName);
|
|
|
|
}
|
|
|
|
return $this->resolvedTypes[$typeOrName];
|
|
|
|
}
|
|
|
|
|
|
|
|
private function loadType($typeName)
|
|
|
|
{
|
|
|
|
$typeLoader = $this->config->typeLoader;
|
|
|
|
|
|
|
|
if (!$typeLoader) {
|
|
|
|
return $this->defaultTypeLoader($typeName);
|
|
|
|
}
|
|
|
|
|
|
|
|
$type = $typeLoader($typeName);
|
|
|
|
// TODO: validate returned value
|
|
|
|
return $type;
|
2016-04-25 00:57:09 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-07-10 16:17:55 +03:00
|
|
|
* Returns true if object type is concrete type of given abstract type
|
|
|
|
* (implementation for interfaces and members of union type for unions)
|
|
|
|
*
|
2016-04-25 00:57:09 +03:00
|
|
|
* @param AbstractType $abstractType
|
|
|
|
* @param ObjectType $possibleType
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function isPossibleType(AbstractType $abstractType, ObjectType $possibleType)
|
|
|
|
{
|
2017-03-04 23:10:52 +03:00
|
|
|
if ($this->config->descriptor) {
|
|
|
|
return !empty($this->config->descriptor->possibleTypeMap[$abstractType->name][$possibleType->name]);
|
|
|
|
}
|
2016-10-18 21:34:46 +03:00
|
|
|
|
2017-03-04 23:10:52 +03:00
|
|
|
if ($abstractType instanceof InterfaceType) {
|
|
|
|
return $possibleType->implementsInterface($abstractType);
|
2015-07-15 20:05:46 +03:00
|
|
|
}
|
2017-03-04 23:10:52 +03:00
|
|
|
|
|
|
|
/** @var UnionType $abstractType */
|
|
|
|
return $abstractType->isPossibleType($possibleType);
|
2015-07-15 20:05:46 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-07-10 16:17:55 +03:00
|
|
|
* Returns a list of directives supported by this schema
|
|
|
|
*
|
2016-04-25 00:57:09 +03:00
|
|
|
* @return Directive[]
|
2015-07-15 20:05:46 +03:00
|
|
|
*/
|
|
|
|
public function getDirectives()
|
|
|
|
{
|
2017-03-04 23:10:52 +03:00
|
|
|
return $this->config->directives ?: GraphQL::getInternalDirectives();
|
2015-07-15 20:05:46 +03:00
|
|
|
}
|
|
|
|
|
2016-04-25 00:57:09 +03:00
|
|
|
/**
|
2017-07-10 16:17:55 +03:00
|
|
|
* Returns instance of directive by name
|
|
|
|
*
|
2016-04-25 00:57:09 +03:00
|
|
|
* @param $name
|
|
|
|
* @return Directive
|
|
|
|
*/
|
|
|
|
public function getDirective($name)
|
2015-07-15 20:05:46 +03:00
|
|
|
{
|
2016-04-25 00:57:09 +03:00
|
|
|
foreach ($this->getDirectives() as $directive) {
|
|
|
|
if ($directive->name === $name) {
|
|
|
|
return $directive;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
2015-07-15 20:05:46 +03:00
|
|
|
}
|
|
|
|
|
2017-03-04 23:10:52 +03:00
|
|
|
/**
|
|
|
|
* @param $typeName
|
|
|
|
* @return Type
|
|
|
|
*/
|
|
|
|
private function defaultTypeLoader($typeName)
|
2015-07-15 20:05:46 +03:00
|
|
|
{
|
2017-03-04 23:10:52 +03:00
|
|
|
// Default type loader simply fallbacks to collecting all types
|
|
|
|
if (!$this->fullyLoaded) {
|
|
|
|
$this->resolvedTypes = $this->collectAllTypes();
|
|
|
|
$this->fullyLoaded = true;
|
|
|
|
}
|
|
|
|
if (!isset($this->resolvedTypes[$typeName])) {
|
|
|
|
return null;
|
2015-07-15 20:05:46 +03:00
|
|
|
}
|
2017-03-04 23:10:52 +03:00
|
|
|
return $this->resolvedTypes[$typeName];
|
2015-07-15 20:05:46 +03:00
|
|
|
}
|
|
|
|
}
|