Laravel Eloquent Query Builder chaining affects original base query

Viewed 443

How can I get this query to work?

$basic_query = Invoices::between_days(15, 7); // This is a collection of invoices from 15 days ago, to seven days in the future. Notice I am not getting this query, it´s still just a builder instance. If you do dd($basic_query->get()) you get 100 invoices, 60 of them paid, 40 of them outstanding.

$paid_invoices = $basic_query->paid()->get(); // This returns the collection of 60 paid invoices without problems. BUT MODIFIES $basic query, if you dd($basic query->get()) at this point, you only get the 60 paid invoices, not the full 100 invoices collection. ¿?!!!

$outstanding_invoices = $basic_query->paid(false)->get(); // This query will always be empty, even though there are many outstanding invoices. If you dd($basic_query->get()) at this point, you get an empty collection. The 100 invoices are lost.

How can I then, have a basic collection as a starter point that will not be modified by subsequent get() operations.

Thank you!

2 Answers

There's a clone method available on the builder (Illuminate\Database\Query\Builder) which can be used

$basic_query = Invoices::between_days(15, 7);

$paid_invoices = $basic_query->clone()->paid()->get();

$outstanding_invoices = $basic_query->clone()->paid(false)->get();

Update

For Laravel verisons below 8.x a macro can be defined either in AppServiceProvider or a new MacroServiceProvider - boot method.

With MacroServiceProvider - don't forget to add it to the providers array in config/app.php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Database\Query\Builder;

class MacroServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Builder::macro('clone', function() {
            return clone $this;
        });
    }

    public function register()
    {
        //
    }
}

If you have a builder and you want a new copy of the builder so you can continue to build a query from it you can "clone" the builder:

$cloned = clone $query;

Now $cloned is its own object and you can build up the query how you want without having an effect upon $query, the original builder.

If you really want a clone method on the builder and it doesn't exist you can macro it:

Illuminate\Database\Query\Builder::macro('clone', function () {
    return clone $this;
});

You can throw that in a Service Provider's boot method.

Related