2

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!

E. Barney
  • 373
  • 4
  • 19
  • they are "builder" objects, they build queries, so every call to them is either adding things to the query it is building or executing but the builder always holds the query you are building up – lagbox Dec 14 '20 at 18:29
  • @OMR, yes, it would have. Basically it´s the same question with different phrasing... sorry I missed that before, but it never showed up in my searches. – E. Barney Dec 14 '20 at 21:04

2 Answers2

3

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()
    {
        //
    }
}
Donkarnash
  • 12,433
  • 5
  • 26
  • 37
  • Thanks @Donkarnash, you are correct, although I cannot get clone() to work. I keep getting: BadMethodCallException Call to undefined method Illuminate\Database\Query\Builder::clone() – E. Barney Dec 14 '20 at 20:37
  • Which Laravel version are you on? – Donkarnash Dec 14 '20 at 20:39
  • Laravel 5.8... maybe that´s why. I am using (clone $basic_query) and it works fine, for the database builder, but I was wondering if there´s a similar function for the Illuminate\Database\Eloquent\Builder. – E. Barney Dec 14 '20 at 20:42
  • Okay for Laravel below v 8.x you can try `$basic_query->cloneWithout([])->paid()->get()`. just replace `clone()` with `cloneWithout([])` – Donkarnash Dec 14 '20 at 20:44
  • @E.Barney For Laravel versions below 8.x you can define a macro in a ServiceProvider - have updated answer – Donkarnash Dec 14 '20 at 21:01
  • I´m gonna take lagbox answer as final, since I couldn´t get the ->cloneWithout([]) method to work either... not sure why. But thanks much! – E. Barney Dec 14 '20 at 21:02
2

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.

lagbox
  • 48,571
  • 8
  • 72
  • 83