/** * Not exactly the same as the executor's definition of getFieldDef, in this * statically evaluated environment we do not always have an Object type, * and need to handle Interface and Union types. * * @return FieldDefinition */ private static function _getFieldDef(Schema $schema, Type $parentType, Field $fieldAST) { $name = $fieldAST->name->value; $schemaMeta = Introspection::schemaMetaFieldDef(); if ($name === $schemaMeta->name && $schema->getQueryType() === $parentType) { return $schemaMeta; } $typeMeta = Introspection::typeMetaFieldDef(); if ($name === $typeMeta->name && $schema->getQueryType() === $parentType) { return $typeMeta; } $typeNameMeta = Introspection::typeNameMetaFieldDef(); if ($name === $typeNameMeta->name && ($parentType instanceof ObjectType || $parentType instanceof InterfaceType || $parentType instanceof UnionType)) { return $typeNameMeta; } if ($parentType instanceof ObjectType || $parentType instanceof InterfaceType) { $fields = $parentType->getFields(); return isset($fields[$name]) ? $fields[$name] : null; } return null; }
/** * 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); }
/** * 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); }
static function isValidLiteralValue($valueAST, Type $type) { // A value can only be not provided if the type is nullable. if (!$valueAST) { return !$type instanceof NonNull; } // Unwrap non-null. if ($type instanceof NonNull) { return self::isValidLiteralValue($valueAST, $type->getWrappedType()); } // This function only tests literals, and assumes variables will provide // values of the correct type. if ($valueAST instanceof Variable) { return true; } if (!$valueAST instanceof Value) { return false; } // Lists accept a non-list value as a list of one. if ($type instanceof ListOfType) { $itemType = $type->getWrappedType(); if ($valueAST instanceof ListValue) { foreach ($valueAST->values as $itemAST) { if (!self::isValidLiteralValue($itemAST, $itemType)) { return false; } } return true; } else { return self::isValidLiteralValue($valueAST, $itemType); } } // Scalar/Enum input checks to ensure the type can serialize the value to // a non-null value. if ($type instanceof ScalarType || $type instanceof EnumType) { return $type->parseLiteral($valueAST) !== null; } // Input objects check each defined field, ensuring it is of the correct // type and provided if non-nullable. if ($type instanceof InputObjectType) { $fields = $type->getFields(); if ($valueAST->kind !== Node::OBJECT) { return false; } $fieldASTs = $valueAST->fields; $fieldASTMap = Utils::keyMap($fieldASTs, function ($field) { return $field->name->value; }); foreach ($fields as $fieldKey => $field) { $fieldName = $field->name ?: $fieldKey; if (!isset($fieldASTMap[$fieldName]) && $field->getType() instanceof NonNull) { // Required fields missing return false; } } foreach ($fieldASTs as $fieldAST) { if (empty($fields[$fieldAST->name->value]) || !self::isValidLiteralValue($fieldAST->value, $fields[$fieldAST->name->value]->getType())) { return false; } } return true; } // Any other kind of type is not an input type, and a literal cannot be used. return false; }
/** * Given a type and a value AST node known to match this type, build a * runtime value. */ private static function coerceValueAST(Type $type, $valueAST, $variables) { if ($type instanceof NonNull) { // Note: we're not checking that the result of coerceValueAST is non-null. // We're assuming that this query has been validated and the value used // here is of the correct type. return self::coerceValueAST($type->getWrappedType(), $valueAST, $variables); } if (!$valueAST) { return null; } if ($valueAST->kind === Node::VARIABLE) { $variableName = $valueAST->name->value; if (!isset($variables, $variables[$variableName])) { return null; } // Note: we're not doing any checking that this variable is correct. We're // assuming that this query has been validated and the variable usage here // is of the correct type. return $variables[$variableName]; } if ($type instanceof ListOfType) { $itemType = $type->getWrappedType(); if ($valueAST->kind === Node::ARR) { $tmp = []; foreach ($valueAST->values as $itemAST) { $tmp[] = self::coerceValueAST($itemType, $itemAST, $variables); } return $tmp; } else { return [self::coerceValueAST($itemType, $valueAST, $variables)]; } } if ($type instanceof InputObjectType) { $fields = $type->getFields(); if ($valueAST->kind !== Node::OBJECT) { return null; } $fieldASTs = Utils::keyMap($valueAST->fields, function ($field) { return $field->name->value; }); $obj = []; foreach ($fields as $fieldName => $field) { $fieldAST = $fieldASTs[$fieldName]; $fieldValue = self::coerceValueAST($field->getType(), $fieldAST ? $fieldAST->value : null, $variables); $obj[$fieldName] = $fieldValue === null ? $field->defaultValue : $fieldValue; } return $obj; } if ($type instanceof ScalarType || $type instanceof EnumType) { $coerced = $type->coerceLiteral($valueAST); if (null !== $coerced) { return $coerced; } } return null; }
/** * Given a type and any value, return a runtime value coerced to match the type. */ private static function coerceValue(Type $type, $value) { $undefined = Utils::undefined(); if ($value === $undefined) { return $undefined; } if ($type instanceof NonNull) { if ($value === null) { // Intentionally return no value. return $undefined; } return self::coerceValue($type->getWrappedType(), $value); } if (null === $value) { return null; } if ($type instanceof ListOfType) { $itemType = $type->getWrappedType(); if (is_array($value) || $value instanceof \Traversable) { $coercedValues = []; foreach ($value as $item) { $itemValue = self::coerceValue($itemType, $item); if ($undefined === $itemValue) { // Intentionally return no value. return $undefined; } $coercedValues[] = $itemValue; } return $coercedValues; } else { $coercedValue = self::coerceValue($itemType, $value); if ($coercedValue === $undefined) { // Intentionally return no value. return $undefined; } return [$coercedValue]; } } if ($type instanceof InputObjectType) { $coercedObj = []; $fields = $type->getFields(); foreach ($fields as $fieldName => $field) { if (!array_key_exists($fieldName, $value)) { if ($field->defaultValueExists()) { $coercedObj[$fieldName] = $field->defaultValue; } else { if ($field->getType() instanceof NonNull) { // Intentionally return no value. return $undefined; } } continue; } $fieldValue = self::coerceValue($field->getType(), $value[$fieldName]); if ($fieldValue === $undefined) { // Intentionally return no value. return $undefined; } $coercedObj[$fieldName] = $fieldValue; } return $coercedObj; } if ($type instanceof LeafType) { $parsed = $type->parseValue($value); if (null === $parsed) { // null or invalid values represent a failure to parse correctly, // in which case no value is returned. return $undefined; } return $parsed; } throw new InvariantViolation('Must be input type'); }
/** * 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 isValidPHPValue. return self::coerceValue($type->getWrappedType(), $value); } if (null === $value) { return null; } if ($type instanceof ListOfType) { $itemType = $type->getWrappedType(); if (is_array($value) || $value instanceof \Traversable) { return Utils::map($value, function ($item) use($itemType) { return Values::coerceValue($itemType, $item); }); } 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; } if ($type instanceof LeafType) { return $type->parseValue($value); } throw new InvariantViolation('Must be input type'); }
/** * Utility for validators which determines if a value literal AST is valid given * an input type. * * Note that this only validates literal values, variables are assumed to * provide values of the correct type. * * @return array */ public static function isValidLiteralValue(Type $type, $valueAST) { // A value must be provided if the type is non-null. if ($type instanceof NonNull) { $wrappedType = $type->getWrappedType(); if (!$valueAST) { if ($wrappedType->name) { return ["Expected \"{$wrappedType->name}!\", found null."]; } return ['Expected non-null value, found null.']; } return static::isValidLiteralValue($wrappedType, $valueAST); } if (!$valueAST) { return []; } // This function only tests literals, and assumes variables will provide // values of the correct type. if ($valueAST instanceof Variable) { return []; } // Lists accept a non-list value as a list of one. if ($type instanceof ListOfType) { $itemType = $type->getWrappedType(); if ($valueAST instanceof ListValue) { $errors = []; foreach ($valueAST->values as $index => $itemAST) { $tmp = static::isValidLiteralValue($itemType, $itemAST); if ($tmp) { $errors = array_merge($errors, Utils::map($tmp, function ($error) use($index) { return "In element #{$index}: {$error}"; })); } } return $errors; } else { return static::isValidLiteralValue($itemType, $valueAST); } } // Input objects check each defined field and look for undefined fields. if ($type instanceof InputObjectType) { if ($valueAST->kind !== Node::OBJECT) { return ["Expected \"{$type->name}\", found not an object."]; } $fields = $type->getFields(); $errors = []; // Ensure every provided field is defined. $fieldASTs = $valueAST->fields; foreach ($fieldASTs as $providedFieldAST) { if (empty($fields[$providedFieldAST->name->value])) { $errors[] = "In field \"{$providedFieldAST->name->value}\": Unknown field."; } } // Ensure every defined field is valid. $fieldASTMap = Utils::keyMap($fieldASTs, function ($fieldAST) { return $fieldAST->name->value; }); foreach ($fields as $fieldName => $field) { $result = static::isValidLiteralValue($field->getType(), isset($fieldASTMap[$fieldName]) ? $fieldASTMap[$fieldName]->value : null); if ($result) { $errors = array_merge($errors, Utils::map($result, function ($error) use($fieldName) { return "In field \"{$fieldName}\": {$error}"; })); } } return $errors; } if ($type instanceof LeafType) { // Scalar/Enum input checks to ensure the type can parse the value to // a non-null value. $parseResult = $type->parseLiteral($valueAST); if (null === $parseResult) { $printed = Printer::doPrint($valueAST); return ["Expected type \"{$type->name}\", found {$printed}."]; } return []; } throw new InvariantViolation('Must be input type'); }