Skip to content

Commit

Permalink
Merge pull request #89 from moufmouf/hide_parameter_annotation
Browse files Browse the repository at this point in the history
Adding a new @HideParameter annotation
  • Loading branch information
moufmouf committed Jun 26, 2019
2 parents 70766cc + d3f0d0a commit 107099a
Show file tree
Hide file tree
Showing 7 changed files with 201 additions and 2 deletions.
10 changes: 9 additions & 1 deletion docs/annotations_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ name | *yes* | string | The GraphQL input type name extended by t

## @Autowire annotation

Resolves a PHP parameter from the container.
[Resolves a PHP parameter from the container](autowiring.md).

Useful to inject services directly into `@Field` method arguments.

Expand All @@ -152,3 +152,11 @@ Attribute | Compulsory | Type | Definition
---------------|------------|------|--------
*for* | *yes* | string | The name of the PHP parameter
*identifier* | *no* | string | The identifier of the service to fetch. This is optional. Please avoid using this attribute as this leads to a "service locator" anti-pattern.

## @HideParameter annotation

Removes [an argument from the GraphQL schema](input_types.md#ignoring_some_parameters).

Attribute | Compulsory | Type | Definition
---------------|------------|------|--------
*for* | *yes* | string | The name of the PHP parameter to hide
27 changes: 26 additions & 1 deletion docs/input_types.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ You can use the `@UseInputType` annotation to force an input type of a parameter

Let's say you want to force a parameter to be of type "ID", you can use this:

```
```php
/**
* @Factory()
* @UseInputType(for="$id", inputType="ID!")
Expand Down Expand Up @@ -211,3 +211,28 @@ class ProductController
}
}
```

### Ignoring some parameters
<small>Available in GraphQLite 4.0+</small>

GraphQLite will automatically map all your parameters to an input type.
But sometimes, you might want to avoid exposing some of those parameters.

Image your `getProductById` has an additional `lazyLoad` parameter. This parameter is interesting when you call
directly the function in PHP because you can have some level of optimisation on your code. But it is not something that
you want to expose in the GraphQL API. Let's hide it!

```php
/**
* @Factory()
* @HideParameter(for="$lazyLoad")
*/
public function getProductById(string $id, bool $lazyLoad = true): Product
{
return $this->productRepository->findById($id, $lazyLoad);
}
```

With the `@HideParameter` annotation, you can choose to remove from the GraphQL schema any argument.

To be able to hide an argument, the argument must have a default value.
40 changes: 40 additions & 0 deletions src/Annotations/HideParameter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

declare(strict_types=1);

namespace TheCodingMachine\GraphQLite\Annotations;

use BadMethodCallException;
use function ltrim;

/**
* Use this annotation to tell GraphQLite to ignore a parameter and not expose it as an input parameter.
* This is only possible if the parameter has a default value.
*
* @Annotation
* @Target({"METHOD"})
* @Attributes({
* @Attribute("for", type = "string")
* })
*/
class HideParameter implements ParameterAnnotationInterface
{
/** @var string */
private $for;

/**
* @param array<string, mixed> $values
*/
public function __construct(array $values)
{
if (! isset($values['for'])) {
throw new BadMethodCallException('The @HideParameter annotation must be passed a target. For instance: "@HideParameter(for="$myParameterToHide")"');
}
$this->for = ltrim($values['for'], '$');
}

public function getTarget(): string
{
return $this->for;
}
}
21 changes: 21 additions & 0 deletions src/Mappers/Parameters/CannotHideParameterException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace TheCodingMachine\GraphQLite\Mappers\Parameters;

use ReflectionMethod;
use ReflectionParameter;
use TheCodingMachine\GraphQLite\GraphQLException;
use Webmozart\Assert\Assert;

class CannotHideParameterException extends GraphQLException
{
public static function needDefaultValue(ReflectionParameter $parameter): self
{
$method = $parameter->getDeclaringFunction();
Assert::isInstanceOf($method, ReflectionMethod::class);

return new self('For parameter $' . $parameter->getName() . ' of method ' . $method->getDeclaringClass()->getName() . '::' . $method->getName() . '(), cannot use the @HideParameter annotation. The parameter needs to provide a default value.');
}
}
11 changes: 11 additions & 0 deletions src/Mappers/Parameters/TypeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@
use ReflectionMethod;
use ReflectionParameter;
use ReflectionType;
use TheCodingMachine\GraphQLite\Annotations\HideParameter;
use TheCodingMachine\GraphQLite\Annotations\ParameterAnnotations;
use TheCodingMachine\GraphQLite\Annotations\UseInputType;
use TheCodingMachine\GraphQLite\InvalidDocBlockException;
use TheCodingMachine\GraphQLite\Mappers\CannotMapTypeException;
use TheCodingMachine\GraphQLite\Mappers\CannotMapTypeExceptionInterface;
use TheCodingMachine\GraphQLite\Mappers\RecursiveTypeMapperInterface;
use TheCodingMachine\GraphQLite\Mappers\Root\RootTypeMapperInterface;
use TheCodingMachine\GraphQLite\Parameters\DefaultValueParameter;
use TheCodingMachine\GraphQLite\Parameters\InputTypeParameter;
use TheCodingMachine\GraphQLite\Parameters\ParameterInterface;
use TheCodingMachine\GraphQLite\TypeMappingException;
Expand Down Expand Up @@ -115,6 +117,15 @@ private function getDocBlocReturnType(DocBlock $docBlock, ReflectionMethod $refM

public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations): ParameterInterface
{
$hideParameter = $parameterAnnotations->getAnnotationByType(HideParameter::class);
if ($hideParameter) {
if ($parameter->isDefaultValueAvailable() === false) {
throw CannotHideParameterException::needDefaultValue($parameter);
}

return new DefaultValueParameter($parameter->getDefaultValue());
}

/** @var UseInputType|null $useInputType */
$useInputType = $parameterAnnotations->getAnnotationByType(UseInputType::class);
if ($useInputType !== null) {
Expand Down
35 changes: 35 additions & 0 deletions src/Parameters/DefaultValueParameter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace TheCodingMachine\GraphQLite\Parameters;

use GraphQL\Type\Definition\ResolveInfo;

/**
* Fills a parameter with a default value. Always.
*/
class DefaultValueParameter implements ParameterInterface
{
/** @var mixed */
private $defaultValue;

/**
* @param mixed $defaultValue
*/
public function __construct($defaultValue)
{
$this->defaultValue = $defaultValue;
}

/**
* @param array<string, mixed> $args
* @param mixed $context
*
* @return mixed
*/
public function resolve(?object $source, array $args, $context, ResolveInfo $info)
{
return $this->defaultValue;
}
}
59 changes: 59 additions & 0 deletions tests/Mappers/Parameters/TypeMapperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

namespace TheCodingMachine\GraphQLite\Mappers\Parameters;

use GraphQL\Type\Definition\ResolveInfo;
use ReflectionMethod;
use Symfony\Component\Cache\Simple\ArrayCache;
use TheCodingMachine\GraphQLite\AbstractQueryProviderTest;
use TheCodingMachine\GraphQLite\Annotations\HideParameter;
use TheCodingMachine\GraphQLite\Mappers\CannotMapTypeException;
use TheCodingMachine\GraphQLite\Mappers\Root\BaseTypeMapper;
use TheCodingMachine\GraphQLite\Mappers\Root\CompositeRootTypeMapper;
use TheCodingMachine\GraphQLite\Mappers\Root\MyCLabsEnumTypeMapper;
use TheCodingMachine\GraphQLite\Parameters\DefaultValueParameter;
use TheCodingMachine\GraphQLite\Reflection\CachedDocBlockFactory;

class TypeMapperTest extends AbstractQueryProviderTest
Expand All @@ -31,10 +34,66 @@ public function testMapScalarUnionException()
$typeMapper->mapReturnType($refMethod, $docBlockObj);
}

public function testHideParameter()
{
$typeMapper = new TypeMapper($this->getTypeMapper(), $this->getArgumentResolver(), new CompositeRootTypeMapper([
new MyCLabsEnumTypeMapper(),
new BaseTypeMapper($this->getTypeMapper())
]), $this->getTypeResolver());

$cachedDocBlockFactory = new CachedDocBlockFactory(new ArrayCache());

$refMethod = new ReflectionMethod($this, 'withDefaultValue');
$refParameter = $refMethod->getParameters()[0];
$docBlockObj = $cachedDocBlockFactory->getDocBlock($refMethod);
$annotations = $this->getAnnotationReader()->getParameterAnnotations($refParameter);

$param = $typeMapper->mapParameter($refParameter, $docBlockObj, null, $annotations);

$this->assertInstanceOf(DefaultValueParameter::class, $param);

$resolveInfo = $this->createMock(ResolveInfo::class);
$this->assertSame(24, $param->resolve(null, [], null, $resolveInfo));
}

public function testHideParameterException()
{
$typeMapper = new TypeMapper($this->getTypeMapper(), $this->getArgumentResolver(), new CompositeRootTypeMapper([
new MyCLabsEnumTypeMapper(),
new BaseTypeMapper($this->getTypeMapper())
]), $this->getTypeResolver());

$cachedDocBlockFactory = new CachedDocBlockFactory(new ArrayCache());

$refMethod = new ReflectionMethod($this, 'withoutDefaultValue');
$refParameter = $refMethod->getParameters()[0];
$docBlockObj = $cachedDocBlockFactory->getDocBlock($refMethod);
$annotations = $this->getAnnotationReader()->getParameterAnnotations($refParameter);

$this->expectException(CannotHideParameterException::class);
$this->expectExceptionMessage('For parameter $foo of method TheCodingMachine\GraphQLite\Mappers\Parameters\TypeMapperTest::withoutDefaultValue(), cannot use the @HideParameter annotation. The parameter needs to provide a default value.');

$typeMapper->mapParameter($refParameter, $docBlockObj, null, $annotations);
}

/**
* @return int|string
*/
private function dummy() {

}

/**
* @HideParameter(for="$foo")
*/
private function withDefaultValue($foo = 24) {

}

/**
* @HideParameter(for="$foo")
*/
private function withoutDefaultValue($foo) {

}
}

0 comments on commit 107099a

Please sign in to comment.