Efficiency of dashboard widgets

Hi,

for a large site we have several panel dashboard widgets that all filter the site and display pages based on different conditions like approval status or publishing date.

So we have a code like this in the widget:

'html' => function() {
  $currentUser = panel()->user()->username();

  return tpl::load(__DIR__ . DS . 'myOpenContentApprovals.html.php', array(
    'pages' => panel()
      ->site()
      ->index()
      ->filter(function($page) use($currentUser) {
        return strpos($page->intendedTemplate(), 'element') !== false
          && $page->isVisible()
          && $currentUser == $page->contentapprover()
          && $page->approvecontent() == ''
          && $page->approvelanguage() == ''
          && !strstr($page->intendedTemplate(), 'legal');
      })
      ->sortBy('date', 'desc')
      ->limit(10)
  ));
}

The problem is, if we do that several times, the dashboard loading time becomes very slow (20 to 30 sec. for a larger site). I would love to handle this more efficiently to reduce the loading time. I.e. I don’t want to create the index and filter it by slight variants over and over again.

Are there any strategies I can apply to handle this better? Is there anything else I could do?
Thank you!

Kind regards,
Georg

using ->site->index() can be slow on large sites because I am pretty sure it pulls everything (pages, files, images). I think you can swap that part out to something else that only pulls back just the pages to see an improvement in speed.

It also might be possible to use a shared controller to get the site index in one place and re-use the data, rather then fetching it repeatedly in each widget.

Thank you! But what would be the alternative to ->site->index()? ->site->grandChildren() only returns the second level children from the root but I need the complete page tree. Is there any other method?

Try $pages->index() without the extra bit for the grandchildren. Should give you all pages. See here

$site->children()->index() might also work. If you think about it everything is a child of each other, regardless of depth, but if you use grandChildren it cuts out the grandparents, limiting you to the second level.

It works with panel()->site()->children()->index()->filter(function($page) ... but sadly I don’t see an improvement in speed (from 20 sec to 19sec).

Is there anything like a global widget controller or something else that could help with that?

Well as far as i know you can turn the widget into a plugin (it still works as a widget) and that opens the door to having its own controllers and assets etc. See here

Also, what PHP version are you running? Using PHP 7.x gives much noticeable speed improvements.

I don’t know much about creating plugins, but if it helps heres a real example of a plugin that is actually a widget (poking around in the source code might be useful)…

Yeah, thats exactly what I do already. The widgets are all in one plugin. I will play a bit more with it, but I struggle to pass a global variable or controller into the widget declaration.

We are on PHP 7.1, so that should be no issue.
Thank you!

Well i use shared controllers for normal page controllers (not a standard feature), I guess the same principle should work in the plugin…

<?php
return function($site, $pages, $page) {

  // Import Global Controller
  require_once kirby()->roots()->controllers() . '/shared/global.php';

  // Template Code

  // Pass these off to the template
  return compact('yourfunction');

};

The shared controller file is just a straight PHP file (in the example above, the yourfunction function is actually in the global.php file):

<?php
function yourfunction() {
// Some stuff
}

You will have to amend the path above so that it pulls the global code from the plugins directory rather then the usual controllers folder, as you would for a template.

Thank you, I will give it another shot.

It doesn’t really make a difference if you put the $site->index() code into a separate script or not, because the code will be executed for each widget, no matter where you put it.

Do you really have to filter the complete site index?

Maybe you can cache the collections using custom cache.

The filter itself could probably be improved a little bit by filtering by $site->index()->visible()before you add the filter callback…

I was thinking it could be stored temporarily and re-used the first time it gets fetched rather than fetched multiple times.

As I said, nothing is stored anywhere automatically. If you call a function, it still generates the collection every time you call the function, so you have to use a caching mechanism to prevent that. I.e. the function checks if the collection is stored in the cache, if yes, delivers it from cache, if not, recreates it.