2015-07-15 20:05:46 +03:00
|
|
|
<?php
|
|
|
|
namespace GraphQL\Executor;
|
|
|
|
|
|
|
|
|
|
|
|
use GraphQL\Error;
|
2015-08-17 17:01:55 +03:00
|
|
|
use GraphQL\Language\AST\Argument;
|
2015-07-15 20:05:46 +03:00
|
|
|
use GraphQL\Language\AST\VariableDefinition;
|
|
|
|
use GraphQL\Language\Printer;
|
|
|
|
use GraphQL\Schema;
|
|
|
|
use GraphQL\Type\Definition\EnumType;
|
2015-08-17 17:01:55 +03:00
|
|
|
use GraphQL\Type\Definition\FieldArgument;
|
2015-07-15 20:05:46 +03:00
|
|
|
use GraphQL\Type\Definition\InputObjectType;
|
2015-08-17 17:01:55 +03:00
|
|
|
use GraphQL\Type\Definition\InputType;
|
2015-07-15 20:05:46 +03:00
|
|
|
use GraphQL\Type\Definition\ListOfType;
|
|
|
|
use GraphQL\Type\Definition\NonNull;
|
|
|
|
use GraphQL\Type\Definition\ScalarType;
|
|
|
|
use GraphQL\Type\Definition\Type;
|
|
|
|
use GraphQL\Utils;
|
|
|
|
|
|
|
|
class Values
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* 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.
|
2015-08-17 17:01:55 +03:00
|
|
|
*
|
|
|
|
* @param Schema $schema
|
|
|
|
* @param VariableDefinition[] $definitionASTs
|
|
|
|
* @param array $inputs
|
|
|
|
* @return array
|
|
|
|
* @throws Error
|
2015-07-15 20:05:46 +03:00
|
|
|
*/
|
2015-08-17 17:01:55 +03:00
|
|
|
public static function getVariableValues(Schema $schema, $definitionASTs, array $inputs)
|
2015-07-15 20:05:46 +03:00
|
|
|
{
|
|
|
|
$values = [];
|
|
|
|
foreach ($definitionASTs as $defAST) {
|
|
|
|
$varName = $defAST->variable->name->value;
|
|
|
|
$values[$varName] = self::getvariableValue($schema, $defAST, isset($inputs[$varName]) ? $inputs[$varName] : null);
|
|
|
|
}
|
|
|
|
return $values;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Prepares an object map of argument values given a list of argument
|
|
|
|
* definitions and list of argument AST nodes.
|
2015-08-17 17:01:55 +03:00
|
|
|
*
|
|
|
|
* @param FieldArgument[] $argDefs
|
|
|
|
* @param Argument[] $argASTs
|
|
|
|
* @param $variableValues
|
|
|
|
* @return array
|
2015-07-15 20:05:46 +03:00
|
|
|
*/
|
2015-08-17 17:01:55 +03:00
|
|
|
public static function getArgumentValues($argDefs, $argASTs, $variableValues)
|
2015-07-15 20:05:46 +03:00
|
|
|
{
|
2015-10-23 14:30:02 +03:00
|
|
|
if (!$argDefs) {
|
2015-08-17 17:01:55 +03:00
|
|
|
return [];
|
2015-07-15 20:05:46 +03:00
|
|
|
}
|
|
|
|
$argASTMap = $argASTs ? Utils::keyMap($argASTs, function ($arg) {
|
|
|
|
return $arg->name->value;
|
|
|
|
}) : [];
|
|
|
|
$result = [];
|
|
|
|
foreach ($argDefs as $argDef) {
|
|
|
|
$name = $argDef->name;
|
|
|
|
$valueAST = isset($argASTMap[$name]) ? $argASTMap[$name]->value : null;
|
2016-05-01 00:02:04 +03:00
|
|
|
$value = Utils\AST::valueFromAST($valueAST, $argDef->getType(), $variableValues);
|
2015-08-17 17:01:55 +03:00
|
|
|
|
|
|
|
if (null === $value) {
|
|
|
|
$value = $argDef->defaultValue;
|
|
|
|
}
|
|
|
|
if (null !== $value) {
|
|
|
|
$result[$name] = $value;
|
|
|
|
}
|
2015-07-15 20:05:46 +03:00
|
|
|
}
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
2015-08-17 17:01:55 +03:00
|
|
|
public static function valueFromAST($valueAST, InputType $type, $variables = null)
|
2015-07-15 20:05:46 +03:00
|
|
|
{
|
2016-05-01 00:02:04 +03:00
|
|
|
return Utils\AST::valueFromAST($valueAST, $type, $variables);
|
2015-07-15 20:05:46 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Given a variable definition, and any value of input, return a value which
|
|
|
|
* adheres to the variable definition, or throw an error.
|
|
|
|
*/
|
|
|
|
private static function getVariableValue(Schema $schema, VariableDefinition $definitionAST, $input)
|
|
|
|
{
|
|
|
|
$type = Utils\TypeInfo::typeFromAST($schema, $definitionAST->type);
|
2015-08-17 17:01:55 +03:00
|
|
|
$variable = $definitionAST->variable;
|
|
|
|
|
|
|
|
if (!$type || !Type::isInputType($type)) {
|
|
|
|
$printed = Printer::doPrint($definitionAST->type);
|
|
|
|
throw new Error(
|
|
|
|
"Variable \"\${$variable->name->value}\" expected value of type " .
|
|
|
|
"\"$printed\" which cannot be used as an input type.",
|
|
|
|
[ $definitionAST ]
|
|
|
|
);
|
2015-07-15 20:05:46 +03:00
|
|
|
}
|
2016-05-01 00:02:04 +03:00
|
|
|
|
|
|
|
$inputType = $type;
|
|
|
|
$errors = self::isValidPHPValue($input, $inputType);
|
|
|
|
|
|
|
|
if (empty($errors)) {
|
2015-07-15 20:05:46 +03:00
|
|
|
if (null === $input) {
|
|
|
|
$defaultValue = $definitionAST->defaultValue;
|
|
|
|
if ($defaultValue) {
|
2016-05-01 00:02:04 +03:00
|
|
|
return Utils\AST::valueFromAST($defaultValue, $inputType);
|
2015-07-15 20:05:46 +03:00
|
|
|
}
|
|
|
|
}
|
2016-05-01 00:02:04 +03:00
|
|
|
return self::coerceValue($inputType, $input);
|
2015-07-15 20:05:46 +03:00
|
|
|
}
|
|
|
|
|
2016-05-01 00:02:04 +03:00
|
|
|
if (null === $input) {
|
|
|
|
$printed = Printer::doPrint($definitionAST->type);
|
|
|
|
|
|
|
|
throw new Error(
|
|
|
|
"Variable \"\${$variable->name->value}\" of required type " .
|
|
|
|
"\"$printed\" was not provided.",
|
|
|
|
[ $definitionAST ]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
$message = $errors ? "\n" . implode("\n", $errors) : '';
|
|
|
|
$val = json_encode($input);
|
2015-07-15 20:05:46 +03:00
|
|
|
throw new Error(
|
2016-05-01 00:02:04 +03:00
|
|
|
"Variable \"\${$variable->name->value}\" got invalid value ".
|
|
|
|
"{$val}.{$message}",
|
|
|
|
[ $definitionAST ]
|
2015-07-15 20:05:46 +03:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-08-17 17:01:55 +03:00
|
|
|
* Given a PHP value and a GraphQL type, determine if the value will be
|
|
|
|
* accepted for that type. This is primarily useful for validating the
|
|
|
|
* runtime values of query variables.
|
|
|
|
*
|
|
|
|
* @param $value
|
|
|
|
* @param Type $type
|
2016-05-01 00:02:04 +03:00
|
|
|
* @return array
|
2015-07-15 20:05:46 +03:00
|
|
|
*/
|
2016-05-01 00:02:04 +03:00
|
|
|
private static function isValidPHPValue($value, InputType $type)
|
2015-07-15 20:05:46 +03:00
|
|
|
{
|
2016-05-01 00:02:04 +03:00
|
|
|
// A value must be provided if the type is non-null.
|
2015-07-15 20:05:46 +03:00
|
|
|
if ($type instanceof NonNull) {
|
2016-05-01 00:02:04 +03:00
|
|
|
$ofType = $type->getWrappedType();
|
2015-07-15 20:05:46 +03:00
|
|
|
if (null === $value) {
|
2016-05-01 00:02:04 +03:00
|
|
|
if ($ofType->name) {
|
|
|
|
return [ "Expected \"{$ofType->name}!\", found null." ];
|
|
|
|
}
|
|
|
|
return [ 'Expected non-null value, found null.' ];
|
2015-07-15 20:05:46 +03:00
|
|
|
}
|
2016-05-01 00:02:04 +03:00
|
|
|
return self::isValidPHPValue($value, $ofType);
|
2015-07-15 20:05:46 +03:00
|
|
|
}
|
|
|
|
|
2016-05-01 00:02:04 +03:00
|
|
|
if (null === $value) {
|
|
|
|
return [];
|
2015-07-15 20:05:46 +03:00
|
|
|
}
|
|
|
|
|
2016-05-01 00:02:04 +03:00
|
|
|
// Lists accept a non-list value as a list of one.
|
2015-07-15 20:05:46 +03:00
|
|
|
if ($type instanceof ListOfType) {
|
|
|
|
$itemType = $type->getWrappedType();
|
|
|
|
if (is_array($value)) {
|
2016-05-02 00:42:05 +03:00
|
|
|
$tmp = [];
|
|
|
|
foreach ($value as $index => $item) {
|
|
|
|
$errors = self::isValidPHPValue($item, $itemType);
|
|
|
|
$tmp = array_merge($tmp, Utils::map($errors, function ($error) use ($index) {
|
|
|
|
return "In element #$index: $error";
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
return $tmp;
|
2015-07-15 20:05:46 +03:00
|
|
|
}
|
2016-05-01 00:02:04 +03:00
|
|
|
return self::isValidPHPValue($value, $itemType);
|
2015-07-15 20:05:46 +03:00
|
|
|
}
|
|
|
|
|
2016-05-01 00:02:04 +03:00
|
|
|
// Input objects check each defined field.
|
2015-07-15 20:05:46 +03:00
|
|
|
if ($type instanceof InputObjectType) {
|
2016-05-01 00:02:04 +03:00
|
|
|
if (!is_object($value) && !is_array($value)) {
|
|
|
|
return ["Expected \"{$type->name}\", found not an object."];
|
2015-08-17 17:01:55 +03:00
|
|
|
}
|
2015-07-15 20:05:46 +03:00
|
|
|
$fields = $type->getFields();
|
2016-05-01 00:02:04 +03:00
|
|
|
$errors = [];
|
2015-08-17 17:01:55 +03:00
|
|
|
|
2016-05-01 00:02:04 +03:00
|
|
|
// Ensure every provided field is defined.
|
|
|
|
$props = is_object($value) ? get_object_vars($value) : $value;
|
|
|
|
foreach ($props as $providedField => $tmp) {
|
|
|
|
if (!isset($fields[$providedField])) {
|
|
|
|
$errors[] = "In field \"{$providedField}\": Unknown field.";
|
2015-07-15 20:05:46 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-01 00:02:04 +03:00
|
|
|
// Ensure every defined field is valid.
|
|
|
|
foreach ($fields as $fieldName => $tmp) {
|
2016-05-02 00:42:05 +03:00
|
|
|
$newErrors = self::isValidPHPValue(isset($value[$fieldName]) ? $value[$fieldName] : null, $fields[$fieldName]->getType());
|
2016-05-01 00:02:04 +03:00
|
|
|
$errors = array_merge(
|
|
|
|
$errors,
|
|
|
|
Utils::map($newErrors, function ($error) use ($fieldName) {
|
|
|
|
return "In field \"{$fieldName}\": {$error}";
|
|
|
|
})
|
|
|
|
);
|
2015-08-17 17:01:55 +03:00
|
|
|
}
|
2016-05-01 00:02:04 +03:00
|
|
|
return $errors;
|
|
|
|
}
|
|
|
|
|
|
|
|
Utils::invariant(
|
|
|
|
$type instanceof ScalarType || $type instanceof EnumType,
|
|
|
|
'Must be input type'
|
|
|
|
);
|
2015-08-17 17:01:55 +03:00
|
|
|
|
2016-05-01 00:02:04 +03:00
|
|
|
// Scalar/Enum input checks to ensure the type can parse the value to
|
|
|
|
// a non-null value.
|
|
|
|
$parseResult = $type->parseValue($value);
|
|
|
|
if (null === $parseResult) {
|
|
|
|
$v = json_encode($value);
|
|
|
|
return [
|
|
|
|
"Expected type \"{$type->name}\", found $v."
|
|
|
|
];
|
2015-07-15 20:05:46 +03:00
|
|
|
}
|
|
|
|
|
2016-05-01 00:02:04 +03:00
|
|
|
return [];
|
2015-07-15 20:05:46 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
2016-05-01 00:02:04 +03:00
|
|
|
// We only call this function after calling isValidPHPValue.
|
2015-07-15 20:05:46 +03:00
|
|
|
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) {
|
2015-08-17 17:01:55 +03:00
|
|
|
$fieldValue = self::coerceValue($field->getType(), isset($value[$fieldName]) ? $value[$fieldName] : null);
|
|
|
|
if (null === $fieldValue) {
|
|
|
|
$fieldValue = $field->defaultValue;
|
|
|
|
}
|
|
|
|
if (null !== $fieldValue) {
|
|
|
|
$obj[$fieldName] = $fieldValue;
|
2015-07-15 20:05:46 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return $obj;
|
|
|
|
|
|
|
|
}
|
2015-08-17 17:01:55 +03:00
|
|
|
Utils::invariant($type instanceof ScalarType || $type instanceof EnumType, 'Must be input type');
|
|
|
|
return $type->parseValue($value);
|
2015-07-15 20:05:46 +03:00
|
|
|
}
|
|
|
|
}
|