From f0c7ed513e79fee98b03fe96af8e65ff07e53187 Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Wed, 5 May 2021 14:20:43 +0930 Subject: [PATCH] Eloquent: apply scopes when including polymorphic relationships --- src/Adapter/EloquentAdapter.php | 14 +++++-- src/Endpoint/Concerns/IncludesData.php | 57 ++++++++++++++++---------- 2 files changed, 46 insertions(+), 25 deletions(-) diff --git a/src/Adapter/EloquentAdapter.php b/src/Adapter/EloquentAdapter.php index 754f7eb..be777fa 100644 --- a/src/Adapter/EloquentAdapter.php +++ b/src/Adapter/EloquentAdapter.php @@ -211,13 +211,19 @@ class EloquentAdapter implements AdapterInterface // to load anything as the related ID is store directly on the model. (new Collection($models))->loadMissing([ - $this->getRelationshipPath($relationships) => function ($relation) use ($relationships, $scope) { + $this->getRelationshipPath($relationships) => function ($relation) use ($scope) { $query = $relation->getQuery(); if (is_array($scope)) { - // TODO: since https://github.com/laravel/framework/pull/35190 - // was merged, we can now apply loading constraints to - // polymorphic relationships. + // Requires Laravel 8.15+ + foreach ($scope as $v) { + $adapter = $v['resource']->getAdapter(); + if ($adapter instanceof self) { + $relation->constrain([ + get_class($adapter->newModel()) => $v['scope'] + ]); + } + } } else { $scope($query); } diff --git a/src/Endpoint/Concerns/IncludesData.php b/src/Endpoint/Concerns/IncludesData.php index 1462bd5..538c846 100644 --- a/src/Endpoint/Concerns/IncludesData.php +++ b/src/Endpoint/Concerns/IncludesData.php @@ -31,7 +31,7 @@ trait IncludesData if (! empty($queryParams['include'])) { $include = $this->parseInclude($queryParams['include']); - $this->validateInclude($this->resource, $include); + $this->validateInclude([$this->resource], $include); return $include; } @@ -58,26 +58,38 @@ trait IncludesData return $tree; } - private function validateInclude(ResourceType $resource, array $include, string $path = '') + private function validateInclude(array $resources, array $include, string $path = '') { - $fields = $resource->getSchema()->getFields(); - foreach ($include as $name => $nested) { - if ( - ! isset($fields[$name]) - || ! $fields[$name] instanceof Relationship - || ! $fields[$name]->isIncludable() - ) { - throw new BadRequestException("Invalid include [{$path}{$name}]", 'include'); + foreach ($resources as $resource) { + $fields = $resource->getSchema()->getFields(); + + if ( + ! isset($fields[$name]) + || ! $fields[$name] instanceof Relationship + || ! $fields[$name]->isIncludable() + ) { + continue; + } + + $type = $fields[$name]->getType(); + + if (is_string($type)) { + $relatedResource = $this->api->getResource($type); + + $this->validateInclude([$relatedResource], $nested, $name.'.'); + } else { + $relatedResources = is_array($type) ? array_map(function ($type) { + return $this->api->getResource($type); + }, $type) : array_values($this->api->getResources()); + + $this->validateInclude($relatedResources, $nested, $name.'.'); + } + + continue 2; } - if (($type = $fields[$name]->getType()) && is_string($type)) { - $relatedResource = $this->api->getResource($type); - - $this->validateInclude($relatedResource, $nested, $name.'.'); - } elseif ($nested) { - throw new BadRequestException("Invalid include [{$path}{$name}.*]", 'include'); - } + throw new BadRequestException("Invalid include [{$path}{$name}]", 'include'); } } @@ -123,10 +135,13 @@ trait IncludesData }, $relatedResources), array_map(function ($relatedResource) use ($context, $field) { - return function ($query) use ($context, $field, $relatedResource) { - run_callbacks($relatedResource->getSchema()->getListeners('scope'), [$query, $context]); - run_callbacks($field->getListeners('scope'), [$query, $context]); - }; + return [ + 'resource' => $relatedResource, + 'scope' => function ($query) use ($context, $field, $relatedResource) { + run_callbacks($relatedResource->getSchema()->getListeners('scope'), [$query, $context]); + run_callbacks($field->getListeners('scope'), [$query, $context]); + } + ]; }, $relatedResources) ); }