macsidigital / laravel-api-client
Laravel API 客户端构建包
Requires
- php: ^7.3|^8.0|^8.1
- firebase/php-jwt: ^6.0
- guzzlehttp/guzzle: ~7.0|~6.0|~5.0|~4.0
- guzzlehttp/oauth-subscriber: ^0.6.0
- illuminate/support: ^7.0|^8.0|^9.0|^10.0
- macsidigital/laravel-oauth2-client: ^1.2|^2.0
- nesbot/carbon: ^1.26.3 || ^2.0
Requires (Dev)
- orchestra/testbench: ^4.0|^5.0|^6.0|^7.0
- phpunit/phpunit: ^8.0|^9.0|^10.0
- dev-master
- 5.0.0
- 4.1.0
- 4.0.2
- 4.0.1
- 4.0.0
- 3.3.6
- 3.3.5
- 3.3.4
- 3.3.3
- 3.3.2
- 3.3.1
- 3.3.0
- 3.2.0
- 3.1.3
- 3.1.2
- 3.1.1
- 3.1
- 3.0.x-dev
- 3.0.8
- 3.0.7
- 3.0.6
- 3.0.5
- 3.0.4
- 3.0.3
- 3.0.2
- 3.0.1
- 3.0.0
- v2.x-dev
- 2.1.3
- 2.1.2
- 2.1.1
- 2.1.0
- 2.0.9
- 2.0.8
- 2.0.7
- 2.0.6
- 2.0.5
- 2.0.4
- 2.0.3
- 2.0.2
- 2.0.1
- 2.0.0
- v1.x-dev
- 1.1.3
- 1.1.2
- 1.1.1
- 1.1.0
- 1.0.27
- 1.0.26
- 1.0.25
- 1.0.24
- 1.0.23
- 1.0.22
- 1.0.21
- 1.0.20
- 1.0.19
- 1.0.18
- 1.0.17
- 1.0.16
- 1.0.15
- 1.0.14
- 1.0.13
- 1.0.12
- 1.0.10
- 1.0.9
- 1.0.8
- 1.0.7
- 1.0.6
- 1.0.5
- 1.0.4
- 1.0.3
- 1.0.2
- 1.0.1
- 1.0.0
This package is auto-updated.
Last update: 2024-09-25 00:42:19 UTC
README
Laravel 构建 API 客户端的包
一个 API 客户端构建库
安装
您可以通过 composer 安装此包
composer require macsidigital/laravel-api-client
版本
1.0 - Laravel 5.5 - 5.8 - 已弃用且不再维护。
2.0 - Laravel 6.0 - 维护中,欢迎提交 pull request。这是一个双向开源项目。
3.0 - Laravel 7.0 - 8.0 - 维护中,欢迎提交 pull request。这是一个双向开源项目。
使用
该库的主要目的是向模型添加一组通用特性,以便在访问 API 时能够创建、更新、检索和删除记录。显然,所有 API 都不同,因此您应该检查文档以了解如何最佳地使用这些特性。
基本概念是您在 API 库中构建一个客户端,它扩展了该库中的模型。
入口模型
首先要做的是创建一个入口模型,并扩展 MacsiDigital/API/Support/Entry 模型,这是塑造 API 的工作方式。它包含所有属性,因此如果您想要分页或自定义某些内容,它很可能会在这里。它是一个抽象模型,因此必须在您的实现中扩展。
我们建议将其放置在 src 目录中的 Support 文件夹中。
属性列表
// Where the models are protected $modelNamespace = ''; // default query string names for page and per_page fields protected $perPageField = 'page_size'; protected $pageField = 'page'; // Should return raw responses and not models/resultsets protected $raw = false; // Should return raw responses and not models/resultsets protected $raw = false; // Should we throw exceptions in cases where a server error occurs protected $throwExceptionsIfRaw = false; // Should results be paginated by default. protected $pagination = true; // Amount of pagination results per page by default, leave blank if should not paginate // Without pagination rate limits could be hit protected $defaultPaginationRecords = '20'; // Max and Min pagination records per page, will vary by API server protected $maxPaginationRecords = '100'; protected $minPaginationRecords = '1'; // If not paginated, how many queries should we allow per search, leave '' or 0 // for unlimited queries. This of course will eat up any rate limits protected $maxQueries = '5'; // Most APIs should include pagination data - this is the fields we should be looking for // in the response to get this information. We can use names or dot notation, // so for example 'current_page' or 'meta.current_page' protected $resultsPageField = 'meta.current_page'; protected $resultsTotalPagesField = 'meta.last_page'; protected $resultsPageSizeField = 'meta.per_page'; protected $resultsTotalRecordsField = 'meta.total'; // What operands are allowed when filtering protected $allowedOperands = ['=', '!=', '<', '>', '<=', '>=', '<>', 'like']; protected $defaultOperand = '=';
其中大部分都是不言自明的,有一个魔法方法属性,如果我们返回原始数据,我们将接收到实际响应而无需任何错误检查。如果我们设置 $throwExceptionsIfRaw = true,则我们仍然会收到原始数据,但它将检查返回是否成功且不是错误。如果接收到错误,它将抛出 HttpException。您也可以通过在查询上调用 with Exceptions() 来动态设置此值。
在您扩展的 Entry 模型实现中,您必须定义一个 newRequest 函数,这是我们的连接逻辑将放置的地方,并且应该返回一个 MacsiDigital\API\Support\Factory 对象,在您的实现中最好将其重命名为 Client,这将解决 API 网关客户端。
namespace MacsiDigital\Package\Support; use MacsiDigital\Package\Facades\Client; // This should extend the MacsiDigital\API\Support\Factory use MacsiDigital\API\Support\Entry as ApiEntry; class Entry extends ApiEntry { protected $modelNamespace = '\MacsiDigital\Package\\'; // Change any attributes to match API public function newRequest() { return Client::baseUrl(config('api.base_url'))->withOptions(config('api.options')); }
如果您使用 OAuth,则必须输入我们的 OAuth 逻辑,或者对于 OAuth2,可以像这样工作,这是 xero 实现
public function newRequest() { $config = config('xero'); $class = $config['tokenModel']; $token = new $class('xero'); if($token->hasExpired()){ $token = $token->renewToken(); } return Client::baseUrl($config['baseUrl'])->withToken($token->accessToken())->withHeaders(['xero-tenant-id' => $token->tenantId()]); }
如您所见,所有初始逻辑都是关于我们如何打开网关。请注意,API 不处理 OAuth2 授权,对于此,您将需要使用 OAuth2 客户端,如 macsidigital/laravel-oauth2-client 或 league/oauth2-client。macsidigital 客户端处理路由和数据保存到数据库或文件。
入口类中还有一个函数返回要使用的构建器类,如果您自己创建构建器类,以下将提供更多详细信息,这是您需要设置类的位置。
public function getBuilderClass() { return Builder::class; // By default links to MacsiDigital/API/Support/Builder }
默认使用 RESTful API
我们默认使用标准的 RESTful 方法调用:
- get - 检索记录
- post - 创建记录
- patch - 更新记录
- put - 替换记录
- delete - 删除记录
我们还添加了一个 find 调用,因为大多数 API 都有不同的 get 和 find 端点。
- find - 检索单个记录
然而,一些API对于调用什么方法有不同的看法(我们正在关注你,Xero)。
因此,您可以通过向模型中添加这些属性或方法来覆盖默认的创建和更新方法
protected $createMethod = 'post'; protected $updateMethod = 'patch'; public function getUpdateMethod() { return $this->updateMethod; } public function getCreateMethod() { return $this->createMethod; }
此外,一些实现(再次提到你,Xero)使用更新方法来创建模型,并使用创建方法来更新模型,因此您可能需要覆盖资源模型中的端点函数。我们已经添加了逻辑来尝试自动解决这个问题,但如果您遇到问题,则需要在模型中手动设置它们。
Xero使用补丁请求来创建模型,并使用POST请求来更新模型。
//Normal public function getPostEndPoint() { return $this->endPoint; } public function getPutEndPoint() { return $this->endPoint.'/'.$this->getKey(); } //Xero public function getPostEndPoint() { return $this->endPoint.'/'.$this->getKey(); } public function getPutEndPoint() { return $this->endPoint; }
检索模型
您可以通过使用find方法并传递一个ID,或一个ID数组,或者通过运行一个查询并返回first()或last()来检索单个模型;
$user = API::user()->find('ID'); $user = API::user()->where('Name', 'Bob')->first(); // First occurrence $user = API::user()->where('Name', 'Bob')->last(); // Last occurrence
您还可以使用get和all来检索多个模型,这将返回一个结果集,它是一种增强的Laravel Collection。
$users = API::account()->all(); $users = API::account()->where('Type', 'Admin')->get();
一些API(是的,Xero)返回单个结果,结果被包裹在一个数组中,就像多记录搜索一样。因此,您可以覆盖构建器模型上的hydrate()方法。
protected function hydrate($response) { return $this->resource->newFromBuilder($response->json()[$this->getApiDataField()][0]); }
结果集
我们在运行all()和get()这样的多结果返回类型后,会返回一个结果集。这将处理任何分页,并且可以用来回退到网关以检索下一批结果,包含有关我们导航的页码、下一页是什么等信息。这取决于API,我们尽可能利用API返回的元数据。
all()返回方法
使用all()方法,我们尝试通过递归调用API端点来检索所有结果,因为这将非常快地使用速率限制,我们在入口模型中有一个$maxQueries属性,一旦设置,这将是递归调用中可以触发的最大查询量。
如果达到最大值并且您需要更多结果,则可以调用retrieveNextPage()方法,它将下一轮查询添加到结果集中。
get()返回方法
这将返回最多最大查询数(如果没有设置分页)。当设置分页时,它将记录限制为每页的总数。
我们使用get()就像laravel一样,当我们需要过滤、排序或分页时。
结果集函数
如前所述,结果集就像laravel collections一样,具有附加的函数。
因此,如果我们的API每次只返回30个结果,我们的all方法将尽可能返回尽可能多的结果。这通常会导致一些分页。因此,为了检索下一组结果,我们可以这样做。
$meetings = $user->meetings; // Do some logic and discover need more results $meetings = $meetings->nextPage(); // $meetings->previousPage() will go back a page. // We can also iterate directly over the returned results foreach($meetings->nextPage() as $meeting) //Finally for those using json api in SPA app, you can utilise the toArray or toJson functions $meetings->toArray(); // returns array:5 [ "current_page" => 1 "data" => array:2 [ 0 => array:10 [ // $attributes ] 1 => array:10 [ // $attributes ] ] "last_page" => 5 "per_page" => 30 "total" => 137 ]
previousPage()方法将返回前一页,我们缓存了结果,所以返回将不会触发API调用,而是拉取缓存的结果。
有时您可能还希望累积记录,为此您可以调用getNextRecords()方法。请注意不要将此方法与下一页和前一页方法混用。
// will add more records to the current record set. The amount of records retrieved is based on the maxQueries and per page methods. $meetings->getNextRecords();
需要检查此内容,因为认为Xero不返回页码。如果您尝试检索比可用更多的记录,则将抛出异常。
链接
目前还没有使用预定义链接的功能,但我们可能在将来添加。
原始搜索
如果您想接收查询的原始响应,请在查询上设置raw。
$users = API::account()->raw()->all(); $users = API::account()->where('Type', 'Admin')->raw()->get();
HTTP错误
如果我们的调用返回错误并且响应没有设置为raw,则我们将抛出一个新的Http Exception。我们有默认行为,但不同的API对错误的响应不同。因此,您可以在构建器模型上覆盖prepareHttpErrorMessage()方法来自定义异常消息。
$json = $response->json(); if($json['Type'] == 'ValidationException'){ $message = $json['Message']; foreach($json['Elements'][0]['ValidationErrors'] as $error){ $message .= ' : '.$error['Message']; } return $message; } else { return $json['Message']; }
模型
我们有两种基本模型类型,资源和apiResources。
我们在模型中使用 Laravel 的 hasAttributes 特性,因此您可以在任何模型中使用任何像 Laravel 一样的类型转换。
资源
这些通常是作为其他模型关系返回的资源,但它们不直接与 API 交互。要创建一个,请扩展 MacsiDigital/API/Support/Resource。
这应该只用于返回为调用模型子数组的模型。
API 资源
这些是与 API 直接交互或通过父模型间接交互的模型。要创建一个,请扩展 MacsiDigital/API/Support/APIResource。
如果您想自己创建,则需要确保添加 MacsiDigital\API\Traits\InteractsWithAPI 特性。同时,参考我们的支持 API 资源的工作方式。
在这些模型中,我们还包含许多变量,如主键和 API 端点,这些是可用的属性。
// These will map to RESTful requests // index -> get and all // create -> post // show -> first // update -> patch or put // delete -> delete protected $allowedMethods = ['index', 'create', 'show', 'update', 'delete']; protected $endPoint = 'user'; protected $updateMethod = 'patch'; protected $storeResource; protected $updateResource; protected $primaryKey = 'id'; // Most APIs return data in a data attribute. However we need to override on a model basis as some like Xero return it as 'Users' or 'Invoices' protected $apiDataField = 'data'; // Also, some APIs return 'users' for multiple and user for single, set teh multiple field below to wheat is required if different protected $apiMultipleDataField = 'data';
apiData 和 apiMultiple 字段指定了响应中将包含主要正文数据的字段,这应该是良好的 API 中的 'data',但实际上可能有所不同。Zoom 使用 'users' 多条记录和 '' 单条记录。
如果 apiDataField 设置为 '',它将直接返回正文。
protected $apiDataField = '';
Xero 也使用不同的方法。
对于 Xero,可以在 getApiMultipleDataField 函数中覆盖它,这样我们就不需要在所有模型上设置。
public function getApiMultipleDataField() { // Xero uses pluralised end points like 'Users' so we can use this to pick the data from responses return $this->endPoint; }
以类似的方式,我们可以覆盖 primaryKey,因为某些 API 会将 ID 字段保留为 UserID,通过创建以下函数实现:
public function getKeyName() { return $this->endPoint.'ID'; }
端点
关于端点的简要说明,我们可以将端点设置为 'users',但也可以包括类似于 Laravel 路由的绑定模型,当结果作为关系的一部分返回时。
protected $endPoint = 'users/{user:id}/settings';
我们还可以在模型上设置自定义端点,如果端点不遵循约定。
protected $customEndPoints = [ 'get' => 'users/{user:id}/meetings', 'post' => 'users/{user:id}/meetings' ];
关系
我们尽量使模型与 Laravel 模型保持一致,甚至包括关系的工作方式。
默认情况下,我们将尝试为任何返回的输入创建关系对象,如果它们已设置。但是,您可以通过在模型中将 LoadRaw 设置为 true 来覆盖此行为。
protected $loadRaw = false;
有时您可能想要一些自动加载,但不允许所有连接的模型自动加载,在这种情况下,您可以设置不应自动加载的任何模型,如下所示:
protected $dontAutoloadRelation = ['Address'];
对于每个模型,我们需要创建一个函数,就像 Laravel 一样,因此如果有一个具有与地址模型关系的用户模型,您会设置:
public function address() { return $this->hasOne(Address::class); }
这也可以是一个 HasMany 关系,地址的相反将是 belongsTo 方法。
public function user() { return $this->belongsTo(User::class); }
可以传递一个名称和一个字段作为第二个和第三个参数。还可以传递第四个参数,它将是一个数组,包含要传递到所有关系模型中的任何字段和值。这在您需要在子模型上跟踪父 id 时很有用。
我们尝试根据函数名称自动确定名称和字段属性,如果没有传递给您,因此我们将在上述方法中查找数组 'users' 中的字段 'user_id'。但是,不是所有 API 都使用相同的 ID 命名,因此您可以在模型中设置 IDSuffix 来设置。
protected $IdSuffix = 'ID'; // Will now look for userID instead of user_id
使用名称字段时,我们将检查结果中的 User 和 user。
值得注意的是,这是区分大小写的,因此 User 和 user 将产生不同的结果,UserID 和 userID。这同样是因为所有 API 都不同。
还值得指出的是,一些资源不直接与 API 交互,在这些情况下,该字段将被忽略。
目前我们没有 Many to Many 的能力,但将看看在我们的 API 构建任务中是否需要。
现在关系已设置,我们可以使用 Laravel 语法检索和操作模型和关系。
$user = API::user()->find('id'); $address = $user->address; // or you can also call the method for further filtering $address = $user->address()->where('type', 'billing')->first();
我们使用save、saveMany(仅关联多个)、create和createMany(仅关联多个)函数来保存现有模型并直接到关系中创建新模型,只要它们是与API交互的模型。
// save $user = API::user()->find('id'); $address = Api::address()->find('id'); $user->address()->save($address); // create $user = API::user()->find('id'); $user->address()->create([ 'address1' => '17 Test Street', ... ]);
当关系模型不与API交互时,我们暴露新的make()和attach()方法,以便它们可以附加到持久化模型进行保存。
$user = API::user()->find('id'); $user->address()->make([ 'address1' => '21 Test Street', .... ]); $user = API::user()->find('id'); $address = Address::make([...]); // Do some logic checks $user->address()->attach($address);
当关系作为API调用的直接部分返回时,这效果很好,这在API中很常见。然而,有时API不会直接发送这些项目,因此需要不同的端点调用。
在这些情况下,我们使用自定义关系模型
自定义关系模型
首先,您需要扩展HasOne或HasMany关系模型。
在扩展的模型中,您需要创建获取、创建、更新和删除的逻辑,这符合API的要求。不幸的是,由于这些都是特定案例,您需要为任何实现需求创建CustomRelations模型。
要调用它,我们调用hasCustom方法,并将模型类作为第二个参数。参数3和4可以是名称和字段,5可以是添加到任何新模型中的字段和值。
public function addresses() { $this->hasCustom(Address::class, addressHasMany::class); }
自定义类需要一个构造函数、一个save、saveMany(对于hasMany)、create、createMany(对于hasMany)和getResults方法。
这实际上提供了一个空白画布,可以将那些令人头疼的端点引入您的API。
有时关系不是作为请求的一部分返回的,必须单独调用。有时这可能意味着击中不同的端点,这已在模型中设置,请参阅上面的endPoints说明。
我们还可以在每个模型的基础上覆盖默认的customEndPoints。
protected $customEndPoints = [ 'get' => 'users/{user:id}/meetings', 'post' => 'users/{user:id}/meetings' ];
您可以设置find、get、create、update和delete的端点。
过滤器
过滤器的不同之处在于API的不同,所以我们尝试构建一个可扩展的过滤器功能,它很容易实现。
过滤器使用类似于Laravel的语法与where函数一起应用
$user = API::user()->where('Name', 'John Doe')->first(); $users = API::user()->where('Name', '!=', 'John Doe')->get(); $posts = API::post()->where('Title', 'like', 'Mechanical')->get();
当然,您可以堆叠where子句
$posts = API::post()->where('created_at', '>', '2019-01-01')->where('created_at', '<', '2019-12-31')->get();
目前我们只覆盖了where和whereIn,后者仅适用于ID字段,但如果我们发现API支持此功能,我们打算添加更多的Laravel where子句,如whereBetween。
正如所注,所有API都是不同的,所以我们通常必须自定义过滤器逻辑。
首先,您需要创建一个扩展Support/Builder模型的Builder模型。
在Entry模型中,您需要设置以下函数
public function getBuilderClass() { return \Namespace\To\Your\Builder::class; }
然后,在这个Builder类中,您会创建在添加过滤器时和将它们处理成查询字符串时调用的函数。因此,为了修改,我们会修改两个方法
protected function addWhereEquals($column, $operand, $value) { $this->wheres[] = ['column' => $column, 'operand' => $operand, 'value' => $value]; } public function processWhereEquals($detail) // Passed in the array attached in above method { $this->processedWheres[$detail['column']] = $detail['value']; }
由于数组不能使用符号作为键,我们进行了一些转换,所以如果调用'=',我们将将其更改为AddWhereEquals和processWhereEquals,以下是我们进行的转换列表。
case '=': return 'Equals'; case '!=': return 'NotEquals'; case '>': return 'GreaterThan'; case '>=': return 'GreaterThanOrEquals'; case '<': return 'LessThan'; case '<=': return 'LessThanOrEquals'; case '<>': return 'GreaterThanOrLessThan'; case 'like': return 'Contains'; default: return 'Process'.Str::studly($operand);
默认情况下,对任何自定义过滤器进行调用,所以如果您调用where('name', 'StartsWith', 'Bob'),这将调用addWhereStartsWith和processWhereStartsWith方法
现在每个API对过滤器的处理方式都不同,所以这些方法中的逻辑要符合API,以下是一个针对Xero的示例,他们把所有过滤器添加到一个where查询字符串中。他们允许主要类型和3个自定义类型,'Contains'与'like'调用相同,'StartWith'和'EndsWith'。
public function processEquals($detail) { if(!isset($this->processedWheres['where'])){ $this->processedWheres['where'] = ''; } $this->processedWheres['where'] .= $detail['column'].'=="'.$detail['value'].'"'; } public function processNotEquals($detail) { if(!isset($this->processedWheres['where'])){ $this->processedWheres['where'] = ''; } $this->processedWheres['where'] .= $detail['column'].'!="'.$detail['value'].'"'; } public function processGreaterThan($detail) { if(!isset($this->processedWheres['where'])){ $this->processedWheres['where'] = ''; } $this->processedWheres['where'] .= $detail['column'].'>"'.$detail['value'].'"'; } public function processGreaterThanOrEquals($detail) { if(!isset($this->processedWheres['where'])){ $this->processedWheres['where'] = ''; } $this->processedWheres['where'] .= $detail['column'].'>="'.$detail['value'].'"'; } public function processLessThan($detail) { if(!isset($this->processedWheres['where'])){ $this->processedWheres['where'] = ''; } $this->processedWheres['where'] .= $detail['column'].'<"'.$detail['value'].'"'; } public function processLessThanOrEquals($detail) { if(!isset($this->processedWheres['where'])){ $this->processedWheres['where'] = ''; } $this->processedWheres['where'] .= $detail['column'].'<="'.$detail['value'].'"'; } public function processGreaterThanOrLessThan($detail) { if(!isset($this->processedWheres['where'])){ $this->processedWheres['where'] = ''; } $this->processedWheres['where'] .= $detail['column'].'<>"'.$detail['value'].'"'; } public function processContains($detail) { if(!isset($this->processedWheres['where'])){ $this->processedWheres['where'] = ''; } $this->processedWheres['where'] .= $detail['column'].'.Contains("'.$detail['value'].'")'; } public function processStartsWith($detail) { if(!isset($this->processedWheres['where'])){ $this->processedWheres['where'] = ''; } $this->processedWheres['where'] .= $detail['column'].'.StartsWith("'.$detail['value'].'")'; } public function processEndsWith($detail) { if(!isset($this->processedWheres['where'])){ $this->processedWheres['where'] = ''; } $this->processedWheres['where'] .= $detail['column'].'.EndsWith("'.$detail['value'].'")'; }
随着我们创建自定义过滤器方法,这使得过滤变得容易且非常强大,但它确实取决于API允许执行的操作。
例如,在Xero中,我们可以向我们的请求中应用If-Modified-Since头,以只检索自特定日期以来修改过的模型,这可以通过以下方式实现。
// you would need to spoof the column name, as its not used but is still required. // So call something like $user->where('UpdatedDate', 'ModifiedAfter', $date)->get(); public function addWhereModifiedAfter($column, $operand, $value) { $this->wheres[] = ['operand' => $operand, 'value' => $value]; } public function processWhereModifiedAfter($detail) { $this->request->withHeader([ 'If-Modified-Since' => $detail['value'] ]); }
当然,对于这些自定义操作,我们必须更新我们Entry模型中的允许操作数字段,以允许它们被应用。
protected $allowedOperands = ['=', '!=', '<', '>', '<=', '>=', '<>', 'like', 'ModifiedAfter'];
创建和更新
通常,在API中,用于保存和更新的属性是不同的,它们又与检索模型时的属性不同。因此,我们使用'持久化'模型,这些模型将包含验证逻辑和成功保存所需的属性。因此,在我们的模型中,我们需要扩展InteractsWithAPI特质并添加以下2个受保护的属性。
protected $insertResource = 'MacsiDigital\API\Dev\Resources\StoreAddress'; protected $updateResource = 'MacsiDigital\API\Dev\Resources\UpdateAddress';
插入和更新资源应扩展MacsiDigital/API/Support/PersistResource。这些模型将包含要使用的字段和任何验证。
protected $persistAttributes = [ 'name' => 'required|string|max:255', 'email' => 'required|email|string|max:255', 'password' => 'required|string|max:10', ];
如您所见,我们在persistAttribute中设置了字段和任何常规Laravel验证逻辑,如果没有验证,我们可以传递''。
我们还可以扩展它以包含关系。它可能看起来像这样:
protected $persistAttributes = [ 'name' => 'required|string|max:255', 'email' => 'required|email|string|max:255', 'password' => 'required|string|max:10', ]; protected $relatedResource = [ 'address' => '\MacsiDigital\API\Dev\Resources\UpdateAddress' ];
以这种方式利用关系时,它将进入相关资源模型并使用其验证和字段逻辑。因此,在这个例子中,它将拉入以下逻辑
protected $persistAttributes = [ 'street' => 'required|string|max:255', 'address2' => 'nullable|string|max:255', 'town' => 'required|string|max:255', 'county' => 'nullable|string|max:255', 'postcode' => 'required|string|max:10', ];
如果我们不希望创建持久化资源,可以直接传递数组到relatedResource属性。
protected $relatedResource = [ 'address' => [ 'street' => 'required|string|max:255', 'address2' => 'nullable|string|max:255', 'town' => 'required|string|max:255', 'county' => 'nullable|string|max:255', 'postcode' => 'required|string|max:10', ], ];
我们可以使用点符号来要求关系的字段,而不包括关系:
protected $persistAttributes = [ 'name' => 'required|string|max:255', 'email' => 'required|email|string|max:255', 'password' => 'required|string|max:10', 'address.street' => 'required|string|max:255', 'address.town' => 'required|string|max:255', 'address.postcode' => 'required|string|max:10', ];
因此,在这个例子中,我们只对地址模型中的街道、城镇和邮政编码感兴趣。
我们可以使用上述方法的混合,而不是两者之一。
变异
一些API会变异数据,一个常见的例子是将字段包裹在一个新的数组中。
//Normal create [ 'name' => 'John Doe', 'email' => 'john@Example.com' ] // Some APIs may require // [ 'action' => 'create', 'user_info' => [ 'name' => 'John Doe', 'email' => 'john@Example.com' ] ]
为了实现这一点,我们可以在persistModel上使用变异器
protected $persistAttributes = [ 'action' => 'required|string|in:create,autoCreate,custCreate,ssoCreate', 'user_info.name' => 'required|string|max:255', 'user_info.email' => 'required|email|string|max:255', ]; // To apply mutators we set what teh normal key is and what we want it to end up as protected $mutateAttributes = [ 'name' => 'user_info.name', 'email' => 'user_info.email' 'type' => 'status' //can also use to mutate current attributes into attributes for the api ]
这将现在为API创建正确的json,并允许验证仍然进行。
保存
保存很简单,只需调用save()函数。
$user->save();
模型将检查它是否已经存在,并调用正确的插入或更新方法。如果更新,我们只向持久化模型传递脏属性。
您还可以直接在模型中利用其他Laravel方法,如make、create和update。
如果由于API拒绝而失败,我们会返回有用的异常。例如,某些API可能有异常的规则,在Xero中,如果您提供电子邮件地址,则可以传递多个联系人,在这种情况下,我们会得到以下异常:
MacsiDigital/API/Exceptions/HttpException with message 'A validation exception occurred : Additional people cannot be added when the primary person has no email address set.'
删除
要删除,我们只需调用delete函数,如果删除成功,它将返回true,如果有错误,将抛出HttpException。
$user->delete();
调用自定义请求
虽然我们尽力覆盖模型的主要RESTful端点,但总会有一些情况需要创建自定义内容。
我们已尽力使其尽可能简单,每个模型在实例化时都注入了API客户端,因此我们可以创建任何可以调用API的函数。例如,Zoom公开了一个上传个人资料的端点,我们可以创建persistModels,但这可能有些过度,因此我们可以简单地创建一个函数,如下所示
public function updateProfilePicture($image) { $filesize = number_format(filesize($image) / 1048576,2); if($filesize > 2){ throw new FileTooLargeException($image, $filesize, '2MB'); } else { return $this->newQuery()->attachFile('pic_file', file_get_contents($image), $image)->sendRequest('post', ['users/'.$this->id.'/picture'])->successful(); } }
很简单。
待办事项
- 正式文档
- 测试
变更日志
有关最近更改的更多信息,请参阅CHANGELOG。
贡献
有关详细信息,请参阅CONTRIBUTING。
安全
如果您发现任何与安全相关的问题,请通过电子邮件info@macsi.co.uk与我们联系,而不是使用问题跟踪器。
致谢
我们使用了大量直接从Laravel获取的Laravel类型函数,或者对其进行修改后使用,因此我们也需要感谢Laravel团队。
许可证
MIT许可证(MIT)。请参阅许可证文件获取更多信息。
