Laravel 使用子查询和 foreach 进行查询

Laravel query with subqueries and foreach

我想向普通用户展示已验证,未过时且未被禁止:

a) 所有歌曲 b) 按标题或文本分类的歌曲 c ) 歌曲标签

两个补充:

管理员用户可以看到未经验证、禁止和过时的歌曲,艺术家用户也可以看到未经验证或禁止的歌曲,但只能看到他自己的歌曲。

累了好几天了,'where in where, which is in loop...'简直是折磨xD

你能帮我实现 scopeByUser 功能吗?

歌曲型号:

class Song extends Model
{
    use HasFactory, Likable;

    public function artist()
    {
        return $this->belongsTo(Artist::class, 'artist_id');
    }

    public function tags()
    {
        return $this->belongsToMany(Tag::class)->withTimestamps();
    }

    public function scopeByUser()
    {
        $user = current_user();

        if ($user->hasRole('admin')) {
            dd("x");
            return $this;
        } elseif (isset($user->artist)) {

            return $this->where([
                'isVerified' => true,
                'isOutOfDate' => false,
                'isBanned' => false
            ])->orWhere(function () use ($user) {
                foreach ($user->artist as $artist) {
                    $this->where([
                        'artist_id', $artist->id,
                        'isOutOfDate' => false,
                        'isBanned' => false
                    ]);
                }
            });

        } else
            return $this->where([
                'isVerified' => true,
                'isOutOfDate' => false,
                'isBanned' => false
            ]);

    }
}

歌曲控制器:

public function index(Request $request)
    {

        if (request('tag')) {
            $songs = Song::query()
                ->whereHas('tags', function ($query) {
                    $tag = request('tag');
                    $query->where('name', $tag);
                })
                ->ByUser()
                ->withLikes()
                ->get();
        } elseif ($request) {
            $search = $request->input('search');

            $songs = Song::query()
                ->where('title', 'LIKE', "%{$search}%")->orWhere('text', 'LIKE', "%{$search}%")
                ->ByUser()
                ->withLikes()
                ->get();
        } else {
            $songs = Song::latest()
                ->ByUser()
                ->withLikes()
                ->get();

        }

        return view('welcome', compact('songs'));
    }

我喜欢 Laravel Eloquent 的一件事是 when() 方法。

Laravel Collection #when()

我知道 link 在集合中,但它适用于查询。 因此,让我们检查您的查询并将其设置为您可以进行逻辑测试并更改它的查询。

有一个未经测试的代码,其唯一目的是向您展示您可以实现的目标。

    $user = User::with('roles')->with('artists')->find(Auth::user()->id);

    $songs = Song::when($request('tag'), function($query) use($request) {
                        $query->whereHas('tags', function($query2) use($request('tag'))  {
                            $query2->where('name', $request('tag'));
                        });
                    })->when($request('search'), function($query) use($request) {
                        $query->where('title', 'LIKE', "%{$request('search)}%")->orWhere('text', 'LIKE', "%{$request('search)}%");
                    })->when(!isset($request('search')) && !isset($request('tags')), function($query) {
                        $query->latest();
                    })->when(true, function($query) use($user) {//setting when to true let you do more complexe logical test
                        if ($user->hasRole('admin')) {
                            //Nothing to do...
                        } else if (isset($user->artist)) {
                            $query->where([
                                        'isVerified' => true,
                                        'isOutOfDate' => false,
                                        'isBanned' => false
                                    ])->orWhere(function ($query2) use ($user) {
                                        foreach ($user->artist as $artist) {
                                            $query2->where([//probably change this to orWhere. You can also get the array of artist id and use whereIn('id', $arrayOfId)->where(['isOutOfDate' => false, 'isBanned' => false]);
                                                'artist_id', $artist->id,
                                                'isOutOfDate' => false,
                                                'isBanned' => false
                                            ]);
                                        }
                                    });
                        } else {
                            $query->where([
                                        'isVerified' => true,
                                        'isOutOfDate' => false,
                                        'isBanned' => false
                                    ]);
                        }
                    })->ByUser()
                    ->withLikes()
                    ->get();

所以,回到你真正问的问题...使用范围...我不鼓励在进行复杂的逻辑测试时使用范围。为什么???仅仅是因为看起来您的范围正在检索用户,并且我假设此方法至少向数据库发出一个请求...因此,这会导致 N+1 问题。

Scope 适合简单的任务。范围内的一个数据库请求将对每个模型执行请求。避免这种情况。

如果使用得当,when() 方法将帮助您构建复杂的查询,该查询将导致单个数据库查询。