Introduction
Laravel's Eloquent gives a great API to query your database. Instead of writing queries completely by hand you can leverage the strengths of Eloquent. Especially relationship queries are great imho. You define how your models relate to each other through relationships. Next, you can use them to build complex queries. In this post I want to describe how we can query MorphTo relations.
An example
Suppose we have two groups of people: students and teachers. We decide to put these in separate tables and create an associative table to link the two together. A student can have many teachers, a teacher can have many students. So far, we have these database tables:
teachers:
id - integer
name - string
students:
id - integer
name - string
student_teacher:
student_id - integer
teacher_id - integer
Provided we have setup our models (Teacher and Student) correctly using the BelongsToMany relationship we could write a query like this:
// Retrieve teachers without students
Teacher::whereDoesntHave('students')->get();
Now let's assume both teachers and students can create comments. For this we need a one-to-many polymorphic relation (similar to the example in the Laravel docs). So, we'll create one more table:
comments
id - integer
body - text
commentable_id - integer
commentable_type - string
The Comment class knows whether it's linked to a Teacher or a Student through the commentable_type attribute. This is the accompanying class:
class Comment extends Model
{
public function commentable()
{
return $this->morphTo();
}
public function isFromTeacher()
{
return $this->commentable_type === 'App\Teacher';
}
}
Querying polymorphic relations
Now that we have our world set-up, we can query our polymorphic relationship. To find comments from teachers without students we can write:
// Comments from teachers without students
Comment::whereHasMorph('commentable', ['App\Teacher'], function ($query) {
$query->whereDoesntHave('students');
})->get();
The second argument of whereHasMorph
determines which types should be included in the query. You can expand the array to include students:
// Comments from teachers or students with the name john
Comment::whereHasMorph('commentable', ['App\Teacher', 'App\Student'], function ($query) {
// Both teachers and students have a "name" column
$query->where('name', 'like', 'john%');
})->get();
When the morph query should differ per type you can use an extra argument ($type
) and write custom query parts for each included type:
// Comments of:
// - teachers with students with the name john, or
// - students with the name john
Comment::whereHasMorph('commentable', ['App\Teacher', 'App\Student'], function ($query, $type) {
if ($type === 'App\Teacher') {
$query->whereHas('students', function ($query) {
$query->where('name', 'like', 'john%');
});
} else {
$query->where('name', 'like', 'john%');
}
})->get();
Eager loading
Sometimes we want to eager load relations. However, the results in the last example can contain comments from both students and teachers. Therefore, we will have to specify which relations to load per type:
// Load teacher's students with the name john
Comment::whereHasMorph('commentable', ['App\Teacher', 'App\Student'], function ($query, $type) {
if ($type === 'App\Teacher') {
$query->whereHas('students', function ($query) {
$query->where('name', 'like', 'john%');
});
} else {
$query->where('name', 'like', 'john%');
}
})->with(['commentable' => function ($morphTo) {
$morphTo->morphWith([
'App\Teacher' => ['students' => function ($query) {
$query->where('name', 'like', 'john%');
}],
]);
}])->get();
We could also first get the collection, filter it to only contain teachers and then eager load the relations using loadMissing
. Since the collection only contains Teacher objects, we can safely assume the students
relatioship is present.
// Collection of comments
$comments->filter->isFromTeacher()->loadMissing(['commentable.students' => function ($query) {
$query->where('name', 'like', 'john%');
}]);
Conclusion
You can build some pretty flexible and powerful queries using Eloquent morph relations :-)