Skip to content

Alias for arguments #517

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Nov 25, 2019
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ CHANGELOG
--------------
### Added
- Allow passing through an instance of a `Field` [\#521 / georgeboot](https://github.com/rebing/graphql-laravel/pull/521/files)
- Add the ability to alias query and mutations arguments as well as input objects [\#517 / crissi](https://github.com/rebing/graphql-laravel/pull/517/files)
### Fixed
- Fix validation rules for non-null list of non-null objects [\#511 / crissi](https://github.com/rebing/graphql-laravel/pull/511/files)
- Add morph type to returned models [\#503 / crissi](https://github.com/rebing/graphql-laravel/pull/503)
Expand Down
96 changes: 95 additions & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ To work this around:
## Usage

- [Laravel GraphQL](#laravel-graphql)
- [Note: these are the docs for 3.*, please see the `v1` branch for the 1.* docs](#note-these-are-the-docs-for-3-please-see-the-v1-branch-for-the-1-docs)
- [Note: these are the docs for the current release, please see the `v1` branch for the 1.* docs](#note-these-are-the-docs-for-the-current-release-please-see-the-v1-branch-for-the-1-docs)
- [Installation](#installation)
- [Dependencies:](#dependencies)
- [Installation:](#installation)
Expand All @@ -128,6 +128,7 @@ To work this around:
- [Interfaces](#interfaces)
- [Sharing Interface fields](#sharing-interface-fields)
- [Input Object](#input-object)
- [Input Alias](#input-alias)
- [JSON Columns](#json-columns)
- [Field deprecation](#field-deprecation)
- [Default Field Resolver](#default-field-resolver)
Expand Down Expand Up @@ -1556,6 +1557,99 @@ class TestMutation extends GraphQLType {
}
```

### Input Alias

It is possible to alias query and mutation arguments as well as input object fields.

It can be especially useful for mutations saving data to the database.

Here you might want the input names to be different from the column names in the database.

Example, where the database columns are `first_name` and `last_name`:

```php
<?php

namespace App\GraphQL\InputObject;

use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\InputType;

class UserInput extends InputType
{
protected $attributes = [
'name' => 'UserInput',
'description' => 'A review with a comment and a score (0 to 5)'
];

public function fields(): array
{
return [
'firstName' => [
'alias' => 'first_name',
'description' => 'A comment (250 max chars)',
'type' => Type::string(),
'rules' => ['max:250']
],
'lastName' => [
'alias' => 'last_name',
'description' => 'A score (0 to 5)',
'type' => Type::int(),
'rules' => ['min:0', 'max:5']
]
];
}
}
```


```php
<?php

namespace App\GraphQL\Mutations;

use CLosure;
use App\User;
use GraphQL;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\ResolveInfo;
use Rebing\GraphQL\Support\Mutation;

class UpdateUserMutation extends Mutation
{
protected $attributes = [
'name' => 'UpdateUser'
];

public function type(): Type
{
return GraphQL::type('user');
}

public function args(): array
{
return [
'id' => [
'type' => Type::nonNull(Type::string())
],
'input' => [
'type' => GraphQL::type('UserInput')
]
];
}

public function resolve($root, $args, $context, ResolveInfo $resolveInfo, Closure $getSelectFields)
{
$user = User::find($args['id']);
$user->fill($args['input']));
$user->save();

return $user;
}
}
```


### JSON Columns

When using JSON columns in your database, the field won't be defined as a "relationship",
Expand Down
84 changes: 84 additions & 0 deletions src/Support/AliasArguments/AliasArguments.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

declare(strict_types=1);

namespace Rebing\GraphQL\Support\AliasArguments;

use GraphQL\Type\Definition\InputObjectField;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\ListOfType;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\WrappingType;

class AliasArguments
{
private $typedArgs;
private $arguments;

public function get(array $typedArgs, array $arguments): array
{
$pathsWithAlias = $this->getAliasesInFields($typedArgs, '');

return (new ArrayKeyChange())->modify($arguments, $pathsWithAlias);
}

private function getAliasesInFields(array $fields, $prefix = '', $parentType = null): array
{
$pathAndAlias = [];
foreach ($fields as $name => $arg) {
// $arg is either an array DSL notation or an InputObjectField
$arg = $arg instanceof InputObjectField ? $arg : (object) $arg;

$type = $arg->type ?? null;

if (null === $type) {
continue;
}

$newPrefix = $prefix ? $prefix.'.'.$name : $name;

if (isset($arg->alias)) {
$pathAndAlias[$newPrefix] = $arg->alias;
}

if ($this->isWrappedInList($type)) {
$newPrefix .= '.*';
}

$type = $this->getWrappedType($type);

if (! ($type instanceof InputObjectType)) {
continue;
}

if ($parentType && $type->toString() === $parentType->toString()) {
// in case the field is a self reference we must not do
// a recursive call as it will never stop
continue;
}

$pathAndAlias = $pathAndAlias + $this->getAliasesInFields($type->getFields(), $newPrefix, $type);
}

return $pathAndAlias;
}

private function isWrappedInList(Type $type): bool
{
if ($type instanceof NonNull) {
$type = $type->getWrappedType();
}

return $type instanceof ListOfType;
}

private function getWrappedType(Type $type): Type
{
if ($type instanceof WrappingType) {
$type = $type->getWrappedType(true);
}

return $type;
}
}
62 changes: 62 additions & 0 deletions src/Support/AliasArguments/ArrayKeyChange.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

declare(strict_types=1);

namespace Rebing\GraphQL\Support\AliasArguments;

class ArrayKeyChange
{
public function modify(array $array, array $pathKeyMappings): array
{
$pathKeyMappings = $this->orderPaths($pathKeyMappings);

foreach ($pathKeyMappings as $path => $replaceKey) {
$array = $this->changeKey($array, explode('.', $path), $replaceKey);
}

return $array;
}

/**
* @return array<string, string>
*/
private function orderPaths(array $paths): array
{
uksort($paths, function (string $a, string $b): int {
return $this->pathLevels($b) <=> $this->pathLevels($a);
});

return $paths;
}

private function pathLevels(string $path): int
{
return substr_count($path, '.');
}

private function changeKey(array $target, array $segments, string $replaceKey): array
{
$segment = array_shift($segments);

if (empty($segments)) {
if (isset($target[$segment])) {
$target[$replaceKey] = $target[$segment];
unset($target[$segment]);
}

return $target;
}

if ('*' === $segment) {
foreach ($target as $index => $inner) {
$target[$index] = $this->changeKey($inner, $segments, $replaceKey);
}

return $target;
}

$target[$segment] = $this->changeKey($target[$segment], $segments, $replaceKey);

return $target;
}
}
13 changes: 13 additions & 0 deletions src/Support/Field.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Illuminate\Support\Facades\Validator;
use Rebing\GraphQL\Error\AuthorizationError;
use Rebing\GraphQL\Error\ValidationError;
use Rebing\GraphQL\Support\AliasArguments\AliasArguments;

/**
* @property string $name
Expand Down Expand Up @@ -212,6 +213,8 @@ protected function getResolver(): ?Closure
};
}

$arguments[1] = $this->getArgs($arguments);

// Authorize
if (call_user_func_array($authorize, $arguments) != true) {
throw new AuthorizationError('Unauthorized');
Expand All @@ -221,6 +224,16 @@ protected function getResolver(): ?Closure
};
}

protected function aliasArgs(array $arguments): array
{
return (new AliasArguments())->get($this->args(), $arguments[1]);
}

protected function getArgs(array $arguments): array
{
return $this->aliasArgs($arguments);
}

/**
* Get the attributes from the container.
*
Expand Down
Loading