Using Typesense as your search provider for Laravel Scout and Instantsearch.

Typesense is a great open-source alternative for Algolia. Given that Algolia has a pretty expensive pricing package, it could be worth having a look to Typesense.

On top of that, it has already some integrations with famous packages like Instantsearch.js and Laravel Scout.

It started recently, so it’s documentation still lacks some examples. That is why I have written this post:

Using Typesense for Laravel

What is great is that there is a Laravel Scout package available: https://github.com/devloopsnet/laravel-scout-typesense-engine

Fixing the first Typesense errors with Laravel:

Most likely, you will receive errors when you use the default settings as described in the Typesense Laravel Scout package:

{"message": "Document's `id` field should be a string."}

That is solvable by converting the id to a string in the Searchable Array:

/**
* Get the indexable data array for the model.
*
* @return array
*/
public function toSearchableArray()
{
   $array = $this->toArray();
   // Typesense specific
   if (config('scout.driver') == 'typesensesearch') {
      $array['id'] = (string)$array['id']; $array['created_at'] = (integer)\Carbon::parse($array['created_at'])->timestamp;
   }
   return $array;
}

Note that I also converted the created_at datetime value to a timestamp integer, so that Typesense could easy sort on the integer value of the date.

Adding the Instantsearch.js Typesense Adapter to Laravel

You can follow the steps as described by the Typesense Adapter instructions. Also don’t forget to npm install algoliasearch and instantsearch.js .

Inserting in Laravel Mix

You can insert the following code in the webpack.mix.js file for Laravel Mix:

mix.extract(['algoliasearch', 'instantsearch.js', 'typesense-instantsearch-adapter'])
   .js(['resources/js/app.js'], 'public/js');

In your resources/js/app.js also get the imports ready:

import instantsearch from "instantsearch.js";
window.instantsearch = instantsearch;

import { searchBox, hits, index } from "instantsearch.js/es/widgets";
window.searchBox = searchBox;
window.hits = hits;
window.index = index;

import { connectAutocomplete } from 'instantsearch.js/es/connectors';
window.connectAutocomplete = connectAutocomplete;

import TypesenseInstantSearchAdapter from "typesense-instantsearch-adapter";
window.TypesenseInstantSearchAdapter = TypesenseInstantSearchAdapter;

Note that I have also included index and connectAutocomplete which are required for the Autocomplete Widget. If you are not planning to use those, you can leave them out.

Run Laravel Mix with (also use npm install if Mix has never been initialized):

npm run dev

In your main layout blade file add the generated mix files:

<!-- Scripts -->
<script src="{{ mix('/js/manifest.js') }}"></script>
<script src="{{ mix('/js/vendor.js') }}"></script>
<script src="{{ mix('/js/app.js') }}"></script>

Then you can simply insert the hits and searchbox:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/instantsearch.css@7.4.5/themes/reset-min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/instantsearch.css@7.4.5/themes/algolia-min.css">


<div id="hits"></div>
<div id="searchbox"></div>

<script>
const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter({
  server: {
    apiKey: "{{ env('TYPESENSE_SEARCH_KEY') }}", // Be sure to use the search-only-api-key
    nodes: [
      {
        host: "{{ env('TYPESENSE_HOST') }}",
        port: "443",
        protocol: "https"
      }
    ]
  },
  // The following parameters are directly passed to Typesense's search API endpoint.
  //  So you can pass any parameters supported by the search endpoint below. 
  //  queryBy is required. 
  additionalSearchParameters: {
    queryBy: "name"
  }
});
const searchClient = typesenseInstantsearchAdapter.searchClient;

const search = instantsearch({
  searchClient,
  indexName: "products"
});

// Create the custom widget
const customAutocomplete = connectAutocomplete(
  renderAutocomplete
);

// Instantiate the custom widget
search.addWidgets([
  index({ searchClient, indexName: 'autolandings' }),

  customAutocomplete({
    container: document.querySelector('#autocomplete'),
  })
]);

search.start();
</script>

I’ve added the CSS theming of instantsearch.js to give it some basic styling. This one does not seem to work for Autocomplete yet. You have to fully style this one yourself (as far as I know).

How to use Instantsearch.js Autocomplete with Typesense

Typesense wrote an adapter that works with Instantsearch.js for Algolia. It has some basic examples to use the hits and searchBox component.

In some cases you would like to extend this functionality. For example, if you would like to use the Instantsearch.js autocomplete functionality you have to write additional code. To make a basic autocomplete functionality with multiple indices you can use this code:

Using multiple indices and Autocomplete for Instantsearch.js

In this example I have copied the code from Algolia’s Instantsearch.js Autocomplete website and integrated the Typesense adapter:

<div id="autocomplete"></div>

<script>
const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter({
  server: {
    apiKey: "{{ env('TYPESENSE_SEARCH_KEY') }}", // Be sure to use the search-only-api-key
    nodes: [
      {
        host: "{{ env('TYPESENSE_HOST') }}",
        port: "443",
        protocol: "https"
      }
    ]
  },
  // The following parameters are directly passed to Typesense's search API endpoint.
  //  So you can pass any parameters supported by the search endpoint below. 
  //  queryBy is required. 
  additionalSearchParameters: {
    queryBy: "name"
  }
});
const searchClient = typesenseInstantsearchAdapter.searchClient;

const search = instantsearch({
  searchClient,
  indexName: "products"
});

// Helper for the render function
const renderIndexListItem = ({ indexId, hits }) => `
  <li>
    Index: ${indexId}
    <ol>
      ${hits
        .map(
          (hit, hitIndex) =>
            `
              <li>
                <p>${instantsearch.highlight({ attribute: 'name', hit })}</p>
                <button
                  type="button"
                  class="btn-add-to-cart"
                  data-index-id="${indexId}"
                  data-hit-index="${hitIndex}"
                >
                  Add to Cart
                </button>
              </li>
            `
        )
        .join('')}
    </ol>
  </li>
`;

// Create the render function
const renderAutocomplete = (renderOptions, isFirstRender) => {
  const { indices, currentRefinement, refine, widgetParams } = renderOptions;

  if (isFirstRender) {
    const input = document.createElement('input');
    const ul = document.createElement('ul');

    input.addEventListener('input', event => {
      refine(event.currentTarget.value);
    });

    widgetParams.container.appendChild(input);
    widgetParams.container.appendChild(ul);

    ul.addEventListener('click', (event) => {
      if (event.target.className === 'btn-add-to-cart') {
        const indexId = event.target.getAttribute('data-index-id');
        const hitIndex = event.target.getAttribute('data-hit-index');
        const index = indices.find(index => index.indexId === indexId);
        const hit = index.hits[hitIndex];

        index.sendEvent('conversion', hit, 'Product Added');
      }
    });
  }

  widgetParams.container.querySelector('input').value = currentRefinement;
  widgetParams.container.querySelector('ul').innerHTML = indices
    .map(renderIndexListItem)
    .join('');
};

// Create the custom widget
const customAutocomplete = connectAutocomplete(
  renderAutocomplete
);

// Instantiate the custom widget
search.addWidgets([
  index({ searchClient, indexName: 'landings' }),

  customAutocomplete({
    container: document.querySelector('#autocomplete'),
  })
]);

search.start();
</script>

Don’t forget to fill out the TYPESENSE_SEARCH_KEY and the TYPESENSE_HOST in your environment variables. And replace the first index products with your desired index. Replace ‘landings’ with your desired second index. You can add any more indices by adding:

index({ searchClient, indexName: 'anotherIndex' }),

to the search.AddWidgets part.

Mind that we also need to import the autocomplete connector and the index component widget via Laravel Mix.

Setting up CircleCI Continuous Integration with Laravel – Then deploy Forge or Fortrabbit

Including Continuous Integration is great, but there are many providers to choose from. CircleCI has a pretty decent free plan, but using it for your Laravel app might be a bit confusing.

Instructions for deploying Laravel 5.5 with Circle CI:

This site has a nice boilerplate script:

https://medium.com/@wesmahler/circleci-2-0-configuration-for-laravel-5-5-with-laravel-dusk-ab1cc9b9d4a7

But if you found a better script, let us know in the comments.

Make sure you have specified the testing environment variables in your .env.testing and phpunit.xml if you are using PHPUnit.

Getting a Mysql Root error?

Since March 2021, we sometimes got this error:

[ERROR] [Entrypoint]: MYSQL_USER="root", MYSQL_PASSWORD cannot be used for the root user
    Use one of the following to control the root user password:
    - MYSQL_ROOT_PASSWORD
    - MYSQL_ALLOW_EMPTY_PASSWORD
    - MYSQL_RANDOM_ROOT_PASSWORD

We solved it by changing the MYSQL_USER to another name than ‘root’.

Deploying on Laravel Forge via CI

After that call this forge-deploy.sh script in your final step in config.yml:

  - run:
     name: Run deploy script
     command: sudo chmod +x ./deploy.sh; sudo ./deploy.sh

Then create a deploy.sh script in your app and include the Laravel Forge / Envoyer deployment trigger URL (can be found in your app’s dashboard in Laravel Forge / Envoyer):

#!/bin/bash
# Trigger deployment
# Replace the url below with your envoyer/forge url
# APPLICATION1
curl -s 'https://forge.laravel.com/servers/123456...';

# APPLICATION2 - if you have multiple applications that needs to be deployed and triggered
curl -s 'https://forge.laravel.com/servers/12345678...';

echo 'Deployment triggered!'

After this, you can push to Github or Bitbucket and see if the build succeeds and deploys to Laravel Forge / Envoyer. Make sure you disable auto-deploy, as you only would like to deploy to Laravel Forge / Envoyer after a successful CI build.

Deploy on Fortrabbit with CircleCI

Rather want to deploy on Fortrabbit instead of Laravel Forge after a successful CircleCI build?

Read more about using CI with Fortrabbit here.

To setup the deployment to Fortrabbit from CircleCI follow this procedure:

  1. Run this script on a terminal client: ssh-keygen -m PEM -t rsa -C “your_email@example.com”
  2. Copy the private key to CircleCI
  3. Go to the project -> SSH Keys -> Additional SSH Keys Enter hostname: deploy.XYZ.frbit.com (replace XYX with your Fortrabbit server location)
  4. Add the public key in the Fortrabbit Dashboard’s public SSH key manager
  5. Add the fingerprint that you see in the CircleCI dashboard to the config.yml file:
  6. NOTE: adding a SSH key to a third party service always carries out risks, be aware of them and if it’s too risky, don’t take these steps.
version: 2
jobs:
  deploy-job:
    steps:
      - add_ssh_keys:
          fingerprints:
            - "SO:ME:FIN:G:ER:PR:IN:T"

Try to push the updated files to Github or Bitbucket.

See whether this build succeeds. If not, fix the issues until it works.

Add this code to the circleci/config.yml script after the testing steps:

      - run:
         name: Add FRBIT remote
         command: git remote add frbit YOURFRBITGITURL

      - run:
         name: Add FRBIT remote fingerprint to known hosts
         command: ssh-keyscan deploy.XYZ.frbit.com >> ~/.ssh/known_hosts

      - run:
         name: Deploy to FRBIT
         command: git push frbit

Don’t forget to replace the variables YOURFRBITGITURL and XYZ.

Using these scripts and instructions is on your own risk of course.

Got any suggestions for a better workflow? Let us know!

Fixing VBoxManage: error: VMDK: descriptor does not start as expected in

When using Vagrant / Virtualbox and optionally Laravel Homestead it could occur that this message displays:

There was an error while executing `VBoxManage`, a CLI used by Vagrant

for controlling VirtualBox. The command and stderr is shown below.

Command: ["startvm", "{LONGOCDE}", "--type", "headless"]

Stderr: VBoxManage: error: Could not open the medium '/Users/USER/VirtualBox VMs/homestead-7/ubuntu-....vmdk'.

VBoxManage: error: VMDK: descriptor does not start as expected in '/Users/USER/VirtualBox VMs/homestead-7/ubuntu-....vmdk' (VERR_VD_VMDK_INVALID_HEADER).

VBoxManage: error: VD: error VERR_VD_VMDK_INVALID_HEADER opening image file '/Users/USER/VirtualBox VMs/homestead-7/ubuntu-....vmdk' (VERR_VD_VMDK_INVALID_HEADER)

VBoxManage: error: Details: code NS_ERROR_FAILURE (0x80004005), component MediumWrap, interface IMedium

After changing lots of stuff and trying out a lot, I found out it’s best to restore a backup of your vmdk file. For Linux and Windows there seem to be restore tools (not sure if these will work),  but not for Mac.

So, after trying to restore your backup, hopefully everything works (always backup your new file before restoring as well). Everything is at your own risk of course. Good luck!

How to fix the Laravel Gulp Error: Cannot find module ‘internal/fs’

Laravel Gulp Error: Cannot find module ‘internal/fs’

Get this error?

vagrant@homestead:*****$ gulp
module.js:472
    throw err;
    ^

Error: Cannot find module 'internal/fs'
    at Function.Module._resolveFilename (module.js:470:15)
    at Function.Module._load (module.js:418:25)
    at Module.require (module.js:498:17)
    at require (internal/module.js:20:19)
    at evalmachine.:18:20
    at Object. (/usr/lib/node_modules/gulp/node_modules/vinyl-fs/node_modules/graceful-fs/fs.js:11:1)
    at Module._compile (module.js:571:32)
    at Object.Module._extensions..js (module.js:580:10)
    at Module.load (module.js:488:32)
    at tryModuleLoad (module.js:447:12)

How to fix this error?

Thanks to Softwarehorizont, I found the solution:

Try to delete all node modules and reinstall them. A command sequence like this would do:

npm cache clean
rm -Rf node_modules/
npm install

Still got this error?

If you still got this error, you can look up the following references (I have not tested these, at your own risk, like all solutions posted on this blog):

  1. https://github.com/nodejs/node/issues/9377
  2. http://stackoverflow.com/questions/40663489/npm-not-working-cannot-find-module-internal-fs-nodejs

Travis CI – Laravel Forge / webhook integration

While developing tests for my app, I wanted to have an integration with Laravel Forge in this order:

  1. Push to Github after commit
  2. Tests done by Travis CI
  3. Call the deployment trigger url of Laravel Forge / Envoyer if the test runs successfully. This URL looks like: https://forge.laravel.com/servers/111111/sites/2222222/deploy/http?token=TOKEN

To do this, create a travis.yml file like:

language: php

php:
  - 7.1

before_script:
  - cp .env.travis .env
  - mysql -e 'create database db_testing';
  - composer self-update
  - composer install --no-interaction
  - php artisan migrate

script:
  - vendor/bin/phpunit

after_success:
  - chmod +x ./tests.sh; ./tests.sh

Don’t forget to change the environment in phpunit.xml for example to change to a custom database:


The after_success script

Create a file tests.sh and insert:

#!/bin/bash

# Trigger deployment
# Replace the url below with your Forge/Laravel url
curl -s 'https://forge.laravel.com/servers/111111/sites/2222222/deploy/http?token=TOKEN';
echo 'Deployment triggered!'

 

Using other services than Travis or creating a custom test service

https://driesvints.com/blog/continuous-delivery-with-forge-and-envoyer/

Also thanks to:

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

8. Using Eager Loading instead of Lazy Loading

When using Laravel Eloquent models with relations it’s tempting to use ‘lazy loading’. That is a way of getting relational models in a loop. E.g.:

comments->name;
}

In this way, foreach blog the comments will be loaded in separate queries. When using Eager loading, the comments would be loaded in ONE IN query:

get();
foreach ($blogs as $blog)
{
    echo $blog->comments->name;
}

Read more about Eager Loading on Laravel’s documentation.

How to find queries that may need optimization?

Don’t know how to find queries that might need some optimization? E.g. that might need eager loading instead of lazy loading? The easiest way is to install and enable Laravel Debugbar on your development server. With this toolbar you can view the queries per page. If you see lots of queries that could have been simplified with a simple join or in-query, you know it’s your queue to optimize that script with eager loading.

Result: Each query saved, couple of milliseconds per query

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 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

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:


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):

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:

 Login with Facebook

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.