在 Laravel 中利用 Guzzle 提高代码质量
Improve code quality while utilizing Guzzle in Laravel
我是 laravel 的新手,所以请不要苛刻。
我正在构建一个简单的网络,它通过 Guzzle 连接到外部 API(几个端点),获取一些数据,清理它们并存储它们。
目前 - 从一个端点来看 - 我有以下工作:
public function handle(Client $client)
{
try {
$request= $client->request('GET', 'https://api.url./something', [
'headers' => [
'X-RapidAPI-Key'=> env("FOOTBALL_API_KEY"),
'Accept' => 'application/json'
]
]);
$request = json_decode($request->getBody()->getContents(), true);
foreach ($request as $array=>$val) {
foreach ($val['leagues'] as $id) {
League::firstOrCreate(collect($id)->except(['coverage'])->toArray());
}
}
} catch (GuzzleException $e) {
};
}
因此我想要一些代码建议,如何从设计的角度使我的代码更好。
我的想法是:
a) 将 Guzzle 绑定为 service provider
.
b) 使用设计模式来实现对端点的调用。URI
builder maybe?
如有任何帮助,我们将不胜感激。
愿原力与你同在
详细反馈
一些特定于提供的代码本身的指针:
- guzzle 客户端
request
return 的响应与您分配给它的参数名称不匹配
- 调用
json_decode
可能会失败,在这种情况下它们会 return null
。在防御性编程方面,检查那些失败案例是很好的
- 您的案例对响应中的数据做出了一些假设。最好在使用之前检查响应是否是您期望的实际格式。
- 你抓住了所有
GuzzleExceptions
,但在这些情况下什么都不做。我认为您可以通过以下任一方式改善这一点:
- 记录异常
- 抛出您将在 class 捕获的另一个异常,调用
handle()
方法
- 以上两种选择
- 您可以选择注入 api 密钥,而不是直接通过
env()
方法获取它。这将防止 warning block here 中描述的问题
一般反馈
感觉你的代码在混合职责,这被认为是不好的做法。 handle()
方法现在执行以下操作:
- 发送API个请求
- 解码API个请求
- 验证 API 回复
- 解析 API 回复
- 创建模型
您可以考虑将其中的部分或全部移动到单独的 class 中,如下所示:
ApiClient
负责发出请求
ResponseDecoder
负责将响应转换为 \stdClass
ResponseValidator
负责检查响应是否具有预期的数据结构
RepsonseParser
负责将响应 \stdClass
转换为集合
LeagueFactory
负责将集合转换为 League
模型
有人可能会争辩说前四个 class 应该放在一个名为 ApiClient
的 class 中。这完全取决于您。
所以最后你会想出这样的东西:
<?php
namespace App\Example;
use Psr\Log\LoggerInterface;
class LeagueApiHandler
{
/**
* @var ApiClient
*/
private $apiClient;
/**
* @var ResponseDecoder
*/
private $decoder;
/**
* @var ResponseValidator
*/
private $validator;
/**
* @var ResponseParser
*/
private $parser;
/**
* @var LeagueFactory
*/
private $factory;
/**
* @var LoggerInterface
*/
private $logger;
public function __construct(
ApiClient $apiClient,
ResponseDecoder $decoder,
ResponseValidator $validator,
ResponseParser $parser,
LeagueFactory $factory,
LoggerInterface $logger
) {
$this->apiClient = $apiClient;
$this->decoder = $decoder;
$this->validator = $validator;
$this->parser = $parser;
$this->factory = $factory;
$this->logger = $logger;
}
public function handle()
{
try {
$response = $this->apiClient->send();
} catch (\RuntimeException $e) {
$this->logger->error('Unable to send api request', $e->getMessage());
return;
};
try {
$decodedResponse = $this->decoder->decode($response);
} catch (\RuntimeException $e) {
$this->logger->error('Unable to decode api response');
return;
};
if (!$this->validator->isValid($decodedResponse)) {
$this->logger->error('Unable to decode api response');
return;
}
$collections = $this->parser->toCollection($decodedResponse);
foreach ($collections as $collection) {
$this->factory->create($collection);
}
}
}
namespace App\Example;
use GuzzleHttp\Client;
class ApiClient
{
/**
* @var Client
*/
private $client;
/**
* @var string
*/
private $apiKey;
public function __construct(Client $client, string $apiKey)
{
$this->client = $client;
$this->apiKey = $apiKey;
}
public function send()
{
try {
return $this->client->request('GET', 'https://api.url./something', [
'headers' => [
'X-RapidAPI-Key' => $this->apiKey,
'Accept' => 'application/json'
]
]);
} catch (GuzzleException $e) {
throw new \RuntimeException('Unable to send request to api', 0, $e);
};
}
}
namespace App\Example;
use Psr\Http\Message\ResponseInterface;
class ResponseDecoder
{
public function decode(ResponseInterface $response): \stdClass
{
$response = json_decode($response->getBody()->getContents(), true);
if ($response === null) {
throw new \RuntimeException('Unable to decode api response');
}
return $response;
}
}
namespace App\Example;
class ResponseValidator
{
public function isValid(\stdClass $response): bool
{
if (is_array($response) === false) {
return false;
}
foreach ($response as $array) {
if (!isset($array['leagues'])) {
return false;
}
}
return true;
}
}
namespace App\Example;
use Illuminate\Support\Collection;
class ResponseParser
{
/**
* @param \stdClass $response
* @return Collection[]
*/
public function toCollection(\stdClass $response): array
{
$collections = [];
foreach ($response as $array => $val) {
foreach ($val['leagues'] as $id) {
$collections[] = collect($id)->except(['coverage'])->toArray();
}
}
return $collections;
}
}
namespace App\Example;
use Illuminate\Support\Collection;
class LeagueFactory
{
public function create(Collection $collection): void
{
League::firstOrCreate($collection);
}
}
我是 laravel 的新手,所以请不要苛刻。
我正在构建一个简单的网络,它通过 Guzzle 连接到外部 API(几个端点),获取一些数据,清理它们并存储它们。
目前 - 从一个端点来看 - 我有以下工作:
public function handle(Client $client)
{
try {
$request= $client->request('GET', 'https://api.url./something', [
'headers' => [
'X-RapidAPI-Key'=> env("FOOTBALL_API_KEY"),
'Accept' => 'application/json'
]
]);
$request = json_decode($request->getBody()->getContents(), true);
foreach ($request as $array=>$val) {
foreach ($val['leagues'] as $id) {
League::firstOrCreate(collect($id)->except(['coverage'])->toArray());
}
}
} catch (GuzzleException $e) {
};
}
因此我想要一些代码建议,如何从设计的角度使我的代码更好。
我的想法是:
a) 将 Guzzle 绑定为 service provider
.
b) 使用设计模式来实现对端点的调用。URI
builder maybe?
如有任何帮助,我们将不胜感激。
愿原力与你同在
详细反馈
一些特定于提供的代码本身的指针:
- guzzle 客户端
request
return 的响应与您分配给它的参数名称不匹配 - 调用
json_decode
可能会失败,在这种情况下它们会 returnnull
。在防御性编程方面,检查那些失败案例是很好的 - 您的案例对响应中的数据做出了一些假设。最好在使用之前检查响应是否是您期望的实际格式。
- 你抓住了所有
GuzzleExceptions
,但在这些情况下什么都不做。我认为您可以通过以下任一方式改善这一点:- 记录异常
- 抛出您将在 class 捕获的另一个异常,调用
handle()
方法 - 以上两种选择
- 您可以选择注入 api 密钥,而不是直接通过
env()
方法获取它。这将防止 warning block here 中描述的问题
一般反馈
感觉你的代码在混合职责,这被认为是不好的做法。 handle()
方法现在执行以下操作:
- 发送API个请求
- 解码API个请求
- 验证 API 回复
- 解析 API 回复
- 创建模型
您可以考虑将其中的部分或全部移动到单独的 class 中,如下所示:
ApiClient
负责发出请求ResponseDecoder
负责将响应转换为\stdClass
ResponseValidator
负责检查响应是否具有预期的数据结构RepsonseParser
负责将响应\stdClass
转换为集合LeagueFactory
负责将集合转换为League
模型
有人可能会争辩说前四个 class 应该放在一个名为 ApiClient
的 class 中。这完全取决于您。
所以最后你会想出这样的东西:
<?php
namespace App\Example;
use Psr\Log\LoggerInterface;
class LeagueApiHandler
{
/**
* @var ApiClient
*/
private $apiClient;
/**
* @var ResponseDecoder
*/
private $decoder;
/**
* @var ResponseValidator
*/
private $validator;
/**
* @var ResponseParser
*/
private $parser;
/**
* @var LeagueFactory
*/
private $factory;
/**
* @var LoggerInterface
*/
private $logger;
public function __construct(
ApiClient $apiClient,
ResponseDecoder $decoder,
ResponseValidator $validator,
ResponseParser $parser,
LeagueFactory $factory,
LoggerInterface $logger
) {
$this->apiClient = $apiClient;
$this->decoder = $decoder;
$this->validator = $validator;
$this->parser = $parser;
$this->factory = $factory;
$this->logger = $logger;
}
public function handle()
{
try {
$response = $this->apiClient->send();
} catch (\RuntimeException $e) {
$this->logger->error('Unable to send api request', $e->getMessage());
return;
};
try {
$decodedResponse = $this->decoder->decode($response);
} catch (\RuntimeException $e) {
$this->logger->error('Unable to decode api response');
return;
};
if (!$this->validator->isValid($decodedResponse)) {
$this->logger->error('Unable to decode api response');
return;
}
$collections = $this->parser->toCollection($decodedResponse);
foreach ($collections as $collection) {
$this->factory->create($collection);
}
}
}
namespace App\Example;
use GuzzleHttp\Client;
class ApiClient
{
/**
* @var Client
*/
private $client;
/**
* @var string
*/
private $apiKey;
public function __construct(Client $client, string $apiKey)
{
$this->client = $client;
$this->apiKey = $apiKey;
}
public function send()
{
try {
return $this->client->request('GET', 'https://api.url./something', [
'headers' => [
'X-RapidAPI-Key' => $this->apiKey,
'Accept' => 'application/json'
]
]);
} catch (GuzzleException $e) {
throw new \RuntimeException('Unable to send request to api', 0, $e);
};
}
}
namespace App\Example;
use Psr\Http\Message\ResponseInterface;
class ResponseDecoder
{
public function decode(ResponseInterface $response): \stdClass
{
$response = json_decode($response->getBody()->getContents(), true);
if ($response === null) {
throw new \RuntimeException('Unable to decode api response');
}
return $response;
}
}
namespace App\Example;
class ResponseValidator
{
public function isValid(\stdClass $response): bool
{
if (is_array($response) === false) {
return false;
}
foreach ($response as $array) {
if (!isset($array['leagues'])) {
return false;
}
}
return true;
}
}
namespace App\Example;
use Illuminate\Support\Collection;
class ResponseParser
{
/**
* @param \stdClass $response
* @return Collection[]
*/
public function toCollection(\stdClass $response): array
{
$collections = [];
foreach ($response as $array => $val) {
foreach ($val['leagues'] as $id) {
$collections[] = collect($id)->except(['coverage'])->toArray();
}
}
return $collections;
}
}
namespace App\Example;
use Illuminate\Support\Collection;
class LeagueFactory
{
public function create(Collection $collection): void
{
League::firstOrCreate($collection);
}
}