How to Reuse a Base Query in Laravel Without Modifying the Original (Using clone)

When writing queries in Laravel, its common to run similar database queries with only small differences in conditions. For example, imagine you need to calculate :

  1. Total Invoices
  2. Paid Invoices
  3. Unpaid Invoices

A common approach is to write separate queries for each case.

$total = DB::table('invoices')
    ->where('company_id', $company->company_id)
    ->where('draft', 0)
    ->selectRaw("COUNT(*) as total_invoice")
    ->first();

$paid = DB::table('invoices')
    ->where('company_id', $company->company_id)
    ->where('draft', 0)
    ->where('is_paid', 1)
    ->selectRaw("COUNT(*) as total_invoice")
    ->first();

$unpaid = DB::table('invoices')
    ->where('company_id', $company->company_id)
    ->where('draft', 0)
    ->where('is_paid', 0)
    ->selectRaw("COUNT(*) as total_invoice")
    ->first();

In the above queries, the same conditions are repeated, harder to maintain and also violates DRY principle.

Common Mistake

Some developers try this:

$query = DB::table('invoices')
    ->where('company_id', $company->company_id)
    ->whereNull('invoice_type')
    ->where('draft', 0);

$paid = $query->where('is_paid', 1)->get();
$unpaid = $query->where('is_paid', 0)->get();

The above code does not work correctly. WHY?

Because Query Builder is an object.The second query will still include the first where('is_paid', 1) condition.

Laravel query builders are objects.
When you add a condition, you are modifying the same object in memory.

Correct Approach using clone()

Instead, we can create a base query and clone it when needed.

$baseQuery = DB::table('invoices')
    ->where('company_id', $company->company_id)
    ->where('draft', 0);

$total = (clone $baseQuery)
    ->selectRaw("COUNT(*) as total_invoice")
    ->first();

$paid = (clone $baseQuery)
    ->where('is_paid', 1)
    ->selectRaw("COUNT(*) as total_invoice")
    ->first();

$unpaid = (clone $baseQuery)
    ->where('is_paid', 0)
    ->selectRaw("COUNT(*) as total_invoice")
    ->first();

Now each query starts from the base conditions, does not affect other queries and remain clear and maintainable.

Using $query->clone() in Laravel

In addition to PHP’s native clone keyword, Laravel’s Query Builder also provides a clone() method. This method returns a cloned instance of the current query builder.

Example:

$baseQuery = DB::table('invoices')
    ->where('company_id', $company->company_id)
    ->where('draft', 0);

$paid = $baseQuery->clone()
    ->where('is_paid', 1)
    ->get();

$unpaid = $baseQuery->clone()
    ->where('is_paid', 0)
    ->get();

Here, $baseQuery->clone() creates a new query instance before applying additional conditions.

What’s the Difference?

Both of these approaches work.

clone $query;
 
$query->clone();
ApproachTypeNotes
clone $queryPHP keywordWorks for any object
$query->clone()Laravel methodAvailable on Query Builder

Does clone() improve database performance?

No, clone() itself does NOT improve the performance. Because this still runs 3 separate queries. So the database is still being hit 3 times.

clone() improves code structure, maintainability, readability and safety but NOT raw db performance.

clone() improves code quality, not database speed.
To improve performance, reduce the number of queries.

Recommended Approach

You can calculate everything in a single query using conditional aggregation.

$result = DB::table('invoices')
    ->where('company_id', $company->company_id)
    ->where('draft', 0)
    ->selectRaw("
        COUNT(*) as total_invoice,
        SUM(CASE WHEN is_paid = 1 THEN 1 ELSE 0 END) as paid_invoice,
        SUM(CASE WHEN is_paid = 0 THEN 1 ELSE 0 END) as unpaid_invoice
    ")
    ->first();

The above query will hit only one database call and a single table scan.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top