Skip to content

Commit

Permalink
Add description and source field name properties to MagicField and So…
Browse files Browse the repository at this point in the history
…urceField annotations (#402)

* Add description property to MagicField and SourceField annotations

* Add source field name property for MagicField and SourceField annotations

* Update docs for MagicField and SourceField

Co-authored-by: Michael Vostrikov <mikhail.vostrikov@vseinstrumenti.ru>
  • Loading branch information
michael-vostrikov and Michael Vostrikov committed Nov 13, 2021
1 parent ba3637c commit 21bc850
Show file tree
Hide file tree
Showing 13 changed files with 159 additions and 7 deletions.
27 changes: 26 additions & 1 deletion src/Annotations/MagicField.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ class MagicField implements SourceFieldInterface
/** @var string|null */
private $phpType;

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

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

/** @var MiddlewareAnnotations */
private $middlewareAnnotations;

Expand All @@ -43,11 +49,14 @@ class MagicField implements SourceFieldInterface
/**
* @param mixed[] $attributes
*/
public function __construct(array $attributes = [], ?string $name = null, ?string $outputType = null, ?string $phpType = null)
public function __construct(array $attributes = [], ?string $name = null, ?string $outputType = null, ?string $phpType = null, ?string $description = null, ?string $sourceName = null)
{
$this->name = $attributes['name'] ?? $name;
$this->outputType = $attributes['outputType'] ?? $outputType ?? null;
$this->phpType = $attributes['phpType'] ?? $phpType ?? null;
$this->description = $attributes['description'] ?? $description ?? null;
$this->sourceName = $attributes['sourceName'] ?? $sourceName ?? null;

if (! $this->name || (! $this->outputType && ! $this->phpType)) {
throw new BadMethodCallException('The @MagicField annotation must be passed a name and an output type or a php type. For instance: "@MagicField(name=\'phone\', outputType=\'String!\')" or "@MagicField(name=\'phone\', phpType=\'string\')"');
}
Expand Down Expand Up @@ -101,6 +110,22 @@ public function getPhpType(): ?string
return $this->phpType;
}

/**
* Returns the description of the GraphQL query/mutation/field.
*/
public function getDescription(): ?string
{
return $this->description;
}

/**
* Returns the property name in the source class
*/
public function getSourceName(): ?string
{
return $this->sourceName;
}

public function getMiddlewareAnnotations(): MiddlewareAnnotations
{
return $this->middlewareAnnotations;
Expand Down
27 changes: 26 additions & 1 deletion src/Annotations/SourceField.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ class SourceField implements SourceFieldInterface
/** @var string|null */
private $phpType;

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

/** @var string */
private $sourceName;

/** @var MiddlewareAnnotations */
private $middlewareAnnotations;

Expand All @@ -43,7 +49,7 @@ class SourceField implements SourceFieldInterface
/**
* @param mixed[] $attributes
*/
public function __construct(array $attributes = [], ?string $name = null, ?string $outputType = null, ?string $phpType = null)
public function __construct(array $attributes = [], ?string $name = null, ?string $outputType = null, ?string $phpType = null, ?string $description = null, ?string $sourceName = null)
{
$name = $name ?? $attributes['name'] ?? null;
if ($name === null) {
Expand All @@ -53,6 +59,9 @@ public function __construct(array $attributes = [], ?string $name = null, ?strin

$this->outputType = $outputType ?? $attributes['outputType'] ?? null;
$this->phpType = $phpType ?? $attributes['phpType'] ?? null;
$this->description = $description ?? $attributes['description'] ?? null;
$this->sourceName = $sourceName ?? $attributes['sourceName'] ?? null;

if ($this->outputType && $this->phpType) {
throw new BadMethodCallException('In a @SourceField annotation, you cannot use the outputType and the phpType at the same time. For instance: "@SourceField(name=\'phone\', outputType=\'String!\')" or "@SourceField(name=\'phone\', phpType=\'string\')"');
}
Expand Down Expand Up @@ -99,6 +108,22 @@ public function getPhpType(): ?string
return $this->phpType;
}

/**
* Returns the description of the GraphQL query/mutation/field.
*/
public function getDescription(): ?string
{
return $this->description;
}

/**
* Returns the property name in the source class
*/
public function getSourceName(): ?string
{
return $this->sourceName;
}

public function getMiddlewareAnnotations(): MiddlewareAnnotations
{
return $this->middlewareAnnotations;
Expand Down
10 changes: 10 additions & 0 deletions src/Annotations/SourceFieldInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ public function getOutputType(): ?string;
*/
public function getPhpType(): ?string;

/**
* Returns the description of the GraphQL query/mutation/field.
*/
public function getDescription(): ?string;

/**
* Returns the property name in the source class
*/
public function getSourceName(): ?string;

public function getMiddlewareAnnotations(): MiddlewareAnnotations;

/**
Expand Down
9 changes: 6 additions & 3 deletions src/FieldsBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ private function getQueryFieldsFromSourceFields(array $sourceFields, ReflectionC

if (! $sourceField->shouldFetchFromMagicProperty()) {
try {
$refMethod = $this->getMethodFromPropertyName($objectRefClass, $sourceField->getName());
$refMethod = $this->getMethodFromPropertyName($objectRefClass, $sourceField->getSourceName() ?? $sourceField->getName());
} catch (FieldNotFoundException $e) {
throw FieldNotFoundException::wrapWithCallerInfo($e, $refClass->getName());
}
Expand All @@ -597,7 +597,8 @@ private function getQueryFieldsFromSourceFields(array $sourceFields, ReflectionC
$fieldDescriptor->setDeprecationReason(trim((string) $deprecated[0]));
}

$fieldDescriptor->setComment($docBlockComment);
$description = $sourceField->getDescription() ?? $docBlockComment;
$fieldDescriptor->setComment($description);
$args = $this->mapParameters($refMethod->getParameters(), $docBlockObj, $sourceField);

$fieldDescriptor->setParameters($args);
Expand All @@ -612,7 +613,9 @@ private function getQueryFieldsFromSourceFields(array $sourceFields, ReflectionC
$type = $this->typeMapper->mapReturnType($refMethod, $docBlockObj);
}
} else {
$fieldDescriptor->setMagicProperty($sourceField->getName());
$fieldDescriptor->setMagicProperty($sourceField->getSourceName() ?? $sourceField->getName());
$fieldDescriptor->setComment($sourceField->getDescription());

$outputType = $sourceField->getOutputType();
if ($outputType !== null) {
$type = $this->resolveOutputType($outputType, $refClass, $sourceField);
Expand Down
26 changes: 26 additions & 0 deletions tests/FieldsBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
use TheCodingMachine\GraphQLite\Fixtures\TestTypeWithPrefetchMethod;
use TheCodingMachine\GraphQLite\Fixtures\TestTypeWithSourceFieldInterface;
use TheCodingMachine\GraphQLite\Fixtures\TestTypeWithSourceFieldInvalidParameterAnnotation;
use TheCodingMachine\GraphQLite\Fixtures\TestSourceName;
use TheCodingMachine\GraphQLite\Fixtures\TestSourceNameType;
use TheCodingMachine\GraphQLite\Mappers\CannotMapTypeException;
use TheCodingMachine\GraphQLite\Mappers\CannotMapTypeExceptionInterface;
use TheCodingMachine\GraphQLite\Middlewares\AuthorizationFieldMiddleware;
Expand Down Expand Up @@ -253,6 +255,7 @@ public function testSourceField(): void
$this->assertInstanceOf(ObjectType::class, $fields['sibling']->getType()->getWrappedType());
$this->assertSame('TestObject', $fields['sibling']->getType()->getWrappedType()->name);
$this->assertSame('This is a test summary', $fields['test']->description);
$this->assertSame('Test SourceField description', $fields['sibling']->description);
}

public function testSourceFieldOnSelfType(): void
Expand Down Expand Up @@ -777,6 +780,7 @@ public function testMagicField(): void
$this->assertCount(1, $fields);
$query = $fields['foo'];
$this->assertSame('foo', $query->name);
$this->assertSame('Test MagicField description', $query->description);

$resolve = $query->resolveFn;
$result = $resolve(new TestTypeWithMagicProperty(), [], null, $this->createMock(ResolveInfo::class));
Expand Down Expand Up @@ -804,4 +808,26 @@ public function testProxyClassWithMagicPropertyOfPhpType(): void

$this->assertSame('foo', $result);
}

public function testSourceNameInSourceAndMagicFields(): void
{
$controller = new TestSourceNameType();
$queryProvider = $this->buildFieldsBuilder();
$fields = $queryProvider->getFields($controller);
$source = new TestSourceName('foo value', 'bar value');

$this->assertCount(2, $fields);

$query = $fields['foo2'];
$this->assertSame('foo2', $query->name);
$resolve = $query->resolveFn;
$result = $resolve($source, [], null, $this->createMock(ResolveInfo::class));
$this->assertSame('foo value', $result);

$query = $fields['bar2'];
$this->assertSame('bar2', $query->name);
$resolve = $query->resolveFn;
$result = $resolve($source, [], null, $this->createMock(ResolveInfo::class));
$this->assertSame('bar value', $result);
}
}
34 changes: 34 additions & 0 deletions tests/Fixtures/TestSourceName.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace TheCodingMachine\GraphQLite\Fixtures;

use Exception;

class TestSourceName
{
/** @var string */
private $foo;

/** @var string */
private $bar;

public function __construct(string $foo, string $bar)
{
$this->foo = $foo;
$this->bar = $bar;
}

public function __get($name)
{
if ($name !== 'foo') {
throw new Exception('Unknown property: ' . $name);
}

return $this->$name;
}

public function getBar(): string
{
return $this->bar;
}
}
17 changes: 17 additions & 0 deletions tests/Fixtures/TestSourceNameType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace TheCodingMachine\GraphQLite\Fixtures;

use TheCodingMachine\GraphQLite\Fixtures\TestSourceName;
use TheCodingMachine\GraphQLite\Annotations\MagicField;
use TheCodingMachine\GraphQLite\Annotations\SourceField;
use TheCodingMachine\GraphQLite\Annotations\Type;

/**
* @Type(class=TestSourceName::class)
* @MagicField(name="foo2", outputType="String!", sourceName="foo")
* @SourceField(name="bar2", sourceName="bar")
*/
class TestSourceNameType
{
}
2 changes: 1 addition & 1 deletion tests/Fixtures/TestType.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* @SourceField(name="test")
* @SourceField(name="testBool", annotations={@Logged, @HideIfUnauthorized})
* @SourceField(name="testRight", annotations={@Right(name="FOOBAR"), @HideIfUnauthorized})
* @SourceField(name="sibling")
* @SourceField(name="sibling", description="Test SourceField description")
*/
class TestType
{
Expand Down
2 changes: 1 addition & 1 deletion tests/Fixtures/TestTypeWithMagicProperty.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

/**
* @Type()
* @MagicField(name="foo", outputType="String!")
* @MagicField(name="foo", outputType="String!", description="Test MagicField description")
*/
class TestTypeWithMagicProperty
{
Expand Down
4 changes: 4 additions & 0 deletions website/docs/annotations-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ Attribute | Compulsory | Type | Definition
name | *yes* | string | The name of the field.
[outputType](custom-types.mdx) | *no* | string | Forces the GraphQL output type of the field. Otherwise, return type is used.
phpType | *no* | string | The PHP type of the field (as you would write it in a Docblock)
description | *no* | string | Field description displayed in the GraphQL docs. If it's empty PHP doc comment of the method in the source class is used instead.
sourceName | *no* | string | The name of the property in the source class. If not set, the `name` will be used to get property value.
annotations | *no* | array\<Annotations\> | A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #SourceField PHP 8 attribute)

**Note**: `outputType` and `phpType` are mutually exclusive.
Expand All @@ -111,6 +113,8 @@ Attribute | Compulsory | Type | Definition
name | *yes* | string | The name of the field.
[outputType](custom-types.mdx) | *no*(*) | string | The GraphQL output type of the field.
phpType | *no*(*) | string | The PHP type of the field (as you would write it in a Docblock)
description | *no* | string | Field description displayed in the GraphQL docs. If not set, no description will be shown.
sourceName | *no* | string | The name of the property in the source class. If not set, the `name` will be used to get property value.
annotations | *no* | array\<Annotations\> | A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #MagicField PHP 8 attribute)

(*) **Note**: `outputType` and `phpType` are mutually exclusive. You MUST provide one of them.
Expand Down
2 changes: 2 additions & 0 deletions website/docs/external-type-declaration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ class ProductType
By doing so, you let GraphQLite know that the type exposes the `getName` method of the underlying `Product` object.

Internally, GraphQLite will look for methods named `name()`, `getName()` and `isName()`).
You can set different name to look for with `sourceName` attribute.

## `@MagicField` annotation

Expand Down Expand Up @@ -187,6 +188,7 @@ class ProductType
</Tabs>

By doing so, you let GraphQLite know that the type exposes "name" and the "price" magic properties of the underlying `Product` object.
You can set different name to look for with `sourceName` attribute.

This is particularly useful in frameworks like Laravel, where Eloquent is making a very wide use of such properties.

Expand Down
4 changes: 4 additions & 0 deletions website/versioned_docs/version-5.0/annotations-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ Attribute | Compulsory | Type | Definition
name | *yes* | string | The name of the field.
[outputType](custom-types.mdx) | *no* | string | Forces the GraphQL output type of the field. Otherwise, return type is used.
phpType | *no* | string | The PHP type of the field (as you would write it in a Docblock)
description | *no* | string | Field description displayed in the GraphQL docs. If it's empty PHP doc comment of the method in the source class is used instead.
sourceName | *no* | string | The name of the property in the source class. If not set, the `name` will be used to get property value.
annotations | *no* | array\<Annotations\> | A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #SourceField PHP 8 attribute)

**Note**: `outputType` and `phpType` are mutually exclusive.
Expand All @@ -111,6 +113,8 @@ Attribute | Compulsory | Type | Definition
name | *yes* | string | The name of the field.
[outputType](custom-types.mdx) | *no*(*) | string | The GraphQL output type of the field.
phpType | *no*(*) | string | The PHP type of the field (as you would write it in a Docblock)
description | *no* | string | Field description displayed in the GraphQL docs. If not set, no description will be shown.
sourceName | *no* | string | The name of the property in the source class. If not set, the `name` will be used to get property value.
annotations | *no* | array\<Annotations\> | A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #MagicField PHP 8 attribute)

(*) **Note**: `outputType` and `phpType` are mutually exclusive. You MUST provide one of them.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ class ProductType
By doing so, you let GraphQLite know that the type exposes the `getName` method of the underlying `Product` object.

Internally, GraphQLite will look for methods named `name()`, `getName()` and `isName()`).
You can set different name to look for with `sourceName` attribute.

## `@MagicField` annotation

Expand Down Expand Up @@ -187,6 +188,7 @@ class ProductType
</Tabs>

By doing so, you let GraphQLite know that the type exposes "name" and the "price" magic properties of the underlying `Product` object.
You can set different name to look for with `sourceName` attribute.

This is particularly useful in frameworks like Laravel, where Eloquent is making a very wide use of such properties.

Expand Down

0 comments on commit 21bc850

Please sign in to comment.