Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Laravel 5.2 view composer gets executed multiple times

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: http://imgur.com/a/RrmOD

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

like image 983
DevK Avatar asked Dec 23 '16 10:12

DevK


People also ask

Where are Laravel views stored?

Views are stored in the resources/views directory. return view('greeting', ['name' => 'James']); }); As you can see, the first argument passed to the view helper corresponds to the name of the view file in the resources/views directory.

What is laravel View Composer?

View composers are callbacks or class methods that are called when a view is rendered. If you have data that you want to be bound to a view each time that view is rendered, a view composer can help you organize that logic into a single location.

How to define view in Laravel?

Views contain the html code required by your application, and it is a method in Laravel that separates the controller logic and domain logic from the presentation logic. Views are located in the resources folder, and its path is resources/views. Let's see the simple example of views.


1 Answers

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:

  • Backend user uploads image:
  • laravel stores image in /some/path/to/store/data/public/12a/4d8/458/12a4d8458.gif
  • laravel stores image as 12a4d8458.gif in your database
  • -------------------- time passes ------------------------
  • visitor requests page.
  • request doesn't match a file. redirect to index.php(as per .htaccess)
  • request gets send to index.php
  • laravel builds page. Finds image
  • laravel composes full public url path
  • www.awesome.com/data/public/12a/4d8/458/12a4d8458.gif
  • composed html contents get pushed to visitor
  • ---------------- milliseconds pass ------------------------
  • visitor requests image /data/public/12a/4d8/458/12a4d8458.gif
  • request matches file file gets served by apache
  • laravel remains blissfully unaware of request
  • ----------------- not a millisecond has passed -------------
  • visitor requests image /data/public/4e8/d44/98f/4e8d4498f.gif
  • request doesn't match a file. redirect to index.php(as per .htaccess)
  • laravel builds page. doesn't find a route.
  • laravel invokes 404 routines
  • laravel builds 404 page with all triggers associated
  • laravel serves 404 to user

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.

like image 192
Tschallacka Avatar answered Sep 29 '22 07:09

Tschallacka