/** * 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; }
/** * Go through all of the implementations of type, and find other interaces * that they implement. If those interfaces include `field` as a valid field, * return them, sorted by how often the implementations include the other * interface. */ static function getSiblingInterfacesIncludingField(Schema $schema, AbstractType $type, $fieldName) { $types = $schema->getPossibleTypes($type); $suggestedInterfaces = array_reduce($types, function ($acc, $t) use($fieldName) { foreach ($t->getInterfaces() as $i) { if (empty($i->getFields()[$fieldName])) { continue; } if (!isset($acc[$i->name])) { $acc[$i->name] = 0; } $acc[$i->name] += 1; } return $acc; }, []); $suggestedInterfaceNames = array_keys($suggestedInterfaces); usort($suggestedInterfaceNames, function ($a, $b) use($suggestedInterfaces) { return $suggestedInterfaces[$b] - $suggestedInterfaces[$a]; }); return $suggestedInterfaceNames; }
/** * This method looks up the field on the given type defintion. * It has special casing for the two introspection fields, __schema * and __typename. __typename is special because it can always be * queried as a field, even in situations where no other fields * are allowed, like on a Union. __schema could get automatically * added to the query type, but that would require mutating type * definitions, which would cause issues. * * @param Schema $schema * @param ObjectType $parentType * @param $fieldName * * @return FieldDefinition */ private static function getFieldDef(Schema $schema, ObjectType $parentType, $fieldName) { static $schemaMetaFieldDef, $typeMetaFieldDef, $typeNameMetaFieldDef; $schemaMetaFieldDef = $schemaMetaFieldDef ?: Introspection::schemaMetaFieldDef(); $typeMetaFieldDef = $typeMetaFieldDef ?: Introspection::typeMetaFieldDef(); $typeNameMetaFieldDef = $typeNameMetaFieldDef ?: Introspection::typeNameMetaFieldDef(); if ($fieldName === $schemaMetaFieldDef->name && $schema->getQueryType() === $parentType) { return $schemaMetaFieldDef; } else { if ($fieldName === $typeMetaFieldDef->name && $schema->getQueryType() === $parentType) { return $typeMetaFieldDef; } else { if ($fieldName === $typeNameMetaFieldDef->name) { return $typeNameMetaFieldDef; } } } $tmp = $parentType->getFields(); return isset($tmp[$fieldName]) ? $tmp[$fieldName] : null; }
public function testIncludesInterfaceSubtypesInTheTypeMap() { $someInterface = new InterfaceType(['name' => 'SomeInterface', 'fields' => []]); $someSubtype = new ObjectType(['name' => 'SomeSubtype', 'fields' => [], 'interfaces' => [$someInterface]]); $schema = new Schema($someInterface); $this->assertSame($someSubtype, $schema->getType('SomeSubtype')); }
public function testIncludesInterfacesThunkSubtypesInTheTypeMap() { // includes interfaces' thunk subtypes in the type map $someInterface = new InterfaceType(['name' => 'SomeInterface', 'fields' => ['f' => ['type' => Type::int()]]]); $someSubtype = new ObjectType(['name' => 'SomeSubtype', 'fields' => ['f' => ['type' => Type::int()]], 'interfaces' => function () use($someInterface) { return [$someInterface]; }, 'isTypeOf' => function () { return true; }]); $schema = new Schema(new ObjectType(['name' => 'Query', 'fields' => ['iface' => ['type' => $someInterface]]])); $this->assertSame($someSubtype, $schema->getType('SomeSubtype')); }
public function testRejectsWhenAnImplementationIsNotAPossibleType() { // rejects when an implementation is not a possible type $interfaceType = new InterfaceType(['name' => 'InterfaceType', 'fields' => []]); $subType = new ObjectType(['name' => 'SubType', 'fields' => [], 'interfaces' => []]); $tmp = new \ReflectionObject($subType); $prop = $tmp->getProperty('_interfaces'); $prop->setAccessible(true); $prop->setValue($subType, [$interfaceType]); // Sanity check the test. $this->assertEquals([$interfaceType], $subType->getInterfaces()); $this->assertSame(false, $interfaceType->isPossibleType($subType)); // Need to make sure SubType is in the schema! We rely on // possibleTypes to be able to see it unless it's explicitly used. $schema = new Schema($interfaceType, $subType); // Another sanity check. $this->assertSame($subType, $schema->getType('SubType')); $validationResult = SchemaValidator::validate($schema, [SchemaValidator::typesInterfacesMustShowThemAsPossibleRule()]); $this->assertSame(false, $validationResult->isValid); $this->assertSame(1, count($validationResult->errors)); $this->assertSame('SubType implements interface InterfaceType, but InterfaceType does ' . 'not list it as possible!', $validationResult->errors[0]->message); /* var validationResult = validateSchema( schema, [TypesInterfacesMustShowThemAsPossible] ); expect(validationResult.isValid).to.equal(false); expect(validationResult.errors.length).to.equal(1); expect(validationResult.errors[0].message).to.equal( 'SubType implements interface InterfaceType, but InterfaceType does ' + 'not list it as possible!' ); */ }
public function testAllowsShorthandFieldDefinition() { $interface = new InterfaceType(['name' => 'SomeInterface', 'fields' => function () use(&$interface) { return ['value' => Type::string(), 'nested' => $interface, 'withArg' => ['type' => Type::string(), 'args' => ['arg1' => Type::int()]]]; }]); $query = new ObjectType(['name' => 'Query', 'fields' => ['test' => $interface]]); $schema = new Schema(['query' => $query]); $valueField = $schema->getType('SomeInterface')->getField('value'); $nestedField = $schema->getType('SomeInterface')->getField('nested'); $this->assertEquals(Type::string(), $valueField->getType()); $this->assertEquals($interface, $nestedField->getType()); $withArg = $schema->getType('SomeInterface')->getField('withArg'); $this->assertEquals(Type::string(), $withArg->getType()); $this->assertEquals('arg1', $withArg->args[0]->name); $this->assertEquals(Type::int(), $withArg->args[0]->getType()); $testField = $schema->getType('Query')->getField('test'); $this->assertEquals($interface, $testField->getType()); $this->assertEquals('test', $testField->name); }