Speed Up Your Laravel Application

Speed Up Your Laravel Application

Laravel has become one of the most trending PHP framework as it has provided an easy and seamless development process for a web application.

Doing development in laravel is a quick and pleasurable experience. Regardless of that, by the time you get to deploy the application, you might realise the application doesn't perform well under real circumstances.

I have looked deep into the loading time of codemint.net and I have worked on several techniques to ensure the speed is increased here are some of the few techniques used and you can also apply to your own project.

What I'd like you to realise is that there's no silver bullet. Complete optimization comes through hard work, nitpicking every tiny aspect of the application that might slow it down. But use these little tricks I wrote here, and you should be okay

After some investigation and profiling i found potential places to improve your laravel application:

  1. Remove Unused Service
  2. Apply Eager Loading to your data
  3. Views Pipeline
  4. Views Rendering
  5. Too many middleWares
  6. Routes caching:
  7. Other PHP optimization (it keeps getting better as you optimize)

Remove Unused Service

Sometimes, it is better that you don’t load all services in your config, and disable unused service inside the config file. Add a comment to the unused service provider in.config/app.php However, make sure after commenting, you don’t break the whole functionality of your app.

Apply Eager Loading to Your Data

Laravel uses Eloquent ORM to easily map your object models to the database tables supporting it. With a few simple files you can map out your object structure, and Eloquent will handle all the appropriate database interaction needed for the CRUD (create, retrieve, update, delete) operations. When Eloquent does it, it uses a “lazy loading” approach.

That means for any related data, such as the author details for a book, Eloquent won’t actually retrieve the data until it is specifically referenced somewhere else in the code. While it may seem harmless to have one or two extra queries on your details page, making a similar query against a collection of books can result in a lot of queries and reduced performance as you wait for all of your data to return.

Instead, you want to set up your queries to use “eager loading,” which means they will retrieve any associated object models as part of your initial query.

Views Pipeline

As we said earlier Laravel adds a lot of generic stuff to include all possibilities, in the case of Views it’s the loading of data and the checking processes that take place when the render function happens.

To be more specific, one of the overheads that happen on every render is the function gatherData in Illuminate\View, which is usually a lot btw, if you have a very nested structure for your website i.e. a lot of @includes, @sections, @layouts, @for loops, etc.

You’d find inside this function the following code: 

$data = array_merge($this->factory->getShared(),

$this->data);

foreach ($data as $key => $value) {
    if ($value instanceof Renderable) {
        $data[$key] = $value->render();
    }
}

return $data;

And in my experience, few people use the Views with Renderable passed.

So what would be awesome is to override Laravel’s functionality and make the code as below:

protected function gatherData()
{     $data = array_merge($this->factory->getShared(), $this->data);
    return $data; }

After doing this, you’ll notice an increase in performance, which varies based on your server’s speed/page size/so on.

The increment in our case was about 5–10% [around 20ms] of the server-side load time. Check the load speed of codemint.net

Views Rendering:

Views Rendering as mentioned above is something that happens a lot during 1 session call, and the thing is that the pipeline of this Render function is that it works in a recursive way.

That is normal if you have a page that contains a couple of sections and not a lot of nested views. but, if you use a lot of @includes / @sections, especially the ones inside ForLoops then you’d probably have a big decrement in speed.

let’s put it this way, if you have 50 items in a page, it will be something like this:

@foreach ($products as $key => $item)
        @include(
'productView',
          [
            'product' => $item,
          ]
        )
@endforeach

and let’s say inside the ProductView there are 2 other includes. this case will result in 100 render calls when the view is rendered.
And after we profiled our application we found that the speed decreases significantly because of this. the solutions to solve this is to either:

  • Set a standard to avoid more than 2 nested levels to avoid too many rendering.
  • Flatten your views and use less includes (which of course is not a nice solution esp. if it’s a platform that is maintained over a long time).
  • Use a library that will flatten your views on Production Deployment process.

Too many middleWares:

Maybe you know, maybe you don’t that Laravel has a lot of pipelines overhead when it comes to its main feature, so I’d suggest not to use a lot of Middlewares since having pre&post calls to every MiddleWare. I would go for 5 max middleWares as a best practice.

Routes caching:

Config caching

The laravel config spreads across dozens of files, and `including` every one of them for each request is a costly process. To combine all of your config files into one, use:


php artisan config:cache

Keep in mind that any changes to the config will not have any effect once you cache it it. To refresh the config cache, run the above command again. In case you want to completely get rid of the config cache, run


php artisan config:clear

One Caching I love and I have used to improve codemint is

Cache the queries results

Because mysql is not gonna do this for you, not as good as you can do it by yourself. Definitely you're not going to cache every single query result from your application, look at the big picture! What queries are ran most frequently in your application, do they really have to be executed that often? Wouldn't running the query each 15 minutes and serving the result to every user achieve the same result?

The great thing that comes with removing the remember method from the query builder. (which was a great feature, but not great enough - people seemed to overestimate its power) is that you'll use more the Cache::remember function, as follows.

$ departments= Cache::remember(' departments', 30, function()

{

    return Department::whereFeatured(1)->whereVisible(1)->orderBy('name')->take(20)->get();

});

I am optimising more of this to keep improving the load speed of the website and i will keep you updated as i do more.....

Keep coding and keep optimising