Adding Methods to Laravel Query Builders
If you find yourself frequently specifying which relations that you wish to join in your Laravel queries, you may notice it can quickly become cumbersome to add ->with(["posts", "comments", ])
each time you make a query. Or, you may want to add something similar to find()
but for something other than the index, for example findByEmail($email)
.
Thankfully, it is very simple to do this.
First, create a new class, perhaps in /app/Builders
. In this case, we will have a model named Products
where we want to join the category, images, and reviews tables. Our model may look something like this.
class Product extends Model
{
public function category(): BelongsTo
{
return $this->belongsTo(Category::class);
}
public function images(): HasMany
{
return $this->hasMany(ProductImage::class);
}
public function reviews(): HasMany
{
return $this->hasMany(ProductReview::class);
}
}
To select the product along and join all of those tables, we would usually have to use Product::where("id", $id)->with(["category", "images", reviews")->first();
. While this by itself isn’t too bad, if we end up reusing this query elsewhere, we may grow tired of having to type this out each time. More importantly, if we end up having to add an additional table or two later on, we will have to go back and update our query in each of those locations.
Instead, we will create a new class, ProductBuilder
which will extend \Illuminate\Database\Eloquent\Builder
. We will then create the methods we wish to add to the builder. For example, here we will call our function firstWith()
.
class ProductBuilder extends Builder
{
public function firstWith()
{
return parent::with(["category", "images", "reviews"])->first();
}
}
Lastly, we will update our model and override newEloquentBuilder()
to return our new builder.
class Product extends Model
{
public function newEloquentBuilder($query): ProductBuilder
{
return new ProductBuilder($query);
}
}
Now, we can use this method when we form our queries. For example, we could run
$product = Product::where("seller", $seller)
->where("name", $name)
->firstWith();
Similarly, we could create a custom get()
method, or a firstBySeller()
method.
public function getWith()
{
return parent::with("category", "images", "reviews")->get();
}
public function firstBySeller($seller)
{
return parent::where("seller", $seller)->first();
}
$products = Product::limit(20)
->offset(20)
->orderBy("id", "DESC")
->getWith();
$firstBySeller = Product::firstBySeller($seller);
The upside to adding the builder rather than just adding a method firstBySeller
to the model itself is that we can now modify the query if we wish. For example, we can run Product::where("name", $name)->firstBySeller($seller);
. This gives us more room to take advantage of Laravel’s query builders while also allowing us to refrain from duplicating code across our application.