Reputation: 9952
I had some (major) performance issues on a project I'm working on and after logging all the queries that get executed, I realised many of them are executed multiple times and I cannot get to the root of the problem.
All the queries that get executed multiple times are in my view composer provider.
This is what my view composer looks like:
public function boot()
{
view()->composer('partials.sidebar', function ($view) {
$currentCategory = $this->getCurrentCategory();
$sidebarCategories = SidebarCategory::category($currentCategory)
->order()
->get();
$view
->with('sidebarCategories', $sidebarCategories);
});
view()->composer('partials.footer', function ($view) {
$footerLinks = FooterCategory::with('links.translations')->order()->get();
$footerColumn1 = $footerLinks->filter(function ($value, $key) {
return $value->column == 1;
});
$footerColumn2 = $footerLinks->filter(function ($value, $key) {
return $value->column == 2;
});
$footerColumn3 = $footerLinks->filter(function ($value, $key) {
return $value->column == 3;
});
$footerColumn4 = $footerLinks->filter(function ($value, $key) {
return $value->column == 4;
});
$view
->with(compact('footerColumn1', 'footerColumn2', 'footerColumn3', 'footerColumn4'));
});
}
Both of these queries (Sidbar and Footer categories) get executed about 6 times, even though each partials is called exactly once. They are both called in master view with @include('partialname').
I've tried this:
if($view->offsetExists('sidebarCategory'))
return;
But the offsetExists always returns false (even after its called for 5. time).
Any idea why this is happening and what I'm doing wrong?
Edit:
I've realised where the issue is. On the page that I'm visiting (where those multiple queries get executed) there are some 404 elements (images mostly). Each time a file is not found a new 404 exception is thrown. And each time a 404 exception is thrown the 404 view gets executed => meaning footer/sidebar queries get executed as well (as they're a part of 404 view). Example: https://i.sstatic.net/dQ1jo.jpg
So the follow up question is how to prevent the view from getting rendered when there is no need to (example the 404 is an image that wasnt found).
Here is a piece of code from my routes, that I assume is the reason that this is happening:
Route::get('{slug}', ['as' => 'findBySlug', 'uses' => function($slug) {
if(\App\DynamicCategory::findBySlug($slug)->count() > 0)
return App::make('App\Http\Controllers\GeneralController')->getDynamicCategoryIndex($slug);
else if(\App\DynamicPage::noCategory()->findBySlug($slug)->count() > 0)
return App::make('App\Http\Controllers\GeneralController')->getDynamicPage($slug);
else
abort(404);
}]);
PS: I know this piece of code is extremely unoptimized (as it basically executes the same query twice, once to see if the item exists, another time actually in the controller). This is work in progress and it's on todo list.
Edit 2:
I've come up with the next solution, I'm open to improvements as this is a somewhat hack-ish way (if I add more folders, I will need to keep in mind to update this as well). I only have 3 direct subfolders in public folder: something, files and resources.
The solution is to check the url's first segment when rendering the exception (in file app/Exceptions/Handler.php) and return 404 response without the view if it matches one of the 3 folders:
public function render($request, Exception $e)
{
$firstSegment = $request->segment(1);
if(starts_with($firstSegment, 'files') || starts_with($firstSegment, 'something') || starts_with($firstSegment, 'resources')) {
return response('Stran ne obstaja', 404);
}
return parent::render($request, $e);
}
Thanks in advance
Upvotes: 9
Views: 1334
Reputation: 101
You can use config here to resolve multiple times query run issue for view compose. For example show below code.
public function compose(View $view) {
if(!Config::has('composeVars'))
{
Config::set('composeVars') = [
'data' => User::all();
];
}
$view->with('*', Config::get('composeVars'));
}
Upvotes: 0
Reputation: 2929
Instead of checking the url segments for the specific paths, and if you say it will only be images. So if it's only going to be needed for things with specific extensions you can check for that and return a simpler 404 response for those :)
/* \app\Exceptions\Handler.php */
public function render($request, Exception $exception)
{
if ($exception instanceof NotFoundHttpException) {
$ext = pathinfo($request->url(), PATHINFO_EXTENSION);
if (in_array($ext, ['jpg', 'png'])) {
return response('Nope');
}
}
return parent::render($request, $exception);
}
Just realised it was making many requests. Maybe something like this, cache the results for 1 minutes, that should be long enough for the next 404 requests on the page to pick up the cache instead of running the queries :)
public function boot()
{
// Sidebar
$sidebarCategories = Cache::store('file')->remember('sidebarCategories', 1, function () {
$currentCategory = $this->getCurrentCategory();
return SidebarCategory::category($currentCategory)->order()->get();
});
view()->composer('partials.sidebar', function ($view) use ($sidebarCategories) {
$view->with('sidebarCategories', $sidebarCategories);
});
// Footer
$footerColumns = Cache::store('file')->remember('footerColumns', 1, function () {
$footerLinks = FooterCategory::with('links.translations')->order()->get();
return [
1 => $footerLinks->filter(function ($value, $key) { return $value->column == 1; }),
2 => $footerLinks->filter(function ($value, $key) { return $value->column == 2; }),
3 => $footerLinks->filter(function ($value, $key) { return $value->column == 3; }),
4 => $footerLinks->filter(function ($value, $key) { return $value->column == 4; }),
];
});
view()->composer('partials.footer', function ($view) use ($footerColumns) {
$view->with([
'footerColumn1' => $footerColumns[1],
'footerColumn2' => $footerColumns[2],
'footerColumn3' => $footerColumns[3],
'footerColumn4' => $footerColumns[4],
]);
});
}
Upvotes: 1
Reputation: 28722
Don't route file requests through laravel and serve a blank image on 404
.htaccess sample:
RewriteEngine On
RewriteCond %{REQUEST_URI} \.(jpg|jpeg|gif|png|ico)$ [NC]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .*$ /no_picture.png [L]
You would post this above the rules that redirect to index.php
This is assuming that pictures are served from the website and not by streaming database contents.
Basically what happens is this:
So what you want to do in the last step is prevent the image request even reaching laravel, by serving your own image as per defined by .htaccess rules. That way your webserver can respond much faster on the missing image.
Upvotes: 5