4

I'm trying to build a simple news feed for posts in Laravel with Eloquent.

I basically want to retrieve all Posts...

  • where I am author
  • where people I follow are author (followable)
  • where people I follow have commented on
  • where people with same field_id are author
  • where poeple with same school_id are author

in one query.

As I never worked intensivly with joined/combined SQL queries, any help on this is greatly appreciated!

My Tables

users table

+----+
| id |
+----+

posts table

+----+-----------+-------------+
| id | author_id | author_type |
|----|-----------|-------------|
|    | users.id  | 'App\User'  |
+----+-----------+-------------+

comments table

+----+----------------+------------------+-----------+-------------+
| id | commentable_id | commentable_type | author_id | author_type |
|----|----------------|------------------|-----------|-------------|
|    | posts.id       | 'App\Post'       | users.id  | 'App\User'  |
+----+----------------+------------------+-----------+-------------+

schoolables table

+---------+-----------+----------+
| user_id | school_id | field_id |
+---------+-----------+----------+

followables table

+-------------+---------------+---------------+-----------------+
| follower_id | follower_type | followable_id | followable_type |
|-------------|---------------|---------------|-----------------|
| users.id    | 'App\User'    | users.id      | 'App\User'      |
+-------------+---------------+---------------+-----------------+

My Models

class Post extends Model
{
    /**
     * @return \Illuminate\Database\Eloquent\Relations\MorphTo
     */
    public function author()
    {
        return $this->morphTo();
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\MorphMany
     */
    public function comments()
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}

class Comment extends Model
{
    /**
     * @return \Illuminate\Database\Eloquent\Relations\MorphTo
     */
    public function author()
    {
        return $this->morphTo();
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\MorphTo
     */
    public function commentable()
    {
        return $this->morphTo();
    }
}

class User extends Model
{
    /**
     * @return \Illuminate\Database\Eloquent\Relations\MorphMany
     */
    public function posts()
    {
        return $this->morphMany(Post::class, 'author')->orderBy('created_at', 'desc');
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\MorphMany
     */
    public function comments()
    {
        return $this->morphMany(Comment::class, 'author')->orderBy('created_at', 'desc');
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
     */
    public function schoolables()
    {
        return $this->hasMany(Schoolable::class);
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\MorphMany
     */
    public function following()
    {
        return $this->morphMany(Followable::class, 'follower');
    }
}
2
  • So where is your code for your attempt? Or are you just hoping someone will write the entire thing for you? Commented Oct 7, 2015 at 15:29
  • @pitchinnate I don't hope for anything, I simply want to learn how to build such queries or gather some more resources for doing so. Commented Oct 10, 2015 at 18:14

2 Answers 2

2

You can try to use left joins for this query, but it would become a complex thing, because you have to make all the joins with leftJoins and then a nested orWhere clause on all the

$posts = Post::leftJoin('..', '..', '=', '..')
  ->where(function($query){
    $query->where('author_id', Auth::user()->id); // being the author
  })->orWhere(function($query){
    // second clause...
  })->orWhere(function($query){
    // third clause...
  .....
  })->get();

I don't think this will be manageable, so I would advice using UNIONS, http://laravel.com/docs/5.1/queries#unions

So it would be something like..

$written = Auth::user()->posts();
$following = Auth::user()->following()->posts();

After getting the different queries, without getting the results, you can unite them..

$posts = $written->union($following)->get();

Hopefully this will direct you in the right direction

Sign up to request clarification or add additional context in comments.

Comments

1

Okay, so you want to retrieve all posts where any of the 5 conditions above apply. The trick to writing such queries is to break them up into smaller, more manageable pieces.

$query = Post::query();

So let's say you are $me.

The ids of users you are following can be obtained with

$followingUserIds = $me
    ->following()
    ->where('followable_type', User::class)
    ->lists('followable_id');

The ids of users in the same fields as you can be obtained with

$myFieldIds = $me->schoolables()->lists('field_id');
$sharedFieldUserIds = Schoolable::whereIn('field_id', $myFieldIds)->lists('user_id');

Similarly, users in the same school as you can be obtained with

$mySchoolIds = $me->schoolables()->lists('school_id');
$sharedSchoolUserIds = Schoolable::whereIn('school_id', $mySchoolIds)->lists('user_id');

Let's define each of those conditions:

Where I am author

$query->where(function($inner) use ($me) {
    $inner->where('posts.author_type', User::class);
    $inner->where('posts.author_id', $me->id);
});

Where people I follow are author (followable)

$query->orWhere(function($inner) use ($followingUserIds) {
    $inner->where('posts.author_type', User::class);
    $inner->whereIn('posts.author_id', $followingUserIds);
});

where people I follow have commented on
This one is actually slightly tricky: we need to use the ->whereHas construct, which finds posts with at least 1 comment matching the subquery.

$query->orWhereHas('comments', function($subquery) use ($followingUserIds) {
    $subquery->where('comments.author_type', User::class);
    $subquery->whereIn('comments.author_id', $followingUserIds);
});

The remaining two are simple.

where people with same field_id are author

$query->orWhere(function($inner) use ($sharedFieldUserIds) {
    $inner->where('posts.author_type', User::class);
    $inner->whereIn('posts.author_id', $sharedFieldUserIds);
});

and you can see where this is going

where poeple with same school_id are author

$query->orWhere(function($inner) use ($sharedSchoolUserIds) {
    $inner->where('posts.author_type', User::class);
    $inner->whereIn('posts.author_id', $sharedSchoolUserIds);
});

To get the matching posts, you just need to do

$posts = $query->get();

While constructing the query from scratch works in this particular case, it will create a fairly brittle structure if your requirements ever change. For added flexibility, you probably want to build query scopes on the Post and Comments models for each of those components. That will mean that you only need to figure out one time what it means for a post to be authored by someone a user follows, then you could simply do Post::authoredBySomeoneFollowedByUser($me) to get the collection of posts authored by someone you follow.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.