Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to integrate Facebook PHP SDK with Laravel 5.4?

I was searching for an easy way to integrate Facebook PHP SDK with Laravel 5.4. Essentially, I wanted to make it available as a service within my Laravel app. Of course there is SammyK/LaravelFacebookSdk on github. But for some reason I didn't want to use it. I felt the setup was adding another layer that I would have to understand, and work within the constraints.

There is also Laravel's own Socialite package. But this is essentially for simple authentication only. If I want to upload photos, comments, make batch requests, these are not possible. Socialite does not use Facebook's PHP SDK as a dependency. It uses Guzzle package to make direct API requests for authentication only.

Since there is no simple SO answer to directly integrating Facebook SDK in least number of steps, I thought I'd write this.

like image 727
Adam Ranganathan Avatar asked Mar 22 '17 07:03

Adam Ranganathan


1 Answers

First, edit composer.json in the project's root folder to include the Facebook SDK:

{
  "require" : {
    "facebook/graph-sdk" : "~5.0"
  }
}

Next run composer update at shell prompt to pull in the sdk to the vendor folder.

Now we would like to use the Facebook SDK within our app as a service provider. But before doing that, let us setup our app_id, app_secret and default_graph_version which are parameters required while making requests to Facebook API. The app_id and app_secret can be obtained by registering at Facebook Developers website.

Once we have these credentials from Facebook, we would now edit .env file in the project's root folder. Add them to the end:

FACEBOOK_APP_ID=xxxxxxxxxxxxxxxx
FACEBOOK_APP_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
FACEBOOK_DEFAULT_GRAPH_VERSION=v2.8

Replace the xxx.. with the values provided to you. Note that the variable names are just my own creation. You can name them whatever you'd like. We would now have to use these variables to setup a separate config file. We need to do this so that we can use Laravel's config() helper function to retrieve the values wherever we want within the app. So let's create facebook.php in the config folder and add the following:

<?php

return [
    'config' => [
        'app_id' => env('FACEBOOK_APP_ID', null),
        'app_secret' => env('FACEBOOK_APP_SECRET', null),
        'default_graph_version' => env('FACEBOOK_DEFAULT_GRAPH_VERSION', 'v2.8'),
    ],
];

With this simple setup we can now call config('facebook.config') from anywhere in the app. It would return the array along with the relevant values matched from the .env file.

Now let us set this up as a service provider so that we don't have to retrieve these credentials and build new Facebook object every time we are making a call to the Facebook API.

In Laravel 5.4, open the file app\Providers\AppServiceProvider.php. If you don't have this file or want to make a separate one, then you could create a new service provider at shell:

php artisan make:provider FacebookServiceProvider

We can now edit FacebookServiceProvider.php in the Providers folder instead. The only difference is that we need to register this in the our config/app.php file. You would add the following at the end of the $providers array:

App\Providers\FacebookServiceProvider::class,

To continue with the relevant code, in either AppServiceProvider.php or our new FacebookServiceProvider.php we first include: use Facebook\Facebook; at the top. Then within the register() method add the following:

$this->app->singleton(Facebook::class, function ($app) {
            return new Facebook(config('facebook.config'));
        });

You might notice that I am binding the class as a singleton since for my app I would like to reuse the same object from the service container. There are other types of bindings provided by Laravel that you might want to check out.

The whole code would look like below (I am using AppServiceProvider.php):

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Facebook\Facebook;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton(Facebook::class, function ($app) {
            return new Facebook(config('facebook.config'));
        });
    }
}

That's about it. We now have Facebook available as a service to the app. We can now inject the facebook object wherever we would like to use it within our app. From here on one can simply follow instructions from Facebook documentation to call their API.'

Extra Stuff, just as an example:

Before proceeding, I'd like to mention that I found the series of posts written by the folks at Symfony to be very helpful to understand the concepts of Service Container and Dependency Injection. You can find them here

Let's now try to do a basic operation such as retrieving a person's name from facebook. In order to get information about a facebook user we would need to call the Facebook API by sending basic parameters: user's facebook id along with an access token. Let's call these uid and access_token respectively. You would need to retrieve these using one of Facebook's methods:

  1. FacebookRedirectLoginHelper - when you want facebook authentication to happen from server side.
  2. FacebookCanvasHelper - for client side canvas based app authentication
  3. FacebookJavaScriptHelper - for client side javascript authentication

You can set up the kind of authentication you want by following the steps provided in Facebook's getting started guide.

My app is pretty simple and so I went with client side javascript authentication. I also use jquery alongside. Since I use Laravel's blade engine, my javascript was embedded in the view file directly so that I could include Laravel's csrf_token() and use other helper functions such as url(). The client side javascript looks like below. Remember to replace appId with your values and save the file as login.blade.php.

<html>
<head>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<button id="btn-login" type="button" class="btn btn-primary btn-lg">
    <span> Login with Facebook</span>
</button>

<script>
$(document).ready(function() {
    $.ajaxSetup({ cache: true }); // since I am using jquery as well in my app
    $.getScript('//connect.facebook.net/en_US/sdk.js', function () {
        // initialize facebook sdk
        FB.init({
            appId: 'xxxxxxxxxxxxxxxx', // replace this with your id
            status: true,
            cookie: true,
            version: 'v2.8'
        });

        // attach login click event handler
        $("#btn-login").click(function(){
            FB.login(processLoginClick, {scope:'public_profile,email,user_friends', return_scopes: true});  
        });
    });

// function to send uid and access_token back to server
// actual permissions granted by user are also included just as an addition
function processLoginClick (response) {    
    var uid = response.authResponse.userID;
    var access_token = response.authResponse.accessToken;
    var permissions = response.authResponse.grantedScopes;
    var data = { uid:uid, 
                 access_token:access_token, 
                 _token:'{{ csrf_token() }}', // this is important for Laravel to receive the data
                 permissions:permissions 
               };        
    postData("{{ url('/login') }}", data, "post");
}

// function to post any data to server
function postData(url, data, method) 
{
    method = method || "post";
    var form = document.createElement("form");
    form.setAttribute("method", method);
    form.setAttribute("action", url);
    for(var key in data) {
        if(data.hasOwnProperty(key)) 
        {
            var hiddenField = document.createElement("input");
            hiddenField.setAttribute("type", "hidden");
            hiddenField.setAttribute("name", key);
            hiddenField.setAttribute("value", data[key]);
            form.appendChild(hiddenField);
         }
    }
    document.body.appendChild(form);
    form.submit();
}

</script>
</body>
</html>

If your app has a different set of permissions needed from user, then edit the scope field. For all available facebook permissions see here.

So basically, my code has a login button and when user clicks it, the javascript sdk kicks in and pops up a login window. Once user logs in, I post the data back to server as if a form is being posted.

Now back to server side, since we have uid and access_token we can store these to database and then make a simple call to Facebook API from our server. Set up a route in routes/web.php to receive the form data:

Route::post('login', 'FacebookUser@store');

Make FacebookUser controller at shell:

php artisan make:controller FacebookUser

And the controller's code is as follows:

<?php

namespace App\Http\Controllers;

use Request;
use App\User; // you need to define the model appropriately
use Facebook\Facebook;

class FacebookUser extends Controller
{
    public function store(Facebook $fb) //method injection
    {
        // retrieve form input parameters
        $uid = Request::input('uid');
        $access_token = Request::input('access_token');
        $permissions = Request::input('permissions');

        // assuming we have a User model already set up for our database
        // and assuming facebook_id field to exist in users table in database
        $user = User::firstOrCreate(['facebook_id' => $uid]); 

        // get long term access token for future use
        $oAuth2Client = $fb->getOAuth2Client();

        // assuming access_token field to exist in users table in database
        $user->access_token = $oAuth2Client->getLongLivedAccessToken($access_token)->getValue();
        $user->save();

        // set default access token for all future requests to Facebook API            
        $fb->setDefaultAccessToken($user->access_token);

        // call api to retrieve person's public_profile details
        $fields = "id,cover,name,first_name,last_name,age_range,link,gender,locale,picture,timezone,updated_time,verified";
        $fb_user = $fb->get('/me?fields='.$fields)->getGraphUser();
        dump($fb_user);
    }    
}

Note that by using setDefaultAccessToken() the $fb object can be retrieved from service container in any part of subsequent processing of app's codebase. $fb can be used directly to make a Facebook API request. No need to build $fb again using app_id, app_secret and no need to set access token again for the current user during the current request-response lifecycle. This is because $fb is singleton and so the same object is returned when service container is called for Facebook service during current request-response lifecycle.

If you are unable to do method injection to get the $fb object, then there are other ways of resolving it. My personal favourite is to use the resolve() helper since it works irrespective of object context or static function in a class.

$fb = resolve('Facebook\Facebook');
like image 151
Adam Ranganathan Avatar answered Oct 22 '22 00:10

Adam Ranganathan