From b890ef7cfe1b9805cafb53fd284c164a0894a521 Mon Sep 17 00:00:00 2001
From: vladar <vladimir.razuvaev@gmail.com>
Date: Wed, 24 Feb 2016 23:25:36 +0600
Subject: [PATCH] Ability to define InputObjectType fields with closure (#22)

---
 src/Type/Definition/InputObjectType.php | 36 ++++++++++++++++++++-----
 tests/Type/DefinitionTest.php           | 31 +++++++++++++++++++++
 2 files changed, 60 insertions(+), 7 deletions(-)

diff --git a/src/Type/Definition/InputObjectType.php b/src/Type/Definition/InputObjectType.php
index 8230c72..88de86e 100644
--- a/src/Type/Definition/InputObjectType.php
+++ b/src/Type/Definition/InputObjectType.php
@@ -1,12 +1,16 @@
 <?php
 namespace GraphQL\Type\Definition;
 
+use GraphQL\Utils;
+
 class InputObjectType extends Type implements InputType
 {
     /**
      * @var array<InputObjectField>
      */
-    private $_fields = [];
+    private $_fields;
+
+    public $config;
 
     public function __construct(array $config)
     {
@@ -21,12 +25,7 @@ class InputObjectType extends Type implements InputType
             'description' => Config::STRING
         ]);
 
-        if (!empty($config['fields'])) {
-            foreach ($config['fields'] as $name => $field) {
-                $this->_fields[$name] = new InputObjectField($field + ['name' => $name]);
-            }
-        }
-
+        $this->config = $config;
         $this->name = $config['name'];
         $this->description = isset($config['description']) ? $config['description'] : null;
     }
@@ -36,6 +35,29 @@ class InputObjectType extends Type implements InputType
      */
     public function getFields()
     {
+        if (null === $this->_fields) {
+            $this->_fields = [];
+            $fields = isset($this->config['fields']) ? $this->config['fields'] : [];
+            $fields = is_callable($fields) ? call_user_func($fields) : $fields;
+            foreach ($fields as $name => $field) {
+                $this->_fields[$name] = new InputObjectField($field + ['name' => $name]);
+            }
+        }
+
         return $this->_fields;
     }
+
+    /**
+     * @param string $name
+     * @return InputObjectField
+     * @throws \Exception
+     */
+    public function getField($name)
+    {
+        if (null === $this->_fields) {
+            $this->getFields();
+        }
+        Utils::invariant(isset($this->_fields[$name]), "Field '%s' is not defined for type '%s'", $name, $this->name);
+        return $this->_fields[$name];
+    }
 }
diff --git a/tests/Type/DefinitionTest.php b/tests/Type/DefinitionTest.php
index 2e6ea77..9bf3bd5 100644
--- a/tests/Type/DefinitionTest.php
+++ b/tests/Type/DefinitionTest.php
@@ -402,6 +402,37 @@ class DefinitionTest extends \PHPUnit_Framework_TestCase
 
         $this->assertNotNull($blog->getField('owner'));
         $this->assertSame($user, $blog->getField('owner')->getType()->getWrappedType(true));
+    }
 
+    public function testInputObjectTypeAllowsRecursiveDefinitions()
+    {
+        $called = false;
+        $inputObject = new InputObjectType([
+            'name' => 'InputObject',
+            'fields' => function() use (&$inputObject, &$called) {
+                $called = true;
+                return [
+                    'value' => ['type' => Type::string()],
+                    'nested' => ['type' => $inputObject ]
+                ];
+            }
+        ]);
+        $someMutation = new ObjectType([
+            'name' => 'SomeMutation',
+            'fields' => [
+                'mutateSomething' => [
+                    'type' => $this->blogArticle,
+                    'args' => ['input' => ['type' => $inputObject]]
+                ]
+            ]
+        ]);
+
+        $schema = new Schema($this->blogQuery, $someMutation);
+
+        $this->assertTrue($called);
+        $this->assertSame($inputObject, $schema->getType('InputObject'));
+        $this->assertEquals(count($inputObject->getFields()), 2);
+        $this->assertSame($inputObject->getField('nested')->getType(), $inputObject);
+        $this->assertSame($inputObject->getField('value')->getType(), Type::string());
     }
 }