diff --git a/src/Execution/Arguments/NestedOneToMany.php b/src/Execution/Arguments/NestedOneToMany.php index 754a5bc710..f3e04dd4f4 100644 --- a/src/Execution/Arguments/NestedOneToMany.php +++ b/src/Execution/Arguments/NestedOneToMany.php @@ -50,11 +50,31 @@ public static function createUpdateUpsert(ArgumentSet $args, Relation $relation) } if ($args->has('update')) { - $updateModel = new ResolveNested(new UpdateModel(new SaveModel($relation))); + $updateModel = new ResolveNested(new SaveModel($relation)); - foreach ($args->arguments['update']->value as $childArgs) { - // @phpstan-ignore-next-line Relation&Builder mixin not recognized - $updateModel($relation->make(), $childArgs); + // @phpstan-ignore-next-line Relation&Builder mixin not recognized + $model = $relation->make(); + + $ids = []; + + $updateArgs = $args->arguments['update']->value; + + foreach ($updateArgs as $childArgs) { + $ids[UpdateModel::getId($childArgs, $model)] = true; + } + + $models = $model->newModelQuery() + ->whereIn($model->getKeyName(), array_keys($ids)) + ->get(); + + foreach ($models as $instance) { + $id = $instance->getKey(); + $ids[$id] = $instance; + } + + foreach ($updateArgs as $childArgs) { + $instance = $ids[UpdateModel::pullId($childArgs, $model)]; + $updateModel($instance, $childArgs); } } diff --git a/src/Execution/Arguments/UpdateModel.php b/src/Execution/Arguments/UpdateModel.php index 65b8775ee2..6bca33dd87 100644 --- a/src/Execution/Arguments/UpdateModel.php +++ b/src/Execution/Arguments/UpdateModel.php @@ -3,6 +3,7 @@ namespace Nuwave\Lighthouse\Execution\Arguments; use GraphQL\Error\Error; +use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Arr; use Nuwave\Lighthouse\Support\Contracts\ArgResolver; @@ -24,10 +25,23 @@ public function __construct(callable $previous) } /** - * @param \Illuminate\Database\Eloquent\Model $model + * @param Model $model * @param \Nuwave\Lighthouse\Execution\Arguments\ArgumentSet $args */ public function __invoke($model, $args) + { + $id = self::pullId($args, $model); + $instance = $model->newQuery()->findOrFail($id); + + return ($this->previous)($instance, $args); + } + + /** + * Extract and remove the model ID from the given args. + * + * @return mixed any non-null ID value + */ + public static function pullId(ArgumentSet $args, Model $model) { /** @var \Nuwave\Lighthouse\Execution\Arguments\Argument|null $id */ $id = Arr::pull($args->arguments, 'id') @@ -38,8 +52,25 @@ public function __invoke($model, $args) throw new Error(self::MISSING_PRIMARY_KEY_FOR_UPDATE); } - $model = $model->newQuery()->findOrFail($id->value); + return $id->value; + } + + /** + * Extract and remove the model ID from the given args. + * + * @return mixed any non-null ID value + */ + public static function getId(ArgumentSet $args, Model $model) + { + /** @var \Nuwave\Lighthouse\Execution\Arguments\Argument|null $id */ + $id = Arr::get($args->arguments, 'id') + ?? Arr::get($args->arguments, $model->getKeyName()) + ?? null; + + if (null === $id) { + throw new Error(self::MISSING_PRIMARY_KEY_FOR_UPDATE); + } - return ($this->previous)($model, $args); + return $id->value; } } diff --git a/tests/Integration/Execution/MutationExecutor/HasManyTest.php b/tests/Integration/Execution/MutationExecutor/HasManyTest.php index 5b8854454b..76f644b4eb 100644 --- a/tests/Integration/Execution/MutationExecutor/HasManyTest.php +++ b/tests/Integration/Execution/MutationExecutor/HasManyTest.php @@ -373,8 +373,8 @@ public function testUpdateHasMany(string $action): void $user = factory(User::class)->create(); $user->tasks() - ->save( - factory(Task::class)->create() + ->saveMany( + factory(Task::class, 2)->create() ); $this->graphQL(/** @lang GraphQL */ << [ [ 'id' => '1', + 'name' => 'foo', + ], + [ + 'id' => '2', 'name' => 'bar', ], ], @@ -433,10 +443,16 @@ public function testUpsertHasMany(string $action): void id: 1 name: "foo" tasks: { - upsert: [{ - id: 1 - name: "bar" - }] + upsert: [ + { + id: 1 + name: "foo" + }, + { + id: 2 + name: "bar" + }, + ] } }) { id @@ -456,6 +472,10 @@ public function testUpsertHasMany(string $action): void 'tasks' => [ [ 'id' => '1', + 'name' => 'foo', + ], + [ + 'id' => '2', 'name' => 'bar', ], ],