public static map ( array | Traversable $traversable, callable $fn ) : array | ||
$traversable | array | Traversable | |
$fn | callable | function($value, $key) => $newValue |
return | array |
private function withModifiers($types) { return array_merge(Utils::map($types, function ($type) { return Type::listOf($type); }), Utils::map($types, function ($type) { return Type::nonNull($type); }), Utils::map($types, function ($type) { return Type::nonNull(Type::listOf($type)); })); }
/** * Return implementations of `type` that include `fieldName` as a valid field. * * @param Schema $schema * @param AbstractType $type * @param $fieldName * @return array */ static function getImplementationsIncludingField(Schema $schema, AbstractType $type, $fieldName) { $types = $schema->getPossibleTypes($type); $types = Utils::filter($types, function ($t) use($fieldName) { return isset($t->getFields()[$fieldName]); }); $types = Utils::map($types, function ($t) { return $t->name; }); sort($types); return $types; }
private function detectCycleRecursive(FragmentDefinition $fragment, ValidationContext $context) { $fragmentName = $fragment->name->value; $this->visitedFrags[$fragmentName] = true; $spreadNodes = $context->getFragmentSpreads($fragment); if (empty($spreadNodes)) { return; } $this->spreadPathIndexByName[$fragmentName] = count($this->spreadPath); for ($i = 0; $i < count($spreadNodes); $i++) { $spreadNode = $spreadNodes[$i]; $spreadName = $spreadNode->name->value; $cycleIndex = isset($this->spreadPathIndexByName[$spreadName]) ? $this->spreadPathIndexByName[$spreadName] : null; if ($cycleIndex === null) { $this->spreadPath[] = $spreadNode; if (empty($this->visitedFrags[$spreadName])) { $spreadFragment = $context->getFragment($spreadName); if ($spreadFragment) { $this->detectCycleRecursive($spreadFragment, $context); } } array_pop($this->spreadPath); } else { $cyclePath = array_slice($this->spreadPath, $cycleIndex); $nodes = $cyclePath; if (is_array($spreadNode)) { $nodes = array_merge($nodes, $spreadNode); } else { $nodes[] = $spreadNode; } $context->reportError(new Error(self::cycleErrorMessage($spreadName, Utils::map($cyclePath, function ($s) { return $s->name->value; })), $nodes)); } } $this->spreadPathIndexByName[$fragmentName] = null; }
private function subfieldConflicts(array $conflicts, $responseName, FieldNode $ast1, FieldNode $ast2) { if (!empty($conflicts)) { return [[$responseName, Utils::map($conflicts, function ($conflict) { return $conflict[0]; })], array_reduce($conflicts, function ($allFields, $conflict) { return array_merge($allFields, $conflict[1]); }, [$ast1]), array_reduce($conflicts, function ($allFields, $conflict) { return array_merge($allFields, $conflict[2]); }, [$ast2])]; } }
/** * 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, $valueNode) { // A value must be provided if the type is non-null. if ($type instanceof NonNull) { if (!$valueNode || $valueNode instanceof NullValueNode) { return ['Expected "' . Utils::printSafe($type) . '", found null.']; } return static::isValidLiteralValue($type->getWrappedType(), $valueNode); } if (!$valueNode || $valueNode instanceof NullValueNode) { return []; } // This function only tests literals, and assumes variables will provide // values of the correct type. if ($valueNode instanceof VariableNode) { return []; } // Lists accept a non-list value as a list of one. if ($type instanceof ListOfType) { $itemType = $type->getWrappedType(); if ($valueNode instanceof ListValueNode) { $errors = []; foreach ($valueNode->values as $index => $itemNode) { $tmp = static::isValidLiteralValue($itemType, $itemNode); 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, $valueNode); } } // Input objects check each defined field and look for undefined fields. if ($type instanceof InputObjectType) { if ($valueNode->kind !== NodeKind::OBJECT) { return ["Expected \"{$type->name}\", found not an object."]; } $fields = $type->getFields(); $errors = []; // Ensure every provided field is defined. $fieldNodes = $valueNode->fields; foreach ($fieldNodes as $providedFieldNode) { if (empty($fields[$providedFieldNode->name->value])) { $errors[] = "In field \"{$providedFieldNode->name->value}\": Unknown field."; } } // Ensure every defined field is valid. $fieldNodeMap = Utils::keyMap($fieldNodes, function ($fieldNode) { return $fieldNode->name->value; }); foreach ($fields as $fieldName => $field) { $result = static::isValidLiteralValue($field->getType(), isset($fieldNodeMap[$fieldName]) ? $fieldNodeMap[$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($valueNode); if (null === $parseResult) { $printed = Printer::doPrint($valueNode); return ["Expected type \"{$type->name}\", found {$printed}."]; } return []; } throw new InvariantViolation('Must be input type'); }
/** * @it produces double linked list of tokens, including comments */ public function testDoubleLinkedList() { $lexer = new Lexer(new Source('{ #comment field }')); $startToken = $lexer->token; do { $endToken = $lexer->advance(); // Lexer advances over ignored comment tokens to make writing parsers // easier, but will include them in the linked list result. $this->assertNotEquals('Comment', $endToken->kind); } while ($endToken->kind !== '<EOF>'); $this->assertEquals(null, $startToken->prev); $this->assertEquals(null, $endToken->next); $tokens = []; for ($tok = $startToken; $tok; $tok = $tok->next) { if (!empty($tokens)) { // Tokens are double-linked, prev should point to last seen token. $this->assertSame($tokens[count($tokens) - 1], $tok->prev); } $tokens[] = $tok; } $this->assertEquals(['<SOF>', '{', 'Comment', 'Name', '}', '<EOF>'], Utils::map($tokens, function ($tok) { return $tok->kind; })); }
/** * Returns array representation of error suitable for serialization * * @return array */ public function toSerializableArray() { $arr = ['message' => $this->getMessage()]; $locations = Utils::map($this->getLocations(), function (SourceLocation $loc) { return $loc->toSerializableArray(); }); if (!empty($locations)) { $arr['locations'] = $locations; } if (!empty($this->path)) { $arr['path'] = $this->path; } return $arr; }
public function testRespectsListsOfAbstractTypeWhenResolvingViaMap() { $type1 = null; $type2 = null; $type3 = null; $resolveType = function ($value) use(&$type1, &$type2, &$type3) { switch ($value['type']) { case 'Type1': return $type1; case 'Type2': return $type2; case 'Type3': default: return $type3; } }; $mapValues = function ($typeValues, $args) { return Utils::map($typeValues, function ($value) use($args) { if (array_key_exists('foo', $value)) { return json_encode(['value' => $value, 'args' => $args]); } else { return null; } }); }; $interface = new InterfaceType(['name' => 'SomeInterface', 'fields' => ['foo' => ['type' => Type::string()]], 'resolveType' => $resolveType]); $type1 = new ObjectType(['name' => 'Type1', 'fields' => ['foo' => ['type' => Type::string(), 'map' => $mapValues]], 'interfaces' => [$interface]]); $type2 = new ObjectType(['name' => 'Type2', 'fields' => ['foo' => ['type' => Type::string(), 'map' => $mapValues]], 'interfaces' => [$interface]]); $type3 = new ObjectType(['name' => 'Type3', 'fields' => ['bar' => ['type' => Type::listOf(Type::string()), 'map' => function ($type3Values, $args) { return Utils::map($type3Values, function ($value) use($args) { return [json_encode(['value' => $value, 'args' => $args])]; }); }]]]); $union = new UnionType(['name' => 'SomeUnion', 'types' => [$type1, $type3], 'resolveType' => $resolveType]); $complexType = new ObjectType(['name' => 'ComplexType', 'fields' => ['iface' => ['type' => $interface], 'ifaceList' => ['type' => Type::listOf($interface)], 'union' => ['type' => $union], 'unionList' => ['type' => Type::listOf($union)]]]); $type1values = [['type' => 'Type1', 'foo' => 'str1'], ['type' => 'Type1'], ['type' => 'Type1', 'foo' => null]]; $type2values = [['type' => 'Type2', 'foo' => 'str1'], ['type' => 'Type2', 'foo' => null], ['type' => 'Type2']]; $type3values = [['type' => 'Type3', 'bar' => ['str1', 'str2']], ['type' => 'Type3', 'bar' => null]]; $complexTypeValues = ['iface' => $type1values[0], 'ifaceList' => array_merge($type1values, $type2values), 'union' => $type3values[0], 'unionList' => array_merge($type1values, $type3values)]; $expected = ['data' => ['test' => ['iface' => ['foo' => json_encode(['value' => $type1values[0], 'args' => []])], 'ifaceList' => [['foo' => '{"value":{"type":"Type1","foo":"str1"},"args":[]}'], ['foo' => null], ['foo' => '{"value":{"type":"Type1","foo":null},"args":[]}'], ['foo' => '{"value":{"type":"Type2","foo":"str1"},"args":[]}'], ['foo' => '{"value":{"type":"Type2","foo":null},"args":[]}'], ['foo' => null]], 'union' => ['bar' => ['{"value":{"type":"Type3","bar":["str1","str2"]},"args":[]}']], 'unionList' => [['foo' => '{"value":{"type":"Type1","foo":"str1"},"args":[]}'], ['foo' => null], ['foo' => '{"value":{"type":"Type1","foo":null},"args":[]}'], ['bar' => ['{"value":{"type":"Type3","bar":["str1","str2"]},"args":[]}']], ['bar' => ['{"value":{"type":"Type3","bar":null},"args":[]}']]]]]]; $schema = new Schema(new ObjectType(['name' => 'Query', 'fields' => ['test' => ['type' => $complexType, 'resolve' => function () use($complexTypeValues) { return $complexTypeValues; }]]])); $query = '{ test { iface{foo}, ifaceList{foo} union { ... on Type1 { foo } ... on Type3 { bar } } unionList { ... on Type1 { foo } ... on Type3 { bar } } } }'; $query = Parser::parse($query); $result = Executor::execute($schema, $query); $this->assertEquals($expected, $result->toArray()); }
/** * 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 InputType $type * @return array */ private static function isValidPHPValue($value, InputType $type) { // A value must be provided if the type is non-null. if ($type instanceof NonNull) { if (null === $value) { return ['Expected "' . Utils::printSafe($type) . '", found null.']; } return self::isValidPHPValue($value, $type->getWrappedType()); } if (null === $value) { return []; } // Lists accept a non-list value as a list of one. if ($type instanceof ListOfType) { $itemType = $type->getWrappedType(); if (is_array($value)) { $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; } return self::isValidPHPValue($value, $itemType); } // Input objects check each defined field. if ($type instanceof InputObjectType) { if (!is_object($value) && !is_array($value)) { return ["Expected \"{$type->name}\", found not an object."]; } $fields = $type->getFields(); $errors = []; // 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."; } } // Ensure every defined field is valid. foreach ($fields as $fieldName => $tmp) { $newErrors = self::isValidPHPValue(isset($value[$fieldName]) ? $value[$fieldName] : null, $fields[$fieldName]->getType()); $errors = array_merge($errors, Utils::map($newErrors, 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->parseValue($value); if (null === $parseResult) { $v = json_encode($value); return ["Expected type \"{$type->name}\", found {$v}."]; } return []; } 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'); }
/** * @return InterfaceType[] */ public function getInterfaces() { if (null === $this->interfaces) { $interfaces = isset($this->config['interfaces']) ? $this->config['interfaces'] : []; $interfaces = is_callable($interfaces) ? call_user_func($interfaces) : $interfaces; // TODO: Return some sort of generator to avoid multiple loops $interfaces = Utils::map($interfaces, function ($iface) { return $iface instanceof DefinitionContainer ? $iface->getDefinition() : $iface; }); $this->interfaces = $interfaces; } return $this->interfaces; }