简体   繁体   中英

Laravel's Eloquent ORM versus Query Builder

I have 2 tables: users and articles . users table has a column named is_suspended which accepts yes or no , and the articles table has a column named is_published which accepts 0 or 1 . To fetch an article data from the database, articles.is_published must equals 1 and users.is_suspended must equals no . Now suppose that users.is_suspended equals yes , if I try to fetch data of an article using Query Builder like this:

// first code:

    $articles = Article::join('users', 'articles.user_id', '=', 'users.user_id')
        ->select(['articles.*', 'users.user_name', 'users.user_email'])
        ->where('articles.article_id', '=', 9)
        ->where('articles.is_published', '=', 1)
        ->where('users.is_suspended', '=', 'no')
        ->get();

This will work perfectly and will return nothing (because users.is_suspended = yes ). Now let's try to fetch the same article with Laravel Eloquent ORM like this:

// second code:

$articles = Article::with([
    'user' => function($query) {
        $query->select(['user_id', 'user_name', 'user_email'])
            ->where('users.is_suspended', '=', 'no');
    }])
    ->where('articles.article_id', '=', 9)
    ->where('articles.is_published', '=', 1)
    ->get();

In this case I will get the next result:

[
  {
    article_id: 9,
    user_id: 1,
    article_title: "Article title here",
    article_body: "Article body here",
    is_published: 1,
    created_at: "2015-01-17 02:26:24",
    updated_at: "2015-01-17 02:26:24",
    user: null
  }
]

It will fetch data of the article even if the user is suspended, which is wrong. So my question is how to fix the second code to act like the first code?

You may want to use with to make sure you lazy load relationships but any constraints you put on it only apply to the loaded relations. To limit the response to articles where the user is not suspended you would want to use the whereHas method.

$articles = Article::with(['user' => function($q){
        $q->select(['user_id', 'user_name', 'user_email', 'created_at']);
    }])
    ->whereHas('user', function($q) {
        $q->where('is_suspended', '=', 'no');
    })
    ->where('articles.article_id', '=', 9)
    ->where('articles.is_published', '=', 1)
    ->get();

You need whereHas instead of with (or both, but no need for with in this case), like @DavidBarker said.

However I suggest more, consider readability of this code:

$article = Article::whereHas('user', function ($q) {
   $q->active();
})->published()->find(9);

It makes use of scopes .

So here's what you can do to make your life easier:

  1. Change is_suspended to bool because:

     $user->is_suspended; // no (bool) $user->is_suspended; // true 
  2. Define scopes suspended on User model and published on Article :

     // Article model - on User accordingly public function scopePublished($query) { $query->where('is_published', 1); } 
  3. When you want to fetch single model use find($id) or where('col', $id)->first() instead of get , because the latter will return a collection, even though there's jut one result.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM