Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP OOP :: Building an API Wrapper class

I have an app that is essentially a wrapper for a 3rd party API. The app does not use a database and only stores a single cookie which is the session ID that the API requires.

The API is a shopping system which allows users to

-login/register/edit profile/logout

-buy merchandise

-make a donation

-become a member

The API has 50 or so methods that my app needs to connect to. Example API calls are addItemToBasket(), addDonation(), GetUserDetails() etc.

I am trying to work out what should be classes in my application. Here is what I have so far:

Classes

1) APIManager() Class Contains the methods that match one-to-one with the methods exposed in the 3rd party API and provides the mechanism to make a connection to the remote API server. So a user would be logged in via

APIManager->loginUser($sessionKey, $uid, $pwd);

and the remote API would set the user as logged in. If needs be, my app can check the logged in status of any session key by calling the API:

 APIManager->isLoggedIn($sessionKey);

2) User() Class This holds methods that contain business logic required before processing API calls such as Register or Login. An example method is:

function login($_POST) {
    //perform sanity checks, apply business rules etc.
    //if certain conditions are met, we may pass in a promo code, $pc

    APIManager->loginUser($sessionkey, $_POST['uid'], $_POST['pwd'], $pc);
}

I realise that I could probably just make a call to APIManager from the login page, rather than having a User class per se, but I felt that since some business logic needs to run before we actually call the API's loginUser() method, it felt right to have that handled within a User class. I'd be keen to know what people think about this.

3) Basket() Class

The basket is managed in the 3rd Party API, so my app's role is to make the appropriate API calls to add new items to the basket, remove items, view the basket etc. My app knows nothing about the basket until the data is retrieved from the API, nor can it make any changes to the basket without going via the API. Again, it felt appropriate to group this related logic into a Basket class. The front end web page might call something like:

Basket->addItem(23);

and this addItem() method in the Basket class would looks something like:

addItem($itemID) {
   //perform checks, apply business rules e.g. if user is eligible for discount
        APIManager->addToCart($itemID, $discount);
}

where addToCart() is the third party API method we call to process the item.

4) Donation() Class

This allows users to make a donation. The donation appears in the basket and can be removed from the basket. I thought of just adding an addDonate() method to the Basket class and not worry about having a Donation object at all, but... (see below)

5) Membership() Class

... memberships are actually a type of donation! The API will treat donation going into a certain account as being a 1 year membership, and not a plain donation. We cannot change the logic/behaviour of the 3rd party API.

So, if I donate $50 to account '1' then it's just a normal donation, but if I donate $100 to account '8' then I become a member with all the member benefits (reduced prices, no postage fee etc).

Here's where I'm not sure of the best way to design this.

Should I create a Donation class and then extend that with Membership, since all of the Donation methods will be required by Membership. But Membership will need additional methods such as renew() or getExpiry() etc.

Also, should I look at extending User to become Member? Again, a member has all of the core methods that User has, but also has additional ones such as getSpecialOffers() or getDiscountCode() that only members would access.

Any guidance in how to best approach the design would be very much appreciated.

Thanks, James

like image 647
James Agnew Avatar asked Jan 08 '11 15:01

James Agnew


1 Answers

Personally, I would build this in 3 layers.

Layer 1: API Interface

This layer is where the actual line-level calls to the remote API take place. This layer is all about the protocol. There should be nothing in this layer that's API specific. Everything should be 100% generic, but should be able to be used by the middle layer to interact with the API. Note that this layer can come from a library or another source like a framework. Or you could write it custom. It all depends on where you are and your exact needs.

Some classes that might belong here:

  • XMLRPC_Client
  • SOAP_Client
  • REST_Client

Layer 2: API Adapter

This layer actually has the API information hard-coded into it. This is basically the Adapter Pattern. Basically the job of this layer is to convert the remote API into a local API using the Interface layer. So, depending on your need, you can mirror the remote API in a 1:1 manor, or you could bend this to your needs a little bit more. But the thing to keep in mind is that this class is not about providing functionality to your program. The purpose is to decouple the remote API from your local code. By swapping out this class, your code should be able to quickly adapt to use different versions of the remote API and possibly even different remote APIs all together.

An important thing to remember is that this Adapter layer is meant to encompass the API. So the scope of each individual class is the entirety of an API implementation. So there should be a 1:1 mapping between adapters and remote APIs.

Some classes that might be here:

  • RemoteAPI_v1_5
  • RemoteAPI2_v1

Layer 3: Internal Objects

This layer should be your internal representation of the different objects (In your specific case: User, Basket, Cart, Donation, Membership, etc). They should not directly call the API, but use Composition (Dependency Injection) to become what's basically a bridge to the API. By keeping it separated, you should be able to vary the API completely independent from the internal classes (and vise versa).

So, one of your classes might look like this:

class User {
    protected $api;
    public function __construct(iAPIAdapter $api) {
        $this->api = $api;
    }
    public function login() {
        $this->api->loginUser($blah);
    }
}

That way, there's no real need for an API manager so to speak of. Just create a new instance of the API at the start of the program, and pass it around to the rest of your code. But it has the major benefit of being quite flexible in the sense that you should be able to change APIs (either version or the call itself) by simply swapping out the adapter layer in your code (when you instantiate the adapter). Everything else should just work, and you should be completely isolated from changes to either your code or the remote API (not to mention that it should be quite testable if built this way)...

That's my $0.02. It might be overkill, but that's really depending on your exact need...

like image 157
ircmaxell Avatar answered Sep 26 '22 04:09

ircmaxell