向 eloquent 资源添加过滤器以有条件地附加关系

Posted

技术标签:

【中文标题】向 eloquent 资源添加过滤器以有条件地附加关系【英文标题】:Adding filter to eloquent resource to attach relationship conditionally 【发布时间】:2019-12-20 01:03:29 【问题描述】:

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

如果查询参数存在,那么它应该加载带有帖子的 cmets,或者它应该只加载帖子/帖子

我通过引用 blog post 来做到这一点

现在,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;
    

【问题讨论】:

看看这个包 - 它应该非常适合您的需求:github.com/spatie/laravel-query-builder @jtwes 谢谢你的回复,已经看过了,它似乎是用于查询生成器的,我正在使用 eloquent 资源,所以我想让它在没有那个包的情况下工作,无论如何谢谢你。 【参考方案1】:

我会使用

为帖子创建自定义资源
php artisan make_resource 

命令。 例如。发布资源。 资源的 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));
             

【讨论】:

我有资源,让我试试你的代码,但是comments 可以是图片之类的其他东西,可以是commentsimages,所以我不能让它成为静态的。 你的代码需要一点修正` $filter = $request->query->get('include', ''); if($filter) $data['cmets'] = $this->resource->cmets; ` 并且它会工作,但需要使其动态化并且想要制作一个通用的解决方案,它只适用于特定的资源。 动态将是` $filter = $request->query->get('include', ''); if($filter) $data[$filter] = $this->resource->$filter; ` 仅用于发布,不想在每个资源中添加此过滤器,因为资源的工作是格式化数据而不是过滤。 @PrafullaKumarSahu,是的,对不起,我对问题的标题感到困惑,这就是我使用 fiter 的原因:试试这个:$includes = collect(explode(',', $request->查询->get('includes', ''))); 刚试过,好像不行,只能获取帖子资源。【参考方案2】:

我想像这样有条件地加载关系

使用load() 调用的延迟加载

Lazy Eager Loading 实现了与 Laravel 中的 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&amp;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() 仅在您将模型定义为自动预先加载您想要有条件加载的关系时才需要调用。

在 Controller 中过滤所有数据后,只需确保在 PostResource 中仅显示加载的关系

public function toArray($request) 
    $data = [...];

    foreach ($this->relations as $name => $relation)
    
        $data[$name] = $relation;
    

    return $data;

【讨论】:

这将与我的工作类似,需要在每个资源中实现,我想创建一些通用的东西。 创建一个父类以在 PostResource 中扩展怎么样,例如 PostResource extends InjectRelationResource ?逻辑会将任何加载的关系注入到数据输出中,这是我现在能想到的 也许这样可以正常工作,但我们还需要考虑指定属性,并且它应该允许分页。没有一个明确的想法,但似乎这可能是一个很好的解决方案。 我还不清楚你想要实现什么,除了关系之外,你还想有条件地包含资源字段吗? 普通资源和资源集合提供什么,指定你想要返回的内容,关系和分页,但我们现在可以专注于关系。【参考方案3】:

部分解决方案

需要对资源类做些小改动

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;

如果加载,它将加载 cmets 和图像,这取决于 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()”,如果有人提出一个完整的解决方案,那将是一个很大的帮助,如果我能做到,我会在这里更新。

【讨论】:

以上是关于向 eloquent 资源添加过滤器以有条件地附加关系的主要内容,如果未能解决你的问题,请参考以下文章

有条件地将 where 子句添加到 eloquent 查询

Laravel 在方法启动后有条件地添加全局范围

反应路由器链接以有条件地渲染按钮

将 PostgreSQL 函数包装在另一个中以有条件地组合结果

如何设置 automake 和 autoconf 以有条件地构建程序(测试或其他)

估计浏览器 JS 引擎速度以有条件地禁用动画