Добавление фильтра в красноречивый ресурс для условного прикрепления отношений
Laravel 5.8 PHP 7.4
Я хочу загрузить отношения условно, как
http://127.0.0.1:8000/api/posts
а также
http://127.0.0.1:8000/api/posts/1 are my end points now, I want to load comments like
http://127.0.0.1:8000/api/posts/?include=comments
а также
http://127.0.0.1:8000/api/posts/1/?include=comments
Если есть параметр запроса, только тогда он должен загружать комментарии с постами или он должен загружать только посты / пост
Я делаю это, ссылаясь на сообщение в блоге
сейчас RequestQueryFilter
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
class RequestQueryFilter
{
public function attach($resource, Request $request = null)
{
$request = $request ?? request();
return tap($resource, function($resource) use($request) {
$this->getRequestIncludes($request)->each(function($include) use($resource) {
$resource->load($include);
});
});
}
protected function getRequestIncludes(Request $request)
{
// return collect(data_get($request->input(), 'include', [])); //single relationship
return collect(array_map('trim', explode(',', data_get($request->input(), 'include', [])))); //multiple relationships
}
}
и в помощнике
<?php
if ( ! function_exists('filter') ) {
function filter($attach)
{
return app('filter')->attach($attach);
}
}
?>
в PostController
public funciton show(Request $request, Post $post) {
return new PostResource(filter($post));
}
но когда я пытаюсь получить
http://127.0.0.1:8000/api/posts/1/?include=comments getting no comments, with no error in log
Обходной путь будет PostResource
public function toArray($request)
{
// return parent::toArray($request);
$data = [
'id' => $this->id,
'name' => $this->title,
'body' => $this->content,
];
$filter = $request->query->get('include', '');
if($filter){
$data[$filter] = $this->resource->$filter;
}
return $data;
}
3 ответа
Я хочу загрузить отношения условно, как
Lazy Eager Загрузка с помощью load()
вызов
Lazy Eager Loading выполняет те же конечные результаты, что и with()
в Ларавеле, однако, не автоматически. Например:
?include=comments
// Get all posts.
$posts = Post::without('comments')->all();
if (request('include') == 'comments')) {
$posts->load('comments');
}
return PostResource::collection($posts);
В качестве альтернативы, вы могли бы потребовать include
Строка запроса, чтобы быть массивом:
?include[]=comments&include[]=tags
// Validate the names against a set of allowed names beforehand, so there's no error.
$posts = Post::without(request('includes'))->all();
foreach (request('includes') as $include) {
$posts->load($include);
}
return PostResource::collection($posts);
Звонок without()
требуется только в том случае, если вы определили свою модель для автоматической загрузки отношений, которые вы хотите загрузить условно.
Со всеми данными, отфильтрованными в Контроллере, просто убедитесь, что отображаются только загруженные отношения в вашем PostResource
public function toArray($request) {
$data = [...];
foreach ($this->relations as $name => $relation)
{
$data[$name] = $relation;
}
return $data;
}
Я бы создал собственный ресурс для постов с
php artisan make_resource
команда. Например, PostResource. Функция toArray ресурса должна возвращать данные.
PostResource.php
public function toArray($request){
$data =['title' => $this->resource->title,
'body' => $this->resource->body,
'images' => new ImageCollection($this->whenLoaded('images')),
];
$filter = $request->query->get('filter', '');
if($filter){
$data['comments'] => new CommentCollection($this->resource->comments);
}
return $data;
}
Также для коллекций вам необходимо создать ResourceCollection.
PostResourceCollection.php
class PostResourceCollection extends ResourceCollection
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
return [
'data' => $this->collection,
];
}
}
В вашем контроллере:PostsController.php
//show one post
public function show(Post $post, Request $request)
{
/**this response is for API or vue.js if you need to generate view, pass the resource to the view */
return $this->response->json( new PostResource($post));
}
//list of posts
public function index(Request $request)
{
$posts = Post::all();
/**this response is for API or vue.js if you need to generate view, pass the resource to the view */
return $this->response->json( new PostResourceCollection($posts));
}
Частичное решение
Для этого потребуется небольшое изменение в классе ресурса
public function toArray($request)
{
// return parent::toArray($request);
$data = [
'id' => $this->id,
'title' => $this->title,
'body' => $this->body,
'comments' => new CommentCollection($this->whenLoaded('comments')),
'images' => new ImageCollection($this->whenLoaded('images')),
];
return $data;
}
и он будет загружать комментарии и изображения, если загружены, и это зависит от include
Параметр запроса, если он не включен, не будет загружать отношения.
Тем не мение,
В почтовой коллекции
return [
'data' => $this->collection->transform(function($post){
return [
'id' => $post->id,
'title' => $post->title,
'body' => $post->body,
'comments' => new CommentCollection($post->whenLoaded('comments')),
'images' => new ImageCollection($post->whenLoaded('images')),
];
}),
];
приведет к
"Вызовите неопределенный метод App\Models\Customer::whenLoaded()", если кто-нибудь предложит полное решение, это будет очень полезно, если я смогу это сделать, я обновлю его здесь.