Skip to content

Commit

Permalink
Merge pull request #207 from mll-lab/nested-mutations
Browse files Browse the repository at this point in the history
[Feat] Nested mutations
  • Loading branch information
chrissm79 authored Aug 7, 2018
2 parents 068c55a + 7198183 commit 89d615d
Show file tree
Hide file tree
Showing 11 changed files with 443 additions and 65 deletions.
101 changes: 101 additions & 0 deletions src/Execution/MutationExecutor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php

namespace Nuwave\Lighthouse\Execution;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection;

class MutationExecutor
{
public static function executeCreate(Model $model, Collection $args, HasMany $parentRelation = null): Model
{
list($belongsTo, $remaining) = self::extractBelongsToArgs($model, $args);
list($hasMany, $remaining) = self::extractHasManyArgs($model, $remaining);

$model->fill($remaining->all());

$belongsTo->each(function ($value, $key) use ($model) {
$model->{$key}()->associate($value);
});

$parentRelation
? $parentRelation->save($model)
: $model->save();

$hasMany->each(function ($nestedOperations, $key) use ($model) {
/** @var HasMany $relation */
$relation = $model->{$key}();

collect($nestedOperations)->each(function ($values, $operationKey) use ($relation) {
if ($operationKey === 'create') {
self::handleHasManyCreate(collect($values), $relation);
}
});
});

return $model;
}

protected static function handleHasManyCreate(Collection $multiValues, HasMany $relation)
{
$multiValues->each(function ($singleValues) use ($relation) {
self::executeCreate($relation->getModel()->newInstance(), collect($singleValues), $relation);
});
}

public static function executeUpdate(Model $model, Collection $args, HasMany $parentRelation = null): Model
{
list($belongsTo, $remaining) = self::extractBelongsToArgs($model, $args);
list($hasMany, $remaining) = self::extractHasManyArgs($model, $remaining);

$model = $model->newQuery()->findOrFail($args->pull('id'));
$model->fill($remaining->all());

$belongsTo->each(function ($value, $key) use ($model) {
$model->{$key}()->associate($value);
});

$parentRelation
? $parentRelation->save($model)
: $model->save();

$hasMany->each(function ($nestedOperations, $key) use ($model) {
/** @var HasMany $relation */
$relation = $model->{$key}();

collect($nestedOperations)->each(function ($values, $operationKey) use ($relation) {
if ($operationKey === 'create') {
self::handleHasManyCreate(collect($values), $relation);
}

if ($operationKey === 'update') {
collect($values)->each(function ($singleValues) use ($relation) {
self::executeUpdate($relation->getModel()->newInstance(), collect($singleValues), $relation);
});
}

if ($operationKey === 'delete') {
$relation->getModel()::destroy($values);
}
});
});

return $model;
}

protected static function extractBelongsToArgs(Model $model, Collection $args): Collection
{
return $args->partition(function ($value, $key) use ($model) {
return method_exists($model, $key) && ($model->{$key}() instanceof BelongsTo);
});
}

protected static function extractHasManyArgs(Model $model, Collection $args): Collection
{
return $args->partition(function ($value, $key) use ($model) {
return method_exists($model, $key) && ($model->{$key}() instanceof HasMany);
});
}
}
14 changes: 10 additions & 4 deletions src/Schema/Directives/Fields/CreateDirective.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

namespace Nuwave\Lighthouse\Schema\Directives\Fields;

use Nuwave\Lighthouse\Schema\Directives\BaseDirective;
use Nuwave\Lighthouse\Schema\Values\FieldValue;
use Nuwave\Lighthouse\Execution\MutationExecutor;
use Nuwave\Lighthouse\Schema\Directives\BaseDirective;
use Nuwave\Lighthouse\Support\Contracts\FieldResolver;
use Nuwave\Lighthouse\Support\Exceptions\DirectiveException;

class CreateDirective extends BaseDirective implements FieldResolver
{
Expand All @@ -28,8 +28,14 @@ public function name()
*/
public function resolveField(FieldValue $value)
{
return $value->setResolver(function ($root, array $args){
return $this->getModelClass()::create($args);
return $value->setResolver(function ($root, $args) {
$modelClassName = $this->getModelClass();
$model = new $modelClassName();

$flatten = $this->directiveArgValue('flatten', false);
$args = $flatten ? reset($args) : $args;

return MutationExecutor::executeCreate($model, collect($args));
});
}
}
52 changes: 11 additions & 41 deletions src/Schema/Directives/Fields/UpdateDirective.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@

namespace Nuwave\Lighthouse\Schema\Directives\Fields;

use GraphQL\Type\Definition\IDType;
use Nuwave\Lighthouse\Schema\Directives\BaseDirective;
use Nuwave\Lighthouse\Schema\Resolvers\NodeResolver;
use Nuwave\Lighthouse\Schema\Values\FieldValue;
use Nuwave\Lighthouse\Execution\MutationExecutor;
use Nuwave\Lighthouse\Support\Traits\HandlesGlobalId;
use Nuwave\Lighthouse\Schema\Directives\BaseDirective;
use Nuwave\Lighthouse\Support\Contracts\FieldResolver;
use Nuwave\Lighthouse\Support\Exceptions\DirectiveException;
use Nuwave\Lighthouse\Support\Traits\HandlesGlobalId;

class UpdateDirective extends BaseDirective implements FieldResolver
{
Expand All @@ -34,47 +33,18 @@ public function name()
*/
public function resolveField(FieldValue $value)
{
$idArg = $this->getIDField($value);
$globalId = $this->directiveArgValue('globalId', false);
return $value->setResolver(function ($root, $args) {
$modelClassName = $this->getModelClass();
$model = new $modelClassName();

if (!$idArg) {
new DirectiveException(sprintf(
'The `update` requires that you have an `ID` field on %s',
$value->getNodeName()
));
}
$flatten = $this->directiveArgValue('flatten', false);
$args = $flatten ? reset($args) : $args;

return $value->setResolver(function ($root, array $args) use ($idArg, $globalId) {
$id = $globalId ? $this->decodeGlobalId(array_get($args, $idArg))[1] : array_get($args, $idArg);

$model = $this->getModelClass()::find($id);

if ($model) {
$attributes = collect($args)->except([$idArg])->toArray();
$model->fill($attributes);
$model->save();
if($this->directiveArgValue('globalId', false)){
$args['id'] = $this->decodeGlobalId($args['id'])[1];
}

return $model;
return MutationExecutor::executeUpdate($model, collect($args));
});
}

/**
* Check if field has an ID argument.
*
* @param FieldValue $value
*
* @return bool
*/
protected function getIDField(FieldValue $value)
{
return collect($value->getField()->arguments)->filter(function ($arg) {
$type = NodeResolver::resolve($arg->type);
$type = method_exists($type, 'getWrappedType') ? $type->getWrappedType() : $type;

return $type instanceof IDType;
})->map(function ($arg) {
return $arg->name->value;
})->first();
}
}
124 changes: 124 additions & 0 deletions tests/Integration/Schema/Directives/Fields/CreateDirectiveTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<?php

namespace Tests\Integration\Schema\Directives\Fields;

use Tests\DBTestCase;
use Tests\Utils\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;

class CreateDirectiveTest extends DBTestCase
{
use RefreshDatabase;

/**
* @test
*/
public function itCanCreateFromFieldArguments()
{
$schema = '
type Company {
id: ID!
name: String!
}
type Mutation {
createCompany(name: String): Company @create
}
';
$query = '
mutation {
createCompany(name: "foo") {
id
name
}
}
';
$result = $this->execute($schema, $query);

$this->assertSame('1', array_get($result, 'data.createCompany.id'));
$this->assertSame('foo', array_get($result, 'data.createCompany.name'));
}

/**
* @test
*/
public function itCanCreateFromInputObject()
{
$schema = '
type Company {
id: ID!
name: String!
}
type Mutation {
createCompany(input: CreateCompanyInput!): Company @create(flatten: true)
}
input CreateCompanyInput {
name: String
}
';
$query = '
mutation {
createCompany(input: {
name: "foo"
}) {
id
name
}
}
';
$result = $this->execute($schema, $query);

$this->assertSame('1', array_get($result, 'data.createCompany.id'));
$this->assertSame('foo', array_get($result, 'data.createCompany.name'));
}

/**
* @test
*/
public function itCanCreateWithBelongsTo()
{
factory(User::class)->create();

$schema = '
type Task {
id: ID!
name: String!
user: User @belongsTo
}
type User {
id: ID
}
type Mutation {
createTask(input: CreateTaskInput!): Task @create(flatten: true)
}
input CreateTaskInput {
name: String
user: ID
}
';
$query = '
mutation {
createTask(input: {
name: "foo"
user: 1
}) {
id
name
user {
id
}
}
}
';
$result = $this->execute($schema, $query);

$this->assertSame('1', array_get($result, 'data.createTask.id'));
$this->assertSame('foo', array_get($result, 'data.createTask.name'));
$this->assertSame('1', array_get($result, 'data.createTask.user.id'));
}
}
Loading

0 comments on commit 89d615d

Please sign in to comment.