在 Laravel 中使用第三方服务

发布日期:作者:

Working with third party services in laravel image

大约两年前,我写了一篇关于如何在 Laravel 中使用第三方服务的教程。时至今日,它仍然是我网站上访问量最高的页面。然而,在过去的两年里,情况发生了变化,我决定重新审视这个主题。

我使用第三方服务已经很久了,我甚至不记得没有使用它们的时候是什么时候。作为一名初级开发者,我将 API 集成到 Joomla、Magento 和 WordPress 等其他平台中。现在,我主要将 API 集成到我的 Laravel 应用程序中,通过依赖其他服务来扩展业务逻辑。

本教程将描述我目前如何通常处理与 API 的集成。如果你读过我的上一篇教程,请继续阅读,因为一些内容已经改变,我认为这些改变是合理的。

让我们从一个 API 开始。我们需要一个可以集成的 API。我的最初教程是集成 PingPing,它是 Laravel 社区提供的一个优秀的运行状况监控解决方案。但是,这一次我想尝试一个不同的 API。

在本教程中,我们将使用 Planetscale API。Planetscale 是一款很棒的数据库服务,我在日常工作中使用它来使我的读写操作更接近我的用户。

我们的集成会做什么?想象一下,我们有一个应用程序,它允许我们管理我们的基础设施。我们的服务器通过 Laravel Forge 运行,我们的数据库在 Planetscale 上。没有一种干净的方式来管理这个工作流,所以我们创建了自己的方法。为此,我们需要一个或多个集成。

最初,我将我的集成保存在 app/Services 下;但是,随着我的应用程序越来越大越来越复杂,我需要使用 Services 命名空间来处理内部服务,导致命名空间变得很混乱。我已经将我的集成迁移到了 app/Http/Integrations。这样做是有意义的,而且这是一个我从 Sam Carrè 的 Saloon 中学到的技巧。

现在我可以使用 Saloon 来进行我的 API 集成,但我想要解释一下如何在没有包的情况下进行集成。如果在 2023 年需要 API 集成,我强烈建议使用 Saloon。它非常棒!

那么,让我们从创建一个集成目录开始。你可以使用以下 bash 命令

mkdir app/Http/Integrations/Planetscale

创建 Planetscale 目录后,我们需要创建一种连接到它的方式。我从 Saloon 库中学到的另一个命名约定是将这些基类视为连接器——因为它们的目的是允许你连接到特定的 API 或第三方。

app/Http/Integrations/Planetscale 目录中创建一个名为 PlanetscaleConnector 的新类,我们可以详细说明这个类需要什么,这将很有趣。

因此,我们必须将这个类注册到我们的容器中,以便解析它或在其周围构建一个外观。我们可以在服务提供者中以“长”的方式注册它——但我的最新方法是让这些连接器自行注册——有点像...

declare(strict_types=1);
 
namespace App\Http\Integrations\Planetscale;
 
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Http;
 
final readonly class PlanetscaleConnector
{
public function __construct(
private PendingRequest $request,
) {}
 
public static function register(Application $app): void
{
$app->bind(
abstract: PlanetscaleConnector::class,
concrete: fn () => new PlanetscaleConnector(
request: Http::baseUrl(
url: '',
)->timeout(
seconds: 15,
)->withHeaders(
headers: [],
)->asJson()->acceptJson(),
),
);
}
}

因此,这里的想法是,关于这个类如何注册到容器中的所有信息都存在于类本身中。服务提供者只需要调用类上的静态注册方法!这为我节省了很多时间,因为我无需在许多 API 集成中搜索提供者并找到正确的绑定。我只需要去相关的类,所有信息都在我面前。

你会注意到,目前我们没有将任何东西传递给请求中的 token 或 base url 方法。接下来让我们解决这个问题。你可以在你的 Planetscale 帐户中获取这些信息。

在你的 .env 文件中创建以下记录。

PLANETSCALE_SERVICE_ID="your-service-id-goes-here"
PLANETSCALE_SERVICE_TOKEN="your-token-goes-here"
PLANETSCALE_URL="https://api.planetscale.com/v1"

接下来,需要将这些信息拉到应用程序的配置中。它们都属于 config/services.php,因为这是通常配置第三方服务的 地方。

return [
// the rest of your services config
 
'planetscale' => [
'id' => env('PLANETSCALE_SERVICE_ID'),
'token' => env('PLANETSCALE_SERVICE_TOKEN'),
'url' => env('PLANETSCALE_URL'),
],
];

现在我们可以在 PlanetscaleConnector 中的注册方法中使用这些信息。

declare(strict_types=1);
 
namespace App\Http\Integrations\Planetscale;
 
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Http;
 
final readonly class PlanetscaleConnector
{
public function __construct(
private PendingRequest $request,
) {}
 
public static function register(Application $app): void
{
$app->bind(
abstract: PlanetscaleConnector::class,
concrete: fn () => new PlanetscaleConnector(
request: Http::baseUrl(
url: config('services.planetscale.url'),
)->timeout(
seconds: 15,
)->withHeaders(
headers: [
'Authorization' => config('services.planetscale.id') . ':' . config('services.planetscale.token'),
],
)->asJson()->acceptJson(),
),
);
}
}

你需要以 service-id:service-token 的格式将令牌发送到 Planetscale,所以我们不能使用默认的 withToken 方法,因为它不允许我们根据需要自定义它。

现在我们已经创建了一个基本的类,我们可以开始考虑集成的范围。在创建我们的服务令牌时,我们必须这样做,以添加正确的权限。在我们的应用程序中,我们希望能够执行以下操作:列出数据库。列出数据库区域。列出数据库备份。创建数据库备份。删除数据库备份。

因此,我们可以考虑将这些操作分组到两个类别中:数据库。备份。

让我们向连接器添加两个新方法来创建我们需要的内容

declare(strict_types=1);
 
namespace App\Http\Integrations\Planetscale;
 
use App\Http\Integrations\Planetscale\Resources\BackupResource;
use App\Http\Integrations\Planetscale\Resources\DatabaseResource;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Http;
 
final readonly class PlanetscaleConnector
{
public function __construct(
private PendingRequest $request,
) {}
 
public function databases(): DatabaseResource
{
return new DatabaseResource(
connector: $this,
);
}
 
public function backups(): BackupResource
{
return new BackupResource(
connector: $this,
);
}
 
public static function register(Application $app): void
{
$app->bind(
abstract: PlanetscaleConnector::class,
concrete: fn () => new PlanetscaleConnector(
request: Http::baseUrl(
url: config('services.planetscale.url'),
)->timeout(
seconds: 15,
)->withHeaders(
headers: [
'Authorization' => config('services.planetscale.id') . ':' . config('services.planetscale.token'),
],
)->asJson()->acceptJson(),
),
);
}
}

如你所见,我们创建了两个新方法,databasesbackups。它们将返回新的资源类,并传递连接器。逻辑现在可以在资源类中实现,但我们稍后需要向连接器添加另一个方法。

<?php
 
declare(strict_types=1);
 
namespace App\Http\Integrations\Planetscale\Resources;
 
use App\Http\Integrations\Planetscale\PlanetscaleConnector;
 
final readonly class DatabaseResource
{
public function __construct(
private PlanetscaleConnector $connector,
) {}
 
public function list()
{
//
}
 
public function regions()
{
//
}
}

这是我们的 DatabaseResource;我们现在已经为我们要实现的方法创建了占位符。你也可以对 BackupResource 做同样的事情。它看起来会有点类似。

因此,结果可以在数据库列表中进行分页。但是,我在这里不会处理这个问题——我建议使用 Saloon,因为它对分页结果的实现非常棒。在这个例子中,我们不会担心分页。在我们填充 DatabaseResource 之前,我们需要向 PlanetscaleConnector 添加另一个方法来很好地发送请求。为此,我使用的是我名为 juststeveking/http-helpers 的包,它有一个枚举,包含我使用的所有典型 HTTP 方法。

public function send(Method $method, string $uri, array $options = []): Response
{
return $this->request->send(
method: $method->value,
url: $uri,
options: $options,
)->throw();
}

现在我们可以回到我们的 DatabaseResource 并开始填充列表方法的逻辑。

declare(strict_types=1);
 
namespace App\Http\Integrations\Planetscale\Resources;
 
use App\Http\Integrations\Planetscale\PlanetscaleConnector;
use Illuminate\Support\Collection;
use JustSteveKing\HttpHelpers\Enums\Method;
use Throwable;
 
final readonly class DatabaseResource
{
public function __construct(
private PlanetscaleConnector $connector,
) {}
 
public function list(string $organization): Collection
{
try {
$response = $this->connector->send(
method: Method::GET,
uri: "/organizations/{$organization}/databases"
);
} catch (Throwable $exception) {
throw $exception;
}
 
return $response->collect('data');
}
 
public function regions()
{
//
}
}

我们的列表方法接受参数 organization 来传递组织,以列出数据库。然后,我们使用它通过连接器向特定 URL 发送请求。将它包装在 try-catch 语句中,可以让我们捕获连接器 send 方法可能发生的异常。最后,我们可以从方法返回一个集合,以便在我们的应用程序中使用它。

我们可以更详细地了解这个请求,因为我们可以开始将数据从数组映射到使用 DTO 时更有语义意义的东西。我曾在 这里 讨论过这个问题,所以这里不再重复。

让我们快速看一下 BackupResource,看看不仅仅是 get 请求。

declare(strict_types=1);
 
namespace App\Http\Integrations\Planetscale\Resources;
 
use App\Http\Integrations\Planetscale\Entities\CreateBackup;
use App\Http\Integrations\Planetscale\PlanetscaleConnector;
use JustSteveKing\HttpHelpers\Enums\Method;
use Throwable;
 
final readonly class BackupResource
{
public function __construct(
private PlanetscaleConnector $connector,
) {}
 
public function create(CreateBackup $entity): array
{
try {
$response = $this->connector->send(
method: Method::POST,
uri: "/organizations/{$entity->organization}/databases/{$entity->database}/branches/{$entity->branch}",
options: $entity->toRequestBody(),
);
} catch (Throwable $exception) {
throw $exception;
}
 
return $response->json('data');
}
}

我们的创建方法接受一个实体类,我使用它来将数据通过应用程序传递到需要的地方。当 URL 需要一组参数并且我们需要通过请求主体发送数据时,这很有用。

我还没有介绍测试,但我写了一篇关于如何 使用 PestPHP 测试 JSON:API 端点 的教程,其中包含类似的概念,用于测试类似的集成。

使用这种方法,我可以创建可靠且可扩展的第三方集成。它被分成逻辑部分,所以我可以处理大量的逻辑。通常我会拥有更多的集成,因此可以将一些逻辑提取到特性中,并在集成之间继承行为。

Steve McDougall photo

技术作家,就职于 Laravel 新闻,开发者倡导者,就职于 Treblle。API 专家,经验丰富的 PHP/Laravel 工程师。YouTube 直播主.

Cube

Laravel 时事通讯

加入 40,000 多名其他开发者,永远不会错过新的提示、教程等。

Laravel Forge logo

Laravel Forge

轻松创建和管理您的服务器,并在几秒钟内部署您的 Laravel 应用程序。

Laravel Forge
Tinkerwell logo

Tinkerwell

Laravel 开发人员必备的代码运行器。利用 AI、自动完成和即时反馈功能,在本地和生产环境中进行调试。

Tinkerwell
No Compromises logo

绝不妥协

Joel 和 Aaron,两位来自“绝不妥协”播客的经验丰富的开发者,现已开放接单,为您的 Laravel 项目服务。 ⬧ 固定费用:$7500/月。 ⬧ 无需冗长的销售流程。 ⬧ 无需签订合同。 ⬧ 100% 退款保证。

绝不妥协
Kirschbaum logo

Kirschbaum

提供创新和稳定性,确保您的 Web 应用程序取得成功。

Kirschbaum
Shift logo

Shift

运行的是旧版本的 Laravel?立即使用自动化的 Laravel 升级和代码现代化功能,使您的应用程序焕然一新。

Shift
Bacancy logo

Bacancy

用经验丰富的 Laravel 开发人员为您的项目增光添彩,他们拥有 4-6 年的经验,月薪仅为 $2500。获得 160 小时的专业知识和 15 天的无风险试用期。立即安排电话会议!

Bacancy
Lucky Media logo

Lucky Media

现在就来体验 Lucky 的魅力 - Laravel 开发的理想选择,拥有十多年的经验!

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel 电子商务

Laravel 电子商务。一个开源包,将现代无头电子商务功能的强大功能带到 Laravel。

Lunar: Laravel 电子商务
LaraJobs logo

LaraJobs

官方 Laravel 工作板

LaraJobs
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS 启动工具包

SaaSykit 是一个 Laravel SaaS 启动工具包,包含运行现代 SaaS 所需的所有功能。支付、精美结账、管理面板、用户仪表板、身份验证、预制组件、统计、博客、文档等等。

SaaSykit: Laravel SaaS 启动工具包
Rector logo

Rector

您的无缝 Laravel 升级合作伙伴,降低成本,加速创新,助力企业成功。

Rector
MongoDB logo

MongoDB

通过 MongoDB 和 Laravel 的强大集成增强您的 PHP 应用程序,使开发人员能够轻松高效地构建应用程序。支持事务、搜索、分析和移动用例,同时使用熟悉的 Eloquent API。探索灵活的现代数据库 MongoDB 如何改变您的 Laravel 应用程序。

MongoDB

最新动态

查看全部 →
Maska is a Simple Zero-dependency Input Mask Library image

Maska 是一个简单的零依赖输入掩码库。

阅读文章
Add Swagger UI to Your Laravel Application image

将 Swagger UI 添加到您的 Laravel 应用程序中

阅读文章
Assert the Exact JSON Structure of a Response in Laravel 11.19 image

在 Laravel 11.19 中断言响应的精确 JSON 结构

阅读文章
Build SSH Apps with PHP and Laravel Prompts image

使用 PHP 和 Laravel Prompts 构建 SSH 应用程序

阅读文章
Building fast, fuzzy site search with Laravel and Typesense image

使用 Laravel 和 Typesense 构建快速、模糊的网站搜索

阅读文章
Add Comments to your Laravel Application with the Commenter Package image

使用 Commenter 包为您的 Laravel 应用程序添加评论

阅读文章