diff --git a/benchmarks/HugeSchemaBench.php b/benchmarks/HugeSchemaBench.php new file mode 100644 index 0000000..d641bee --- /dev/null +++ b/benchmarks/HugeSchemaBench.php @@ -0,0 +1,90 @@ +schemaBuilder = new SchemaGenerator([ + 'totalTypes' => 600, + 'fieldsPerType' => 8, + 'listFieldsPerType' => 2, + 'nestingLevel' => 10 + ]); + + $this->schema = $this->schemaBuilder->buildSchema(); + + $queryBuilder = new QueryGenerator($this->schema, 0.05); + $this->descriptor = $this->schema->getDescriptor(); + $this->smallQuery = $queryBuilder->buildQuery(); + } + + public function benchSchema() + { + $this->schemaBuilder + ->buildSchema(); + } + + public function benchSchemaLazy() + { + $this->createLazySchema(); + } + + public function benchSmallQuery() + { + $result = GraphQL::execute($this->schema, $this->smallQuery); + } + + public function benchSmallQueryLazy() + { + $schema = $this->createLazySchema(); + $result = GraphQL::execute($schema, $this->smallQuery); + } + + private function createLazySchema() + { + $strategy = new LazyResolution( + $this->descriptor, + function($name) { + return $this->schemaBuilder->loadType($name); + } + ); + + return new Schema([ + 'query' => $this->schemaBuilder->buildQueryType(), + 'typeResolution' => $strategy, + ]); + } +} diff --git a/benchmarks/LexerBench.php b/benchmarks/LexerBench.php index 5f83cd8..ca79169 100644 --- a/benchmarks/LexerBench.php +++ b/benchmarks/LexerBench.php @@ -29,7 +29,7 @@ class LexerBench $lexer = new Lexer($this->introQuery); do { - $token = $lexer->nextToken(); + $token = $lexer->advance(); } while ($token->kind !== Token::EOF); } } diff --git a/benchmarks/StarWarsBench.php b/benchmarks/StarWarsBench.php new file mode 100644 index 0000000..1690808 --- /dev/null +++ b/benchmarks/StarWarsBench.php @@ -0,0 +1,97 @@ +introQuery = Introspection::getIntrospectionQuery(); + } + + public function benchSchema() + { + StarWarsSchema::build(); + } + + public function benchHeroQuery() + { + $q = ' + query HeroNameQuery { + hero { + name + } + } + '; + + GraphQL::execute( + StarWarsSchema::build(), + $q + ); + } + + public function benchNestedQuery() + { + $q = ' + query NestedQuery { + hero { + name + friends { + name + appearsIn + friends { + name + } + } + } + } + '; + GraphQL::execute( + StarWarsSchema::build(), + $q + ); + } + + public function benchQueryWithFragment() + { + $q = ' + query UseFragment { + luke: human(id: "1000") { + ...HumanFragment + } + leia: human(id: "1003") { + ...HumanFragment + } + } + + fragment HumanFragment on Human { + name + homePlanet + } + '; + + GraphQL::execute( + StarWarsSchema::build(), + $q + ); + } + + public function benchStarWarsIntrospectionQuery() + { + GraphQL::execute( + StarWarsSchema::build(), + $this->introQuery + ); + } +} diff --git a/benchmarks/Utils/QueryGenerator.php b/benchmarks/Utils/QueryGenerator.php new file mode 100644 index 0000000..f89b5be --- /dev/null +++ b/benchmarks/Utils/QueryGenerator.php @@ -0,0 +1,100 @@ +schema = $schema; + + Utils::invariant(0 < $percentOfLeafFields && $percentOfLeafFields <= 1); + + $totalFields = 0; + foreach ($schema->getTypeMap() as $type) { + if ($type instanceof ObjectType) { + $totalFields += count($type->getFields()); + } + } + + $this->maxLeafFields = max(1, round($totalFields * $percentOfLeafFields)); + $this->currentLeafFields = 0; + } + + public function buildQuery() + { + $qtype = $this->schema->getQueryType(); + + $ast = new DocumentNode([ + 'definitions' => [ + new OperationDefinitionNode([ + 'name' => new NameNode(['value' => 'TestQuery']), + 'operation' => 'query', + 'selectionSet' => $this->buildSelectionSet($qtype->getFields()) + ]) + ] + ]); + + return Printer::doPrint($ast); + } + + /** + * @param FieldDefinition[] $fields + * @return SelectionSetNode + */ + public function buildSelectionSet($fields) + { + $selections[] = new FieldNode([ + 'name' => new NameNode(['value' => '__typename']) + ]); + $this->currentLeafFields++; + + foreach ($fields as $field) { + if ($this->currentLeafFields >= $this->maxLeafFields) { + break; + } + + $type = $field->getType(); + + if ($type instanceof WrappingType) { + $type = $type->getWrappedType(true); + } + + if ($type instanceof ObjectType || $type instanceof InterfaceType) { + $selectionSet = $this->buildSelectionSet($type->getFields()); + } else { + $selectionSet = null; + $this->currentLeafFields++; + } + + $selections[] = new FieldNode([ + 'name' => new NameNode(['value' => $field->name]), + 'selectionSet' => $selectionSet + ]); + } + + $selectionSet = new SelectionSetNode([ + 'selections' => $selections + ]); + + return $selectionSet; + } +} diff --git a/benchmarks/Utils/SchemaGenerator.php b/benchmarks/Utils/SchemaGenerator.php new file mode 100644 index 0000000..6cc9145 --- /dev/null +++ b/benchmarks/Utils/SchemaGenerator.php @@ -0,0 +1,159 @@ + 100, + 'nestingLevel' => 10, + 'fieldsPerType' => 10, + 'listFieldsPerType' => 2 + ]; + + private $typeIndex = 0; + + private $objectTypes = []; + + /** + * BenchmarkSchemaBuilder constructor. + * @param array $config + */ + public function __construct(array $config) + { + $this->config = array_merge($this->config, $config); + } + + public function buildSchema() + { + return new Schema([ + 'query' => $this->buildQueryType() + ]); + } + + public function buildQueryType() + { + $this->typeIndex = 0; + $this->objectTypes = []; + + return $this->createType(0); + } + + public function loadType($name) + { + $tokens = explode('_', $name); + $nestingLevel = (int) $tokens[1]; + + return $this->createType($nestingLevel, $name); + } + + protected function createType($nestingLevel, $typeName = null) + { + if ($this->typeIndex > $this->config['totalTypes']) { + throw new \Exception( + "Cannot create new type: there are already {$this->typeIndex} ". + "which exceeds allowed number of {$this->config['totalTypes']} types total" + ); + } + + $this->typeIndex++; + if (!$typeName) { + $typeName = 'Level_' . $nestingLevel . '_Type' . $this->typeIndex; + } + + $type = new ObjectType([ + 'name' => $typeName, + 'fields' => function() use ($typeName, $nestingLevel) { + return $this->createTypeFields($typeName, $nestingLevel + 1); + } + ]); + + $this->objectTypes[$typeName] = $type; + return $type; + } + + protected function getFieldTypeAndName($nestingLevel, $fieldIndex) + { + if ($nestingLevel >= $this->config['nestingLevel']) { + $fieldType = Type::string(); + $fieldName = 'leafField' . $fieldIndex; + } else if ($this->typeIndex >= $this->config['totalTypes']) { + $fieldType = $this->objectTypes[array_rand($this->objectTypes)]; + $fieldName = 'randomTypeField' . $fieldIndex; + } else { + $fieldType = $this->createType($nestingLevel); + $fieldName = 'field' . $fieldIndex; + } + + return [$fieldType, $fieldName]; + } + + protected function createTypeFields($typeName, $nestingLevel) + { + $fields = []; + for ($index = 0; $index < $this->config['fieldsPerType']; $index++) { + list($type, $name) = $this->getFieldTypeAndName($nestingLevel, $index); + $fields[] = [ + 'name' => $name, + 'type' => $type, + 'resolve' => [$this, 'resolveField'] + ]; + } + for ($index = 0; $index < $this->config['listFieldsPerType']; $index++) { + list($type, $name) = $this->getFieldTypeAndName($nestingLevel, $index); + $name = 'listOf' . ucfirst($name); + + $fields[] = [ + 'name' => $name, + 'type' => Type::listOf($type), + 'args' => $this->createFieldArgs($name, $typeName), + 'resolve' => function() { + return [ + 'string1', + 'string2', + 'string3', + 'string4', + 'string5', + ]; + } + ]; + } + return $fields; + } + + protected function createFieldArgs($fieldName, $typeName) + { + return [ + 'argString' => [ + 'type' => Type::string() + ], + 'argEnum' => [ + 'type' => new EnumType([ + 'name' => $typeName . $fieldName . 'Enum', + 'values' => [ + "ONE", "TWO", "THREE" + ] + ]) + ], + 'argInputObject' => [ + 'type' => new InputObjectType([ + 'name' => $typeName . $fieldName . 'Input', + 'fields' => [ + 'field1' => Type::string(), + 'field2' => Type::int() + ] + ]) + ] + ]; + } + + public function resolveField($value, $args, $context, $resolveInfo) + { + return $resolveInfo->fieldName . '-value'; + } +} diff --git a/src/GraphQL.php b/src/GraphQL.php index 8574236..d0d3ec6 100644 --- a/src/GraphQL.php +++ b/src/GraphQL.php @@ -18,7 +18,7 @@ class GraphQL { /** * @param Schema $schema - * @param $requestString + * @param string|DocumentNode $requestString * @param mixed $rootValue * @param array|null $variableValues * @param string|null $operationName @@ -41,7 +41,7 @@ class GraphQL /** * @param Schema $schema - * @param $requestString + * @param string|DocumentNode $requestString * @param null $rootValue * @param null $variableValues * @param null $operationName diff --git a/src/Language/Source.php b/src/Language/Source.php index 5083f40..d831b29 100644 --- a/src/Language/Source.php +++ b/src/Language/Source.php @@ -1,6 +1,8 @@ body = $body; $this->length = mb_strlen($body, 'UTF-8'); $this->name = $name ?: 'GraphQL'; diff --git a/src/Type/EagerResolution.php b/src/Type/EagerResolution.php index e7a9aa9..ed15faf 100644 --- a/src/Type/EagerResolution.php +++ b/src/Type/EagerResolution.php @@ -36,10 +36,11 @@ class EagerResolution implements Resolution */ public function __construct(array $initialTypes) { + $typeMap = []; foreach ($initialTypes as $type) { - $this->extractTypes($type); + $typeMap = Utils\TypeInfo::extractTypes($type, $typeMap); } - $this->typeMap += Type::getInternalTypes(); + $this->typeMap = $typeMap + Type::getInternalTypes(); // Keep track of all possible types for abstract types foreach ($this->typeMap as $typeName => $type) { @@ -112,50 +113,4 @@ class EagerResolution implements Resolution 'possibleTypeMap' => $possibleTypesMap ]; } - - /** - * @param $type - * @return array - */ - private function extractTypes($type) - { - if (!$type) { - return $this->typeMap; - } - - if ($type instanceof WrappingType) { - return $this->extractTypes($type->getWrappedType(true)); - } - - if (!empty($this->typeMap[$type->name])) { - Utils::invariant( - $this->typeMap[$type->name] === $type, - "Schema must contain unique named types but contains multiple types named \"$type\"." - ); - return $this->typeMap; - } - $this->typeMap[$type->name] = $type; - - $nestedTypes = []; - - if ($type instanceof UnionType) { - $nestedTypes = $type->getTypes(); - } - 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(FieldArgument $arg) { return $arg->getType(); }, $field->args); - $nestedTypes = array_merge($nestedTypes, $fieldArgTypes); - } - $nestedTypes[] = $field->getType(); - } - } - foreach ($nestedTypes as $type) { - $this->extractTypes($type); - } - return $this->typeMap; - } }