I want to write a module (framework specific), that would wrap and extend Facebook PHP-sdk (https://github.com/facebook/php-sdk/). My problem is - how to organize classes, in a nice way.
So getting into details - Facebook PHP-sdk consists of two classes:
Now I have some functionality to add:
I know something has gone very wrong, because I have too much inheritance that doesn't look very normal. Wrapping everything in one "complex extension" class also seems too much. I think I should have few working togheter classes - but i get into problems like: if cache class doesn't really extend and override BaseFacebook::api() method - shorthand and authentication classes won't be able to use the caching.
Maybe some kind of a pattern would be right in here? How would you organize these classes and their dependencies?
EDIT 04.07.2012
Bits of code, related to the topic:
This is how the base class of Facebook PHP-sdk:
abstract class BaseFacebook {
// ... some methods
public function api(/* polymorphic */)
{
// ... method, that makes api calls
}
public function getUser()
{
// ... tries to get user id from session
}
// ... other methods
abstract protected function setPersistentData($key, $value);
abstract protected function getPersistentData($key, $default = false);
// ... few more abstract methods
}
Normaly Facebook class extends it, and impelements those abstract methods. I replaced it with my substitude - Facebook_Session class:
class Facebook_Session extends BaseFacebook {
protected function setPersistentData($key, $value)
{
// ... method body
}
protected function getPersistentData($key, $default = false)
{
// ... method body
}
// ... implementation of other abstract functions from BaseFacebook
}
Ok, then I extend this more with shorthand methods and configuration variables:
class Facebook_Custom extends Facebook_Session {
public function __construct()
{
// ... call parent's constructor with parameters from framework config
}
public function api_batch()
{
// ... a wrapper for parent's api() method
return $this->api('/?batch=' . json_encode($calls), 'POST');
}
public function redirect_to_auth_dialog()
{
// method body
}
// ... more methods like this, for common queries / authorization
}
I'm not sure, if this isn't too much for a single class ( authorization / shorthand methods / configuration). Then there comes another extending layer - cache:
class Facebook_Cache extends Facebook_Custom {
public function api()
{
$cache_file_identifier = $this->getUser();
if(/* cache_file_identifier is not null
and found a valid file with cached query result */)
{
// return the result
}
else
{
try {
// call Facebook_Custom::api, cache and return the result
} catch(FacebookApiException $e) {
// if Access Token is expired force refreshing it
parent::redirect_to_auth_dialog();
}
}
}
// .. some other stuff related to caching
}
Now this pretty much works. New instance of Facebook_Cache gives me all the functionality. Shorthand methods from Facebook_Custom use caching, because Facebook_Cache overwrited api() method. But here is what is bothering me:
So again, this works but I'm just asking about patterns / ways of doing this in a cleaner and smarter way.
Auxiliary features such as caching are usually implemented as a decorator (which I see you already mentioned in another comment). Decorators work best with interfaces, so I would begin by creating one:
interface FacebookService {
public function api();
public function getUser();
}
Keep it simple, don't add anything you don't need externally (such as setPersistentData
). Then wrap the existing BaseFacebook
class in your new interface:
class FacebookAdapter implements FacebookService {
private $fb;
function __construct(BaseFacebook $fb) {
$this->fb = $fb;
}
public function api() {
// retain variable arguments
return call_user_func_array(array($fb, 'api'), func_get_args());
}
public function getUser() {
return $fb->getUser();
}
}
Now it's easy to write a caching decorator:
class CachingFacebookService implements FacebookService {
private $fb;
function __construct(FacebookService $fb) {
$this->fb = $fb;
}
public function api() {
// put caching logic here and maybe call $fb->api
}
public function getUser() {
return $fb->getUser();
}
}
And then:
$baseFb = new Facebook_Session();
$fb = new FacebookAdapter($baseFb);
$cachingFb = new CachingFacebookService($fb);
Both $fb
and $cachingFb
expose the same FacebookService
interface -- so you can choose whether you want caching or not, and the rest of the code won't change at all.
As for your Facebook_Custom
class, it is just a bunch of helper methods right now; you should factor it into one or more independent classes that wrap FacebookService
and provide specific functionality. Some example use cases:
$x = new FacebookAuthWrapper($fb);
$x->redirect_to_auth_dialog();
$x = new FacebookBatchWrapper($fb);
$x->api_batch(...);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With