simple-as-fuck/php-api-toolkit

此包的最新版本(0.4.5)没有可用的许可证信息。

0.4.5 2024-08-25 16:05 UTC

README

提供一系列服务,以尽可能简化的方式实现API,并带有标准化的依赖。

安装

composer require simple-as-fuck/php-api-toolkit

支持

如果composer.json中列出的任何PHP平台要求以安全支持结尾,请将包版本视为不受支持,除非是最新版本。

支持的PHP版本.

使用方法

API客户端服务

API客户端需要guzzle客户端,psr客户端接口不够好,因为没有异步请求。第二个主要依赖是一些配置,你可以实现自己的配置加载。可选地,你可以添加弃用记录器以自动记录弃用日落响应头。

Laravel将从services.php配置文件中自动加载配置。

    'some_api_name' => [ // this key is value of first parameter ApiClient::request method
        'base_url' => 'https://some-host/some-base-url',
        'token' => 'tokenexample', // optional default null, authentication token for https://swagger.org.cn/docs/specification/authentication/bearer-authentication/
        'verify' => true, // optional default true, turn on/off certificates verification
        'deprecated_header' => 'Deprecated', // optional default 'Deprecated', define name of deprecated response header logged into deprecation log
    ],

如果你在Laravel中定义了logging.deprecations配置键,弃用或日落头将被记录到指定的日志通道。

/**
 * @var \SimpleAsFuck\ApiToolkit\Service\Client\Config $config
 * @var \Psr\Log\LoggerInterface $logger
 */

/** @var \SimpleAsFuck\ApiToolkit\Service\Client\DeprecationsLogger|null $deprecationsLogger */
$deprecationsLogger = new \SimpleAsFuck\ApiToolkit\Service\Client\DeprecationsLogger(
    $config,
    $logger,
    new \GuzzleHttp\Psr7\HttpFactory()
);

$client = new \SimpleAsFuck\ApiToolkit\Service\Client\ApiClient(
    $config,
    new \GuzzleHttp\Client(),
    new \GuzzleHttp\Psr7\HttpFactory(),
    $deprecationsLogger
);

/**
 * with transformer, YourClass can be converted into different api structure
 *
 * @implements \SimpleAsFuck\ApiToolkit\Service\Transformation\Transformer<YourClass>
 */
final class YourTransformer implements \SimpleAsFuck\ApiToolkit\Service\Transformation\Transformer
{
    /**
     * @param YourClass $transformed
     */
    public function toApi($transformed): \stdClass
    {
        $apiData = new \stdClass();
        $apiData->some_property = $transformed->someProperty;
        return $apiData;
    }
}

/**
 * @var YourClass $yourModelForRequestBody
 * @var \SimpleAsFuck\Validator\Rule\Custom\UserClassRule<YourOtherClass> $classRuleForResponseModel
 */

try {
    $responseObject = $client->requestObject('some_api_name', 'POST', '/to-some-action', $yourModelForRequestBody, new YourTransformer());
    /*
     * response has getter for json decoded body which is validated after decoding by rule chain
     * request method return object rule, so you can easily validate response json structure
     * is recommended use some you class rule documented here: https://github.com/simple-as-fuck/php-validator#user-class-rule
     * and convert api data structure into some your concrete object instance
     */
    $yourModelFromResponseBody = $responseObject->class($classRuleForResponseModel)->notNull();
}
catch (\SimpleAsFuck\ApiToolkit\Model\Client\ApiException $exception) {
    /*
     * if anything go wrong in request/response processing or response json parsing
     * \SimpleAsFuck\ApiToolkit\Model\Client\ApiException is thrown,
     * and you can handle any error from communication
     */
    $exception->getCode(); // if exception contains http response, http status is here, otherwise zero is returned
    $exception->getMessage(); // if http response contains json object with message string property, json message overwrite exception message
}

API客户端钩子工具

API客户端服务提供两个辅助方法来注册和注销钩子监听URL。辅助方法调用与这些控制器兼容的数据结构AddListenerRemoveListener

/**
 * @var \SimpleAsFuck\ApiToolkit\Service\Client\ApiClient $client
 */

// method call POST /webhook request
$webhook = $client->addWebhookListener('some_api_name', 'some_webhook_event_type', 'https://some-client/listening-url');

// you can register listener URL with priority and required webhook attributes,
// it means than listener SHOULD be called only if server dispatch webhook type
// with attributes containing all specified attributes in webhook registration
// (webhook dispatch can contain more attributes than required)
// priority specified which webhook listener SHOULD be called first,
// if on server is more than one listener for same webhook type,
// this behaviours is implemented in server services in this package,
// but other server implementations can behave differently
// or webhook functionality may not be implemented, so always read specific API documentation!
$webhook = $client->addWebhookListener(
    'some_api_name',
    'some_webhook_event_type',
    'https://some-client/listening-url',
    \SimpleAsFuck\ApiToolkit\Model\Webhook\Priority::NORMAL,
    ['some_key' => '89']
);

// you can save webhook identifier for future use
// deletion while listening is no longer needed, or some data loading in listening url
$webhook->id();
/**
 * @var \SimpleAsFuck\ApiToolkit\Service\Client\ApiClient $client
 * @var non-empty-string $webhookId
 */

// method call DELETE /webhook request
$client->removeWebhookListener('some_api_name', $webhookId);

为了监听分发的钩子,你需要在服务器可访问的URL上准备一些POST操作。操作将接收在JSON体中分发的钩子实例。你注册的URL不应被服务器修改,这是此包中服务器服务默认行为。

你可以在钩子监听操作之前添加一些身份验证中间件,此包中的服务器服务允许使用自定义HTTP头分发,并支持自动添加https://swagger.org.cn/docs/specification/authentication/bearer-authentication/

class YourListeningController
{
    public function handle(
        \Psr\Http\Message\ServerRequestInterface $request
        //\Symfony\Component\HttpFoundation\Request $request
    ): \Psr\Http\Message\ResponseInterface {
    //): \Symfony\Component\HttpFoundation\Response {
        $webhook = \SimpleAsFuck\ApiToolkit\Factory\Server\Validator::make($request)
        //$webhook = \SimpleAsFuck\ApiToolkit\Factory\Symfony\Validator::make($request)
            ->json()
            ->object()
            ->class(new \SimpleAsFuck\ApiToolkit\Service\Webhook\WebhookTransformer())
            ->notNull()
        ;

        // run some you logic
        // you should expect than listening action can be called multiple times
        // because of some network error or another failure

        $result = new \SimpleAsFuck\ApiToolkit\Model\Webhook\Result();
        $result = new \SimpleAsFuck\ApiToolkit\Model\Webhook\Result(
            // you can inform server site application to stop
            // dispatching webhook for another listener after current listener
            // which has less priority
            // server services in this package support this functionality
            stopDispatching: true
        )
        // you SHOULD return valid json object,
        // server services in this package expect to receive result object,
        // otherwise dispatch can be detected as failed because of some syntax error
        // and server can dispatch webhook agan
        return \SimpleAsFuck\ApiToolkit\Factory\Server\ResponseFactory::makeJson(
        //return \SimpleAsFuck\ApiToolkit\Factory\Symfony\ResponseFactory::makeJson(
            $result,
            new \SimpleAsFuck\ApiToolkit\Service\Webhook\ResultTransformer(),
            // you MUST return successful response, otherwise
            // server site application can send webhook agan
            // because error response will look like failed dispatch
            \Kayex\HttpCodes::HTTP_OK
        );
    }
}

API服务器控制器工具

为了请求处理,已准备验证器和响应工厂。有关验证规则的更多信息,请参阅Simple as fuck / Php Validator的README。

如果你使用symfony请求和响应,你可以使用来自不同命名空间的工厂,如示例中注释所示。

// star of your action

$rules = \SimpleAsFuck\ApiToolkit\Factory\Server\Validator::make($request);
//$rules = \SimpleAsFuck\ApiToolkit\Factory\Symfony\Validator::make($request);

// validate some query parameter
$someQueryValidValue = $rules->query()->key('someKey')->string()->parseInt()->min(1)->notNull();

/** @var \SimpleAsFuck\ApiToolkit\Service\Server\UserQueryRule<YourClass> $yourQueryRule */
$yourObjectFromRequestQuery = $rules->query()->class($yourQueryRule)->notNull();

// validate something from request body with json format
$someJsonValidValue = $rules->json()->object()->property('someProperty')->string()->notEmpty()->max(255)->notNull();

/** @var \SimpleAsFuck\Validator\Rule\Custom\UserClassRule<YourClass> $yourClassRule */
$yourObjectFromRequestBody = $rules->json()->object()->class($yourClassRule)->notNull();

// end of your action

/**
 * @var YourClass $yourModelForResponseBody
 * @var \SimpleAsFuck\ApiToolkit\Service\Transformation\Transformer<YourClass> $transformer 
 */

// response with one object
$response = \SimpleAsFuck\ApiToolkit\Factory\Server\ResponseFactory::makeJson($yourModelForResponseBody, $transformer, \Kayex\HttpCodes::HTTP_OK);
//$response = \SimpleAsFuck\ApiToolkit\Factory\Symfony\ResponseFactory::makeJson($yourModelForResponseBody, $transformer, \Kayex\HttpCodes::HTTP_OK);

// response with some array or collection (avoiding out of memory problem recommended some lazy loading iterator)
$response = \SimpleAsFuck\ApiToolkit\Factory\Server\ResponseFactory::makeJsonStream(new \ArrayIterator([$yourModelForResponseBody]), $transformer);
//$response = \SimpleAsFuck\ApiToolkit\Factory\Symfony\ResponseFactory::makeJsonStream(new \ArrayIterator([$yourModelForResponseBody]), $transformer);
//$response = \SimpleAsFuck\ApiToolkit\Factory\Symfony\ResponseFactory::makeJsonStream([$yourModelForResponseBody], $transformer);

API服务器中间件工具

如果出现任何问题,你可以在异常捕获中间件或某些异常处理程序中使用异常转换器。

为Laravel准备了一个Laravel配置适配器,它将自动加载ExceptionTransformer的配置,你可以轻松地从DI中获取此转换器,无需任何新配置(使用Laravel的标准配置)。

/**
 * @var \SimpleAsFuck\ApiToolkit\Service\Config\Repository $configRepository
 */

try {
    // some breakable logic
}
catch(\SimpleAsFuck\ApiToolkit\Model\Server\ApiException $exception) {
//catch(\Symfony\Component\HttpKernel\Exception\HttpException $exception) {
    $response = \SimpleAsFuck\ApiToolkit\Factory\Server\ResponseFactory::makeJson(
    //$response = \SimpleAsFuck\ApiToolkit\Factory\Symfony\ResponseFactory::makeJson(
        $exception,
        // transformer will convert exception in to json object with message property with original exception message
        new \SimpleAsFuck\ApiToolkit\Service\Server\ApiExceptionTransformer(),
        $exception->getStatusCode()
    );
}
catch (\Throwable $exception) {
    $response = \SimpleAsFuck\ApiToolkit\Factory\Server\ResponseFactory::makeJson(
    //$response = \SimpleAsFuck\ApiToolkit\Factory\Symfony\ResponseFactory::makeJson(
        $exception,
        // transformer will convert exception in to json object with message property
        // if application has turned off debug, message property contain only "Internal server error"
        // but with enabled debug message contains exception type, message, file and line where was exception thrown
        new \SimpleAsFuck\ApiToolkit\Service\Server\ExceptionTransformer($configRepository),
        \Kayex\HttpCodes::HTTP_INTERNAL_SERVER_ERROR
    );
}

API服务器钩子工具

从服务器端向客户端进行webhook派发时,这里准备了WebhookDispatcher。WebhookDispatcher将通过抽象的webhook Repository 查找必要的webhook,然后通过抽象的webhook Client 来调用它们。

您需要实现webhook Repository、webhook Client,并为持久化webhook准备一些存储,还需要为webhook调用重试准备一些队列。

对于Laravel,已经准备了使用Laravel queuesLaravelMysqlRepositoryLaravelClient

Laravel webhook实现会自动从 webhook.php 配置文件中加载配置,该配置文件可以通过此包发布。

php artisan vendor:publish --tag=api-toolkit-config

Webhook存储在MySql数据库表中,它们在Laravel迁移中定义,可以从此包发布。

php artisan vendor:publish --tag=api-toolkit-migration
/**
 * @var \SimpleAsFuck\ApiToolkit\Service\Webhook\Repository $webhookRepository
 * @var \SimpleAsFuck\ApiToolkit\Service\Webhook\Client $webhookClient
 */

$dispatcher = new \SimpleAsFuck\ApiToolkit\Service\Webhook\WebhookDispatcher($webhookRepository, $webhookClient);

// simplest dispatch, when something happened on the server side,
// webhooks calls are added into a queue
$dispatcher->dispatch('some_webhook_event_type');

// webhook call with some attribute,
// for example, you can dispatch an event type with some concrete entity id
$dispatcher->dispatch('some_webhook_event_type', ['some_attribute' => '1256']);

// webhook dispatch with third parameter $synchronouslyFirstTry as true
// will first webhook call try immediately without adding call into queue
// only if the first call fails, webhook call is added into queue for retry
$dispatcher->dispatch('some_webhook_event_type', [], synchronouslyFirstTry: true);

// simple dispatch when the first call will try after 1 minute
$dispatcher->dispatchWithDelay('some_webhook_event_type', [], 60);

对于服务器端上的webhook监听器注册,您可以使用控制器 AddListenerRemoveListenerSymfony 等价控制器,或者只需在任意操作中使用webhook Repository,并使用此处提供的模型和控制器作为灵感。

由于安全原因,此包中的控制器没有发布功能或不提供自动注册到您的路由器。您应该始终完全控制您的应用程序,什么将被监听!

您可以将控制器手动复制到您的应用程序中,并将它们放在其他控制器之间。您应该为webhook操作添加与之前操作相同的身份验证中间件,以确保相同的安全性。

如果您在POST /webhook路由上注册了AddListener,并在DELETE /webhook上注册了RemoveListener,则您的webhook操作将与API客户端的webhook辅助方法兼容。