PHP OOP :: Building an API Wrapper class
Asked Answered
M

2

11

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

Musketry answered 8/1, 2011 at 15:31 Comment(1)
Not the answer you are looking for, but what helps me when designing anything in an object orientated design is to draw out a diagram of the objects, working out where there is commonality etc. If you do this before you dive in and code you will find you have a much easier time of it. And you will end up with something more maintainabke long term.Yarn
G
23

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...

Girhiny answered 1/2, 2011 at 23:34 Comment(5)
Good suggestions and explanations. Dependency Injection is key in Composition over Inheritance.Hexastich
@ircmaxwell good post. I am about to build an API wrapper following your advice. Can you link to an example of an API wrapper built on this model that I could look at?Vibes
Agreed, apologies. I didn't realise that it was important to accept answers back in the day, then was away from SO for a few years doing other stuff. Back now, and the great answer is duly accepted. Thank you very much @GirhinyMusketry
@Vibes Did you ever find a good example for this? I'm about to do this as well and I'd love to look at an example before I do.Forepaw
brilliant. Thanks for sharing.Shipboard
F
0

I'd say:

  1. create a donation class with all that it needs
  2. create a member variable of membership (which should be of the type Donation).
  3. you can have a method in the membership class such:

    public function makeDonation($data) {
        $this->donation = new Donation($data) // maybe with the data parameter;
        $this->donation->commit() // whatever it does to set things up
          ......
          whatever you need
    }
    

This way you have nice decoupling between the items. Also the Donation should implement an iterface so that if behavior is later changed it should still contain the methods required by the memeber class.

Thsi way it is more flexible than inheritance. I had asked a similar question some time ago and got a good answer:

Elegant alternatives to the weird multiple inheritance

Feudalism answered 1/3, 2011 at 9:23 Comment(1)
To make it even better, you could use dependency injection instead of instantiating the class within the method. So makeDonation(Donation $donation) { $this->donation = $donation; .... It's even better. But I agree, always favor composition over inheritance...Girhiny

© 2022 - 2024 — McMap. All rights reserved.