A fully unit-tested package for easily integrating the Facebook SDK v4.0 into Laravel 4.2 which also harnesses the power of Facebook Query Builder.
This is package for Laravel 4.2
For Laravel 5.1, see the 3.0 branch. For Laravel 5.0, see the 2.0 branch.
- Installation
- Facebook Query Builder
- Examples
- Extensibility
- Error Handling
- Testing
- Contributing
- Credits
- License
The redirect login functionality of the 1.x branch is inherently broken. You can still obtain an access token from JavaScript reliably, but don't use the getLoginurl("")
methods below in production. Version 2.0 works fine but it requires Laravel 5.x. Sorry guys, I can't fix it in 1.x without breaking all the things. :/
Add the Laravel Facebook Sdk package to your composer.json
file.
{
"require": {
"sammyk/laravel-facebook-sdk": "~1.1"
}
}
Or via the command line in the root of your Laravel installation.
$ composer require "sammyk/laravel-facebook-sdk:~1.1"
In your app config, add the LaravelFacebookSdkServiceProvider
to the providers array.
'providers' => [
'SammyK\LaravelFacebookSdk\LaravelFacebookSdkServiceProvider',
];
If you want to make use of the facade, add it to the aliases array in your app config.
'aliases' => [
'Facebook' => 'SammyK\LaravelFacebookSdk\FacebookFacade',
];
After creating an app in Facebook, you'll need to provide the app ID and secret. First publish the configuration file.
$ php artisan config:publish sammyk/laravel-facebook-sdk
Then you can update the app_id
and app_secret
values in the app/config/packages/sammyk/laravel-facebook-sdk/config.php
file.
If you plan on integrating Facebook user authentication with your existing User
model, you'll need to add some fields to your user table. LaravelFacebookSdk makes this a painless process with an artisan command to create a migration.
Note: Make sure to change
users
to the name of your user table.
$ php artisan laravel-facebook-sdk:table users
$ php artisan migrate
If you're using the Eloquent ORM, make sure to hide the access_token
field from possible exposure in your User
model.
Also add the FacebookableTrait
to your model to get some really great functionality for syncing Facebook data with your User
model.
use SammyK\LaravelFacebookSdk\FacebookableTrait;
class User extends Eloquent implements UserInterface
{
use FacebookableTrait;
protected $hidden = ['access_token'];
}
Check out the migration file that it generated. If you plan on using the Facebook user ID as the primary key, make sure you have a column called id
that is a big int and indexed. If you are storing the Facebook ID in a different field, make sure that field exists in the database and make sure to map to it in your model.
Any model that implements the FacebookableTrait
will have the createOrUpdateFacebookObject()
method applied to it. This method really makes it easy to take data that was returned directly from Facebook and create or update it in the local database.
$facebook_event = Facebook::object('some-event-id')->fields('id', 'name')->get();
// Create the event if not exists or update existing
$event = Event::createOrUpdateFacebookObject($facebook_event);
Since the names of the fields in your database might not match the names of the fields in Graph, you can map the field names in your User
model using the $facebook_field_aliases
static variable.
The keys of the array are the names of the fields in Graph. The values of the array are the names of the columns in the local database.
use SammyK\LaravelFacebookSdk\FacebookableTrait;
class User extends Eloquent implements UserInterface
{
use FacebookableTrait;
protected static $facebook_field_aliases = [
'facebook_field_name' => 'database_column_name',
'id' => 'facebook_user_id',
'name' => 'full_name',
];
}
Since the Graph API will return some of the fields from a request as other nodes/objects, you can reference the fields on those using nested parameter syntax.
An example might be making a request to me/events
and looping through all the events and saving them to your Event
model. The Event node will return the Location node via a Page node. The response data might look like this:
{
"data": [
{
"id": "123",
"name": "Foo Event",
"place": {
"location": {
"city": "Dearborn",
"state": "MI",
"country": "United States",
. . .
},
"id": "827346"
}
},
. . .
]
}
Let's assume you have an event table like this:
Schema::create('events', function(Blueprint $table)
{
$table->increments('id');
$table->bigInteger('facebook_id')->nullable()->unsigned()->index();
$table->string('name')->nullable();
$table->string('city')->nullable();
$table->string('state')->nullable();
$table->string('country')->nullable();
});
Here's how you would map the nested fields to your database table in your Event
model:
use SammyK\LaravelFacebookSdk\FacebookableTrait;
class Event extends Eloquent
{
use FacebookableTrait;
protected static $facebook_field_aliases = [
'id' => 'facebook_id',
'place[location][city]' => 'city',
'place[location][state]' => 'state',
'place[location][country]' => 'country',
];
}
In our original request we made use of nested requests to make sure we're only getting back fields that we need. Our raw query might look like:
/me/events?fields=id,name,place{location{city,state,country}}
This is important because when we use createOrUpdateFacebookObject()
, it will blindly try to insert all the data it receives.
This can cause unexpected behavior since the Graph API will sometimes return extra data we didn't specify. In the above example, the response data will always contain a place[id]
value even though we didn't request it.
Since we don't have a column in our event table for place[id]
, we'll get an "Unknown column" error from our database. So we need to tell our model to ignore this column using the $facebook_ignore_fields
array.
use SammyK\LaravelFacebookSdk\FacebookableTrait;
class Event extends Eloquent
{
use FacebookableTrait;
protected static $facebook_field_aliases = [
'id' => 'facebook_id',
'place[location][city]' => 'city',
'place[location][state]' => 'state',
'place[location][country]' => 'country',
];
protected static $facebook_ignore_fields = [
'place[id]',
];
}
Since the FQB will automatically cast any dates as Carbon
instances, any dates returned from the Graph API will need to be accessed from the field_name[date]
key. Note that there is also a field_name[timezone_type]
and field_name[timezone]
key associated with Carbon
instances so if all you need to store is the date, your mapping might look like this:
use SammyK\LaravelFacebookSdk\FacebookableTrait;
class Event extends Eloquent
{
use FacebookableTrait;
protected static $facebook_field_aliases = [
'start_time[date]' => 'start_time',
];
protected static $facebook_ignore_fields = [
'start_time[timezone_type]',
'start_time[timezone]',
];
}
You may wish to store extra data for a node before saving it for the first time in a database. Or you might have some logic that would keep a model from updating in the database. To help with these two scenarios, there are two methods that will get called on your model if they exist. The methods will get called just before a model is inserted or updated. If the methods return false
, the model will not save to the database.
use SammyK\LaravelFacebookSdk\FacebookableTrait;
class Event extends Eloquent
{
use FacebookableTrait;
public static function facebookObjectWillCreate(Event $model)
{
// Prevent this specific entry from creating
if ($model->name == 'Evil Guy') {
return false;
}
// Update the model here if you like
$model->meta_data = 'Created';
return $model;
}
public static function facebookObjectWillUpdate(Event $model)
{
// Prevent this specific entry from updating
if ($model->email == 'foo@example.com') {
return false;
}
// Update the model here if you like
$model->meta_data = 'Updated';
return $model;
}
}
LaravelFacebookSdk is a wrapper for Facebook Query Builder. Any of the Facebook Query Builder methods are accessible via the Facebook
facade. For a full list of available methods, consult the Facebook Query Builder documentation.
// This is done for you automatically with the config you provide,
// but you can overwrite it here if you're working with multiple apps.
Facebook::setAppCredentials('your_app_id', 'your_app_secret');
// . . .
// This access token will be used for all calls to Graph.
Facebook::setAccessToken('access_token');
// . . .
// Get the logged in user's profile.
$user = Facebook::object('me')->fields('id', 'email')->get();
// . . .
// Get latest 5 photos of user and their name.
$photos = Facebook::edge('photos')->fields('id', 'source')->limit(5);
$user = Facebook::object('me')->fields('name', $photos)->get();
// . . .
// Post a status update.
$status_update = ['message' => 'My witty status update.'];
$response = Facebook::object('me/feed')->with($status_update)->post();
$status_update_id = $response['id'];
// Comment on said status update.
$comment = ['message' => 'My witty comment on your status update.'];
$response = Facebook::object($status_update_id . '/comments')->with($comment)->post();
// Delete the status update.
$response = Facebook::object($status_update_id)->delete();
Warning: See why you should not use the redirect login in 1.x in production.
You can get a login URL just like you can in Facebook Query Builder.
$login_link = Facebook::auth()->getLoginUrl('http://my-callback/url');
But if your callback URL is already set in the config file, you can use the wrapper which will default the callback and permission scope to whatever you set in the config file.
$login_link = Facebook::getLoginUrl();
Alternatively you can pass the permissions and a custom callback to the wrapper to overwrite the default config.
$login_link = Facebook::getLoginUrl(['email', 'user_status'], 'http://my-custom-callback/url');
Warning: See why you should not use the redirect login in 1.x in production.
Just like getLoginurl("")
, there is a wrapper for getTokenFromRedirect()
that defaults the callback URL to whatever is set in the config.
try
{
$token = Facebook::getTokenFromRedirect();
}
catch (FacebookQueryBuilderException $e)
{
// Failed to obtain access token
echo 'Error:' . $e->getMessage();
}
See the documentation for all the ways to obtain an AccessToken object.
In most cases you won't need to save the access token to a database unless you plan on making requests to the Graph API on behalf of the user when they are not browsing your app (like a 3AM CRON job for example).
After you obtain an access token, you can store it in a session to be used for subsequent requests.
Session::put('facebook_access_token', (string) $token);
Then in each script that makes calls to the Graph API you can pull the token out of the session and set it as the default.
$token = Session::get('facebook_access_token');
Facebook::setAccessToken($token);
Warning: See why you should not use the redirect login in 1.x in production.
Here's how you might log a user into your site, get a long-lived access token and save the user to your users
table if they don't already exist then log them in.
// Fancy wrapper for login URL
Route::get('/login', function()
{
return Redirect::to(Facebook::getLoginUrl());
});
// Endpoint that is redirected to after an authentication attempt
Route::get('/facebook/login', function()
{
/**
* Obtain an access token.
*/
try
{
$token = Facebook::getTokenFromRedirect();
if ( ! $token)
{
return Redirect::to('/')->with('error', 'Unable to obtain access token.');
}
}
catch (FacebookQueryBuilderException $e)
{
return Redirect::to('/')->with('error', $e->getPrevious()->getMessage());
}
if ( ! $token->isLongLived())
{
/**
* Extend the access token.
*/
try
{
$token = $token->extend();
}
catch (FacebookQueryBuilderException $e)
{
return Redirect::to('/')->with('error', $e->getPrevious()->getMessage());
}
}
Facebook::setAccessToken($token);
/**
* Get basic info on the user from Facebook.
*/
try
{
$facebook_user = Facebook::object('me')->fields('id','name')->get();
}
catch (FacebookQueryBuilderException $e)
{
return Redirect::to('/')->with('error', $e->getPrevious()->getMessage());
}
// Create the user if not exists or update existing
$user = User::createOrUpdateFacebookObject($facebook_user);
// Log the user into Laravel
Facebook::auth()->login($user);
return Redirect::to('/')->with('message', 'Successfully logged in with Facebook');
});
You can add extensibility is by registering a closure.
Facebook::extend('getUserEventsAndPhotos', function($facebook)
{
$events = $facebook->edge('events')->fields('id', 'name')->limit(10);
$photos = $facebook->edge('photos')->fields('id', 'source')->limit(10);
return $facebook->object('me')->fields('id', 'name', $events, $photos)->get();
});
Then you can call getUserEventsAndPhotos()
from the Facade.
$users_events_and_photos = Facebook::getUserEventsAndPhotos();
There are three types of exceptions that can be thrown.
- In most cases a
\SammyK\FacebookQueryBuilder\FacebookQueryBuilderException
will be thrown from the Facebook Query Builder. Those exceptions are not caught from within this package so that you can handle them directly. These are usually thrown if Graph returns an error or there was an issue communicating with Graph. - A
LaravelFacebookSdkException
will be thrown if there was an error adding extensibility or if there was a problem validating data on an Eloquent model. - In uncommon scenarios you might have a
\Facebook\FacebookSDKException
thrown at you. This is an exception that the base Facebook PHP SDK v4 throws. Most of these are caught & rethrown under the\SammyK\FacebookQueryBuilder\FacebookQueryBuilderException
, but some of them might sneak through.
To get the tests to pass, you'll need to run phpunit
from within the root of your Laravel installation and point it to the LaravelFacebookSdk installation in the vendor
directory.
$ cd /path/to/laravel-installation
$ phpunit vendor/sammyk/laravel-facebook-sdk
Please see CONTRIBUTING for details.
This package is maintained by Sammy Kaye Powers. See a full list of contributors.
The MIT License (MIT). Please see License File for more information.