First, let’s create a new migration for the categories table. You can use the following command to generate a migration file:
php artisan make:migration create_categories_table --create=categories
Then, in the up method of the migration file, define the schema for the categories table:
public function up()
{
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->integer('parent_id')->unsigned()->nullable();
$table->timestamps();
});
}
Here, we have defined the name column to store the name of the category, the parent_id column to store the ID of the parent category (if any), and the usual created_at and updated_at columns.
Next, let’s create the Category model. You can use the following command to generate the model file:
php artisan make:model Category
Then, in the Category model, define the relationships with itself (for subcategories) and with the Ad model (for filtering):
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
use HasFactory;
public function children()
{
return $this->hasMany(Category::class, 'parent_id');
}
public function parent()
{
return $this->belongsTo(Category::class, 'parent_id');
}
public function ads()
{
return $this->hasMany(Ad::class);
}
}
Here, we have defined two relationships with itself: children to get the subcategories and parent to get the parent category (if any). We have also defined a relationship with the Ad model using the hasMany method.
Finally, to filter the Ad model by category or subcategory, you can use the following code:
$category = Category::findOrFail($categoryId);
$ads = $category->ads()->with('category')->get();
Here, we first retrieve the category object using the $categoryId. Then, we use the ads relationship to get all the ads related to that category (and its subcategories) using the with method to eager load the related category for each ad. The result is a collection of Ad objects that you can use for further processing.
To retrieve ads from both the parent category and its subcategories, you can use Laravel’s whereHas method in your query. Here’s an example code:
$category = Category::findOrFail($categoryId);
$ads = Ad::whereHas('category', function ($query) use ($category) {
$query->where('id', $category->id)->orWhere('parent_id', $category->id);
})->get();
Here, we first retrieve the $category object using its ID. Then, we use the whereHas method on the Ad model to filter the ads based on their category relationship. The whereHas method takes a closure that defines the condition for the relationship. In our case, we check if the ad’s category ID is equal to the $category ID or if the ad’s category parent ID is equal to the $category ID. This will retrieve ads that belong to the $category itself as well as its subcategories.
The result is a collection of Ad objects that you can use for further processing.
To filter all ads in the Real Estate main category (Real Estate > Residential > Houses), you can use Laravel’s whereHas method with nested closures to filter based on the category and its subcategories. Here’s an example code:
$category = Category::where('name', 'Real Estate')->firstOrFail();
$ads = Ad::whereHas('category', function ($query) use ($category) {
$query->where('id', $category->id)->orWhere(function ($query) use ($category) {
$query->where('parent_id', $category->id)->orWhere(function ($query) use ($category) {
$query->where('parent_id', function ($query) use ($category) {
$query->select('id')->from('categories')->where('parent_id', $category->id);
});
});
});
})->get();
Here, we first retrieve the $category object for the Real Estate category using its name. Then, we use the whereHas method on the Ad model to filter the ads based on their category relationship. We use nested closures to define the conditions for the category and its subcategories. Here’s how the nested closures work:
- The first closure checks if the ad’s category ID is equal to the
$categoryID. - The second closure checks if the ad’s category parent ID is equal to the
$categoryID. - The third closure checks if the ad’s category grandparent ID (i.e., the parent of the parent) is equal to the
$categoryID. We use a subquery to retrieve the parent categories of the$categoryand then use the ID of the first parent category as the condition.
This will retrieve ads that belong to the Real Estate category and its subcategories, including Residential and .Houses
The generated MySQL query may look like this:
select
*
from
`ads`
where
exists (
select
*
from
`categories`
where
`ads`.`category_id` = `categories`.`id`
and (
`categories`.`id` = 1
or (
`categories`.`parent_id` = 1
or exists (
select
*
from
`categories`
where
`categories`.`parent_id` = 1
and `categories`.`parent_id` in (
select
`id`
from
`categories`
where
`parent_id` = 1
)
)
)
)
)
In this query, the whereHas method generates a subquery that checks if an ad’s category_id matches the id of the Real Estate category or any of its subcategories up to 2 levels deep. The subquery is then used in the exists clause of the main query to filter the ads based on their category relationship.


Leave a Reply