Skip to content

Commit 304b145

Browse files
jorritmghoneimy
authored andcommitted
Add support for UNION object types
1 parent d89588f commit 304b145

File tree

13 files changed

+382
-14
lines changed

13 files changed

+382
-14
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"bin": ["bin/generate_schema_objects"],
3636
"require": {
3737
"php": "^7.1 || ^8.0",
38-
"gmostafa/php-graphql-client": "^1.6 || ^1.9"
38+
"gmostafa/php-graphql-client": "^1.12"
3939
},
4040
"require-dev": {
4141
"phpunit/phpunit": "^7.5|^8.0",

src/Enumeration/FieldTypeKindEnum.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ class FieldTypeKindEnum
1515
const OBJECT = 'OBJECT';
1616
const INPUT_OBJECT = 'INPUT_OBJECT';
1717
const ENUM_OBJECT = 'ENUM';
18-
}
18+
const UNION_OBJECT = 'UNION';
19+
}

src/SchemaGenerator/CodeGenerator/QueryObjectClassBuilder.php

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace GraphQL\SchemaGenerator\CodeGenerator;
44

5+
use GraphQL\Enumeration\FieldTypeKindEnum;
56
use GraphQL\SchemaGenerator\CodeGenerator\CodeFile\ClassFile;
67
use GraphQL\SchemaObject\QueryObject;
78
use GraphQL\Util\StringLiteralFormatter;
@@ -50,12 +51,15 @@ public function addScalarField(string $fieldName, bool $isDeprecated, ?string $d
5051
/**
5152
* @param string $fieldName
5253
* @param string $typeName
54+
* @param string $typeKind
5355
* @param string $argsObjectName
56+
* @param bool $isDeprecated
57+
* @param string|null $deprecationReason
5458
*/
55-
public function addObjectField(string $fieldName, string $typeName, string $argsObjectName, bool $isDeprecated, ?string $deprecationReason)
59+
public function addObjectField(string $fieldName, string $typeName, string $typeKind, string $argsObjectName, bool $isDeprecated, ?string $deprecationReason)
5660
{
5761
$upperCamelCaseProp = StringLiteralFormatter::formatUpperCamelCase($fieldName);
58-
$this->addObjectSelector($fieldName, $upperCamelCaseProp, $typeName, $argsObjectName, $isDeprecated, $deprecationReason);
62+
$this->addObjectSelector($fieldName, $upperCamelCaseProp, $typeName, $typeKind, $argsObjectName, $isDeprecated, $deprecationReason);
5963
}
6064

6165
/**
@@ -79,14 +83,17 @@ protected function addSimpleSelector(string $propertyName, string $upperCamelNam
7983
* @param string $fieldName
8084
* @param string $upperCamelName
8185
* @param string $fieldTypeName
86+
* @param string $fieldTypeKind
8287
* @param string $argsObjectName
88+
* @param bool $isDeprecated
89+
* @param string|null $deprecationReason
8390
*/
84-
protected function addObjectSelector(string $fieldName, string $upperCamelName, string $fieldTypeName, string $argsObjectName, bool $isDeprecated, ?string $deprecationReason)
91+
protected function addObjectSelector(string $fieldName, string $upperCamelName, string $fieldTypeName, string $fieldTypeKind, string $argsObjectName, bool $isDeprecated, ?string $deprecationReason)
8592
{
86-
$objectClassName = $fieldTypeName . 'QueryObject';
93+
$objectClass = $fieldTypeName . ($fieldTypeKind === FieldTypeKindEnum::UNION_OBJECT ? 'UnionObject' : 'QueryObject');
8794
$method = "public function select$upperCamelName($argsObjectName \$argsObject = null)
8895
{
89-
\$object = new $objectClassName(\"$fieldName\");
96+
\$object = new $objectClass(\"$fieldName\");
9097
if (\$argsObject !== null) {
9198
\$object->appendArguments(\$argsObject->toArray());
9299
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
namespace GraphQL\SchemaGenerator\CodeGenerator;
4+
5+
use GraphQL\SchemaGenerator\CodeGenerator\CodeFile\ClassFile;
6+
use GraphQL\Util\StringLiteralFormatter;
7+
8+
/**
9+
* Class EnumObjectBuilder
10+
*
11+
* @package GraphQL\SchemaGenerator\CodeGenerator
12+
*/
13+
class UnionObjectBuilder implements ObjectBuilderInterface
14+
{
15+
/**
16+
* @var ClassFile
17+
*/
18+
protected $classFile;
19+
20+
/**
21+
* EnumObjectBuilder constructor.
22+
*
23+
* @param string $writeDir
24+
* @param string $objectName
25+
* @param string $namespace
26+
*/
27+
public function __construct(string $writeDir, string $objectName, string $namespace = self::DEFAULT_NAMESPACE)
28+
{
29+
$className = $objectName . 'UnionObject';
30+
31+
$this->classFile = new ClassFile($writeDir, $className);
32+
$this->classFile->setNamespace($namespace);
33+
if ($namespace !== self::DEFAULT_NAMESPACE) {
34+
$this->classFile->addImport('GraphQL\\SchemaObject\\UnionObject');
35+
}
36+
$this->classFile->extendsClass('UnionObject');
37+
}
38+
39+
/**
40+
* @param string $typeName
41+
*/
42+
public function addPossibleType(string $typeName)
43+
{
44+
$upperCamelCaseTypeName = StringLiteralFormatter::formatUpperCamelCase($typeName);
45+
$objectClassName = $typeName . 'QueryObject';
46+
$method = "public function on$upperCamelCaseTypeName()
47+
{
48+
\$object = new $objectClassName();
49+
\$this->addPossibleType(\$object);
50+
51+
return \$object;
52+
}";
53+
$this->classFile->addMethod($method);
54+
}
55+
56+
/**
57+
* @return void
58+
*/
59+
public function build(): void
60+
{
61+
$this->classFile->writeFile();
62+
}
63+
}

src/SchemaGenerator/SchemaClassGenerator.php

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use GraphQL\SchemaGenerator\CodeGenerator\InputObjectClassBuilder;
1010
use GraphQL\SchemaGenerator\CodeGenerator\ObjectBuilderInterface;
1111
use GraphQL\SchemaGenerator\CodeGenerator\QueryObjectClassBuilder;
12+
use GraphQL\SchemaGenerator\CodeGenerator\UnionObjectBuilder;
1213
use GraphQL\SchemaObject\QueryObject;
1314
use GraphQL\Util\StringLiteralFormatter;
1415
use RuntimeException;
@@ -118,7 +119,7 @@ private function appendQueryObjectFields(QueryObjectClassBuilder $queryObjectBui
118119
if ($argsObjectGenerated) {
119120

120121
// Add sub type as a field to the query object if all generation happened successfully
121-
$queryObjectBuilder->addObjectField($name, $typeName, $argsObjectName, $fieldArray['isDeprecated'], $fieldArray['deprecationReason']);
122+
$queryObjectBuilder->addObjectField($name, $typeName, $typeKind, $argsObjectName, $fieldArray['isDeprecated'], $fieldArray['deprecationReason']);
122123
}
123124
}
124125
}
@@ -140,6 +141,8 @@ protected function generateObject(string $objectName, string $objectKind): bool
140141
return $this->generateInputObject($objectName);
141142
case FieldTypeKindEnum::ENUM_OBJECT:
142143
return $this->generateEnumObject($objectName);
144+
case FieldTypeKindEnum::UNION_OBJECT:
145+
return $this->generateUnionObject($objectName);
143146
default:
144147
print "Couldn't generate type $objectName: generating $objectKind kind is not supported yet" . PHP_EOL;
145148
return false;
@@ -229,7 +232,7 @@ protected function generateEnumObject(string $objectName): bool
229232
$objectArray = $this->schemaInspector->getEnumObjectSchema($objectName);
230233
$objectName = $objectArray['name'];
231234
$objectBuilder = new EnumObjectBuilder($this->writeDir, $objectName, $this->generationNamespace);
232-
235+
233236
foreach ($objectArray['enumValues'] as $enumValue) {
234237
$name = $enumValue['name'];
235238
//$description = $enumValue['description'];
@@ -240,6 +243,32 @@ protected function generateEnumObject(string $objectName): bool
240243
return true;
241244
}
242245

246+
/**
247+
* @param string $objectName
248+
*
249+
* @return bool
250+
*/
251+
protected function generateUnionObject(string $objectName): bool
252+
{
253+
if (array_key_exists($objectName, $this->generatedObjects)) {
254+
return true;
255+
}
256+
257+
$this->generatedObjects[$objectName] = true;
258+
259+
$objectArray = $this->schemaInspector->getUnionObjectSchema($objectName);
260+
$objectName = $objectArray['name'];
261+
$objectBuilder = new UnionObjectBuilder($this->writeDir, $objectName, $this->generationNamespace);
262+
263+
foreach ($objectArray['possibleTypes'] as $possibleType) {
264+
$this->generateObject($possibleType['name'], $possibleType['kind']);
265+
$objectBuilder->addPossibleType($possibleType['name']);
266+
}
267+
$objectBuilder->build();
268+
269+
return true;
270+
}
271+
243272
/**
244273
* @param string $argsObjectName
245274
* @param array $arguments
@@ -255,7 +284,7 @@ protected function generateArgumentsObject(string $argsObjectName, array $argume
255284
$this->generatedObjects[$argsObjectName] = true;
256285

257286
$objectBuilder = new ArgumentsObjectClassBuilder($this->writeDir, $argsObjectName, $this->generationNamespace);
258-
287+
259288
foreach ($arguments as $argumentArray) {
260289
$name = $argumentArray['name'];
261290
//$description = $inputFieldArray['description'];

src/SchemaGenerator/SchemaInspector.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,4 +161,26 @@ enumValues {
161161

162162
return $response->getData()['__type'];
163163
}
164+
165+
/**
166+
* @param string $objectName
167+
*
168+
* @return array
169+
*/
170+
public function getUnionObjectSchema(string $objectName): array
171+
{
172+
$schemaQuery = "{
173+
__type(name: \"$objectName\") {
174+
name
175+
kind
176+
possibleTypes {
177+
kind
178+
name
179+
}
180+
}
181+
}";
182+
$response = $this->client->runRawQuery($schemaQuery, true);
183+
184+
return $response->getData()['__type'];
185+
}
164186
}

src/SchemaObject/UnionObject.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace GraphQL\SchemaObject;
4+
5+
use GraphQL\InlineFragment;
6+
use GraphQL\Query;
7+
8+
/**
9+
* Class UnionObject
10+
*
11+
* @package GraphQL\SchemaObject
12+
*/
13+
abstract class UnionObject extends QueryObject
14+
{
15+
protected function addPossibleType(QueryObject $possibleType)
16+
{
17+
$fragment = new InlineFragment($possibleType::OBJECT_NAME, $possibleType);
18+
$this->selectField($fragment);
19+
}
20+
}

tests/QueryObjectClassBuilderTest.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace GraphQL\Tests;
44

5+
use GraphQL\Enumeration\FieldTypeKindEnum;
56
use GraphQL\SchemaGenerator\CodeGenerator\QueryObjectClassBuilder;
67
use GraphQL\SchemaObject\QueryObject;
78

@@ -100,7 +101,7 @@ public function testAddObjectSelector()
100101
$objectName = 'ObjectSelector';
101102
$classBuilder = new QueryObjectClassBuilder(static::getGeneratedFilesDir(), $objectName, static::TESTING_NAMESPACE);
102103
$objectName .= 'QueryObject';
103-
$classBuilder->addObjectField('others', 'Other', 'RootOthersArgumentsObject', false, null);
104+
$classBuilder->addObjectField('others', 'Other', FieldTypeKindEnum::OBJECT, 'RootOthersArgumentsObject', false, null);
104105
$classBuilder->build();
105106

106107
$this->assertFileEquals(
@@ -120,8 +121,8 @@ public function testAddMultipleObjectSelectors()
120121
$objectName = 'MultipleObjectSelectors';
121122
$classBuilder = new QueryObjectClassBuilder(static::getGeneratedFilesDir(), $objectName, static::TESTING_NAMESPACE);
122123
$objectName .= 'QueryObject';
123-
$classBuilder->addObjectField('right', 'MultipleObjectSelectorsRight', 'MultipleObjectSelectorsRightArgumentsObject', false, null);
124-
$classBuilder->addObjectField('left_objects', 'Left', 'MultipleObjectSelectorsLeftObjectsArgumentsObject', true, null);
124+
$classBuilder->addObjectField('right', 'MultipleObjectSelectorsRight', FieldTypeKindEnum::OBJECT, 'MultipleObjectSelectorsRightArgumentsObject', false, null);
125+
$classBuilder->addObjectField('left_objects', 'Left', FieldTypeKindEnum::OBJECT, 'MultipleObjectSelectorsLeftObjectsArgumentsObject', true, null);
125126
$classBuilder->build();
126127

127128
$this->assertFileEquals(

tests/SchemaClassGeneratorTest.php

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -780,7 +780,7 @@ public function testGenerateQueryObjectWithObjectFields()
780780
static::getExpectedFilesDir() . "/query_objects/$objectName.php",
781781
static::getGeneratedFilesDir() . "/$objectName.php"
782782
);
783-
783+
784784
// Test if the right classes are generated.
785785
$this->assertFileExists(static::getGeneratedFilesDir() . "/LeftQueryObject.php", "The query object name for the left field should consist of the type name Left plus QueryObject");
786786
$this->assertFileExists(static::getGeneratedFilesDir() . "/MultipleObjectSelectorsLeftObjectsArgumentsObject.php", "The argument object name for the left field should consist of the parent type name MultipleObjectSelectors plus the field name LeftObjects plus ArgumentsObject");
@@ -815,6 +815,80 @@ public function testGenerateRootObject()
815815
);
816816
}
817817

818+
/**
819+
* @covers \GraphQL\SchemaGenerator\SchemaClassGenerator::generateUnionObject
820+
*/
821+
public function testGenerateUnionObject()
822+
{
823+
$objectName = 'UnionTestObject';
824+
// Add mock responses
825+
$this->mockHandler->append(new Response(200, [], json_encode([
826+
'data' => [
827+
'__type' => [
828+
'name' => $objectName,
829+
'kind' => FieldTypeKindEnum::UNION_OBJECT,
830+
'possibleTypes' => [
831+
[
832+
'kind' => FieldTypeKindEnum::OBJECT,
833+
'name' => 'UnionObject1',
834+
], [
835+
'kind' => FieldTypeKindEnum::OBJECT,
836+
'name' => 'UnionObject2',
837+
],
838+
]
839+
]
840+
]
841+
])));
842+
$this->mockHandler->append(new Response(200, [], json_encode([
843+
'data' => [
844+
'__type' => [
845+
'name' => 'UnionObject1',
846+
'kind' => FieldTypeKindEnum::OBJECT,
847+
'fields' => [
848+
[
849+
'name' => 'union',
850+
'description' => null,
851+
'isDeprecated' => false,
852+
'deprecationReason' => null,
853+
'type' => [
854+
'name' => $objectName,
855+
'kind' => FieldTypeKindEnum::UNION_OBJECT,
856+
'description' => null,
857+
'ofType' => null,
858+
],
859+
'args' => null,
860+
],
861+
],
862+
]
863+
]
864+
])));
865+
$this->mockHandler->append(new Response(200, [], json_encode([
866+
'data' => [
867+
'__type' => [
868+
'name' => 'UnionObject2',
869+
'kind' => FieldTypeKindEnum::OBJECT,
870+
'fields' => [],
871+
]
872+
]
873+
])));
874+
875+
$this->classGenerator->generateUnionObject($objectName);
876+
877+
$objectName .= 'UnionObject';
878+
$this->assertFileEquals(
879+
static::getExpectedFilesDir() . "/union_objects/UnionObject1QueryObject.php",
880+
static::getGeneratedFilesDir() . "/UnionObject1QueryObject.php"
881+
);
882+
$this->assertFileEquals(
883+
static::getExpectedFilesDir() . "/union_objects/UnionObject2QueryObject.php",
884+
static::getGeneratedFilesDir() . "/UnionObject2QueryObject.php"
885+
);
886+
$this->assertFileEquals(
887+
static::getExpectedFilesDir() . "/union_objects/$objectName.php",
888+
static::getGeneratedFilesDir() . "/$objectName.php"
889+
);
890+
}
891+
818892
///**
819893
// * @covers \GraphQL\SchemaGenerator\SchemaClassGenerator::generateObject
820894
// */
@@ -869,4 +943,9 @@ public function getTypeInfo(array $dataArray): array
869943
{
870944
return parent::getTypeInfo($dataArray);
871945
}
946+
947+
public function generateUnionObject(string $objectName): bool
948+
{
949+
return parent::generateUnionObject($objectName);
950+
}
872951
}

0 commit comments

Comments
 (0)