PHP OOP :: 构建 API 包装器类

发布于 2024-10-11 07:55:18 字数 2299 浏览 2 评论 0原文

我有一个应用程序,本质上是第 3 方 API 的包装器。该应用程序不使用数据库,仅存储一个 cookie,即 API 所需的会话 ID。

该 API 是一个购物系统,允许用户

- 登录/注册/编辑个人资料/注销

- 购买商品

- 捐款

- 成为会员

该 API 有 50 种左右的方法,我的应用程序需要连接到。示例 API 调用有 addItemToBasket()、addDonation()、GetUserDetails() 等。

我正在尝试找出我的应用程序中应该包含哪些类。到目前为止,这是我所拥有的:

1) APIManager() 类 包含与第 3 方 API 中公开的方法一对一匹配的方法,并提供与远程 API 服务器建立连接的机制。因此,用户将通过以下方式登录

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

,远程 API 会将用户设置为已登录。如果需要,我的应用程序可以通过调用 API 检查任何会话密钥的登录状态:

 APIManager->isLoggedIn($sessionKey);

2) User() 类 它包含的方法包含处理 API 调用(例如注册或登录)之前所需的业务逻辑。一个示例方法是:

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);
}

我意识到我可能只需要从登录页面调用 APIManager,而不是使用 User 类本身,但我觉得因为在我们实际调用 API 的 loginUser( ) 方法,在 User 类中处理它感觉是正确的。我很想知道人们对此有何看法。

3) Basket() 类

购物篮是在第 3 方 API 中管理的,因此我的应用程序的作用是进行适当的 API 调用以向购物篮添加新商品、删除商品、查看购物篮等。在从 API 检索数据之前,我的应用程序对购物篮一无所知,也无法在不通过 API 的情况下对购物篮进行任何更改。同样,将这些相关逻辑分组到 Basket 类中感觉很合适。前端网页可能会调用类似以下内容的内容:

Basket->addItem(23);

Basket 类中的 addItem() 方法类似于:

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

其中 addToCart() 是我们调用来处理该项目的第三方 API 方法。

4) Donation() 类

这允许用户进行捐赠。捐款出现在购物篮中,并且可以从购物篮中删除。我想过只向 Basket 类添加一个 addDonate() 方法,根本不用担心有 Donation 对象,但是...(见下文)

5) Membership() Class

... 会员资格实际上是一种捐赠! API 会将进入特定帐户的捐款视为 1 年会员资格,而不是普通捐款。我们无法更改第 3 方 API 的逻辑/行为。

因此,如果我向账户“1”捐赠 50 美元,那么这只是正常捐赠,但如果我向账户“8”捐赠 100 美元,那么我就成为会员,享受所有会员福利(降低价格、免邮费等)。

这是我不确定设计这个的最佳方法的地方。

我是否应该创建一个捐赠类,然后使用会员资格扩展该类,因为会员资格需要所有捐赠方法。但是会员资格需要额外的方法,例如 renew() 或 getExpiry() 等。

另外,我应该考虑扩展用户成为会员吗?同样,会员拥有用户拥有的所有核心方法,但还拥有只有会员才能访问的其他方法,例如 getSpecialOffers() 或 getDiscountCode()。

任何有关如何最好地进行设计的指导将不胜感激。

谢谢,詹姆斯

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

那一片橙海, 2024-10-18 07:55:18

就我个人而言,我会用 3 层来构建它。

第 1 层: API 接口

该层是对远程 API 进行实际行级调用的地方。这一层是关于协议的。该层中不应该有任何特定于 API 的内容。一切都应该是 100% 通用的,但应该能够被中间层用来与 API 交互。请注意,该层可以来自库或框架等其他来源。或者你可以自定义编写。这完全取决于您所在的位置以及您的具体需求。

可能属于此处的一些类:

  • XMLRPC_Client
  • SOAP_Client
  • REST_Client

第 2 层: API 适配器

该层实际上将 API 信息硬编码到其中。这基本上就是适配器模式。基本上,该层的工作是使用接口层将远程 API 转换为本地 API。因此,根据您的需要,您可以以 1:1 的方式镜像远程 API,或者您可以根据您的需要进行更多调整。但要记住的是,此类并不是为您的程序提供功能。目的是将远程 API 与本地代码解耦。通过交换此类,您的代码应该能够快速适应使用不同版本的远程 API,甚至可能一起使用不同的远程 API。

需要记住的重要一点是,该适配器层旨在包含 API。因此,每个单独的类的范围是整个 API 实现。因此适配器和远程 API 之间应该存在 1:1 的映射。

可能位于此处的一些类:

  • RemoteAPI_v1_5
  • RemoteAPI2_v1

第 3 层: 内部对象

该层应该是不同对象的内部表示(在您的具体情况下:用户、购物篮、购物车、捐赠、会员资格等) 。他们不应该直接调用 API,而应该使用组合(依赖注入)来基本上成为一个桥梁 API。通过保持其分离,您应该能够完全独立于内部类来改变 API(反之亦然)。

因此,您的一个类可能如下所示:

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

这样,就可以说并不真正需要 API 管理器了。只需在程序开始时创建一个新的 API 实例,并将其传递给代码的其余部分即可。但它的主要优点是非常灵活,您应该能够通过简单地交换代码中的适配器层(当您实例化适配器时)来更改 API(版本或调用本身)。其他一切都应该正常工作,并且您应该完全独立于代码或远程 API 的更改(更不用说如果以这种方式构建,它应该是非常可测试的)...

这是我的 0.02 美元。这可能有点矫枉过正,但这实际上取决于您的具体需要......

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

樱桃奶球 2024-10-18 07:55:18

我想说:

  1. 创建一个捐赠类,其中包含它需要的所有内容
  2. ,创建一个成员资格的成员变量(应该是捐赠类型)。
  3. 您可以在成员资格类中有一个方法,例如:

    public function makeDonation($data) {
        $this->donation = new Donation($data) // 可能带有 data 参数;
        $this->donation->commit() // 无论它做什么来设置
          ......
          无论你需要什么
    }
    

这样您就可以在项目之间实现良好的解耦。此外,捐赠应该实现一个 iterface,以便如果以后行为发生更改,它仍然应该包含 memeber 类所需的方法。

这种方式比继承更灵活。
我前段时间问过类似的问题并得到了一个很好的答案:

优雅的替代品奇怪的多重继承

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文