Friday 12 November 2010

Facebook Integration on a symfony project - part I

Introduction
Facebook is the world's leading social network, with over 500 million users. As so, it is most useful to integrate your symfony project with it, bringing more users to your website, and providing them some commodities.
There is a symfony plugin, sfFacebookConnectPlugin, that does most of the things you would want. Unfortunately, and at the time I write this, it uses the old REST API, wich is no longer recommended, since facebook has adopted the Graph API. As so, this tutorial will cover how to make the integration using the official PHP SDK.

What Will I Cover?
- Facebook Connect
- Authentication
- Permissions
- Wall Publish
- App Tab (now on facebook pages only :/ )

Requirements
You will need:
- To be using sfGuardPlugin
- Propel (some simple queries, used old Criteria for compatibility)
- Have a field in your user profile table to store the facebook uid (optional, but recommended; preferably an index to speed up search)
- PHP SDK
- Javascript SDK
- PHP cUrl enabled
- A Facebook App: http://www.facebook.com/developers/

You can download the Javascript SDK from http://connect.facebook.net/en_US/all.js

Facebook Connect
You must have already seen the facebook popup in many sites, either asking for your login details (if not logged on facebook), or for permissions, or for sharing content. To get the same experience on your symfony project, you must use Javascript SDK.
After downloading the .js and placing it in your web/js directory (and perhaps renaming it facebook.js), you will need to include it in all the pages you want the user to be facebook connected. This is so because the user may logout of facebook at any time, so you need to keep checking the user login status everytime a page loads. So go ahead and add facebook.js to the application config/view.yml, on the javascripts section.
Now you need to call the initializer on your pages. You may either hardcode it on your layout file, create a partial for it in case you use many different layouts, or even create a slot, if you will only need it on certain pages. It's up to you and whatever your needs are.
So this is how you can call the initializer:

<div id="fb-root"></div>
<script>
  FB.init({
    appId  : 'YOUR APP ID',
    status : true, // check login status
    cookie : true, // enable cookies to allow the server to access the session
    xfbml  : true  // parse XFBML
  });
</script>

You will need to have the div "fb-root" somewhere on the page.
There is also an asynchronous way to do it, check this link

Authentication
The single click signin is, in my opinion, the best feature in facebook integration.

The Button:
The login button comes with the Javascript SDK, and is one of the facebook "social plugins".
You can put it anywhere by simply typing <fb:login-button></fb:login-button>. You can also configure it by passing arguments (check the social plugin page) and write any text inside the tag to personalize the button text.
Pressing the button will either popup a window asking for facebook user and password (if user is not logged on and has no cookie stored), or for permissions if it's the first visit to your site, or nothing at all for the rest. But before the popup windows can appear, it is needed to overcome javascript security concern limitation, cross-domain requests. For that, you will need the file xd_reciever.html inside you web/ directory. You can create it and paste this code inside it.

The login:
You will now want to login the user into your site. You will need to check the user status on the facebook platform and redirect him accordingly. You can do that with the following code, which has to be used after FB.init is executed (can be right after it, still inside the script tag):


    FB.Event.subscribe('auth.sessionChange', function(response) {
        if (response.session) {
          // A user has logged in, and a new cookie has been saved
            window.location="<?php echo url_for('@facebook_connect',true); ?>";
        } else {
          // The user has logged out, and the cookie has been cleared
                window.location="<?php echo url_for('@homepage',true); ?>";
        }
      });

This code will check any change in the user facebook session. It redirects the user to the route facebook_connect  (you can of course just link it to the module/action route) if he has/is logged in, and to the homepage if he has logged out. You now need to create the action to manage the user session and authenticate the user into sfGuard; I will use facebook/connect on this tutorial.
On the connect action you will switch to the PHP SDK. This sdk will allow you to connect to facebook and retrieve the user account details, allowing you to authenticate him on your application. You will only need to have the facebook.php file somewhere inside your lib folder, so that it is autoloaded.
I will cover three user cases:

  1. The user has logged on to your website before, using facebook connect.
  2. The user is registered to your website, but its the first time he logs using facebook connect.
  3. The user is new to your website, therefore, he is not in your database.

        $facebook = new Facebook(array(
                'appId'  => 'YOUR APP ID GOES HERE',
                'secret' => 'YOUR APP SECRET GOES HERE',
                'cookie' => true,
        ));

        $session = $facebook->getSession();
        $me = null;
        // Session based API call.
        if ($session) {
          try{
                $uid = $facebook->getUser();
                $me = $facebook->api('/me');
                $c=new Criteria();
                $c->add(YOUR_PROFILE_CLASSPeer::FACEBOOK_UID,$uid);
                $user=YOUR_PROFILE_CLASSPeer::doSelectOne($c);
                if($user) //User case 1 - you have the fb uid in your database
               {
                        $this->getUser()->signin($user->getsfGuardUser());
               }else
               {
                        $c=new Criteria();
                        $c->add(YOUR_PROFILE_CLASSPeer::EMAIL,$me['email']);
                        $user=YOUR_PROFILE_CLASSPeer::doSelectOne($c);
                        if($user) //User case 2 - you don't have fb uid, but you do have his e-mail
                        {
                                //Store his uid
                                $user->setFacebookUid($uid);
                                $user->save();
                                $this->getUser()->signin($user->getsfGuardUser());
                        }else
                        {
                          //User case 3 - you might register an account for him, or redirect to register form with some pre-filled fields.
                        }
               }

               if($referer=$request->getReferer())
               {
                       //Your user might be coming from an external link, so redirect to the intended link
                       $this->redirect($request->getReferer());
               }else
                       $this->redirect('@homepage');
          }
          catch (FacebookApiException $e) {
            error_log($e);
          }
      }

You may ask: why check for the user facebook uid, if all we need is the e-mail??? For starters, you can change the e-mail associated with your facebook account at any time. Secondly, it happens that a facebook user can have multiple e-mails associated with his account. Being so, you may prevent a facebook connected user from your database from having to register again on your website, just because his facebook e-mail has changed.

Wednesday 28 July 2010

DbFinder in symfony1.3 / symfony1.4

It was a common practise to use DbFinder plugin in symfony 1.2 and earlier to atone for the then limited propel capabilities (propel 1.4 and earlier). All of that has changed ofcourse, with propel 1.5 delivering what DbFinder did and much more. Perhaps as a result of that, development on the DbFinder plugin was abandoned, and the latest version isn't compatible with symfony 1.3/1.4. For those who, as myself, for some reason had to upgrade old symfony1.2 projects (that used DbFinder), this might be a saviour, allowing you to save countless hours rewritting all those queries in Propel1.5.

Here it is, a small patch by Daniel Lohse (and Jan Fabry?) to make DbFinder compatible with sumfony 1.3/1.4. Tested myself with 1.3 and it works flawlessly.

Patch here

For more information click here

Friday 2 July 2010

Admin generator - table relations in 1 line (Propel)

Here goes a video from François Zaninotto of how to get table relations done in the admin generator, writing only 1 line, using the new propel1.5 capabilities.

Introduction

Hello there!
This blog is going to become a recollection of some of the most awesome capabilities of the best PHP framework out there, symfony!
Over time I will share with you readers the things I have been discovering while messing around with this great tool.

Hope you enjoy them,
Ricardo Oliveira.

Pages