/** * Prepares an object map of variables of the correct type based on the provided * variable definitions and arbitrary input. If the input cannot be coerced * to match the variable definitions, a Error will be thrown. * * @param Schema $schema * @param VariableDefinitionNode[] $definitionNodes * @param array $inputs * @return array * @throws Error */ public static function getVariableValues(Schema $schema, $definitionNodes, array $inputs) { $coercedValues = []; foreach ($definitionNodes as $definitionNode) { $varName = $definitionNode->variable->name->value; $varType = Utils\TypeInfo::typeFromAST($schema, $definitionNode->type); if (!Type::isInputType($varType)) { throw new Error('Variable "$' . $varName . '" expected value of type ' . '"' . Printer::doPrint($definitionNode->type) . '" which cannot be used as an input type.', [$definitionNode->type]); } if (!array_key_exists($varName, $inputs)) { $defaultValue = $definitionNode->defaultValue; if ($defaultValue) { $coercedValues[$varName] = Utils\AST::valueFromAST($defaultValue, $varType); } if ($varType instanceof NonNull) { throw new Error('Variable "$' . $varName . '" of required type ' . '"' . Utils::printSafe($varType) . '" was not provided.', [$definitionNode]); } } else { $value = $inputs[$varName]; $errors = self::isValidPHPValue($value, $varType); if (!empty($errors)) { $message = "\n" . implode("\n", $errors); throw new Error('Variable "$' . $varName . '" got invalid value ' . json_encode($value) . '.' . $message, [$definitionNode]); } $coercedValue = self::coerceValue($varType, $value); Utils::invariant($coercedValue !== Utils::undefined(), 'Should have reported error.'); $coercedValues[$varName] = $coercedValue; } } return $coercedValues; }
/** * ScalarType constructor. */ public function __construct() { if (!isset($this->name)) { $this->name = $this->tryInferName(); } Utils::invariant($this->name, 'Type must be named.'); }
/** * @param $name * @return FieldDefinition * @throws \Exception */ public function getField($name) { if (null === $this->fields) { $this->getFields(); } Utils::invariant(isset($this->fields[$name]), 'Field "%s" is not defined for type "%s"', $name, $this->name); return $this->fields[$name]; }
private function getTypeASTName(Type $typeAST) { if ($typeAST->kind === Node::NAME) { return $typeAST->value; } Utils::invariant($typeAST->type, 'Must be wrapping type'); return $this->getTypeASTName($typeAST->type); }
/** * @param Schema $schema * @param $inputTypeAst * @return ListOfType|NonNull|Name * @throws \Exception */ public static function typeFromAST(Schema $schema, $inputTypeAst) { if ($inputTypeAst instanceof ListType) { $innerType = self::typeFromAST($schema, $inputTypeAst->type); return $innerType ? new ListOfType($innerType) : null; } if ($inputTypeAst instanceof NonNullType) { $innerType = self::typeFromAST($schema, $inputTypeAst->type); return $innerType ? new NonNull($innerType) : null; } Utils::invariant($inputTypeAst instanceof Name, 'Must be a type name'); return $schema->getType($inputTypeAst->value); }
/** * ObjectType constructor. * @param array $config */ public function __construct(array $config) { if (!isset($config['name'])) { $config['name'] = $this->tryInferName(); } Utils::invariant(!empty($config['name']), 'Every type is expected to have name'); // Note: this validation is disabled by default, because it is resource-consuming // TODO: add bin/validate script to check if schema is valid during development Config::validate($config, ['name' => Config::NAME | Config::REQUIRED, 'fields' => Config::arrayOf(FieldDefinition::getDefinition(), Config::KEY_AS_NAME | Config::MAYBE_THUNK | Config::MAYBE_TYPE), 'description' => Config::STRING, 'interfaces' => Config::arrayOf(Config::INTERFACE_TYPE, Config::MAYBE_THUNK), 'isTypeOf' => Config::CALLBACK, 'resolveField' => Config::CALLBACK]); $this->name = $config['name']; $this->description = isset($config['description']) ? $config['description'] : null; $this->resolveFieldFn = isset($config['resolveField']) ? $config['resolveField'] : null; $this->config = $config; }
public function __construct($config) { Config::validate($config, ['name' => Config::STRING | Config::REQUIRED, 'types' => Config::arrayOf(Config::OBJECT_TYPE | Config::REQUIRED), 'resolveType' => Config::CALLBACK, 'description' => Config::STRING]); Utils::invariant(!empty($config['types']), ""); /** * Optionally provide a custom type resolver function. If one is not provided, * the default implemenation will call `isTypeOf` on each implementing * Object type. */ $this->name = $config['name']; $this->description = isset($config['description']) ? $config['description'] : null; $this->_types = $config['types']; $this->_resolveType = isset($config['resolveType']) ? $config['resolveType'] : null; }
/** * @return ObjectType[] */ public function getTypes() { if (null === $this->types) { if ($this->config['types'] instanceof \Closure) { $types = call_user_func($this->config['types']); } else { $types = $this->config['types']; } Utils::invariant(is_array($types), 'Option "types" of union "%s" is expected to return array of types (or closure returning array of types)', $this->name); $this->types = []; foreach ($types as $type) { $this->types[] = Type::resolve($type); } } return $this->types; }
public function __invoke(ValidationContext $context) { return [Node::ARGUMENT => function (Argument $node) use($context) { $fieldDef = $context->getFieldDef(); if ($fieldDef) { $argDef = null; foreach ($fieldDef->args as $arg) { if ($arg->name === $node->name->value) { $argDef = $arg; break; } } if (!$argDef) { $parentType = $context->getParentType(); Utils::invariant($parentType); return new Error(Messages::unknownArgMessage($node->name->value, $fieldDef->name, $parentType->name), [$node]); } } }]; }
public function __invoke(ValidationContext $context) { return [NodeKind::ARGUMENT => function (ArgumentNode $node, $key, $parent, $path, $ancestors) use($context) { $argumentOf = $ancestors[count($ancestors) - 1]; if ($argumentOf->kind === NodeKind::FIELD) { $fieldDef = $context->getFieldDef(); if ($fieldDef) { $fieldArgDef = null; foreach ($fieldDef->args as $arg) { if ($arg->name === $node->name->value) { $fieldArgDef = $arg; break; } } if (!$fieldArgDef) { $parentType = $context->getParentType(); Utils::invariant($parentType); $context->reportError(new Error(self::unknownArgMessage($node->name->value, $fieldDef->name, $parentType->name), [$node])); } } } else { if ($argumentOf->kind === NodeKind::DIRECTIVE) { $directive = $context->getDirective(); if ($directive) { $directiveArgDef = null; foreach ($directive->args as $arg) { if ($arg->name === $node->name->value) { $directiveArgDef = $arg; break; } } if (!$directiveArgDef) { $context->reportError(new Error(self::unknownDirectiveArgMessage($node->name->value, $directive->name), [$node])); } } } } }]; }
private function _extractTypes($type, &$map) { if (!$type) { return $map; } if ($type instanceof WrappingType) { return $this->_extractTypes($type->getWrappedType(), $map); } if (!empty($map[$type->name])) { Utils::invariant($map[$type->name] === $type, "Schema must contain unique named types but contains multiple types named \"{$type}\"."); return $map; } $map[$type->name] = $type; $nestedTypes = []; if ($type instanceof InterfaceType || $type instanceof UnionType) { $nestedTypes = $type->getPossibleTypes(); } if ($type instanceof ObjectType) { $nestedTypes = array_merge($nestedTypes, $type->getInterfaces()); } if ($type instanceof ObjectType || $type instanceof InterfaceType || $type instanceof InputObjectType) { foreach ((array) $type->getFields() as $fieldName => $field) { if (isset($field->args)) { $fieldArgTypes = array_map(function ($arg) { return $arg->getType(); }, $field->args); $nestedTypes = array_merge($nestedTypes, $fieldArgTypes); } $nestedTypes[] = $field->getType(); } } foreach ($nestedTypes as $type) { $this->_extractTypes($type, $map); } return $map; }
/** * @param bool $recurse * @return mixed * @throws \Exception */ public function getWrappedType($recurse = false) { $type = Type::resolve($this->ofType); Utils::invariant(!$type instanceof NonNull, 'Cannot nest NonNull inside NonNull'); return $recurse && $type instanceof WrappingType ? $type->getWrappedType($recurse) : $type; }
/** * Implements the instructions for completeValue as defined in the * "Field entries" section of the spec. * * If the field type is Non-Null, then this recursively completes the value * for the inner type. It throws a field error if that completion returns null, * as per the "Nullability" section of the spec. * * If the field type is a List, then this recursively completes the value * for the inner type on each item in the list. * * If the field type is a Scalar or Enum, ensures the completed value is a legal * value of the type by calling the `coerce` method of GraphQL type definition. * * Otherwise, the field type expects a sub-selection set, and will complete the * value by evaluating all sub-selections. */ private static function completeField(ExecutionContext $exeContext, Type $fieldType, $fieldASTs, &$result) { // If field type is NonNull, complete for inner type, and throw field error // if result is null. if ($fieldType instanceof NonNull) { $completed = self::completeField($exeContext, $fieldType->getWrappedType(), $fieldASTs, $result); if ($completed === null) { throw new Error('Cannot return null for non-nullable type.', $fieldASTs instanceof \ArrayObject ? $fieldASTs->getArrayCopy() : $fieldASTs); } return $completed; } // If result is null-like, return null. if (null === $result) { return null; } // If field type is List, complete each item in the list with the inner type if ($fieldType instanceof ListOfType) { $itemType = $fieldType->getWrappedType(); Utils::invariant(is_array($result) || $result instanceof \Traversable, 'User Error: expected iterable, but did not find one.'); $tmp = []; foreach ($result as $item) { $tmp[] = self::completeField($exeContext, $itemType, $fieldASTs, $item); } return $tmp; } // If field type is Scalar or Enum, coerce to a valid value, returning null // if coercion is not possible. if ($fieldType instanceof ScalarType || $fieldType instanceof EnumType) { Utils::invariant(method_exists($fieldType, 'coerce'), 'Missing coerce method on type'); return $fieldType->coerce($result); } // Field type must be Object, Interface or Union and expect sub-selections. $objectType = $fieldType instanceof ObjectType ? $fieldType : ($fieldType instanceof InterfaceType || $fieldType instanceof UnionType ? $fieldType->resolveType($result) : null); if (!$objectType) { return null; } // Collect sub-fields to execute to complete this value. $subFieldASTs = new \ArrayObject(); $visitedFragmentNames = new \ArrayObject(); for ($i = 0; $i < count($fieldASTs); $i++) { $selectionSet = $fieldASTs[$i]->selectionSet; if ($selectionSet) { $subFieldASTs = self::collectFields($exeContext, $objectType, $selectionSet, $subFieldASTs, $visitedFragmentNames); } } return self::executeFields($exeContext, $objectType, $result, $subFieldASTs); }
public static function resolve($type) { if (is_callable($type)) { $type = $type(); } Utils::invariant($type instanceof Type, 'Expecting instance of ' . __CLASS__ . ' (or callable returning instance of that type), got "%s"', Utils::getVariableType($type)); return $type; }
/** * Complete a list value by completing each item in the list with the * inner type * * @param ExecutionContext $exeContext * @param ListOfType $returnType * @param $fieldNodes * @param ResolveInfo $info * @param array $path * @param $result * @return array|Promise * @throws \Exception */ private static function completeListValue(ExecutionContext $exeContext, ListOfType $returnType, $fieldNodes, ResolveInfo $info, $path, &$result) { $itemType = $returnType->getWrappedType(); Utils::invariant(is_array($result) || $result instanceof \Traversable, 'User Error: expected iterable, but did not find one for field ' . $info->parentType . '.' . $info->fieldName . '.'); $containsPromise = false; $i = 0; $completedItems = []; foreach ($result as $item) { $fieldPath = $path; $fieldPath[] = $i++; $completedItem = self::completeValueCatchingError($exeContext, $itemType, $fieldNodes, $info, $fieldPath, $item); if (!$containsPromise && self::$promiseAdapter->isPromise($completedItem)) { $containsPromise = true; } $completedItems[] = $completedItem; } return $containsPromise ? self::$promiseAdapter->createPromiseAll($completedItems) : $completedItems; }
/** * Implements the instructions for completeValue as defined in the * "Field entries" section of the spec. * * If the field type is Non-Null, then this recursively completes the value * for the inner type. It throws a field error if that completion returns null, * as per the "Nullability" section of the spec. * * If the field type is a List, then this recursively completes the value * for the inner type on each item in the list. * * If the field type is a Scalar or Enum, ensures the completed value is a legal * value of the type by calling the `serialize` method of GraphQL type * definition. * * Otherwise, the field type expects a sub-selection set, and will complete the * value by evaluating all sub-selections. */ private static function completeValue(ExecutionContext $exeContext, Type $returnType, $fieldASTs, ResolveInfo $info, &$result) { // If field type is NonNull, complete for inner type, and throw field error // if result is null. if ($returnType instanceof NonNull) { $completed = self::completeValue($exeContext, $returnType->getWrappedType(), $fieldASTs, $info, $result); if ($completed === null) { throw new Error('Cannot return null for non-nullable type.', $fieldASTs instanceof \ArrayObject ? $fieldASTs->getArrayCopy() : $fieldASTs); } return $completed; } // If result is null-like, return null. if (null === $result) { return null; } // If field type is Scalar or Enum, serialize to a valid value, returning // null if serialization is not possible. if ($returnType instanceof ScalarType || $returnType instanceof EnumType) { return $returnType->serialize($result); } // If field type is List, and return type is Composite - complete by executing these fields with list value as parameter if ($returnType instanceof ListOfType) { $itemType = $returnType->getWrappedType(); Utils::invariant(is_array($result) || $result instanceof \Traversable, 'User Error: expected iterable, but did not find one.'); // For Object[]: // Allow all object fields to process list value in it's `map` callback: if ($itemType instanceof ObjectType) { // Filter out nulls (as `map` doesn't expect it): $list = []; foreach ($result as $index => $item) { if (null !== $item) { $list[] = $item; } } $subFieldASTs = self::collectSubFields($exeContext, $itemType, $fieldASTs); $mapped = self::executeFields($exeContext, $itemType, $list, $subFieldASTs); $i = 0; $completed = []; foreach ($result as $index => $item) { if (null === $item) { // Complete nulls separately $completed[] = self::completeValueCatchingError($exeContext, $itemType, $fieldASTs, $info, $item); } else { // Assuming same order of mapped values $completed[] = $mapped[$i++]; } } return $completed; } else { if ($itemType instanceof AbstractType) { // Values sharded by ObjectType $listPerObjectType = []; // Helper structures to restore ordering after resolve calls $resultTypeMap = []; $typeNameMap = []; $cursors = []; $copied = []; foreach ($result as $index => $item) { $copied[$index] = $item; if (null !== $item) { $objectType = $itemType->getObjectType($item, $info); if ($objectType && !$itemType->isPossibleType($objectType)) { $exeContext->addError(new Error("Runtime Object type \"{$objectType}\" is not a possible type for \"{$itemType}\".")); $copied[$index] = null; } else { $listPerObjectType[$objectType->name][] = $item; $resultTypeMap[$index] = $objectType->name; $typeNameMap[$objectType->name] = $objectType; } } } $mapped = []; foreach ($listPerObjectType as $typeName => $list) { $objectType = $typeNameMap[$typeName]; $subFieldASTs = self::collectSubFields($exeContext, $objectType, $fieldASTs); $mapped[$typeName] = self::executeFields($exeContext, $objectType, $list, $subFieldASTs); $cursors[$typeName] = 0; } // Restore order: $completed = []; foreach ($copied as $index => $item) { if (null === $item) { // Complete nulls separately $completed[] = self::completeValueCatchingError($exeContext, $itemType, $fieldASTs, $info, $item); } else { $typeName = $resultTypeMap[$index]; $completed[] = $mapped[$typeName][$cursors[$typeName]++]; } } return $completed; } else { // For simple lists: $tmp = []; foreach ($result as $item) { $tmp[] = self::completeValueCatchingError($exeContext, $itemType, $fieldASTs, $info, $item); } return $tmp; } } } if ($returnType instanceof ObjectType) { $objectType = $returnType; } else { if ($returnType instanceof AbstractType) { $objectType = $returnType->getObjectType($result, $info); if ($objectType && !$returnType->isPossibleType($objectType)) { throw new Error("Runtime Object type \"{$objectType}\" is not a possible type for \"{$returnType}\"."); } } else { $objectType = null; } } if (!$objectType) { return null; } // If there is an isTypeOf predicate function, call it with the // current result. If isTypeOf returns false, then raise an error rather // than continuing execution. if (false === $objectType->isTypeOf($result, $info)) { throw new Error("Expected value of type {$objectType} but got: {$result}.", $fieldASTs); } // Collect sub-fields to execute to complete this value. $subFieldASTs = self::collectSubFields($exeContext, $objectType, $fieldASTs); $executed = self::executeFields($exeContext, $objectType, [$result], $subFieldASTs); return isset($executed[0]) ? $executed[0] : null; }
/** * @param ObjectType $object * @param InterfaceType $iface * @throws \Exception */ private static function assertObjectImplementsInterface(Schema $schema, ObjectType $object, InterfaceType $iface) { $objectFieldMap = $object->getFields(); $ifaceFieldMap = $iface->getFields(); foreach ($ifaceFieldMap as $fieldName => $ifaceField) { Utils::invariant(isset($objectFieldMap[$fieldName]), "\"{$iface}\" expects field \"{$fieldName}\" but \"{$object}\" does not provide it"); /** @var $ifaceField FieldDefinition */ /** @var $objectField FieldDefinition */ $objectField = $objectFieldMap[$fieldName]; Utils::invariant(Utils\TypeInfo::isTypeSubTypeOf($schema, $objectField->getType(), $ifaceField->getType()), "{$iface}.{$fieldName} expects type \"{$ifaceField->getType()}\" but " . "{$object}.{$fieldName} provides type \"{$objectField->getType()}\"."); foreach ($ifaceField->args as $ifaceArg) { /** @var $ifaceArg FieldArgument */ /** @var $objectArg FieldArgument */ $argName = $ifaceArg->name; $objectArg = $objectField->getArg($argName); // Assert interface field arg exists on object field. Utils::invariant($objectArg, "{$iface}.{$fieldName} expects argument \"{$argName}\" but {$object}.{$fieldName} does not provide it."); // Assert interface field arg type matches object field arg type. // (invariant) Utils::invariant(Utils\TypeInfo::isEqualType($ifaceArg->getType(), $objectArg->getType()), "{$iface}.{$fieldName}({$argName}:) expects type \"{$ifaceArg->getType()}\" " . "but {$object}.{$fieldName}({$argName}:) provides " . "type \"{$objectArg->getType()}\""); // Assert argument set invariance. foreach ($objectField->args as $objectArg) { $argName = $objectArg->name; $ifaceArg = $ifaceField->getArg($argName); Utils::invariant($ifaceArg, "{$iface}.{$fieldName} does not define argument \"{$argName}\" but " . "{$object}.{$fieldName} provides it."); } } } }
public function __construct(Type $querySchema = null, Type $mutationSchema = null) { Utils::invariant($querySchema || $mutationSchema, "Either query or mutation type must be set"); $this->querySchema = $querySchema; $this->mutationSchema = $mutationSchema; }
public function getField($name) { Utils::invariant(isset($this->_fields[$name]), 'Field "%s" is not defined for type "%s"', $name, $this->name); return $this->_fields[$name]; }
private static function _validateEntry($key, $value, $def, $pathStr) { $type = Utils::getVariableType($value); $err = 'Expecting %s at "' . $pathStr . '", but got "' . $type . '"'; if ($def instanceof \stdClass) { if ($def->flags & self::REQUIRED === 0 && $value === null) { return; } Utils::invariant(is_array($value), $err, 'array'); if (!empty($def->isMap)) { if ($def->flags & self::KEY_AS_NAME) { $value += ['name' => $key]; } self::_validateMap($value, $def->definition, $pathStr); } else { if (!empty($def->isArray)) { if ($def->flags & self::REQUIRED) { Utils::invariant(!empty($value), "Value at '{$pathStr}' cannot be empty array"); } $err = "Each entry at '{$pathStr}' must be an array, but '%s' is '%s'"; foreach ($value as $arrKey => $arrValue) { if (is_array($def->definition)) { Utils::invariant(is_array($arrValue), $err, $arrKey, Utils::getVariableType($arrValue)); if ($def->flags & self::KEY_AS_NAME) { $arrValue += ['name' => $arrKey]; } self::_validateMap($arrValue, $def->definition, "{$pathStr}:{$arrKey}"); } else { self::_validateEntry($arrKey, $arrValue, $def->definition, "{$pathStr}:{$arrKey}"); } } } else { throw new \Exception("Unexpected definition: " . print_r($def, true)); } } } else { Utils::invariant(is_int($def), "Definition for '{$pathStr}' is expected to be single integer value"); if ($def & self::REQUIRED) { Utils::invariant($value !== null, 'Value at "%s" can not be null', $pathStr); } switch (true) { case $def & self::ANY: break; case $def & self::BOOLEAN: Utils::invariant(is_bool($value), $err, 'boolean'); break; case $def & self::STRING: Utils::invariant(is_string($value), $err, 'string'); break; case $def & self::NUMERIC: Utils::invariant(is_numeric($value), $err, 'numeric'); break; case $def & self::FLOAT: Utils::invariant(is_float($value), $err, 'float'); break; case $def & self::INT: Utils::invariant(is_int($value), $err, 'int'); break; case $def & self::CALLBACK: Utils::invariant(is_callable($value), $err, 'callable'); break; case $def & self::SCALAR: Utils::invariant(is_scalar($value), $err, 'scalar'); break; case $def & self::INPUT_TYPE: Utils::invariant(is_callable($value) || $value instanceof InputType, $err, 'callable or instance of GraphQL\\Type\\Definition\\InputType'); break; case $def & self::OUTPUT_TYPE: Utils::invariant(is_callable($value) || $value instanceof OutputType, $err, 'callable or instance of GraphQL\\Type\\Definition\\OutputType'); break; case $def & self::INTERFACE_TYPE: Utils::invariant(is_callable($value) || $value instanceof InterfaceType, $err, 'callable or instance of GraphQL\\Type\\Definition\\InterfaceType'); break; case $def & self::OBJECT_TYPE: Utils::invariant(is_callable($value) || $value instanceof ObjectType, $err, 'callable or instance of GraphQL\\Type\\Definition\\ObjectType'); break; default: throw new \Exception("Unexpected validation rule: " . $def); } } }
/** * Implements the instructions for completeValue as defined in the * "Field entries" section of the spec. * * If the field type is Non-Null, then this recursively completes the value * for the inner type. It throws a field error if that completion returns null, * as per the "Nullability" section of the spec. * * If the field type is a List, then this recursively completes the value * for the inner type on each item in the list. * * If the field type is a Scalar or Enum, ensures the completed value is a legal * value of the type by calling the `serialize` method of GraphQL type * definition. * * Otherwise, the field type expects a sub-selection set, and will complete the * value by evaluating all sub-selections. */ private static function completeValue(ExecutionContext $exeContext, Type $returnType, $fieldASTs, ResolveInfo $info, &$result) { // If field type is NonNull, complete for inner type, and throw field error // if result is null. if ($returnType instanceof NonNull) { $completed = self::completeValue($exeContext, $returnType->getWrappedType(), $fieldASTs, $info, $result); if ($completed === null) { throw new Error('Cannot return null for non-nullable type.', $fieldASTs instanceof \ArrayObject ? $fieldASTs->getArrayCopy() : $fieldASTs); } return $completed; } // If result is null-like, return null. if (null === $result) { return null; } // If field type is List, complete each item in the list with the inner type if ($returnType instanceof ListOfType) { $itemType = $returnType->getWrappedType(); Utils::invariant(is_array($result) || $result instanceof \Traversable, 'User Error: expected iterable, but did not find one.'); $tmp = []; foreach ($result as $item) { $tmp[] = self::completeValueCatchingError($exeContext, $itemType, $fieldASTs, $info, $item); } return $tmp; } // If field type is Scalar or Enum, serialize to a valid value, returning // null if serialization is not possible. if ($returnType instanceof ScalarType || $returnType instanceof EnumType) { Utils::invariant(method_exists($returnType, 'serialize'), 'Missing serialize method on type'); return $returnType->serialize($result); } // Field type must be Object, Interface or Union and expect sub-selections. if ($returnType instanceof ObjectType) { $objectType = $returnType; } else { if ($returnType instanceof AbstractType) { $objectType = $returnType->getObjectType($result, $info); if ($objectType && !$returnType->isPossibleType($objectType)) { throw new Error("Runtime Object type \"{$objectType}\" is not a possible type for \"{$returnType}\"."); } } else { $objectType = null; } } if (!$objectType) { return null; } // If there is an isTypeOf predicate function, call it with the // current result. If isTypeOf returns false, then raise an error rather // than continuing execution. if (false === $objectType->isTypeOf($result, $info)) { throw new Error("Expected value of type {$objectType} but got: {$result}.", $fieldASTs); } // Collect sub-fields to execute to complete this value. $subFieldASTs = new \ArrayObject(); $visitedFragmentNames = new \ArrayObject(); for ($i = 0; $i < count($fieldASTs); $i++) { $selectionSet = $fieldASTs[$i]->selectionSet; if ($selectionSet) { $subFieldASTs = self::collectFields($exeContext, $objectType, $selectionSet, $subFieldASTs, $visitedFragmentNames); } } return self::executeFields($exeContext, $objectType, $result, $subFieldASTs); }
/** * Check if value is valid using GraphQL Type * * @param array $value * @param Type $type * * @return boolean */ private function isValidValue($value, Type $type) { if ($type instanceof NonNull) { if (null === $value) { return false; } return $this->isValidValue($value, $type->getWrappedType()); } if ($value === null) { return true; } if ($type instanceof ListOfType) { $itemType = $type->getWrappedType(); if (is_array($value)) { foreach ($value as $item) { if (!$this->isValidValue($item, $itemType)) { return false; } } return true; } else { return $this->isValidValue($value, $itemType); } } if ($type instanceof InputObjectType) { if (!is_array($value)) { return false; } $fields = $type->getFields(); $fieldMap = []; foreach ($fields as $fieldName => $field) { if (!$this->isValidValue(isset($value[$fieldName]) ? $value[$fieldName] : null, $field->getType())) { return false; } $fieldMap[$field->name] = $field; } $diff = array_diff_key($value, $fieldMap); if (!empty($diff)) { return false; } return true; } Utils::invariant($type instanceof ScalarType || $type instanceof EnumType, 'Must be input type'); return null !== $type->parseValue($value); }
/** * Complete a list value by completing each item in the list with the * inner type * * @param ExecutionContext $exeContext * @param ListOfType $returnType * @param $fieldASTs * @param ResolveInfo $info * @param array $path * @param $result * @return array * @throws \Exception */ private static function completeListValue(ExecutionContext $exeContext, ListOfType $returnType, $fieldASTs, ResolveInfo $info, $path, &$result) { $itemType = $returnType->getWrappedType(); Utils::invariant(is_array($result) || $result instanceof \Traversable, 'User Error: expected iterable, but did not find one for field ' . $info->parentType . '.' . $info->fieldName . '.'); $i = 0; $tmp = []; foreach ($result as $item) { $path[] = $i++; $tmp[] = self::completeValueCatchingError($exeContext, $itemType, $fieldASTs, $info, $path, $item); } return $tmp; }
/** * @param string $name * @return FieldDefinition * @throws \Exception */ public function getField($name) { if (false === $this->_initialized) { $this->initialize(); } Utils::invariant(isset($this->_fields[$name]), "Field '%s' is not defined for type '%s'", $name, $this->name); return $this->_fields[$name]; }
/** * @param callable|Type $type */ public function __construct($type) { Utils::invariant($type instanceof Type || is_callable($type), 'Expecting instance of GraphQL\\Type\\Definition\\Type or callable returning instance of that class'); $this->ofType = $type; }
/** * @param string $name * @return FieldDefinition * @throws \Exception */ public function getField($name) { if (null === $this->_fields) { $this->getFields(); } Utils::invariant(isset($this->_fields[$name]), "Field '%s' is not defined for type '%s'", $name, $this->name); return $this->_fields[$name]; }
/** * @param $typeName * @param $key * @param $value * @param $def * @param $pathStr * @throws \Exception */ private static function validateEntry($typeName, $key, $value, $def, $pathStr) { $type = Utils::getVariableType($value); $err = 'Error in "' . $typeName . '" type definition: expecting %s at "' . $pathStr . '", but got "' . $type . '"'; if ($def instanceof \stdClass) { if ($def->flags & self::REQUIRED === 0 && $value === null) { return; } if (($def->flags & self::MAYBE_THUNK) > 0) { Utils::invariant(is_array($value) || is_callable($value), $err, 'array or callable'); } else { Utils::invariant(is_array($value), $err, 'array'); } if (!empty($def->isMap)) { if ($def->flags & self::KEY_AS_NAME) { $value += ['name' => $key]; } self::validateMap($typeName, $value, $def->definition, $pathStr); } else { if (!empty($def->isArray)) { if ($def->flags & self::REQUIRED) { Utils::invariant(!empty($value), 'Error in "' . $typeName . '" type definition: ' . "Value at '{$pathStr}' cannot be empty array"); } $err = 'Error in "' . $typeName . '" type definition: ' . "Each entry at '{$pathStr}' must be an array, but '%s' is '%s'"; foreach ($value as $arrKey => $arrValue) { if (is_array($def->definition)) { if ($def->flags & self::MAYBE_TYPE && $arrValue instanceof Type) { $arrValue = ['type' => $arrValue]; } if ($def->flags & self::MAYBE_NAME && is_string($arrValue)) { $arrValue = ['name' => $arrValue]; } Utils::invariant(is_array($arrValue), $err, $arrKey, Utils::getVariableType($arrValue)); if ($def->flags & self::KEY_AS_NAME) { $arrValue += ['name' => $arrKey]; } self::validateMap($typeName, $arrValue, $def->definition, "{$pathStr}:{$arrKey}"); } else { self::validateEntry($typeName, $arrKey, $arrValue, $def->definition, "{$pathStr}:{$arrKey}"); } } } else { throw new InvariantViolation('Error in "' . $typeName . '" type definition: ' . "unexpected definition: " . print_r($def, true)); } } } else { Utils::invariant(is_int($def), 'Error in "' . $typeName . '" type definition: ' . "Definition for '{$pathStr}' is expected to be single integer value"); if ($def & self::REQUIRED) { Utils::invariant($value !== null, 'Value at "%s" can not be null', $pathStr); } if (null === $value) { return; // Allow nulls for non-required fields } switch (true) { case $def & self::ANY: break; case $def & self::BOOLEAN: Utils::invariant(is_bool($value), $err, 'boolean'); break; case $def & self::STRING: Utils::invariant(is_string($value), $err, 'string'); break; case $def & self::NUMERIC: Utils::invariant(is_numeric($value), $err, 'numeric'); break; case $def & self::FLOAT: Utils::invariant(is_float($value), $err, 'float'); break; case $def & self::INT: Utils::invariant(is_int($value), $err, 'int'); break; case $def & self::CALLBACK: Utils::invariant(is_callable($value), $err, 'callable'); break; case $def & self::SCALAR: Utils::invariant(is_scalar($value), $err, 'scalar'); break; case $def & self::NAME: Utils::invariant(preg_match('~^[_a-zA-Z][_a-zA-Z0-9]*$~', $value), 'Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but "%s" does not.', $value); break; case $def & self::INPUT_TYPE: Utils::invariant(is_callable($value) || $value instanceof InputType, $err, 'callable or InputType definition'); break; case $def & self::OUTPUT_TYPE: Utils::invariant(is_callable($value) || $value instanceof OutputType, $err, 'callable or OutputType definition'); break; case $def & self::INTERFACE_TYPE: Utils::invariant(is_callable($value) || $value instanceof InterfaceType, $err, 'callable or InterfaceType definition'); break; case $def & self::OBJECT_TYPE: Utils::invariant(is_callable($value) || $value instanceof ObjectType, $err, 'callable or ObjectType definition'); break; default: throw new InvariantViolation("Unexpected validation rule: " . $def); } } }
protected function __construct() { Utils::invariant($this->name, 'Type must be named.'); }
/** * Given a type and any value, return a runtime value coerced to match the type. */ private static function coerceValue(Type $type, $value) { if ($type instanceof NonNull) { // Note: we're not checking that the result of coerceValue is non-null. // We only call this function after calling isValidValue. return self::coerceValue($type->getWrappedType(), $value); } if (null === $value) { return null; } if ($type instanceof ListOfType) { $itemType = $type->getWrappedType(); // TODO: support iterable input if (is_array($value)) { return array_map(function ($item) use($itemType) { return Values::coerceValue($itemType, $item); }, $value); } else { return [self::coerceValue($itemType, $value)]; } } if ($type instanceof InputObjectType) { $fields = $type->getFields(); $obj = []; foreach ($fields as $fieldName => $field) { $fieldValue = self::coerceValue($field->getType(), isset($value[$fieldName]) ? $value[$fieldName] : null); if (null === $fieldValue) { $fieldValue = $field->defaultValue; } if (null !== $fieldValue) { $obj[$fieldName] = $fieldValue; } } return $obj; } Utils::invariant($type instanceof ScalarType || $type instanceof EnumType, 'Must be input type'); return $type->parseValue($value); }
/** * @return Type|callable */ public function getWrappedType() { $type = Type::resolve($this->ofType); Utils::invariant(!$type instanceof NonNull, 'Cannot nest NonNull inside NonNull'); return $type; }