Skip to content

Commit

Permalink
Merge pull request #102 from moufmouf/target_extend_type_by_name
Browse files Browse the repository at this point in the history
Allowing targetting @ExtendType by name
  • Loading branch information
moufmouf committed Jul 8, 2019
2 parents 66a9c74 + 6943617 commit b41a6b1
Show file tree
Hide file tree
Showing 15 changed files with 203 additions and 40 deletions.
7 changes: 6 additions & 1 deletion docs/annotations_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ The `@Type` annotation is used to declare a GraphQL object type.
Attribute | Compulsory | Type | Definition
---------------|------------|------|--------
class | *no* | string | The targeted class. If no class is passed, the type applies to the current class. The current class is assumed to be an entity. If the "class" attribute is passed, [the class annotated with `@Type` is a service](external_type_declaration.md).
name | *no* | string | The name of the GraphQL type generated. If not passed, the name of the class is used. If the class ends with "Type", the "Type" suffix is removed
default | *no* | bool | Defaults to *true*. Whether the targeted PHP class should be mapped by default to this type.


## @ExtendType annotation
Expand All @@ -45,7 +47,10 @@ The `@ExtendType` annotation is used to add fields to an existing GraphQL object

Attribute | Compulsory | Type | Definition
---------------|------------|------|--------
class | *yes* | string | The targeted class. [The class annotated with `@ExtendType` is a service](extend_type.md).
class | see below | string | The targeted class. [The class annotated with `@ExtendType` is a service](extend_type.md).
name | see below | string | The targeted GraphQL output type.

One and only one of "class" and "name" parameter can be passed at the same time.

## @Field annotation

Expand Down
23 changes: 23 additions & 0 deletions docs/multiple_output_types.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,26 @@ Notice how the "outputType" attribute is used in the `@Field` annotation to forc
Is a result, when the end user calls the `product` query, we will have the possibility to fetch the `name` and `price` fields,
but if he calls the `products` query, each product in the list will have a `name` field but no `price` field. We managed
to successfully expose a different set of fields based on the query context.

## Extending a non-default type

If you want to extend a type using the `@ExtendType` annotation and if this type is declared as non-default,
you need to target the type by name instead of by class.

So instead of writing:

```php
/**
* @ExtendType(class=Product::class)
*/
```

you will write:

```php
/**
* @ExtendType(name="LimitedProduct")
*/
```

Notice how we use the "name" attribute instead of the "class" attribute in the `@ExtendType` annotation.
25 changes: 19 additions & 6 deletions src/Annotations/ExtendType.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,27 @@
* @Target({"CLASS"})
* @Attributes({
* @Attribute("class", type = "string"),
* @Attribute("name", type = "string"),
* })
*/
class ExtendType
{
/** @var string */
/** @var string|null */
private $class;
/** @var string|null */
private $name;

/**
* @param mixed[] $attributes
*/
public function __construct(array $attributes = [])
{
if (! isset($attributes['class'])) {
throw new BadMethodCallException('In annotation @ExtendType, missing compulsory parameter "class".');
if (! isset($attributes['class']) && ! isset($attributes['name'])) {
throw new BadMethodCallException('In annotation @ExtendType, missing one of the compulsory parameter "class" or "name".');
}
$this->class = $attributes['class'];
if (! class_exists($this->class)) {
$this->class = $attributes['class'] ?? null;
$this->name = $attributes['name'] ?? null;
if ($this->class !== null && ! class_exists($this->class)) {
throw ClassNotFoundException::couldNotFindClass($this->class);
}
}
Expand All @@ -41,8 +45,17 @@ public function __construct(array $attributes = [])
* Returns the name of the GraphQL query/mutation/field.
* If not specified, the name of the method should be used instead.
*/
public function getClass(): string
public function getClass(): ?string
{
if ($this->class === null) {
return null;
}

return ltrim($this->class, '\\');
}

public function getName(): ?string
{
return $this->name;
}
}
14 changes: 14 additions & 0 deletions src/FieldsBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
use TheCodingMachine\GraphQLite\Parameters\ParameterInterface;
use TheCodingMachine\GraphQLite\Reflection\CachedDocBlockFactory;
use TheCodingMachine\GraphQLite\Types\ArgumentResolver;
use TheCodingMachine\GraphQLite\Types\MutableObjectType;
use TheCodingMachine\GraphQLite\Types\TypeResolver;
use Webmozart\Assert\Assert;
use function array_merge;
Expand Down Expand Up @@ -357,6 +358,19 @@ private function getQueryFieldsFromSourceFields(array $sourceFields, ReflectionC
$objectClass = $typeField->getClass();
} elseif ($extendTypeField !== null) {
$objectClass = $extendTypeField->getClass();
if ($objectClass === null) {
// We need to be able to fetch the mapped PHP class from the object type!
$typeName = $extendTypeField->getName();
Assert::notNull($typeName);
$targetedType = $this->recursiveTypeMapper->mapNameToType($typeName);
if (! $targetedType instanceof MutableObjectType) {
throw CannotMapTypeException::extendTypeWithBadTargetedClass($refClass->getName(), $extendTypeField);
}
$objectClass = $targetedType->getMappedClassName();
if ($objectClass === null) {
throw new CannotMapTypeException('@ExtendType(name="' . $extendTypeField->getName() . '") points to a GraphQL type that does not map a PHP class. Therefore, you cannot use the @SourceField annotation in conjunction with this @ExtendType.');
}
}
} else {
throw MissingAnnotationException::missingTypeExceptionToUseSourceField();
}
Expand Down
16 changes: 16 additions & 0 deletions src/Mappers/CannotMapTypeException.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use ReflectionClass;
use ReflectionMethod;
use ReflectionParameter;
use TheCodingMachine\GraphQLite\Annotations\ExtendType;
use TheCodingMachine\GraphQLite\Annotations\SourceFieldInterface;
use Webmozart\Assert\Assert;
use function array_filter;
Expand Down Expand Up @@ -123,4 +124,19 @@ public static function createForDecorateName(string $name, InputObjectType $type
{
return new self('cannot decorate GraphQL input type "' . $type->name . '" with type "' . $name . '". Check your TypeMapper configuration.');
}

public static function extendTypeWithBadTargetedClass(string $className, ExtendType $extendType): self
{
return new self('For ' . self::extendTypeToString($extendType) . ' annotation declared in class "' . $className . '", the pointed at GraphQL type cannot be extended. You can only target types extending the MutableObjectType (like types created with the @Type annotation).');
}

private static function extendTypeToString(ExtendType $extendType): string
{
$attribute = 'class="' . $extendType->getClass() . '"';
if ($extendType->getName() !== null) {
$attribute = 'name="' . $extendType->getName() . '"';
}

return '@ExtendType(' . $attribute . ')';
}
}
6 changes: 3 additions & 3 deletions src/Mappers/GlobExtendAnnotationsCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ class GlobExtendAnnotationsCache
/** @var string|null */
private $extendTypeClassName;

/** @var string|null */
/** @var string */
private $extendTypeName;

public function setExtendType(string $className, string $typeName): void
public function setExtendType(?string $className, string $typeName): void
{
$this->extendTypeClassName = $className;
$this->extendTypeName = $typeName;
Expand All @@ -28,7 +28,7 @@ public function getExtendTypeClassName(): ?string
return $this->extendTypeClassName;
}

public function getExtendTypeName(): ?string
public function getExtendTypeName(): string
{
return $this->extendTypeName;
}
Expand Down
6 changes: 2 additions & 4 deletions src/Mappers/GlobExtendTypeMapperCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,10 @@ public function registerAnnotations(ReflectionClass $refClass, GlobExtendAnnotat
$className = $refClass->getName();

$typeClassName = $globExtendAnnotationsCache->getExtendTypeClassName();
if ($typeClassName === null) {
return;
if ($typeClassName !== null) {
$this->mapClassToExtendTypeArray[$typeClassName][$className] = $className;
}

$this->mapClassToExtendTypeArray[$typeClassName][$className] = $className;

$typeName = $globExtendAnnotationsCache->getExtendTypeName();
$this->mapNameToExtendType[$typeName][$className] = $className;
}
Expand Down
19 changes: 16 additions & 3 deletions src/Mappers/GlobTypeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
use TheCodingMachine\GraphQLite\TypeGenerator;
use TheCodingMachine\GraphQLite\Types\MutableObjectType;
use TheCodingMachine\GraphQLite\Types\ResolvableMutableInputInterface;
use Webmozart\Assert\Assert;
use function class_exists;
use function str_replace;

Expand Down Expand Up @@ -240,10 +241,22 @@ private function buildMapClassToExtendTypeArray(): GlobExtendTypeMapperCache
$extendType = $this->annotationReader->getExtendTypeAnnotation($refClass);

if ($extendType !== null) {
$targetType = $this->recursiveTypeMapper->mapClassToType($extendType->getClass(), null);
$typeName = $targetType->name;
$extendClassName = $extendType->getClass();
if ($extendClassName !== null) {
$targetType = $this->recursiveTypeMapper->mapClassToType($extendClassName, null);
$typeName = $targetType->name;
} else {
$typeName = $extendType->getName();
Assert::notNull($typeName);
$targetType = $this->recursiveTypeMapper->mapNameToType($typeName);
if (! $targetType instanceof MutableObjectType) {
throw CannotMapTypeException::extendTypeWithBadTargetedClass($refClass->getName(), $extendType);
}
$extendClassName = $targetType->getMappedClassName();
}

$extendAnnotationsCache->setExtendType($extendType->getClass(), $typeName);
// FIXME: $extendClassName === NULL!!!!!!
$extendAnnotationsCache->setExtendType($extendClassName, $typeName);

return $extendAnnotationsCache;
}
Expand Down
13 changes: 12 additions & 1 deletion src/Types/MutableObjectType.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,18 @@ class MutableObjectType extends ObjectType

/** @var FieldDefinition[]|null */
private $finalFields;
/** @var string|null */
private $className;

/**
* @param mixed[] $config
*/
public function __construct(array $config)
public function __construct(array $config, ?string $className = null)
{
$this->status = self::STATUS_PENDING;

parent::__construct($config);
$this->className = $className;
}

public function freeze(): void
Expand Down Expand Up @@ -109,4 +112,12 @@ public function getFields(): array

return $this->finalFields;
}

/**
* Returns the PHP class mapping this GraphQL type (if any)
*/
public function getMappedClassName(): ?string
{
return $this->className;
}
}
12 changes: 1 addition & 11 deletions src/Types/TypeAnnotatedObjectType.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,12 @@
*/
class TypeAnnotatedObjectType extends MutableObjectType
{
/** @var string */
private $className;

/**
* @param mixed[] $config
*/
public function __construct(string $className, array $config)
{
$this->className = $className;

parent::__construct($config);
parent::__construct($config, $className);
}

public static function createFromAnnotatedClass(string $typeName, string $className, ?object $annotatedObject, FieldsBuilder $fieldsBuilder, RecursiveTypeMapperInterface $recursiveTypeMapper, bool $doNotMapInterfaces): self
Expand Down Expand Up @@ -64,9 +59,4 @@ public static function createFromAnnotatedClass(string $typeName, string $classN
},
]);
}

public function getMappedClassName(): string
{
return $this->className;
}
}
2 changes: 1 addition & 1 deletion tests/Annotations/ExtendTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class ExtendTypeTest extends TestCase
public function testException()
{
$this->expectException(BadMethodCallException::class);
$this->expectExceptionMessage('In annotation @ExtendType, missing compulsory parameter "class".');
$this->expectExceptionMessage('In annotation @ExtendType, missing one of the compulsory parameter "class" or "name".');
new ExtendType([]);
}
}
13 changes: 13 additions & 0 deletions tests/Fixtures/BadExtendType/BadExtendType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php


namespace TheCodingMachine\GraphQLite\Fixtures\BadExtendType;

use TheCodingMachine\GraphQLite\Annotations\ExtendType;

/**
* @ExtendType(name="TestObjectInput")
*/
class BadExtendType
{
}
27 changes: 27 additions & 0 deletions tests/Fixtures/Integration/Types/ExtendedContactOtherType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php


namespace TheCodingMachine\GraphQLite\Fixtures\Integration\Types;

use function array_search;
use function strtoupper;
use TheCodingMachine\GraphQLite\Annotations\ExtendType;
use TheCodingMachine\GraphQLite\Annotations\Field;
use TheCodingMachine\GraphQLite\Annotations\SourceField;
use TheCodingMachine\GraphQLite\Annotations\Type;
use TheCodingMachine\GraphQLite\Fixtures\Integration\Models\Contact;

/**
* @ExtendType(name="ContactOther")
* @SourceField(name="name")
*/
class ExtendedContactOtherType
{
/**
* @Field()
*/
public function phone(Contact $contact): string
{
return "0123456789";
}
}
6 changes: 5 additions & 1 deletion tests/Integration/EndToEndTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -803,7 +803,9 @@ public function testEndToEndNonDefaultOutputType(): void
$queryString = '
query {
otherContact {
name
fullName
phone
}
}
';
Expand All @@ -815,7 +817,9 @@ public function testEndToEndNonDefaultOutputType(): void

$this->assertSame([
'otherContact' => [
'fullName' => 'JOE'
'name' => 'Joe',
'fullName' => 'JOE',
'phone' => '0123456789'
]
], $result->toArray(Debug::RETHROW_INTERNAL_EXCEPTIONS)['data']);
}
Expand Down
Loading

0 comments on commit b41a6b1

Please sign in to comment.