mapado/rest-client-sdk

hydra API 的 Rest 客户端 SDK

v2.0.0 2024-07-29 14:38 UTC

README

PHP JSON API 的 Rest 客户端 SDK。

此客户端旨在避免为每个 API 实现自定义 SDK 的复杂性。您只需实现模型和少量配置,它将隐藏复杂性。

安装

composer require mapado/rest-client-sdk

使用方法

假设您有这些 API 端点

  • /v2/carts
  • /v2/carts/{id}
  • /v2/cart_items
  • /v2/cart_items/{id}

您需要有两个实体,比如说

  • Foo\Bar\Model\Cart
  • Foo\Bar\Model\CartItem

您需要声明一个包含您两个 ClassMetadataMapping

实体声明

配置实体

假设以下实体

namespace Acme\Foo\Bar;

use Mapado\RestClientSdk\Mapping\Attributes as Rest;

#[Rest\Entity(key: "carts")]
class Cart
{
   #[Rest\Id, Rest\Attribute(name: "@id", type: "string")]
  private $iri;

   #[Rest\Attribute(name: "status", type: "string")]
  private $status;

  #[Rest\Attribute(name: "created_at", type: "datetime")]
  private $createdAt;
  
  #[Rest\OneToMany(name: "cart_items", targetEntity: "CartItem")]
  private $cartItemList;

  // getters & setters ...
}

/**
 * @Rest\Entity(key="cart_items")
 */
#[Rest\Entity(key: "cart_items")]
class CartItem
{
  #[Rest\Id, Rest\Attribute(name: "@id", type: "string")]
  private $iri;

  #[Rest\Attribute(name: "number", type: "integer")]
  private $number;

  #[Rest\ManyToOne(name: "cart", targetEntity: "Cart")]
  private $cart;
}

说明

Entity 定义

  • key 必须是您的 API 端点的键

属性定义

  • name API 返回格式中键的名称
  • type 属性的类型

关系定义

  • name API 返回格式中键的名称
  • targetEntity 目标实体的类名

单元工作

EntityRepository 在 PUT 和 POST 请求中使用 UnitofWork。它与通过 GET 请求存储的实体进行比较,只发送更改的数据,而不是完整的模型。

配置

使用 Symfony 吗?

有一个组件可以轻松集成此组件: mapado/rest-client-sdk-bundle

配置完成后,您可以获取如下客户端

$sdkClient = $this->get('mapado.rest_client_sdk.foo');
// or $sdkClient = $this->get('mapado.rest_client_sdk')->getSdkClient('foo');

不使用 Symfony

您需要这样配置客户端

use Mapado\RestClientSdk\Mapping;
use Mapado\RestClientSdk\RestClient;
use Mapado\RestClientSdk\SdkClient;
use Mapado\RestClientSdk\Mapping\Driver\AttributeDriver;

$restClient = new RestClient(
  new GuzzleHttp\Client(),
  'http://path-to-your-api.root'
);

// if you need to pass some headers to the client, you can do something like this:
// $restClient = new RestClient(
//   new GuzzleHttp\Client(['headers' => [ 'Authorization' => 'Bearer foobar' ]]),
//   'http://path-to-your-api.root'
// );
// See guzzle documentation for more informations

$annotationDriver = new AttributeDriver($cachePath, ($debug = true));

$mapping = new Mapping('/v2'); // /v2 is the prefix of your routes
$mapping->setMapping($annotationDriver->loadDirectory($pathToEntityDirectory));

$sdkClient = new SdkClient($restClient, $mapping);

可选:创建 SdkClientRegistry

use Mapado\RestClientSdk\SdkClientRegistry;

$registry = new Registry(['my-api' => $sdkClient]);

这可以更容易地管理多个客户端。然后您可以调用

$sdkClient = $registry->getSdkClient('my-api');

// or

$sdkClient = $registry->getSdkClientForClass('\App\Entity\SomeEntity');

访问数据

获取实体/实体列表

$repository = $sdkClient->getRepository('Acme\Foo\Bar\Cart');

// you can also access the repository by model key:
// $repository = $sdkClient->getRepository('cart');

// Find entity based on ID as defined in the entity by @Rest\Id
$cart = $repository->find(1);

// If you need to pass extra query parameters:
$cart = $repository->find(1, ['foo' => 'bar']); // will call /v2/carts/1?foo=bar

// Find all entities in the database
$cart = $repository->findAll();

// Find one entity based on the fielddefined in the function name (in this case <Name>)
$cart = $repository->findOneByName('username'); // will call /v2/carts?name=username

// Find one entity based on the criteria defined in the array
$cart = $repository->findOneBy(array(
  'name' => 'username',
  'date' => '1-1-2016',
)); // will call /v2/carts?name=username&date=1-1-2016

// To find all matches for the two examples above replace findOneByName() with findByName() and findOneBy() with findBy()

创建新实例

$cart = new \Acme\Foo\Bar\Cart();
$cart->setStatus('awaiting_payment');
$cart->setCreatedAt(new \DateTime());
$repository->persist($cart);

持久化 操作将发送一个 POST 请求,包含序列化对象到 API 端点,并返回新创建的对象

更新实例

$cart = $repository->find(13);
$cart->setStatus('payed');
$repository->update($cart);

更新 操作将发送一个 PUT 请求,包含序列化对象到 API 端点(使用对象 Id),并返回更新后的对象。

删除实例

$cart = $repository->find(13);
$repository->remove($cart);

删除 操作将发送一个 DELETE 请求到 API 端点,使用对象 Id。

自定义序列化

默认情况下,在序列化时,SDK 只返回现有关系(如一对一)的 Id。

您可能希望序列化一对一关系(例如,您有一个 Cart 并希望更新相关的 cartItems

为此,您可以在 persistupdate 方法中指定一个 $serializationContext

$repostory->update($cart, ['serializeRelations' => ['cartItems']]);

扩展存储库

如果您需要扩展 EntityRepository,您只需做类似的事情

namespace Acme\Foo\Bar\Repository;

use Mapado\RestClientSdk\EntityRepository;

class CartRepository extends EntityRepository
{
  public function findOneByFoo($bar)
  {
    // generate the path to call
    $path = $data = $this->restClient->get($path); // ...
    return $this->sdk->getModelHydrator()->hydrate($data, $this->entityName); // hydrate for an entity, hydrateList for a list
  }
}

更新您的实体 Rest 属性,以便实体了解其存储库

namespace Acme\Foo\Bar;

use Mapado\RestClientSdk\Mapping\Attributes as Rest;

#[Rest\Entity(key: "carts", repository: Acme\Foo\Bar\Repository\CartRepository::class)]
class Cart {

处理异常

如果您的 API 返回的状态代码不是 2xx 或 404,SDK 将抛出异常。

您可以在 此文件夹 中查看所有异常。

如果您需要访问 API 响应体,您可以这样做

use Mapado\RestClientSdk\Exception\RestClientException;

try {
  // do something that will fail
} catch (\RestClientException $e) {
  $response = $e->getResponse(); // $response should be a Psr\Http\Message\ResponseInterface
  $body = $response->getBody();
  var_dump($body); // will dump your response body
}

PHPStan

rest-client-sdk 与 PHPStan 兼容良好。请参阅相关文档

破解

该库经过多版本的 Symfony 和 PHP 的测试,这要归功于 composer-test-scenarios

但是,ocramius/proxy-manager、composer 1&2 和 php 版本之间存在冲突。

为了解决这个问题,对于 php 7.4 的场景,可能需要使用此命令更新场景:

docker run --rm --interactive --tty --volume $PWD:/app -w "/app" roadiz/php74-runner composer scenario:update

这个问题应该可以通过使用 ocramius/proxy-manager#2.9.0 来解决,但它绝对需要 composer 2,而我们(Mapado)还没有准备好。

解决这个问题的路径是,通过将 "ocramius/proxy-manager": "^2.9.0" 升级到主版本,该版本需要 composer^2