Laravel Speed and Performance Optimization 101 – The Guideline

speed-1249610_1920I am working with a pretty heavy Laravel site with many requests and lots of Eloquent/SQL calls. Even though the high-memory and high-cpu VPS, I felt there is room for performance improvement. That is why I would like to write out some improvements to speed up Laravel:

1. Use Database or Redis for cache and sessions

When you navigate to config/cache.php and config/session.php, you see that the default CACHE_DRIVER and SESSION_DRIVER = file. If you have Redis installed, just try to set it as a cache and session driver. Check if Redis is installed by running:

redis-cli

If it is installed, try to define the drivers in your .env file:

CACHE_DRIVER=redis
SESSION_DRIVER=redis

2. Use several artisan pre-made commands

There are various artisan commands that are made to cache several parts of Laravel. You can configure them in the deployment process of Laravel Forge or Envoyer:

php artisan route:cache
php artisan config:cache
php artisan optimize --force

 

Use Developer tools guideline

Do a run at https://developers.google.com/web/fundamentals/performance/ and optimize several steps:

3. Optimize images like PNGs and JPEGs

Images sometime possess useless extra data that can be lossless optimized. Therefore you can use the packages OptiPNG and JPEGOptim. In Ubuntu install OptiPNG:

sudo apt-get update
sudo apt-get install optipng

And also install JPEGOptim:

sudo apt-get install jpegoptim

Now navigate to the folder that you would like to optimize its images. For optipng run:

optipng *

For jpegoptim run:

for i in *.jpg; do jpegoptim --all-progressive "$i"; done

Note that also the subdirectories might be optimized

4. Use HTTP2 instead of HTTP 1.1 if you have an SSL certificate enabled

The following guideline helps you through the process of updating to HTTP2 in Nginx.

Note that you need to have a SSL certificate. Google PageSpeed gave a server speed change from 0.52s (without HTTP2, with SSL) to 0.35s (with HTTP2)

Result: About 30% speed increase

5. Cache response in Redis or File

Sometimes it is unnecessary that the page is called from a database many times. Just caching the page reduces quite some load. That is exactly the same idea of the Laravel Response Cache plugin. The install instructions are quite good. In my case it had a drastic performance improvement.

Result: about 200 ms speed increase in my case

6. Optimize InnoDB innodb_buffer_pool_size

I am not sure why, but for some reason InnoDB has a really small innodb_buffer_pool_size when deploying your initial Laravel Forge server. In my default case, the innodb_buffer_pool_size was 8MB, while some blogs estimate that you can reserve up to 80% of your ram for innodb_buffer_pool_size. So edit the my.conf file:

sudo nano /etc/mysql/my.cnf

And add for example:

innodb_buffer_pool_size = 1G

Result: Several times faster queries

7. Reduce load by adding swap

My server regularly had Redis full-memory issues and high loads. This was solved by adding some swap to Ubuntu. As described by DigitalOcean:

One of the easiest way of increasing the responsiveness of your server and guarding against out of memory errors in your applications is to add some swap space. Swap is an area on a hard drive that has been designated as a place where the operating system can temporarily store data that it can no longer hold in RAM.

Read more about adding swap at the DigitalOcean website.

Result: a reduction in load when entering the ‘top’ command

This list is still a work-in-progress list, although above tips gave me a speed improvement of MORE THAN 1 SECOND. Got any tips, let me know in the comments!

Also share your speed improvements 🙂

Laravel 5.3 change login path and prevent registration

Prevent that /login is the default path for login

Thanks to Stackoverflow. Go to your routes/web.php

And change:

Auth::routes();

Into:

// Login
Route::group(['middleware' => ['web']], function() {
    Route::get('login-new-address', ['as' => 'login', 'uses' => 'Auth\LoginController@showLoginForm']);
    Route::post('login-new-address', ['as' => 'login.post', 'uses' => 'Auth\LoginController@login']);
    Route::post('logout-new-address', ['as' => 'logout', 'uses' => 'Auth\LoginController@logout']);
});
// Registration Routes...
    Route::get('register', ['as' => 'register', 'uses' => 'Auth\RegisterController@showRegistrationForm']);
    Route::post('register', ['as' => 'register.post', 'uses' => 'Auth\RegisterController@register']);

// Password Reset Routes...
    Route::get('password/reset', ['as' => 'password.reset', 'uses' => 'Auth\ForgotPasswordController@showLinkRequestForm']);
    Route::post('password/email', ['as' => 'password.email', 'uses' => 'Auth\ForgotPasswordController@sendResetLinkEmail']);
    Route::get('password/reset/{token}', ['as' => 'password.reset.token', 'uses' => 'Auth\ResetPasswordController@showResetForm']);
    Route::post('password/reset', ['as' => 'password.reset.post', 'uses' => 'Auth\ResetPasswordController@reset']);

Would you like to prevent registration?

Remove the registration and password reset routes if you don’t want people to register, e.g. for admin panels.

Also change your redirect if not logged in

Change in App/Exceptions/Handler.php the redirect to:

return redirect()->guest('login-new-address');

Laravel Eloquent Triple Pivot Relations

Solving the HasManyTriple problem

Sometimes you find yourself in the situation where you have a table like shop_country_category, which has the following structure:

countries
- id
- name

shop
- id
- name

products
- id
- name

shop_country_product
- id
- country_id
- shop_id
- product_id

In this case a shop has specific products that differ per country. So, for example in the Shop.php Eloquent model use:

    /**
     * The country-specific products that belong to a shop (triple pivot relation)
     */
    public function countryProducts()
    {
        return $this->belongsToMany('App\Product','shop_country_product')
                    ->withPivot('country_id');
    }

In this way you can access that shop_country_category

Or use a plugin

I’ve not tested it, but I heard this package might work with Laravel 5.

Do you have a better solution?

Do not hesitate to share them in the comments.

Laravel Forge Ubuntu Update and Upgrade Manual with OpenSSL bugfixes

Be careful: Not all commands have been fully tested, you use these instructions with care and on your own risk. To repeat, as goes for all our articles, using our instructions is on your own risk!
This article primarily focuses on Ubuntu 14.04 and 16.04 servers that are working with Laravel Forge, but it can be useful for all Ubuntu (server) users.

As mentioned, OpenSSL recently had a security vulnerability with code CVE-2016-2107. This vulnerability is fixed in OpenSSL 1.0.2h – 3 May 2016. Check your current version by entering the command:

openssl version -v 

Only updating OpenSSL?

Then you can run:

sudo apt-get install --only-upgrade libssl1.0.0 openssl

Then restart Nginx:

sudo service nginx restart

Check if the version is upgrade by entering the command:

openssl version -v 

If it is all right you should see the version:

OpenSSL 1.0.2h 3 May 2016

Or newer of course.

Doing an update or upgrade within the same version

As instructed by DigitalOcean, you can update or upgrade Ubuntu by updating the package list:

sudo apt-get update

Then, upgrade installed packages to their latest available versions:

sudo apt-get upgrade

You will be shown a list of upgrades, and prompted to continue. Answer y for yes and press Enter. Then, the packages are updated and upgraded

Error: Unmet dependencies?

While upgrading, the following error may occur:

The following packages have unmet dependencies:
linux-image-extra-3.13.0-66-generic: Depends: linux-image-3.13.0-66-generic but it is not installed
linux-image-extra-3.13.0-79-generic: Depends: linux-image-3.13.0-79-generic but it is not installed
linux-image-generic: Depends: linux-image-3.13.0-79-generic but it is not installed"

Therefore you can install the missing images by entering the command:

sudo apt-get install -f

It could be that you get this message:

Unpacking linux-image-3.13.0-79-generic (3.13.0-79.123) ...
dpkg: error processing archive /var/cache/apt/archives/linux-image-3.13.0-79-generic_3.13.0-79.123_amd64.deb (--unpack):
 cannot copy extracted data for './boot/vmlinuz-3.13.0-79-generic' to '/boot/vmlinuz-3.13.0-79-generic.dpkg-new': failed to write (No space left on device)
No apport report written because the error message indicates a disk full error
                                                                              dpkg-deb: error: subprocess paste was killed by signal (Broken pipe)

In that case read the next paragraph.

Full /boot directory?

When installing missing dependencies and running the command:

sudo apt-get install

There might occur an error as described in the previous paragraph. This can be solved by following the following steps as described on Stack Overflow.
When you command:

df -h

You probably would see that the /boot directory is 100% filled. In that case:

First, identify the space to be used,

cd /boot
du -sk *|sort -n

There might be a lot of kernels. Then run:

uname -a

to get the running kernel. The user on Stack Overflow: identified that I was on Linux alternate 2.6.32-43-server and did a tar of 6 of the versions that were not running, and were old.

tar -cvf ~username/boot.tar *2.6.32-44-server *2.6.32-45-server *2.6.32-46-server *2.6.32-47-server *2.6.32-48-server *2.6.32-49-server

Then do a rm -rf of what is backed up:

rm -rf *2.6.32-44-server *2.6.32-45-server *2.6.32-46-server *2.6.32-47-server *2.6.32-48-server *2.6.32-49-server

I am showing these commands as examples, you will have to decide what you will work with for your situation.

Now that you have some space on /boot, you are able to run

apt-get -f install 

To clean up the failed install of 2.6.32-56-server.

Then do:

apt-get remove linux-headers-2.6.32-38 linux-headers-2.6.32-38-server linux-image-2.6.32-38-server
apt-get remove linux-headers-2.6.32-39 linux-headers-2.6.32-39-server linux-image-2.6.32-39-server

This gives room to put back what I had backed up.

tar -xf ~username/boot.tar
rm  ~username/boot.tar    

To clean up, you could could run:

apt-get autoremove

Then reboot and you will see you are using a very small percentage of /boot.

>> Doesn’t that work? You can also try this Stack Overflow-answer, which also worked for me.

Release upgrading from 14.04 to 16.04

Be careful! As “zachleigh” mentions on Laracasts: “If you’re already using php7 in 14.04, then there really isnt much point in upgrading now I guess. 14.04 is supported until spring 2018 so you still have a couple years before you have to do anything. May as well wait until the next long term support release, 18.04, comes out in 2018.”

If you still would like to do this, read the guide by DigitalOcean and run:

sudo do-release-upgrade

3 of the best Javascript Searchable Select Boxes compared – Select2 vs Chosen vs Selectize.js performance

When having large amounts of jQuery/javascript select boxes that need to be searchable it is wise looking at the performance of them. I compared the following select replacement boxes: Select2, Chosen, Selectize.js, Bselect. Default Safari browser select box: initializing about 0.5s.

Setup

Having a backend page that has about 1000 select boxes that need to be initialized by a searchable/autocomplete selectbox. I checked the loading times of the page and the time of the initializitation of the select box. I measured it first by hand and later on by Safari web console.

Chosen performance

  • Measuring by hand: about 8s-11s.
  • Console measure: about 12s.Chosen select box performance

Cons:

  • Has no ajax support, if you want to do this, you can combine it with select2.

Select 2 performance

  • Measuring by hand: initializing takes about 9s-16s.
  • Console meaure:  about 21.69s.Select2 select box performance

Selectize.js performance

Selectize.js: about 19s. Console crashes after 29s. So, I exclude further results.

Bselect performance

Bselect: more than 40s, I got bored counting, so I exclude further results.

And the winner is … Chosen

It is initializes as fastest, being about 1,5x as fast as Select2. Although, Select2 has lot more functionalities and is better maintained. When you only need simple searchable select boxes and mind about performance, Chosen is the best choice. Note that it is only one simple configuration, it might be that in other configurations Select2 is faster than Chosen.

Laravel, Beanstalkd and an overview of queues

In my project I am using Beanstalkd for queue processing. Sometimes jobs get stuck, while I’d like to debug these queue jobs. Mark Needham wrote a great blog about how to get an overview of Beanstalkd jobs.

Connecting

Assuming that Beanstalkd is installed, connect to the console through telnet:

telnet localhost 11300

Getting an overview

Use the command:

stats

You will get output like:

current-jobs-urgent: 0
current-jobs-ready: 2
current-jobs-reserved: 0
current-jobs-delayed: 0
current-jobs-buried: 0
cmd-put: 66
...
current-connections: 6
current-producers: 1
current-workers: 1
current-waiting: 1
total-connections: 58
pid: 15622
version: 1.4.6
rusage-utime: 0.000000
rusage-stime: 0.040002
uptime: 22740
binlog-oldest-index: 0
binlog-current-index: 0
binlog-max-size: 10485760

Listing the first ready job

To see a concrete ready job, use the command:

peek-ready

In terms of Laravel you can get a job result back

{"job":"Illuminate\\Queue\\CallQueuedHandler@call","data":{"...........job\";N;}"}}

If the job got stock, you can clear jobs by pulling in an artisan queue:clear package. Then, you can run the command:

php artisan queue:clear

If your daemon got stock as well, call php artisan queue:restart

Laravel 5.1 logout custom message and redirect to previous page

When you’re logging out in Laravel 5 and 5.1 the AuthenticatesUsers is called and the getLogout method. Since, it’s in the Illuminate directory it’s not nice to write in this file directly. It’s better to rewrite the getLogout method from an Auth\AuthController.

Normally AuthCntroller, it might use this traits:

use AuthenticatesAndRegistersUsers, ThrottlesLogins;

Now we’ll rename the getLogout of the AuthenticatesUsers trait:

use AuthenticatesAndRegistersUsers
    {
        getLogout as authLogout;
    }
    use ThrottlesLogins;

Now we can rewrite the getLogout and still use/inherit the old trait’s class:

    /**
     * Overwrite getLogout method of trait AuthenticatesUsers and calls it again, since it's renamed as $this->authLogout();
     * @return Response
     */
    public function getLogout()
    {
        if (!empty(URL::previous()) && !str_contains(URL::previous(), "auth/"))
        {
            $this->redirectAfterLogout = URL::previous(); // Send back to previous url if possible
        }

        alert()->success('You\'re logged out', 'Logout'); // Send a flash message e.g. with the SweetAlert package: https://github.com/uxweb/sweet-alert
 
        return $this->authLogout(); // rest of the old method of the trait
    }

Laravel 5 socialite with Facebook integration

Would you like to offer a Facebook login functionality next to a regular e-mail based login? This is a tutorial to achieve that with Laravel 5 and the Socialite plugin. This tutorial is based on Matt Stauffer’s tutorial.

First of all pull in Laravel Socialite via composer:

composer require laravel/socialite

Create the users and password_remember migration:

$table->increments('id');
$table->string('name');
$table->string('email')->unique();
$table->string('avatar');
$table->string('password', 60);
$table->boolean('is_admin');
$table->string('facebook_id');
$table->rememberToken();
$table->timestamps();

Get your Facebook developer id and secret at: https://developers.facebook.com/.

Insert the Facebook credentials into the app/services.php file:

'facebook' => [
        'client_id' => env('FACEBOOK_ID'),
        'client_secret' => env('FACEBOOK_SECRET'),
        'redirect' => env('FACEBOOK_URL'),
    ],

In my case, I store them in the .env file as environment variables:

FACEBOOK_ID=xxx
FACEBOOK_SECRET=yyy
FACEBOOK_URL=http://myapp.devapp/auth/facebook/callback

Create a users model and make sure that some fields are fillable:

<?php

namespace App;

use Illuminate\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;

class User extends Model implements AuthenticatableContract, CanResetPasswordContract
{
    use Authenticatable, CanResetPassword;

    /**
     * The database table used by the model.
     *
     * @var string
     */
    protected $table = 'users';

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = ['name', 'email', 'password', 'avatar', 'facebook_id'];

    /**
     * The attributes excluded from the model's JSON form.
     *
     * @var array
     */
    protected $hidden = ['password', 'remember_token'];
}

So the model and migrations are prepared, if necessary, run your migration:

php artisan migrate

Register a new controller in your routes file (if there’s an auth controller already, do it above the auth controller):

Route::get('/auth/facebook', 'Auth\SocialController@redirectToProvider');
Route::get('/auth/facebook/callback', 'Auth\SocialController@handleProviderCallback');

Create the Facebook Social Auth Controller (app/Http/Controllers/Auth/SocialController.php):

<?php namespace App\Http\Controllers\Auth; use Illuminate\Http\Request; use App\User; use App\Http\Requests; use App\Http\Controllers\Controller; use Auth; use Socialite; class SocialController extends Controller { /** * Redirect the user to the Facebook authentication page. * * @return Response */ public function redirectToProvider() { return Socialite::driver('facebook')->redirect();
    }

    /**
     * Obtain the user information from Facebook.
     *
     * @return Response
     */
    public function handleProviderCallback()
    {
        $user = Socialite::driver('facebook')->user();

        $authUser = $this->findOrCreateUser($user);

        Auth::login($authUser, true);

        return redirect()->back();
    }

    /**
     * Return user if exists; create and return if doesn't
     *
     * @param $fbUser
     * @return User
     */
    private function findOrCreateUser($fbUser)
    {

        if ($authUser = User::where('facebook_id', $fbUser->id)->first()) {
            return $authUser;
        }

        return User::create([
            'name' => $fbUser->name,
            'email' => $fbUser->email,
            'facebook_id' => $fbUser->id,
            'avatar' => $fbUser->avatar
        ]);

    }
}

You can now link to your social auth controller from somewhere in your blade view:

<a class="btn btn-primary" href="{{ action('Auth\SocialController@redirectToProvider') }}"> Login with Facebook</a>

Important safety note

If you’d like to keep the possibility for people to login, make sure that you’ve empty password validation checks, so that people can’t sign in with only Facebook e-mail addresses.

That’s it. Do you’ve additions to this tutorial? Let me know in the comments.

Validating meta title and meta description with jQuery

Would you like to auto-display validation hints when a meta_title field is longer than 55 chars and a meta_description field is longer than 155 chars?

Then you can use this package: jQuery Meta Title and Description Validation.

Setup
1. Have at least one input field named meta_title or meta_description

E.g.

<p>
<input type="text" name="meta_title" placeholder="Meta title">
</p>
<p>
<input type="text" name="meta_description" placeholder="Meta description">
</p>

2. Initialize by including jquery-meta-validation.js

<script type="text/javascript" src="js/jquery-meta-validation.js"></script>