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 :
- Total Invoices
- Paid Invoices
- 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();
| Approach | Type | Notes |
| clone $query | PHP keyword | Works for any object |
| $query->clone() | Laravel method | Available 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.