Eloquent: apply scopes when including polymorphic relationships

This commit is contained in:
Toby Zerner 2021-05-05 14:20:43 +09:30
parent 71dcdadb3b
commit f0c7ed513e
2 changed files with 46 additions and 25 deletions

View File

@ -211,13 +211,19 @@ class EloquentAdapter implements AdapterInterface
// to load anything as the related ID is store directly on the model. // to load anything as the related ID is store directly on the model.
(new Collection($models))->loadMissing([ (new Collection($models))->loadMissing([
$this->getRelationshipPath($relationships) => function ($relation) use ($relationships, $scope) { $this->getRelationshipPath($relationships) => function ($relation) use ($scope) {
$query = $relation->getQuery(); $query = $relation->getQuery();
if (is_array($scope)) { if (is_array($scope)) {
// TODO: since https://github.com/laravel/framework/pull/35190 // Requires Laravel 8.15+
// was merged, we can now apply loading constraints to foreach ($scope as $v) {
// polymorphic relationships. $adapter = $v['resource']->getAdapter();
if ($adapter instanceof self) {
$relation->constrain([
get_class($adapter->newModel()) => $v['scope']
]);
}
}
} else { } else {
$scope($query); $scope($query);
} }

View File

@ -31,7 +31,7 @@ trait IncludesData
if (! empty($queryParams['include'])) { if (! empty($queryParams['include'])) {
$include = $this->parseInclude($queryParams['include']); $include = $this->parseInclude($queryParams['include']);
$this->validateInclude($this->resource, $include); $this->validateInclude([$this->resource], $include);
return $include; return $include;
} }
@ -58,26 +58,38 @@ trait IncludesData
return $tree; 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) { foreach ($include as $name => $nested) {
if ( foreach ($resources as $resource) {
! isset($fields[$name]) $fields = $resource->getSchema()->getFields();
|| ! $fields[$name] instanceof Relationship
|| ! $fields[$name]->isIncludable() if (
) { ! isset($fields[$name])
throw new BadRequestException("Invalid include [{$path}{$name}]", 'include'); || ! $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)) { throw new BadRequestException("Invalid include [{$path}{$name}]", 'include');
$relatedResource = $this->api->getResource($type);
$this->validateInclude($relatedResource, $nested, $name.'.');
} elseif ($nested) {
throw new BadRequestException("Invalid include [{$path}{$name}.*]", 'include');
}
} }
} }
@ -123,10 +135,13 @@ trait IncludesData
}, $relatedResources), }, $relatedResources),
array_map(function ($relatedResource) use ($context, $field) { array_map(function ($relatedResource) use ($context, $field) {
return function ($query) use ($context, $field, $relatedResource) { return [
run_callbacks($relatedResource->getSchema()->getListeners('scope'), [$query, $context]); 'resource' => $relatedResource,
run_callbacks($field->getListeners('scope'), [$query, $context]); 'scope' => function ($query) use ($context, $field, $relatedResource) {
}; run_callbacks($relatedResource->getSchema()->getListeners('scope'), [$query, $context]);
run_callbacks($field->getListeners('scope'), [$query, $context]);
}
];
}, $relatedResources) }, $relatedResources)
); );
} }