You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

遵循PSR标准的第三方API SDK开发两类技术问题咨询

第三方API SDK开发的PSR规范相关问题解答

问题1:接口类存放目录规范

PSR标准并未强制要求接口必须放在独立的Contracts目录,两种存放方式均合规,需结合项目场景选择:

  • 同目录存放:接口与对应实现类放在同一目录,便于开发时快速关联查看,适合接口与实现一一绑定、逻辑高度耦合的场景。当前你的目录结构中每个接口都有唯一对应实现,只要命名空间与目录结构匹配(符合PSR-4自动加载规范),这种方式完全合规。
  • 独立Contracts目录:将所有抽象接口统一归类到src/Contracts目录,更突出接口的契约属性,适合接口可能被多个实现复用、或需要对外暴露SDK契约的场景,能更清晰地区分抽象层与实现层。

结论:若当前项目接口仅对应单一实现,同目录存放无任何问题;若未来有扩展多实现的计划,迁移到Contracts目录会更利于长期维护。

问题2:API响应修改的合规实现方式

在PSR标准下修改API响应是合规的,需遵循单一职责原则,两种实现路径各有适用场景:

方式1:在ResponseMediator类中实现

当前ResponseMediator的职责是解析响应内容,在此类中添加全局通用的响应修改逻辑(如字段格式统一、类型转换、冗余字段清理)完全合理,符合PSR-1(单一职责)与PSR-12(编码规范)。

示例修改(以统一字段命名为例):

final class ResponseMediator
{
    /**
     * @throws ApiException|InvalidJsonException
     */
    public static function getContent(ResponseInterface $response): array
    {
        $body = $response->getBody()->__toString();

        if (!str_starts_with($response->getHeaderLine('Content-Type'), 'application/json')) {
            throw new ApiException(sprintf('The response is not JSON: %s', $body));
        }

        try {
            /** @var array<string|int, string|int> $content */
            $content = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
            // 新增:将snake_case字段转换为camelCase
            return array_map(function ($value) {
                if (is_array($value)) {
                    return array_change_key_case($value, CASE_CAMEL);
                }
                return $value;
            }, array_change_key_case($content, CASE_CAMEL));
        } catch (JsonException $e) {
            throw new InvalidJsonException(
                sprintf('Invalid JSON: %s', $e->getMessage())
            );
        }
    }
}

这种方式适合无需动态调整的全局统一处理,实现简单直接。

方式2:通过HttpClient Plugin实现

若修改逻辑是场景化、可插拔的(如仅针对特定接口修改响应、需根据配置开关启用/禁用),则使用HttpPlug的Plugin机制更合适。HttpPlug插件本身就是为在HTTP请求/响应链路中插入可复用逻辑设计的,完全符合PSR-7与PSR-18规范。

示例响应修改插件:

use Http\Client\Common\Plugin;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Psr7\Stream;

class ResponseTransformPlugin implements Plugin
{
    public function handleRequest(RequestInterface $request, callable $next, callable $first): ResponseInterface
    {
        $response = $next($request);
        
        // 仅针对成就接口修改响应字段
        if (str_contains($request->getUri()->getPath(), '/achievements')) {
            $bodyContent = $response->getBody()->__toString();
            $content = json_decode($bodyContent, true);
            
            // 将count字段重命名为total
            if (isset($content['count'])) {
                $content['total'] = $content['count'];
                unset($content['count']);
            }
            
            $newBody = new Stream(fopen('data://text/plain,' . json_encode($content), 'r'));
            return $response->withBody($newBody);
        }
        
        return $response;
    }
}

这种方式更灵活,符合开闭原则,适合需要动态调整的响应处理需求。

结论:全局通用的响应修改放在ResponseMediator;场景化、可插拔的修改用HttpClient Plugin实现,两种方式均符合PSR标准。


附:项目相关信息

Composer依赖配置

{
    "require": {
        "php": ">=8.2",
        "php-http/client-common": "^2.5",
        "php-http/discovery": "^1.14.3",
        "php-http/httplug": "^2.3",
        "php-http/multipart-stream-builder": "^1.2",
        "psr/http-client-implementation": "^1.0",
        "psr/http-factory-implementation": "^1.0",
        "psr/http-message": "^1.0.1"
    },
    "require-dev": {
        "phpunit/phpunit": "^8.5",
        "guzzlehttp/guzzle": "^7.5",
        "guzzlehttp/psr7": "^2.4.1",
        "http-interop/http-factory-guzzle": "^1.2"
    }
}

项目目录结构

./src/
├── Api
│	├── AbstractApi.php
│	├── Achievement.php
│	├── AchievementInterface.php
│	├── Clan.php
│	├── ClanInterface.php
│	├── Game.php
│	├── GameInterface.php
│	├── Rating.php
│	├── RatingInterface.php
│	├── User.php
│	├── UserInterface.php
│	├── Weapon.php
│	└── WeaponInterface.php
├── Client.php
├── ClientExtraInterface.php
├── Enum
│	├── GameClassEnum.php
│	├── GameClassList.php
│	├── Http
│	│	└── RegionList.php
│	└── Leagues.php
├── Exception
│	├── Response
│	│	├── ApiException.php
│	│	└── InvalidJsonException.php
│	├── UndefinedBranchException.php
│	└── UnknownMethodCallException.php
├── Helper
│	└── QueryParamsHelper.php
└── HttpClient
	├── Builder.php
	├── Message
	│	└── ResponseMediator.php
	└── Plugin
		├── BypassTimeoutResponsePlugin.php
		└── ServerSupportPlugin.php

ResponseMediator类代码

final class ResponseMediator
{
    /**
     * @throws ApiException|InvalidJsonException
     */
    public static function getContent(ResponseInterface $response): array
    {
        $body = $response->getBody()->__toString();

        if (!str_starts_with($response->getHeaderLine('Content-Type'), 'application/json')) {
            throw new ApiException(sprintf('The response is not JSON: %s', $body));
        }

        try {
            /** @var array<string|int, string|int> $content */
            return json_decode($body, true, 512, JSON_THROW_ON_ERROR);
        } catch (JsonException $e) {
            throw new InvalidJsonException(
                sprintf('Invalid JSON: %s', $e->getMessage())
            );
        }
    }
}

内容的提问来源于stack exchange,提问作者hyperplural

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 04:10:28