{"action":"created","issue":{"url":"https://api.github.com/repos/webonyx/graphql-php/issues/297","repository_url":"https://api.github.com/repos/webonyx/graphql-php","labels_url":"https://api.github.com/repos/webonyx/graphql-php/issues/297/labels{/name}","comments_url":"https://api.github.com/repos/webonyx/graphql-php/issues/297/comments","events_url":"https://api.github.com/repos/webonyx/graphql-php/issues/297/events","html_url":"https://github.com/webonyx/graphql-php/issues/297","id":336659241,"node_id":"MDU6SXNzdWUzMzY2NTkyNDE=","number":297,"title":"Best practice looking ahead input args of sub types/nodes in resolver?","user":{"login":"mfn","id":87493,"node_id":"MDQ6VXNlcjg3NDkz","avatar_url":"https://avatars0.githubusercontent.com/u/87493?v=4","gravatar_id":"","url":"https://api.github.com/users/mfn","html_url":"https://github.com/mfn","followers_url":"https://api.github.com/users/mfn/followers","following_url":"https://api.github.com/users/mfn/following{/other_user}","gists_url":"https://api.github.com/users/mfn/gists{/gist_id}","starred_url":"https://api.github.com/users/mfn/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mfn/subscriptions","organizations_url":"https://api.github.com/users/mfn/orgs","repos_url":"https://api.github.com/users/mfn/repos","events_url":"https://api.github.com/users/mfn/events{/privacy}","received_events_url":"https://api.github.com/users/mfn/received_events","type":"User","site_admin":false},"labels":[],"state":"closed","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":1,"created_at":"2018-06-28T15:00:10Z","updated_at":"2018-07-07T19:04:50Z","closed_at":"2018-07-07T19:04:50Z","author_association":"NONE","body":"Example query:\r\n```graphql\r\n{\r\n  outer {\r\n    inner(input1: \"value1) {\r\n      moreFields\r\n…\r\n```\r\nUsually in the resolver you receive `ResolveInfo $info` and thanks to `\\GraphQL\\Type\\Definition\\ResolveInfo::getFieldSelection` you can get a simply representation of the requested structure:\r\n```php\r\n[\r\n  'outer' => [\r\n    'inner' => [\r\n      'moreFields' => true,\r\n…\r\n```\r\nBut to write efficient resolvers it may be necessary to look ahead when resolving `outer` to know what input args apply to `inner`, not just the fields.\r\n\r\n<details>\r\n<summary>\r\nI've helped myself with copying and modifying `foldSelectionSet` so it returns a structure which contains input args too:\r\n</summary>\r\n\r\n```php\r\n\r\nuse GraphQL\\Language\\AST\\FieldNode;\r\nuse GraphQL\\Language\\AST\\FragmentSpreadNode;\r\nuse GraphQL\\Language\\AST\\InlineFragmentNode;\r\nuse GraphQL\\Language\\AST\\SelectionSetNode;\r\nuse GraphQL\\Type\\Definition\\ResolveInfo;\r\n\r\n/**\r\n * This adapts \\GraphQL\\Type\\Definition\\ResolveInfo::getFieldSelection but with support for arguments\r\n */\r\nclass ResolveFieldArgumentsUtil\r\n{\r\n    /** @var ResolveInfo */\r\n    public $info;\r\n\r\n    public function __construct(ResolveInfo $info)\r\n    {\r\n        $this->info = $info;\r\n    }\r\n\r\n    /**\r\n     * Helper method that returns names of all fields with attributes selected in query for\r\n     * $this->fieldName up to $depth levels\r\n     *\r\n     * Example:\r\n     * query MyQuery{\r\n     * {\r\n     *   root {\r\n     *     nested(input:value) {\r\n     *      nested1\r\n     *      nested2 {\r\n     *        nested3\r\n     *      }\r\n     *     }\r\n     *   }\r\n     * }\r\n     *\r\n     * Given this ResolveInfo instance is a part of \"root\" field resolution, and $depth === 1,\r\n     * method will return:\r\n     * [\r\n     *      'nested' => [\r\n     *          'args' => [\r\n     *              'input' => 'value',\r\n     *          ],\r\n     *          'fields' => [\r\n     *              'nested1' => [\r\n     *                  'args' => [],\r\n     *                  'fields' => [],\r\n     *              ],\r\n     *              'nested2' => [\r\n     *                  'args' => [],\r\n     *                  'fields' => [],\r\n     *              ],\r\n     *          ],\r\n     *      ],\r\n     * ],\r\n     *\r\n     * Warning: this method it is a naive implementation which does not take into account\r\n     * conditional typed fragments. So use it with care for fields of interface and union types.\r\n     *\r\n     * @see \\GraphQL\\Type\\Definition\\ResolveInfo::getFieldSelection\r\n     * @param int $depth How many levels to include in output\r\n     * @return array\r\n     */\r\n    public function getFieldSelection(int $depth = 0): array\r\n    {\r\n        $fields = [];\r\n\r\n        foreach ($this->info->fieldNodes as $fieldNode) {\r\n            $fields = array_merge_recursive($fields, $this->foldSelectionSet($fieldNode->selectionSet, $depth));\r\n        }\r\n\r\n        return $fields;\r\n    }\r\n\r\n    /**\r\n     * @see \\GraphQL\\Type\\Definition\\ResolveInfo::foldSelectionSet\r\n     * @param SelectionSetNode $selectionSet\r\n     * @param int $descend\r\n     * @return array\r\n     */\r\n    private function foldSelectionSet(SelectionSetNode $selectionSet, int $descend): array\r\n    {\r\n        $fields = [];\r\n\r\n        foreach ($selectionSet->selections as $selectionNode) {\r\n            if ($selectionNode instanceof FieldNode) {\r\n                // Only this part here has been adapted to return arguments too\r\n                $name = $selectionNode->name->value;\r\n\r\n                $fields[$name] = [\r\n                    'args' => [],\r\n                    'fields' => [],\r\n                ];\r\n\r\n                if ($descend > 0 && !empty($selectionNode->selectionSet)) {\r\n                    $fields[$name]['fields'] = $this->foldSelectionSet($selectionNode->selectionSet, $descend - 1);\r\n                }\r\n\r\n                foreach ($selectionNode->arguments ?? [] as $argumentNode) {\r\n                    $fields[$name]['args'][$argumentNode->name->value] = $argumentNode->value->value;\r\n                }\r\n            } elseif ($selectionNode instanceof FragmentSpreadNode) {\r\n                $spreadName = $selectionNode->name->value;\r\n                if (isset($this->info->fragments[$spreadName])) {\r\n                    $fragment = $this->info->fragments[$spreadName];\r\n                    $fields = array_merge_recursive($this->foldSelectionSet($fragment->selectionSet, $descend), $fields);\r\n                }\r\n            } elseif ($selectionNode instanceof InlineFragmentNode) {\r\n                $fields = array_merge_recursive($this->foldSelectionSet($selectionNode->selectionSet, $descend), $fields);\r\n            }\r\n        }\r\n\r\n        return $fields;\r\n    }\r\n}\r\n```\r\n</details>\r\n\r\n```php\r\n[\r\n  'outer' => [\r\n    'args' => [],\r\n    'fields' => [\r\n      'inner' => [\r\n        'args' => ['input1' => 'value1'],\r\n        'fields' => [\r\n          'moreFields' => [\r\n            'input' => [],\r\n            'fields' => [],\r\n…\r\n```\r\n\r\nIt gets the job done but doesn't feel exactly right.\r\n\r\nAre there any recommendations and best practices to achieve this?"},"comment":{"url":"https://api.github.com/repos/webonyx/graphql-php/issues/comments/403236709","html_url":"https://github.com/webonyx/graphql-php/issues/297#issuecomment-403236709","issue_url":"https://api.github.com/repos/webonyx/graphql-php/issues/297","id":403236709,"node_id":"MDEyOklzc3VlQ29tbWVudDQwMzIzNjcwOQ==","user":{"login":"mfn","id":87493,"node_id":"MDQ6VXNlcjg3NDkz","avatar_url":"https://avatars0.githubusercontent.com/u/87493?v=4","gravatar_id":"","url":"https://api.github.com/users/mfn","html_url":"https://github.com/mfn","followers_url":"https://api.github.com/users/mfn/followers","following_url":"https://api.github.com/users/mfn/following{/other_user}","gists_url":"https://api.github.com/users/mfn/gists{/gist_id}","starred_url":"https://api.github.com/users/mfn/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mfn/subscriptions","organizations_url":"https://api.github.com/users/mfn/orgs","repos_url":"https://api.github.com/users/mfn/repos","events_url":"https://api.github.com/users/mfn/events{/privacy}","received_events_url":"https://api.github.com/users/mfn/received_events","type":"User","site_admin":false},"created_at":"2018-07-07T19:04:50Z","updated_at":"2018-07-07T19:04:50Z","author_association":"NONE","body":"Thanks for the answer, explains everything I wanted to know! 👍 "}}
{"actor":{"display_login":"mfn"}}