Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

multi-level inheritance substitution

Tags:

oop

php

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:

  • BaseFacebook - abstract class with all the stuff sdk does
  • Facebook - extends BaseFacebook, and implements parent abstract persistance-related methods with default session usage

Now I have some functionality to add:

  • Facebook class substitution, integrated with framework session class
  • shorthand methods, that run api calls, I use mostly (through BaseFacebook::api()),
  • authorization methods, so i don't have to rewrite this logic every time,
  • configuration, sucked up from framework classes, insted of passed as params
  • caching, integrated with framework cache module

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:

  • I think it's too much inheritance.
  • It's all very tight coupled - like look how i had to specify 'Facebook_Custom::api' instead of 'parent:api', to avoid api() method loop on Facebook_Cache class extending.
  • Overall mess and ugliness.

So again, this works but I'm just asking about patterns / ways of doing this in a cleaner and smarter way.

like image 774
Luigi Avatar asked Jun 30 '12 14:06

Luigi


1 Answers

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(...);
like image 51
casablanca Avatar answered Sep 22 '22 18:09

casablanca