Archive for February, 2008

Facebook: <Fb:page-admin-edit-header/> and canvas page POSTing

I came across this issue today where I was getting an error when trying to add the <Fb:page-admin-edit-header/> to a canvas page of a page-only app.
The app was installed on a Facebook Page of which I was an admin.

Basically I had the following situation:

[0] canvas page that had <Fb:page-admin-edit-header/> rendered correctly at top when the canvas page was first hit

[1] canvas page had a simple non-Ajax form that submitted back to itself via POST

[2] POST url just contained URL of page

When I submitted the form, I kept getting the following error:

---------------------
Errors while loading page from application
Runtime errors:

fb:page-admin-edit-header can only display on a fbpage application.
There are still a few kinks Facebook and the makers of Accountability Journal-cp are trying to iron out. We appreciate your patience as we try to fix these issues. Your problem has been logged - if it persists, please come back in a few days. Thanks!
---------------------

I verified that the app was indeed a 'page-only' app, and that I had admin rights to the Facebook Page on which it was installed.

It seemed to me that this app was working perfectly until today, though I may be wrong about that...

It turns out that you MUST add the 'fb_page_id=[pageID]' URL param to the POST URL. Even though all of the FB variables were present in the original POST (fb_sig_page_id, fb_sig_is_admin=1, etc.) it appears that you must still provide the URL param in order for the FBML processing to insert the <Fb:page-admin-edit-header/> correctly.

Bebo platform is still down, but some new features coming

Bebo is still working on fixing performance problems, and the applications are still down. In a blog post today, the Bebo developers talked about some features that they will be releasing in the near future.

  1. More visible Notifications and Requests – they are going to redesign the home page to show these off more.
  2. Profile application index – just like Facebook, they will have a icon index of all the applications that can be clicked to go to that application.

This sound great for encouraging the spread of applications, but I worry that, like on Facebook, abuses of the system will render it mostly useless. Facebook has implemented limiting features to cut down on the spam, but the Bebo developers made no mention of implementing them on Bebo.

Bebo application platform is turned off!

Bebo posted this today:

Bebo is slow. Sorry! It's been growing a lot recently and we're not quite keeping up with the demand. Even though apps has nothing to do with the problem, we turned off apps temporarily to help reduce the load. We're working hard on a permanent fix and hope to have it all working fast again soon.

This means, of course, that Doorbell and Inner Circle for Bebo are down. The good news is that the Bebo developers seem to generally be on top of their game, so I don’t expect this to last too long.

 

Dealing with MySQL Too Many Connections error

Doorbell on Bebo is growing steadily, despite some randomness in the Bebo platform uptime. One side effect of its popularity was a new error – Too Many Connections. Here is a quick overview of the cause and a solution.

Too Many Connections can be caused by either a lot of simultaneous connections or by old connections not being released soon enough. There are some simple changes you can make to your PHP code and your MySQL settings to prevent both.

There are two built in ways to connect to MySQL from PHP – permanent or interactive. You get a permanent connection using mysql_pconnect(). This creates a permanent connection to the database (permanent is defined as 8 hours by the MySQL wait_timeout system variable). It will only create a new connection if it cannot find an existing permanent connection to reuse. You need to be careful with mysql_pconnect() to make sure that you don’t run out of connections, since these stay open so long and you cannot close them with mysql_close().

The other way is to use mysql_connect(). This creates a new temporary connection, if the new_link parameter is set to true, or reuses an existing one if it is not. This can be better than mysql_pconnect() because these connections can be short lived, and can be closed when needed with mysql_close(). An important parameter is the CLIENT_INTERACTIVE flag. If this is passed, it will use the MySQL interactive_timeout value instead of wait_timeout. Since wait_timeout defaults to 8 hours, this seems like a great idea. However, surprisingly, interactive_timeout also defaults to 8 hours. You should change this value to something smaller that fits your system.

Using mysql_connect() with new_link set to false, passing the CLIENT_INTERACTIVE flag, and adjusting interactive_timeout will help stop problems with old connections jamming up the works.

To deal with many simultaneous connections, adjust the MySQL max_connections variable. By default, it is set to 100. Increase this value to something that will support what you feel is realistic for your system.

A final thing to do after updating your code and MySQL is to flush the bad connections database with “myqladmin flush-hosts”. This will allow your web server to connect back to MySQL after all those Too Many Connection errors.

Facebook API: Undocumented Cookie Management REST APIs

The Facebook API does have a way to set and get cookies, but it is in beta so is not currently part of the FB PHP libraries. I found it out by searching through forums, by trial-and-error, and on the FB Beta docs page. There are 2 functions available:

 
  data.getCookies( user_id, cookie_name );
  data.setCookie( user_id, cookie_name, cookie_value, expires, path );
 

To use these functions from PHP I simply wrapped them in functions that call the RESTful APIs correctly:

 
// to get cookies for a given user (optionally by name):
function get_cookie($uid, $name=null) {
  global $facebook;
  return $facebook->api_client->call_method('data.getCookies', array('uid' => $uid, 'name' => $name) );
}
 
// to set a cookie for a given user:
function set_cookie($uid, $name, $value, $expires=null, $path=null){
  global $facebook;
  return $facebook->api_client->call_method('data.setCookie', array('uid' => $uid, 'name' => $name,
    'value' => $value, 'expires' => $expires, 'path' => $path) );
}
 

Facebook vs. MySpace/OpenSocial Development

After having looked at MySpace's OpenSocial implementation I wanted to provide a comparison between it and the Facebook development platform. They are based upon completely different models and moving from one to the other is not as easy as one would hope.

Here's what's different in MySpace's Open Social Development Platform from what I've seen using the Facebook/Bebo platform. Where the items are known issues I have indicated as such. Some of these items have been derived from research and some from the MySpace forums. I have given links where available.

  1. Your MySpace application code is hosted on MySpace servers, NOT YOURS. There is no callback URL that you can provide that MySpace can use to call back to your server.
  2. It appears that the only way to communicate with your own server from your MySpace app is to use the makeRequest() API call.
    • Currently this is ONLY supported for endpoints running on port 80! Out of luck if your development server runs on a different port.
    • Supposedly MySpace is going to open some ports in a higher range to alleviate this issue. This is really, really frustrating.
    • "MakeRequest gets around the XHR cross-domain restriction by utilizing a custom http proxy hosted by MySpace. This proxy relays incoming requests to other sites and pipes the results back to the browser. It also optionally signs the requests using the OAuth methodology…It is important to note that this is not a standard open proxy--it does its best to verify that the request came from a valid OpenSocial application hosted on MySpace." from MySpace Developer Docs
    • Maybe only able to send as GET params - unknown how to send as POST variables
    • Currently only working on some URLs (does not work for http://www.google.com for example – known issue)
    • Currently only returns responses in uncompressed format (known issue – they are working on this one)
    • MySpace will be providing an 'App Data' 'something' to store application data but this is currently unavailable.
  3. Currently the only way to actually get code into your MySpace app is to paste it in a ridiculously small textarea control on the app profile page. Seriously. And doing so results in:
  4. Currently every time you update the code in your app it puts a NEW copy of the app on the respective page. Known Bug.
  5. During the 'sandbox phase' (length unknown) of the MySpace Development Platform only friends can add apps
    • Only 3 installs of any given app allowed
    • If not a friend, cannot see app
    • There is no directory of apps yet
    • Have to log in as the app (because you have to use a unique email address for each) and friend people as that user in order for them to see and add it
    • More details here.
  6. There are no callbacks for any application events (post-add, pre-remove, etc.)
  7. You cannot encrypt or obfuscate your JavaScript code per the MySpace TOS…
  8. There is no datastore support
  9. There are 3 different places the app can be installed into a user's profile, height is modifiable (to some degree?), but NOT width
    • Home page
    • Profile page
    • NOTE: any portion of your MySpace Applications and MySpace Application Content that will appear on a MySpace Profile must not:
      • contain or create an <iframe>
      • cause their containing <iframe> to be navigated to new location, for example, by using JavaScript code, form posts, or containing links;
      • contain or create <script> tags pointing to external sources except those that are explicitly allowed; and
      • contain or create links to external CSS style sheets.
    • Canvas page
  10. Your app code always runs in an iframe (provided by their container I guess).
  11. There is no formatting language (like FBML) or controls available other than straight (D)HTML. Kind of strange, this one. Facebook uses FBML that it converts to HTML (on its side) which it then pumps to the user's browser. Profile data can be cached on their servers. Very speedy. Could be me but it looks like MySpace is going to require all of your JavaScript to be executed on every refresh of the page – add to that a few calls back and forth to the container/ your server… What is going to happen to MySpace when there are 20 apps installed on each person's profile? Ewwww.
  12. All your JavaScript runs under/is subject to manipulation by Google's Caja; this may limit what your JavaScript can do but makes it safer for MySpace to run 3rd-party scripts. This could slow your JavaScript down as Caja may modify or restrict it.
  13. No/very limited support for 3rd-party JavaScript libraries so far; unknown when more will be available. Appears that jQuery is available based on this post. But if you have existing Facebook code that heavily uses Prototype/Scriptaculous/mootools/EXT/YUI/et.al. you are going to have a lot of reimplementation to do!
  14. Perhaps I am confused but there currently appears to be no obvious/legal way to go from one page of your application to another (from profile->canvas for example). There is an API for this (requestNavigateTo()) but it's not implemented yet. TOS explicitly forbids redirection of the browser window.
  15. No popups are allowed – not sure if this would include Facebook-style AJAX dialogs and their ilk or if this just means browser popup windows. Facebook also disallows popups but does have the FBJS Dialog object available.
  16. oAuth is required to ensure that the user making the request from your MySpace application is valid/trusted. See this post. I am not sure if this API's behavior is fully baked yet, though.

Introduction to the Facebook Data Store API

Facebook released the Data Store API Beta a few months ago, but I hadn’t had a chance to try it out until recently. There are many blog posts discussing the merits of its implementation, but I felt it would be useful to post a quick how-to guide to using it. The Data Store API uses an Object metaphor, which is much different than the table metaphor used by most other databases. However, the mapping between the two models is pretty simple.

Facebook has also made it much easier to work with the Data Store with the Data Store Admin tool added to the Developer page beside each application. This tool lets you create and edit the schema for the database and do FQL queries against it. You can do the same things programmatically, but sometimes it is nice to have a visual editor.

Note: All operations involving schema management can only be done by the developers of the application and will fail with a FacebookRestClientException of 200 – Permission error if done in the context of another user. So, it would be best to move this type of operation to an Admin-only page or do it via the Admin tool.

Another thing to note is that each application gets its own Data Store space, so multiple applications cannot share the same database. The Data Store API has Object Types, Objects and Associations. I will go over each one and show how it maps to the Table metaphor, then do a quick example of creating a table, adding a row and getting its values.

Object Types

Object Types represent Tables and their Properties represent Columns. You create an Object Type like this:

 
  $facebook->api_client->data_createObjectType("tablename");
 

You add properties or Columns like this:

 
  // 1 for integer,2 for string (max. 255 characters), 3 for text blob (max. 64kb)
  $facebook->api_client->data_defineObjectProperty("tablename", "columnname", 3);
 

Once you have created an Object Type, you can then create instances of it or Rows. You only need to create an Object Type once, although can can create many different Object Types if you need many different tables

Objects

Objects represent Rows in the Table. You set the values of the properties to set the value of a Column. You can create or update an existing Object, if you have its id. You can create a new Object and set its properties at the same time like this:

 
  $objectID = $facebook->api_client->data_createObject("tablename", array("columname" => "$value") );
 

or update an object like this:

 
  //pass true to overwrite the properties, or false to merge in
  $objectID = $facebook->api_client->data_updateObject($objectID, array("columname" => "$value"), true );
 

Once you have rows, you can fetch an array of the properties like this:

 
  $properties = $facebook->api_client->data_getObject($objectID);
 

Associations

Creating Objects is easy, but, as you can see above, you need the objectID to get an Object. Where do you get that from? Associations.

An Association is a link between two ids. You can use it to link the current user or page to an objectId, and then get the properties for that object.

After you create a new object, you need to create the association using the objectID you get back like this:

 
  $facebook->api_client->data_setAssociation( "user_to_row", $userID, $objectID );
 

Then you can retrieve the objectID any time by getting it out of the association for the current user:

 
  $objects = $facebook->api_client->data_getAssociatedObjects( "user_to_row", $userID, true );
 
  // There is only a single row for this objectID
  if( isset($objects) && isset($objects[0]) ) {
    // id2 holds the object id
    $objectID = $objects[0]["id2"];
  }
 

Example

The example below defines a Table schema, adds a Row and then uses an Association to get the values.

 
  //Create the table
  $facebook->api_client->data_createObjectType("description");
  $facebook->api_client->data_defineObjectProperty("description", "text", 3);
  $facebook->api_client->data_defineAssociation( "user_to_row", 1, array("alias" => "user_id"), array("alias" => "row_id") );
 
  //Add a row
  $text = "All work and no play makes John unhappy";
  $rowID = $facebook->api_client->data_createObject("description", array("text" => "$text") );
  $facebook->api_client->data_setAssociation( "user_to_row", $userID, $rowID );
 
  //Fetch the row value
  $objects = $facebook->api_client->data_getAssociatedObjects( "user_to_row", $userID, true );
 
  // There is only a single row for this objectID
  if( isset($objects) && isset($objects[0]) ) {
    // id2 holds the object id
    $objectID = $objects[0]["id2"];
  }
 
  $description = $facebook->api_client->data_getObjectProperty( $rowID, "text" );
  echo "The description for <fb:name uid='$userID' /> is $description";
 

Error Handling

You will notice that there is none above. However, many of these calls are likely to throw one of the following:

 
  FacebookAPIErrorCodes::API_EC_DATA_OBJECT_NOT_FOUND
  FacebookAPIErrorCodes::API_EC_DATA_OBJECT_ALREADY_EXISTS
  FacebookAPIErrorCodes::API_EC_PERMISSION
 

so it makes sense to put each call into a try\catch block.

Summary

The Facebook Data Store API is still in Beta, so don’t expect it to work all of the time. However, it is a nice way to eliminate the need for any external database support for your application. You can find more information about the Data Store API here and a nice set of wrapper classes here.

PHP’s mystical __set_state method

PHP 4.x introduced magic methods with the __serialize and __unserialize methods. Since then many have been added. Love them or hate them as a language abomination magic methods are here to stay. Most are well-documented but one, the __set_state() magic method, is not. I hope to change that a bit.

First, a simple description of magic methods is in order. Basically these are special callback functions that are called by PHP in specific circumstances; they usually allow you to do custom processing before or after a certain PHP library function call occurs. For example, when serializing objects, PHP will attempt to call the magic method/member function __sleep() prior to serialization. This is to allow the object to do any last minute clean-up, etc. prior to being serialized. Likewise, when the object is restored using unserialize() the __wakeup() magic method/member function is called. When trying to call a non-existent method on an object instance, PHP will attempt to call the magic method __call on that instance.

Before I go further, I want to address the problem of magic method performance in PHP. Yes, it has been well-documented that using these methods can be 10 – 20 times slower than using a normal method. However, under normal usage patterns no one should be able to detect such performance bottlenecks. Sure, if you use __call 10 million times, you are certainly going to see a hit. Chances are if your PHP code is under tight performance constraints, well, you most likely should not be using PHP to begin with.

So, let's get back to our buddy __set_state. What does it do? Like the __toString() magic method it is used as a formatting callback when your object instance is asked to be converted to a string (note that before PHP 5.2 only a call to echo() or print() resulted in a call to __toString.) The difference is in what event forces the callback. __set_state is called in response to an instance of your object being passed to the var_export function, while __toString is called otherwise. What's the difference? var_export is very different from the other debugging output functions (print_r/var_dump) in that it always must output PARSEABLE PHP CODE. The issue here is that var_export takes an optional second parameter – a boolean that determines whether after the variable passed as the first argument is returned as a string instead of output.

A quick example is in order. What do you think is output from the following code?

 
class Dummy {
  private $value1_;
  private $value2_;
 
  function __construct() {
    $this->value1_ = 100;
    $this->value2_ = "100";
  }
}
 
$aDummy = new Dummy();
var_export($aDummy);
 

Here's the output (on screen):

 
  Dummy::__set_state(array( 'value1_' => 100, 'value2_' => '100', ))
 

Now we're getting somewhere. var_export must print out valid PHP code, remember? And this is valid PHP code. It's referring to a static __set_state method in our Dummy class. Well, then you should be able to execute it. The PHP eval function evaluates the string argument passed to it as valid PHP code so we can use it to test this code. Let's replace that var_export call with this line:

 
  eval(var_export($aDummy, true) . ';'));
 

What's the output? This is:

Fatal error: Call to undefined method Dummy::__set_state() in D:\src\phptests\__set_state.php(21) : eval()'d code on line 1

As you can see, var_export cannot produce executable PHP code for the Dummy type without a __set_state method defined. We need to provide one. var_export takes one argument, and it must be an array. It will contain key-value pairs of the properties or fields of the instance on which it is called.

 
class Dummy {
  private $value1_;
  private $value2_;
 
  function __construct() {
    $this->value1_ = 100;
    $this->value2_ = "100";
  }
 
  static function __set_state(array $array) {
    foreach($array as $k => $v) {
      echo("$k ==> $v <br/>");
    }
  }
}
 

Now the output is this:

 
  value1_ ==> 100
  value2_ ==> 100
 

Better.

But what do you have to put in your __set_state method? That's completely up to you. It just should produce valid PHP code. It's also important to remember that if you're using some 3rd party logging, tracing, or debugging package that it might call var_export on your instance so it's best to be sure. Personally, I think the best thing to do is to basically treat __set_state as another creation method (a copy constructor for those of you with C++ backgrounds). That way if someone requests the evaluation mode in var_export they get expected behavior. Let's change __set_state in our example to reflect this idea:

 
static function __set_state(array $array) {
  $tmp = new Dummy();
  $tmp->value1_ = $array['value1_'];
  $tmp->value2_ = $array['value2_'];
  return $tmp;
}
 

Hopefully that has alleviated some of the confusion around this method. Enjoy!