Crud: [Bug] Tabs feature launches dozens of duplicate queries

Created on 16 Feb 2017  路  10Comments  路  Source: Laravel-Backpack/CRUD

As you can see in the attached pic, as soon as I activate Tabs, previous 6 queries jumps to 30, most of them duplicated. Debugbar traces the queries to view::crud::inc.show_tabbed_fields:41 located in show_tabbed_fields.blade.php, that reads @include('crud::inc.show_fields', ['fields' => $crud->getTabFields($tab)])

I wasn't able to trace it any further, but I confirmed the issue dissapears if I remove the tabs. Confirmed also in Gitter with @OwenMelbz and @automat64 so it doesn't seem to be something with my setup.

image

Most helpful comment

Okay @MarcosBL here we go

First we'll need to create the class we will use to override the base class in the vendor directory. I'll be storing the files in the app/Src/Backpack directory.

Create the following class and call it CrudPanel. Notice this extends \Backpack\CRUD\CrudPanel and we are just overriding the getEntity method (this is found in the read trait in the CrudPanel class we are extending)

namespace App\Src\Backpack;

class CrudPanel extends \Backpack\CRUD\CrudPanel
{
    /**
     * The method we want to override
     */
    public function getEntry($id)
    {
        if (!$this->entry) {
            $this->entry = $this->model->findOrFail($id);
            $this->entry = $this->entry->withFakes();
        }
        return $this->entry;
    }
}

We also need to create a new CrudController as this is where the CrudPanel is used. Create the following class. Notice we are extending the \Backpack\CRUD\app\Http\Controllers\CrudController class. We are only overriding the constructor as we need to ensure we are using our newly created CrudPanel. As we are still in the same namespace App\Src\Backpack the CrudPanel will be our created one :-)

namespace App\Src\Backpack;

class CrudController extends \Backpack\CRUD\app\Http\Controllers\CrudController
{
    public function __construct()
    {
        if (! $this->crud) {
            $this->crud = app()->make(CrudPanel::class);
            $this->middleware(function ($request, $next) {
                $this->request = $request;
                $this->crud->request = $request;
                $this->setup();

                return $next($request);
            });
        }
    }
}

The only thing left to do is use our newly created class App\Src\Backpack\CrudController instead of the standard \Backpack\CRUD\app\Http\Controllers\CrudController in all of our Crud Controllers that have the issue. Just swap out the class you are extending from \Backpack\CRUD\app\Http\Controllers\CrudController to App\Src\Backpack\CrudController.

I hope this helps.

If you're still stuck just ask.

All 10 comments

Anyone seeing the same? Tried to get the root cause, but to no avail

I traced this:

show_tabbed_fields.blade.php does a loop
php @foreach ($crud->getTabs() as $k => $tab)

That getTabs() function, in Tabs.php does:

php $fields = $this->getCurrentFields();

The funcion getCurrentFields() in Fields.php does

php return $this->getUpdateFields($this->entry->getKey());

And getUpdateFields() in Update.php does

php $entry = $this->getEntry($id);

Thus the continuous repetition of the

php select * from `table` where `table`.`id` = 'ID'

The more tabs and fields you have, the more repetitions of the same query

Yes. When I use Tabs, i get more repetitions of the same query ...

I can confirm this issue too and it does stem from tabs.

As a quick work around I have overridden the getEntry method in read trait in CrudPanel class. I just check if $this->entry is set or not. See below. This will prevent the query being called multiple times.

if (!$this->entry) {
    $this->entry = $this->model->findOrFail($id);
    $this->entry = $this->entry->withFakes();
}

If you need any help with overriding the getEntry method just let me know and I can provide instructions.

@splodgeco it would help a lot, I had no idea one could extend it without writing to vendor folder, any help would be appreciated

Okay @MarcosBL here we go

First we'll need to create the class we will use to override the base class in the vendor directory. I'll be storing the files in the app/Src/Backpack directory.

Create the following class and call it CrudPanel. Notice this extends \Backpack\CRUD\CrudPanel and we are just overriding the getEntity method (this is found in the read trait in the CrudPanel class we are extending)

namespace App\Src\Backpack;

class CrudPanel extends \Backpack\CRUD\CrudPanel
{
    /**
     * The method we want to override
     */
    public function getEntry($id)
    {
        if (!$this->entry) {
            $this->entry = $this->model->findOrFail($id);
            $this->entry = $this->entry->withFakes();
        }
        return $this->entry;
    }
}

We also need to create a new CrudController as this is where the CrudPanel is used. Create the following class. Notice we are extending the \Backpack\CRUD\app\Http\Controllers\CrudController class. We are only overriding the constructor as we need to ensure we are using our newly created CrudPanel. As we are still in the same namespace App\Src\Backpack the CrudPanel will be our created one :-)

namespace App\Src\Backpack;

class CrudController extends \Backpack\CRUD\app\Http\Controllers\CrudController
{
    public function __construct()
    {
        if (! $this->crud) {
            $this->crud = app()->make(CrudPanel::class);
            $this->middleware(function ($request, $next) {
                $this->request = $request;
                $this->crud->request = $request;
                $this->setup();

                return $next($request);
            });
        }
    }
}

The only thing left to do is use our newly created class App\Src\Backpack\CrudController instead of the standard \Backpack\CRUD\app\Http\Controllers\CrudController in all of our Crud Controllers that have the issue. Just swap out the class you are extending from \Backpack\CRUD\app\Http\Controllers\CrudController to App\Src\Backpack\CrudController.

I hope this helps.

If you're still stuck just ask.

Wow @splodgeco not only it worked at first try, it's also clean and your explanation easy to follow and understand, HUGE THANKS :clap: :clap: :clap:

@tabacitu @OwenMelbz any chance to get this fix in 3.2 ?

No problem @MarcosBL, glad it helped.

Please be aware this is not a fix, just a workaround. The getEntity method is still getting called multiple times. This workaround just ensures the query is called only once!

@MarcosBL & @splodgeco , thanks a lot for the bug report and fix!

Just pushed it on the dev branch, seems to work perfectly. It will be available to everyone in the next patch update, probably next week.

Cheers! Keep 'em coming :-)

@tabacitu - I'm not convinced this is fixed for me but it could be something I'm doing.

I have:

157     /**
158      * The method we want to override
159      */
160     public function getEntry($id)
161     {
162         if (! $this->entry) {
163             Debugbar::debug("Count not find entry with id '$id'.");
164
165             $this->entry = $this->model->findOrFail($id);
166             $this->entry = $this->entry->withFakes();
167         }
168
169         return $this->entry;
170     }

And although I only see one Debugbar log, I also see 55 queries, most of which are duplicates (6 uniques)...

Was this page helpful?
0 / 5 - 0 ratings